From 2cb6fa1960ac07e186fc8208c6efa960b64a9d4e Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 18 Nov 2019 01:52:35 +0000 Subject: [PATCH] Initial work for file type paths, use it for audio and midi plugins Signed-off-by: falkTX --- resources/ui/carla_settings.ui | 311 +++++++++++++++++- source/backend/CarlaBackend.h | 56 +++- source/backend/CarlaEngine.hpp | 3 + source/backend/CarlaStandalone.cpp | 32 ++ source/backend/engine/CarlaEngine.cpp | 31 +- source/backend/engine/CarlaEngineData.cpp | 22 +- source/backend/engine/CarlaEngineNative.cpp | 3 + source/backend/plugin/CarlaPluginNative.cpp | 28 +- source/frontend/carla_backend.py | 43 ++- source/frontend/carla_host.py | 9 + source/frontend/carla_settings.py | 253 ++++++++++---- source/frontend/carla_shared.py | 11 +- source/includes/CarlaNative.h | 30 +- source/includes/CarlaNative.hpp | 31 +- source/includes/CarlaNativePrograms.hpp | 206 ++++++++++++ source/modules/water/containers/Array.h | 2 +- .../water/memory/SharedResourcePointer.h | 23 +- source/modules/water/threads/SpinLock.h | 2 +- source/native-plugins/_data.cpp | 2 + source/native-plugins/audio-file.cpp | 39 ++- source/native-plugins/midi-file.cpp | 18 +- source/utils/CarlaBackendUtils.hpp | 19 ++ 22 files changed, 1036 insertions(+), 138 deletions(-) create mode 100644 source/includes/CarlaNativePrograms.hpp diff --git a/resources/ui/carla_settings.ui b/resources/ui/carla_settings.ui index b59c2d0de..8c3ceae00 100644 --- a/resources/ui/carla_settings.ui +++ b/resources/ui/carla_settings.ui @@ -88,7 +88,12 @@ - paths + file-paths + + + + + plugin-paths @@ -186,7 +191,7 @@ - Paths + File Paths @@ -203,6 +208,24 @@ + + Plugin Paths + + + + 75 + true + + + + + :/scalable/folder.svgz:/scalable/folder.svgz + + + ItemIsSelectable|ItemIsEnabled + + + Wine @@ -220,7 +243,7 @@ ItemIsSelectable|ItemIsEnabled - + Experimental @@ -1647,7 +1670,253 @@ group_osc_udp_port group_osc_core - + + + + 2 + + + + + + + <b>File Paths</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 48 + 48 + + + + + + + :/scalable/folder.svgz + + + true + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + + + + + + 120 + 0 + + + + + Audio + + + + + MIDI + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + + + 0 + + + + + Used for the "audiofile" plugin + + + + + + + + + 0 + + + 0 + + + + + Used for the "midifile" plugin + + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + + + + + + + + 0 + + + 0 + + + + + + + + + + + + + + Add... + + + + :/16x16/list-add.svgz:/16x16/list-add.svgz + + + + + + + Remove + + + + :/16x16/list-remove.svgz:/16x16/list-remove.svgz + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Change... + + + + :/16x16/edit-rename.svgz:/16x16/edit-rename.svgz + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 2 @@ -1657,7 +1926,7 @@ - <b>Paths</b> + <b>Plugin Paths</b> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -2749,5 +3018,37 @@ This mode is not available for VST plugins. + + cb_filepaths + currentIndexChanged(int) + tw_filepaths + setCurrentIndex(int) + + + 258 + 79 + + + 350 + 312 + + + + + cb_filepaths + currentIndexChanged(int) + tw_filepaths_info + setCurrentIndex(int) + + + 258 + 79 + + + 409 + 79 + + + diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h index be2cb1e3e..fbd204e7c 100644 --- a/source/backend/CarlaBackend.h +++ b/source/backend/CarlaBackend.h @@ -488,6 +488,30 @@ typedef enum { } BinaryType; +/* ------------------------------------------------------------------------------------------------------------ + * File Type */ + +/*! + * File type. + */ +typedef enum { + /*! + * Null file type. + */ + FILE_NONE = 0, + + /*! + * Audio file. + */ + FILE_AUDIO = 1, + + /*! + * MIDI file. + */ + FILE_MIDI = 2 + +} FileType; + /* ------------------------------------------------------------------------------------------------------------ * Plugin Type */ @@ -1233,79 +1257,85 @@ typedef enum { */ ENGINE_OPTION_OSC_PORT_UDP = 16, + /*! + * Set path used for a specific file type. + * Uses value as the file format, valueStr as actual path. + */ + ENGINE_OPTION_FILE_PATH = 17, + /*! * Set path used for a specific plugin type. * Uses value as the plugin format, valueStr as actual path. * @see PluginType */ - ENGINE_OPTION_PLUGIN_PATH = 17, + ENGINE_OPTION_PLUGIN_PATH = 18, /*! * Set path to the binary files. * Default unset. * @note Must be set for plugin and UI bridges to work */ - ENGINE_OPTION_PATH_BINARIES = 18, + ENGINE_OPTION_PATH_BINARIES = 19, /*! * Set path to the resource files. * Default unset. * @note Must be set for some internal plugins to work */ - ENGINE_OPTION_PATH_RESOURCES = 19, + ENGINE_OPTION_PATH_RESOURCES = 20, /*! * Prevent bad plugin and UI behaviour. * @note: Linux only */ - ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 20, + ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 21, /*! * Set UI scaling used in frontend, so backend can do the same for plugin UIs. */ - ENGINE_OPTION_FRONTEND_UI_SCALE = 21, + ENGINE_OPTION_FRONTEND_UI_SCALE = 22, /*! * Set frontend winId, used to define as parent window for plugin UIs. */ - ENGINE_OPTION_FRONTEND_WIN_ID = 22, + ENGINE_OPTION_FRONTEND_WIN_ID = 23, #if !defined(BUILD_BRIDGE_ALTERNATIVE_ARCH) && !defined(CARLA_OS_WIN) /*! * Set path to wine executable. */ - ENGINE_OPTION_WINE_EXECUTABLE = 23, + ENGINE_OPTION_WINE_EXECUTABLE = 24, /*! * Enable automatic wineprefix detection. */ - ENGINE_OPTION_WINE_AUTO_PREFIX = 24, + ENGINE_OPTION_WINE_AUTO_PREFIX = 25, /*! * Fallback wineprefix to use if automatic detection fails or is disabled, and WINEPREFIX is not set. */ - ENGINE_OPTION_WINE_FALLBACK_PREFIX = 25, + ENGINE_OPTION_WINE_FALLBACK_PREFIX = 26, /*! * Enable realtime priority for Wine application and server threads. */ - ENGINE_OPTION_WINE_RT_PRIO_ENABLED = 26, + ENGINE_OPTION_WINE_RT_PRIO_ENABLED = 27, /*! * Base realtime priority for Wine threads. */ - ENGINE_OPTION_WINE_BASE_RT_PRIO = 27, + ENGINE_OPTION_WINE_BASE_RT_PRIO = 28, /*! * Wine server realtime priority. */ - ENGINE_OPTION_WINE_SERVER_RT_PRIO = 28, + ENGINE_OPTION_WINE_SERVER_RT_PRIO = 29, #endif /*! * Capture console output into debug callbacks. */ - ENGINE_OPTION_DEBUG_CONSOLE_OUTPUT = 29 + ENGINE_OPTION_DEBUG_CONSOLE_OUTPUT = 30 } EngineOption; diff --git a/source/backend/CarlaEngine.hpp b/source/backend/CarlaEngine.hpp index d9e8d4b9b..916811222 100644 --- a/source/backend/CarlaEngine.hpp +++ b/source/backend/CarlaEngine.hpp @@ -249,6 +249,9 @@ struct CARLA_API EngineOptions { int oscPortUDP; #endif + const char* pathAudio; + const char* pathMIDI; + const char* pathLADSPA; const char* pathDSSI; const char* pathLV2; diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 237867e7c..30cdbe9d3 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -208,6 +208,12 @@ static void carla_engine_init_common(CarlaEngine* const engine) if (const char* const uiBridgesTimeout = std::getenv("ENGINE_OPTION_UI_BRIDGES_TIMEOUT")) engine->setOption(CB::ENGINE_OPTION_UI_BRIDGES_TIMEOUT, std::atoi(uiBridgesTimeout), nullptr); + if (const char* const pathAudio = std::getenv("ENGINE_OPTION_FILE_PATH_AUDIO")) + engine->setOption(CB::ENGINE_OPTION_FILE_PATH, CB::FILE_AUDIO, pathAudio); + + if (const char* const pathMIDI = std::getenv("ENGINE_OPTION_FILE_PATH_MIDI")) + engine->setOption(CB::ENGINE_OPTION_FILE_PATH, CB::FILE_MIDI, pathMIDI); + if (const char* const pathLADSPA = std::getenv("ENGINE_OPTION_PLUGIN_PATH_LADSPA")) engine->setOption(CB::ENGINE_OPTION_PLUGIN_PATH, CB::PLUGIN_LADSPA, pathLADSPA); @@ -265,6 +271,12 @@ static void carla_engine_init_common(CarlaEngine* const engine) engine->setOption(CB::ENGINE_OPTION_OSC_PORT_TCP, gStandalone.engineOptions.oscPortTCP, nullptr); engine->setOption(CB::ENGINE_OPTION_OSC_PORT_UDP, gStandalone.engineOptions.oscPortUDP, nullptr); + if (gStandalone.engineOptions.pathAudio != nullptr) + engine->setOption(CB::ENGINE_OPTION_FILE_PATH, CB::FILE_AUDIO, gStandalone.engineOptions.pathAudio); + + if (gStandalone.engineOptions.pathMIDI != nullptr) + engine->setOption(CB::ENGINE_OPTION_FILE_PATH, CB::FILE_MIDI, gStandalone.engineOptions.pathMIDI); + if (gStandalone.engineOptions.pathLADSPA != nullptr) engine->setOption(CB::ENGINE_OPTION_PLUGIN_PATH, CB::PLUGIN_LADSPA, gStandalone.engineOptions.pathLADSPA); @@ -733,6 +745,26 @@ void carla_set_engine_option(EngineOption option, int value, const char* valueSt gStandalone.engineOptions.oscPortUDP = value; break; + case CB::ENGINE_OPTION_FILE_PATH: + CARLA_SAFE_ASSERT_RETURN(value > CB::FILE_NONE,); + CARLA_SAFE_ASSERT_RETURN(value <= CB::FILE_MIDI,); + CARLA_SAFE_ASSERT_RETURN(valueStr != nullptr,); + + switch (value) + { + case CB::FILE_AUDIO: + if (gStandalone.engineOptions.pathAudio != nullptr) + delete[] gStandalone.engineOptions.pathAudio; + gStandalone.engineOptions.pathAudio = carla_strdup_safe(valueStr); + break; + case CB::FILE_MIDI: + if (gStandalone.engineOptions.pathMIDI != nullptr) + delete[] gStandalone.engineOptions.pathMIDI; + gStandalone.engineOptions.pathMIDI = carla_strdup_safe(valueStr); + break; + } + break; + case CB::ENGINE_OPTION_PLUGIN_PATH: CARLA_SAFE_ASSERT_RETURN(value > CB::PLUGIN_NONE,); CARLA_SAFE_ASSERT_RETURN(value <= CB::PLUGIN_SFZ,); diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 13b89ba98..be97f38e0 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -1740,6 +1740,34 @@ void CarlaEngine::setOption(const EngineOption option, const int value, const ch #endif break; + case ENGINE_OPTION_FILE_PATH: + CARLA_SAFE_ASSERT_RETURN(value > FILE_NONE,); + CARLA_SAFE_ASSERT_RETURN(value <= FILE_MIDI,); + + switch (value) + { + case FILE_AUDIO: + if (pData->options.pathAudio != nullptr) + delete[] pData->options.pathAudio; + if (valueStr != nullptr) + pData->options.pathAudio = carla_strdup_safe(valueStr); + else + pData->options.pathAudio = nullptr; + break; + case FILE_MIDI: + if (pData->options.pathMIDI != nullptr) + delete[] pData->options.pathMIDI; + if (valueStr != nullptr) + pData->options.pathMIDI = carla_strdup_safe(valueStr); + else + pData->options.pathMIDI = nullptr; + break; + default: + return carla_stderr("CarlaEngine::setOption(%i:%s, %i, \"%s\") - Invalid file type", + option, EngineOption2Str(option), value, valueStr); + break; + } + break; case ENGINE_OPTION_PLUGIN_PATH: CARLA_SAFE_ASSERT_RETURN(value > PLUGIN_NONE,); CARLA_SAFE_ASSERT_RETURN(value <= PLUGIN_SFZ,); @@ -1803,7 +1831,8 @@ void CarlaEngine::setOption(const EngineOption option, const int value, const ch pData->options.pathSFZ = nullptr; break; default: - return carla_stderr("CarlaEngine::setOption(%i:%s, %i, \"%s\") - Invalid plugin type", option, EngineOption2Str(option), value, valueStr); + return carla_stderr("CarlaEngine::setOption(%i:%s, %i, \"%s\") - Invalid plugin type", + option, EngineOption2Str(option), value, valueStr); break; } break; diff --git a/source/backend/engine/CarlaEngineData.cpp b/source/backend/engine/CarlaEngineData.cpp index a2480ad3f..5c63e268b 100644 --- a/source/backend/engine/CarlaEngineData.cpp +++ b/source/backend/engine/CarlaEngineData.cpp @@ -208,6 +208,8 @@ EngineOptions::EngineOptions() noexcept oscPortTCP(22752), oscPortUDP(22752), #endif + pathAudio(nullptr), + pathMIDI(nullptr), pathLADSPA(nullptr), pathDSSI(nullptr), pathLV2(nullptr), @@ -232,61 +234,61 @@ EngineOptions::~EngineOptions() noexcept delete[] audioDriver; audioDriver = nullptr; } - if (audioDevice != nullptr) { delete[] audioDevice; audioDevice = nullptr; } - + if (pathAudio != nullptr) + { + delete[] pathAudio; + pathAudio = nullptr; + } + if (pathMIDI != nullptr) + { + delete[] pathMIDI; + pathMIDI = nullptr; + } if (pathLADSPA != nullptr) { delete[] pathLADSPA; pathLADSPA = nullptr; } - if (pathDSSI != nullptr) { delete[] pathDSSI; pathDSSI = nullptr; } - if (pathLV2 != nullptr) { delete[] pathLV2; pathLV2 = nullptr; } - if (pathVST2 != nullptr) { delete[] pathVST2; pathVST2 = nullptr; } - if (pathVST3 != nullptr) { delete[] pathVST3; pathVST3 = nullptr; } - if (pathSF2 != nullptr) { delete[] pathSF2; pathSF2 = nullptr; } - if (pathSFZ != nullptr) { delete[] pathSFZ; pathSFZ = nullptr; } - if (binaryDir != nullptr) { delete[] binaryDir; binaryDir = nullptr; } - if (resourceDir != nullptr) { delete[] resourceDir; diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 71c687233..38316b904 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -1636,6 +1636,9 @@ public: CarlaEngineNative* const engine = handlePtr; return (intptr_t)(CarlaEngine*)engine; } + case NATIVE_PLUGIN_OPCODE_IDLE: + //handlePtr->idle(); + return 0; } return 0; diff --git a/source/backend/plugin/CarlaPluginNative.cpp b/source/backend/plugin/CarlaPluginNative.cpp index 63ef1b574..fd244da8b 100644 --- a/source/backend/plugin/CarlaPluginNative.cpp +++ b/source/backend/plugin/CarlaPluginNative.cpp @@ -252,6 +252,7 @@ public: fIsOffline(false), fIsUiAvailable(false), fIsUiVisible(false), + fNeedsIdle(false), fInlineDisplayNeedsRedraw(false), fInlineDisplayLastRedrawTime(0), fAudioAndCvInBuffers(nullptr), @@ -921,6 +922,12 @@ public: void idle() override { + if (fNeedsIdle) + { + fNeedsIdle = false; + fDescriptor->dispatcher(fHandle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, nullptr, 0.0f); + } + if (fInlineDisplayNeedsRedraw) { // TESTING @@ -2555,7 +2562,8 @@ protected: return pData->engine->runFileCallback(FILE_CALLBACK_SAVE, isDir, title, filter); } - intptr_t handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt) + intptr_t handleDispatcher(const NativeHostDispatcherOpcode opcode, + const int32_t index, const intptr_t value, void* const ptr, const float opt) { carla_debug("CarlaPluginNative::handleDispatcher(%i, %i, " P_INTPTR ", %p, %f)", opcode, index, value, ptr, static_cast(opt)); @@ -2603,12 +2611,26 @@ protected: CARLA_SAFE_ASSERT_RETURN(index >= 0, 0); pData->engine->touchPluginParameter(pData->id, static_cast(index), value != 0); break; + case NATIVE_HOST_OPCODE_REQUEST_IDLE: + fNeedsIdle = true; + break; + case NATIVE_HOST_OPCODE_GET_FILE_PATH: + CARLA_SAFE_ASSERT_RETURN(ptr != nullptr, 0); + { + const EngineOptions& opts(pData->engine->getOptions()); + const char* const filetype = (const char*)ptr; + + if (std::strcmp(filetype, "audio") == 0) + return static_cast((uintptr_t)opts.pathAudio); + if (std::strcmp(filetype, "midi") == 0) + return static_cast((uintptr_t)opts.pathMIDI); + } + break; } return ret; // unused for now - (void)ptr; (void)opt; } @@ -2792,6 +2814,8 @@ private: bool fIsOffline; bool fIsUiAvailable; bool fIsUiVisible; + volatile bool fNeedsIdle; + bool fInlineDisplayNeedsRedraw; int64_t fInlineDisplayLastRedrawTime; diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index d9c4390e5..b7255b86c 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -387,6 +387,19 @@ BINARY_WIN64 = 4 # Other binary type. BINARY_OTHER = 5 +# ------------------------------------------------------------------------------------------------------------ +# File Type +# File type. + +# Null file type. +FILE_NONE = 0 + +# Audio file. +FILE_AUDIO = 1 + +# MIDI file. +FILE_MIDI = 2 + # ------------------------------------------------------------------------------------------------------------ # Plugin Type # Plugin type. @@ -885,51 +898,55 @@ ENGINE_OPTION_OSC_PORT_TCP = 15 # @note Valid ports begin at 1024 and end at 32767 (inclusive) ENGINE_OPTION_OSC_PORT_UDP = 16 +# Set path used for a specific file type. +# Uses value as the file format, valueStr as actual path. +ENGINE_OPTION_FILE_PATH = 17 + # Set path used for a specific plugin type. # Uses value as the plugin format, valueStr as actual path. # @see PluginType -ENGINE_OPTION_PLUGIN_PATH = 17 +ENGINE_OPTION_PLUGIN_PATH = 18 # Set path to the binary files. # Default unset. # @note Must be set for plugin and UI bridges to work -ENGINE_OPTION_PATH_BINARIES = 18 +ENGINE_OPTION_PATH_BINARIES = 19 # Set path to the resource files. # Default unset. # @note Must be set for some internal plugins to work -ENGINE_OPTION_PATH_RESOURCES = 19 +ENGINE_OPTION_PATH_RESOURCES = 20 # Prevent bad plugin and UI behaviour. # @note: Linux only -ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 20 +ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR = 21 # Set UI scaling used in frontend, so backend can do the same for plugin UIs. -ENGINE_OPTION_FRONTEND_UI_SCALE = 21 +ENGINE_OPTION_FRONTEND_UI_SCALE = 22 # Set frontend winId, used to define as parent window for plugin UIs. -ENGINE_OPTION_FRONTEND_WIN_ID = 22 +ENGINE_OPTION_FRONTEND_WIN_ID = 23 # Set path to wine executable. -ENGINE_OPTION_WINE_EXECUTABLE = 23 +ENGINE_OPTION_WINE_EXECUTABLE = 24 # Enable automatic wineprefix detection. -ENGINE_OPTION_WINE_AUTO_PREFIX = 24 +ENGINE_OPTION_WINE_AUTO_PREFIX = 25 # Fallback wineprefix to use if automatic detection fails or is disabled, and WINEPREFIX is not set. -ENGINE_OPTION_WINE_FALLBACK_PREFIX = 25 +ENGINE_OPTION_WINE_FALLBACK_PREFIX = 26 # Enable realtime priority for Wine application and server threads. -ENGINE_OPTION_WINE_RT_PRIO_ENABLED = 26 +ENGINE_OPTION_WINE_RT_PRIO_ENABLED = 27 # Base realtime priority for Wine threads. -ENGINE_OPTION_WINE_BASE_RT_PRIO = 27 +ENGINE_OPTION_WINE_BASE_RT_PRIO = 28 # Wine server realtime priority. -ENGINE_OPTION_WINE_SERVER_RT_PRIO = 28 +ENGINE_OPTION_WINE_SERVER_RT_PRIO = 29 # Capture console output into debug callbacks -ENGINE_OPTION_DEBUG_CONSOLE_OUTPUT = 29 +ENGINE_OPTION_DEBUG_CONSOLE_OUTPUT = 30 # ------------------------------------------------------------------------------------------------------------ # Engine Process Mode diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index b0749b3fc..b70f22096 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -3101,6 +3101,15 @@ def setEngineSettings(host): setHostSettings(host) + # -------------------------------------------------------------------------------------------------------- + # file paths + + FILE_PATH_AUDIO = toList(settings.value(CARLA_KEY_PATHS_AUDIO, CARLA_DEFAULT_FILE_PATH_AUDIO)) + FILE_PATH_MIDI = toList(settings.value(CARLA_KEY_PATHS_MIDI, CARLA_DEFAULT_FILE_PATH_MIDI)) + + host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_AUDIO, splitter.join(FILE_PATH_AUDIO)) + host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_MIDI, splitter.join(FILE_PATH_MIDI)) + # -------------------------------------------------------------------------------------------------------- # plugin paths diff --git a/source/frontend/carla_settings.py b/source/frontend/carla_settings.py index f1c1f9f96..fdd5f0cd2 100755 --- a/source/frontend/carla_settings.py +++ b/source/frontend/carla_settings.py @@ -324,19 +324,24 @@ class CarlaSettingsW(QDialog): TAB_INDEX_CANVAS = 1 TAB_INDEX_ENGINE = 2 TAB_INDEX_OSC = 3 - TAB_INDEX_PATHS = 4 - TAB_INDEX_WINE = 5 - TAB_INDEX_EXPERIMENTAL = 6 - TAB_INDEX_NONE = 7 - - # Path indexes - PATH_INDEX_LADSPA = 0 - PATH_INDEX_DSSI = 1 - PATH_INDEX_LV2 = 2 - PATH_INDEX_VST2 = 3 - PATH_INDEX_VST3 = 4 - PATH_INDEX_SF2 = 5 - PATH_INDEX_SFZ = 6 + TAB_INDEX_FILEPATHS = 4 + TAB_INDEX_PLUGINPATHS = 5 + TAB_INDEX_WINE = 6 + TAB_INDEX_EXPERIMENTAL = 7 + TAB_INDEX_NONE = 8 + + # File Path indexes + FILEPATH_INDEX_AUDIO = 0 + FILEPATH_INDEX_MIDI = 1 + + # Plugin Path indexes + PLUGINPATH_INDEX_LADSPA = 0 + PLUGINPATH_INDEX_DSSI = 1 + PLUGINPATH_INDEX_LV2 = 2 + PLUGINPATH_INDEX_VST2 = 3 + PLUGINPATH_INDEX_VST3 = 4 + PLUGINPATH_INDEX_SF2 = 5 + PLUGINPATH_INDEX_SFZ = 6 # Single and Multiple client mode is only for JACK, # but we still want to match QComboBox index to backend defines, @@ -377,7 +382,8 @@ class CarlaSettingsW(QDialog): if host.isControl: self.ui.lw_page.hideRow(self.TAB_INDEX_ENGINE) - self.ui.lw_page.hideRow(self.TAB_INDEX_PATHS) + self.ui.lw_page.hideRow(self.TAB_INDEX_FILEPATHS) + self.ui.lw_page.hideRow(self.TAB_INDEX_PLUGINPATHS) self.ui.ch_exp_export_lv2.hide() self.ui.group_experimental_engine.hide() @@ -450,6 +456,13 @@ class CarlaSettingsW(QDialog): self.ui.lw_sf2.currentRowChanged.connect(self.slot_pluginPathRowChanged) self.ui.lw_sfz.currentRowChanged.connect(self.slot_pluginPathRowChanged) + self.ui.b_filepaths_add.clicked.connect(self.slot_addFilePath) + self.ui.b_filepaths_remove.clicked.connect(self.slot_removeFilePath) + self.ui.b_filepaths_change.clicked.connect(self.slot_changeFilePath) + self.ui.cb_filepaths.currentIndexChanged.connect(self.slot_filePathTabChanged) + self.ui.lw_files_audio.currentRowChanged.connect(self.slot_filePathRowChanged) + self.ui.lw_files_midi.currentRowChanged.connect(self.slot_filePathRowChanged) + self.ui.ch_main_experimental.toggled.connect(self.slot_enableExperimental) self.ui.ch_exp_wine_bridges.toggled.connect(self.slot_enableWineBridges) self.ui.cb_exp_plugin_bridges.toggled.connect(self.slot_pluginBridgesToggled) @@ -468,8 +481,14 @@ class CarlaSettingsW(QDialog): self.ui.lw_sf2.setCurrentRow(0) self.ui.lw_sfz.setCurrentRow(0) + self.ui.lw_files_audio.setCurrentRow(0) + self.ui.lw_files_midi.setCurrentRow(0) + self.ui.lw_page.setCurrentCell(0, 0) + self.slot_filePathTabChanged(0) + self.slot_pluginPathTabChanged(0) + self.adjustSize() # -------------------------------------------------------------------------------------------------------- @@ -602,7 +621,24 @@ class CarlaSettingsW(QDialog): self.ui.rb_osc_udp_port_specific.setChecked(True) # ---------------------------------------------------------------------------------------------------- - # Paths + # File Paths + + audioPaths = toList(settings.value(CARLA_KEY_PATHS_AUDIO, CARLA_DEFAULT_FILE_PATH_AUDIO)) + midiPaths = toList(settings.value(CARLA_KEY_PATHS_MIDI, CARLA_DEFAULT_FILE_PATH_MIDI)) + + audioPaths.sort() + midiPaths.sort() + + for audioPath in audioPaths: + if not audioPath: continue + self.ui.lw_files_audio.addItem(audioPath) + + for midiPath in midiPaths: + if not midiPath: continue + self.ui.lw_files_midi.addItem(midiPath) + + # ---------------------------------------------------------------------------------------------------- + # Plugin Paths ladspas = toList(settings.value(CARLA_KEY_PATHS_LADSPA, CARLA_DEFAULT_LADSPA_PATH)) dssis = toList(settings.value(CARLA_KEY_PATHS_DSSI, CARLA_DEFAULT_DSSI_PATH)) @@ -794,7 +830,25 @@ class CarlaSettingsW(QDialog): settings.setValue(CARLA_KEY_OSC_UDP_PORT_NUMBER, self.ui.sb_osc_udp_port_number.value()) # ---------------------------------------------------------------------------------------------------- - # Paths + # File Paths + + audioPaths = [] + midiPaths = [] + + for i in range(self.ui.lw_files_audio.count()): + audioPaths.append(self.ui.lw_files_audio.item(i).text()) + + for i in range(self.ui.lw_files_midi.count()): + midiPaths.append(self.ui.lw_files_midi.item(i).text()) + + self.host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_AUDIO, splitter.join(audioPaths)) + self.host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_MIDI, splitter.join(midiPaths)) + + settings.setValue(CARLA_KEY_PATHS_AUDIO, audioPaths) + settings.setValue(CARLA_KEY_PATHS_MIDI, midiPaths) + + # ---------------------------------------------------------------------------------------------------- + # Plugin Paths ladspas = [] dssis = [] @@ -934,12 +988,24 @@ class CarlaSettingsW(QDialog): self.ui.rb_osc_udp_port_specific.setChecked(True) # ---------------------------------------------------------------------------------------------------- - # Paths + # Plugin Paths + + elif currentRow == self.TAB_INDEX_FILEPATHS: + curIndex = self.ui.tw_filepaths.currentIndex() + + if curIndex == self.FILEPATH_INDEX_AUDIO: + self.ui.lw_files_audio.clear() + + elif curIndex == self.FILEPATH_INDEX_MIDI: + self.ui.lw_files_midi.clear() + + # ---------------------------------------------------------------------------------------------------- + # Plugin Paths - elif currentRow == self.TAB_INDEX_PATHS: + elif currentRow == self.TAB_INDEX_PLUGINPATHS: curIndex = self.ui.tw_paths.currentIndex() - if curIndex == self.PATH_INDEX_LADSPA: + if curIndex == self.PLUGINPATH_INDEX_LADSPA: paths = CARLA_DEFAULT_LADSPA_PATH paths.sort() self.ui.lw_ladspa.clear() @@ -948,7 +1014,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_ladspa.addItem(path) - elif curIndex == self.PATH_INDEX_DSSI: + elif curIndex == self.PLUGINPATH_INDEX_DSSI: paths = CARLA_DEFAULT_DSSI_PATH paths.sort() self.ui.lw_dssi.clear() @@ -957,7 +1023,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_dssi.addItem(path) - elif curIndex == self.PATH_INDEX_LV2: + elif curIndex == self.PLUGINPATH_INDEX_LV2: paths = CARLA_DEFAULT_LV2_PATH paths.sort() self.ui.lw_lv2.clear() @@ -966,7 +1032,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_lv2.addItem(path) - elif curIndex == self.PATH_INDEX_VST2: + elif curIndex == self.PLUGINPATH_INDEX_VST2: paths = CARLA_DEFAULT_VST2_PATH paths.sort() self.ui.lw_vst.clear() @@ -975,7 +1041,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_vst.addItem(path) - elif curIndex == self.PATH_INDEX_VST3: + elif curIndex == self.PLUGINPATH_INDEX_VST3: paths = CARLA_DEFAULT_VST3_PATH paths.sort() self.ui.lw_vst3.clear() @@ -984,7 +1050,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_vst3.addItem(path) - elif curIndex == self.PATH_INDEX_SF2: + elif curIndex == self.PLUGINPATH_INDEX_SF2: paths = CARLA_DEFAULT_SF2_PATH paths.sort() self.ui.lw_sf2.clear() @@ -993,7 +1059,7 @@ class CarlaSettingsW(QDialog): if not path: continue self.ui.lw_sf2.addItem(path) - elif curIndex == self.PATH_INDEX_SFZ: + elif curIndex == self.PLUGINPATH_INDEX_SFZ: paths = CARLA_DEFAULT_SFZ_PATH paths.sort() self.ui.lw_sfz.clear() @@ -1113,57 +1179,57 @@ class CarlaSettingsW(QDialog): curIndex = self.ui.tw_paths.currentIndex() - if curIndex == self.PATH_INDEX_LADSPA: + if curIndex == self.PLUGINPATH_INDEX_LADSPA: self.ui.lw_ladspa.addItem(newPath) - elif curIndex == self.PATH_INDEX_DSSI: + elif curIndex == self.PLUGINPATH_INDEX_DSSI: self.ui.lw_dssi.addItem(newPath) - elif curIndex == self.PATH_INDEX_LV2: + elif curIndex == self.PLUGINPATH_INDEX_LV2: self.ui.lw_lv2.addItem(newPath) - elif curIndex == self.PATH_INDEX_VST2: + elif curIndex == self.PLUGINPATH_INDEX_VST2: self.ui.lw_vst.addItem(newPath) - elif curIndex == self.PATH_INDEX_VST3: + elif curIndex == self.PLUGINPATH_INDEX_VST3: self.ui.lw_vst3.addItem(newPath) - elif curIndex == self.PATH_INDEX_SF2: + elif curIndex == self.PLUGINPATH_INDEX_SF2: self.ui.lw_sf2.addItem(newPath) - elif curIndex == self.PATH_INDEX_SFZ: + elif curIndex == self.PLUGINPATH_INDEX_SFZ: self.ui.lw_sfz.addItem(newPath) @pyqtSlot() def slot_removePluginPath(self): curIndex = self.ui.tw_paths.currentIndex() - if curIndex == self.PATH_INDEX_LADSPA: + if curIndex == self.PLUGINPATH_INDEX_LADSPA: self.ui.lw_ladspa.takeItem(self.ui.lw_ladspa.currentRow()) - elif curIndex == self.PATH_INDEX_DSSI: + elif curIndex == self.PLUGINPATH_INDEX_DSSI: self.ui.lw_dssi.takeItem(self.ui.lw_dssi.currentRow()) - elif curIndex == self.PATH_INDEX_LV2: + elif curIndex == self.PLUGINPATH_INDEX_LV2: self.ui.lw_lv2.takeItem(self.ui.lw_lv2.currentRow()) - elif curIndex == self.PATH_INDEX_VST2: + elif curIndex == self.PLUGINPATH_INDEX_VST2: self.ui.lw_vst.takeItem(self.ui.lw_vst.currentRow()) - elif curIndex == self.PATH_INDEX_VST3: + elif curIndex == self.PLUGINPATH_INDEX_VST3: self.ui.lw_vst3.takeItem(self.ui.lw_vst3.currentRow()) - elif curIndex == self.PATH_INDEX_SF2: + elif curIndex == self.PLUGINPATH_INDEX_SF2: self.ui.lw_sf2.takeItem(self.ui.lw_sf2.currentRow()) - elif curIndex == self.PATH_INDEX_SFZ: + elif curIndex == self.PLUGINPATH_INDEX_SFZ: self.ui.lw_sfz.takeItem(self.ui.lw_sfz.currentRow()) @pyqtSlot() def slot_changePluginPath(self): curIndex = self.ui.tw_paths.currentIndex() - if curIndex == self.PATH_INDEX_LADSPA: + if curIndex == self.PLUGINPATH_INDEX_LADSPA: currentPath = self.ui.lw_ladspa.currentItem().text() - elif curIndex == self.PATH_INDEX_DSSI: + elif curIndex == self.PLUGINPATH_INDEX_DSSI: currentPath = self.ui.lw_dssi.currentItem().text() - elif curIndex == self.PATH_INDEX_LV2: + elif curIndex == self.PLUGINPATH_INDEX_LV2: currentPath = self.ui.lw_lv2.currentItem().text() - elif curIndex == self.PATH_INDEX_VST2: + elif curIndex == self.PLUGINPATH_INDEX_VST2: currentPath = self.ui.lw_vst.currentItem().text() - elif curIndex == self.PATH_INDEX_VST3: + elif curIndex == self.PLUGINPATH_INDEX_VST3: currentPath = self.ui.lw_vst3.currentItem().text() - elif curIndex == self.PATH_INDEX_SF2: + elif curIndex == self.PLUGINPATH_INDEX_SF2: currentPath = self.ui.lw_sf2.currentItem().text() - elif curIndex == self.PATH_INDEX_SFZ: + elif curIndex == self.PLUGINPATH_INDEX_SFZ: currentPath = self.ui.lw_sfz.currentItem().text() else: currentPath = "" @@ -1173,38 +1239,38 @@ class CarlaSettingsW(QDialog): if not newPath: return - if curIndex == self.PATH_INDEX_LADSPA: + if curIndex == self.PLUGINPATH_INDEX_LADSPA: self.ui.lw_ladspa.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_DSSI: + elif curIndex == self.PLUGINPATH_INDEX_DSSI: self.ui.lw_dssi.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_LV2: + elif curIndex == self.PLUGINPATH_INDEX_LV2: self.ui.lw_lv2.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_VST2: + elif curIndex == self.PLUGINPATH_INDEX_VST2: self.ui.lw_vst.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_VST3: + elif curIndex == self.PLUGINPATH_INDEX_VST3: self.ui.lw_vst3.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_SF2: + elif curIndex == self.PLUGINPATH_INDEX_SF2: self.ui.lw_sf2.currentItem().setText(newPath) - elif curIndex == self.PATH_INDEX_SFZ: + elif curIndex == self.PLUGINPATH_INDEX_SFZ: self.ui.lw_sfz.currentItem().setText(newPath) # -------------------------------------------------------------------------------------------------------- @pyqtSlot(int) def slot_pluginPathTabChanged(self, index): - if index == self.PATH_INDEX_LADSPA: + if index == self.PLUGINPATH_INDEX_LADSPA: row = self.ui.lw_ladspa.currentRow() - elif index == self.PATH_INDEX_DSSI: + elif index == self.PLUGINPATH_INDEX_DSSI: row = self.ui.lw_dssi.currentRow() - elif index == self.PATH_INDEX_LV2: + elif index == self.PLUGINPATH_INDEX_LV2: row = self.ui.lw_lv2.currentRow() - elif index == self.PATH_INDEX_VST2: + elif index == self.PLUGINPATH_INDEX_VST2: row = self.ui.lw_vst.currentRow() - elif index == self.PATH_INDEX_VST3: + elif index == self.PLUGINPATH_INDEX_VST3: row = self.ui.lw_vst3.currentRow() - elif index == self.PATH_INDEX_SF2: + elif index == self.PLUGINPATH_INDEX_SF2: row = self.ui.lw_sf2.currentRow() - elif index == self.PATH_INDEX_SFZ: + elif index == self.PLUGINPATH_INDEX_SFZ: row = self.ui.lw_sfz.currentRow() else: row = -1 @@ -1221,6 +1287,73 @@ class CarlaSettingsW(QDialog): # -------------------------------------------------------------------------------------------------------- + @pyqtSlot() + def slot_addFilePath(self): + newPath = QFileDialog.getExistingDirectory(self, self.tr("Add Path"), "", QFileDialog.ShowDirsOnly) + + if not newPath: + return + + curIndex = self.ui.tw_filepaths.currentIndex() + + if curIndex == self.FILEPATH_INDEX_AUDIO: + self.ui.lw_files_audio.addItem(newPath) + elif curIndex == self.FILEPATH_INDEX_MIDI: + self.ui.lw_files_midi.addItem(newPath) + + @pyqtSlot() + def slot_removeFilePath(self): + curIndex = self.ui.tw_filepaths.currentIndex() + + if curIndex == self.FILEPATH_INDEX_AUDIO: + self.ui.lw_files_audio.takeItem(self.ui.lw_files_audio.currentRow()) + elif curIndex == self.FILEPATH_INDEX_MIDI: + self.ui.lw_files_midi.takeItem(self.ui.lw_files_midi.currentRow()) + + @pyqtSlot() + def slot_changeFilePath(self): + curIndex = self.ui.tw_filepaths.currentIndex() + + if curIndex == self.FILEPATH_INDEX_AUDIO: + currentPath = self.ui.lw_files_audio.currentItem().text() + elif curIndex == self.FILEPATH_INDEX_MIDI: + currentPath = self.ui.lw_files_midi.currentItem().text() + else: + currentPath = "" + + newPath = QFileDialog.getExistingDirectory(self, self.tr("Add Path"), currentPath, QFileDialog.ShowDirsOnly) + + if not newPath: + return + + if curIndex == self.FILEPATH_INDEX_AUDIO: + self.ui.lw_files_audio.currentItem().setText(newPath) + elif curIndex == self.FILEPATH_INDEX_MIDI: + self.ui.lw_files_midi.currentItem().setText(newPath) + + # -------------------------------------------------------------------------------------------------------- + + @pyqtSlot(int) + def slot_filePathTabChanged(self, index): + if index == self.FILEPATH_INDEX_AUDIO: + row = self.ui.lw_files_audio.currentRow() + elif index == self.FILEPATH_INDEX_MIDI: + row = self.ui.lw_files_midi.currentRow() + else: + row = -1 + + check = bool(row >= 0) + self.ui.b_filepaths_remove.setEnabled(check) + self.ui.b_filepaths_change.setEnabled(check) + + @pyqtSlot(int) + def slot_filePathRowChanged(self, row): + check = bool(row >= 0) + self.ui.b_filepaths_remove.setEnabled(check) + self.ui.b_filepaths_change.setEnabled(check) + + # -------------------------------------------------------------------------------------------------------- + def done(self, r): QDialog.done(self, r) self.close() diff --git a/source/frontend/carla_shared.py b/source/frontend/carla_shared.py index 8e2dbed33..436a79b4d 100644 --- a/source/frontend/carla_shared.py +++ b/source/frontend/carla_shared.py @@ -222,6 +222,9 @@ CARLA_KEY_OSC_UDP_PORT_ENABLED = "OSC/UDPEnabled" CARLA_KEY_OSC_UDP_PORT_NUMBER = "OSC/UDPNumber" CARLA_KEY_OSC_UDP_PORT_RANDOM = "OSC/UDPRandom" +CARLA_KEY_PATHS_AUDIO = "Paths/Audio" +CARLA_KEY_PATHS_MIDI = "Paths/MIDI" + CARLA_KEY_PATHS_LADSPA = "Paths/LADSPA" CARLA_KEY_PATHS_DSSI = "Paths/DSSI" CARLA_KEY_PATHS_LV2 = "Paths/LV2" @@ -303,7 +306,7 @@ if CARLA_DEFAULT_AUDIO_DRIVER == "JACK": else: CARLA_DEFAULT_PROCESS_MODE = ENGINE_PROCESS_MODE_PATCHBAY CARLA_DEFAULT_TRANSPORT_MODE = ENGINE_TRANSPORT_MODE_INTERNAL - + # OSC CARLA_DEFAULT_OSC_ENABLED = not WINDOWS CARLA_DEFAULT_OSC_TCP_PORT_ENABLED = True @@ -329,6 +332,12 @@ CARLA_DEFAULT_EXPERIMENTAL_LV2_EXPORT = False CARLA_DEFAULT_EXPERIMENTAL_PREVENT_BAD_BEHAVIOUR = False CARLA_DEFAULT_EXPERIMENTAL_LOAD_LIB_GLOBAL = False +# ------------------------------------------------------------------------------------------------------------ +# Default File Folders + +CARLA_DEFAULT_FILE_PATH_AUDIO = [] +CARLA_DEFAULT_FILE_PATH_MIDI = [] + # ------------------------------------------------------------------------------------------------------------ # Default Plugin Folders (get) diff --git a/source/includes/CarlaNative.h b/source/includes/CarlaNative.h index 562f3e226..5b94d0d3b 100644 --- a/source/includes/CarlaNative.h +++ b/source/includes/CarlaNative.h @@ -65,7 +65,8 @@ typedef enum { NATIVE_PLUGIN_USES_TIME = 1 << 10, NATIVE_PLUGIN_USES_PARENT_ID = 1 << 11, /** can set transient hint to parent */ NATIVE_PLUGIN_HAS_INLINE_DISPLAY = 1 << 12, - NATIVE_PLUGIN_USES_CONTROL_VOLTAGE = 1 << 13 + NATIVE_PLUGIN_USES_CONTROL_VOLTAGE = 1 << 13, + NATIVE_PLUGIN_REQUESTS_IDLE = 1 << 15, } NativePluginHints; typedef enum { @@ -96,21 +97,24 @@ typedef enum { NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED = 2, /** uses opt */ NATIVE_PLUGIN_OPCODE_OFFLINE_CHANGED = 3, /** uses value (0=off, 1=on) */ NATIVE_PLUGIN_OPCODE_UI_NAME_CHANGED = 4, /** uses ptr */ - NATIVE_PLUGIN_OPCODE_GET_INTERNAL_HANDLE = 5 /** nothing */ + NATIVE_PLUGIN_OPCODE_GET_INTERNAL_HANDLE = 5, /** nothing */ + NATIVE_PLUGIN_OPCODE_IDLE = 6 /** nothing */ } NativePluginDispatcherOpcode; typedef enum { - NATIVE_HOST_OPCODE_NULL = 0, /** nothing */ - NATIVE_HOST_OPCODE_UPDATE_PARAMETER = 1, /** uses index, -1 for all */ - NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM = 2, /** uses index, -1 for all; may use value for channel */ - NATIVE_HOST_OPCODE_RELOAD_PARAMETERS = 3, /** nothing */ - NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS = 4, /** nothing */ - NATIVE_HOST_OPCODE_RELOAD_ALL = 5, /** nothing */ - NATIVE_HOST_OPCODE_UI_UNAVAILABLE = 6, /** nothing */ - NATIVE_HOST_OPCODE_HOST_IDLE = 7, /** nothing */ - NATIVE_HOST_OPCODE_INTERNAL_PLUGIN = 8, /** nothing */ - NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY = 9, /** nothing */ - NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER = 10 /** uses index, value as bool */ + NATIVE_HOST_OPCODE_NULL = 0, /** nothing */ + NATIVE_HOST_OPCODE_UPDATE_PARAMETER = 1, /** uses index, -1 for all */ + NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM = 2, /** uses index, -1 for all; may use value for channel */ + NATIVE_HOST_OPCODE_RELOAD_PARAMETERS = 3, /** nothing */ + NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS = 4, /** nothing */ + NATIVE_HOST_OPCODE_RELOAD_ALL = 5, /** nothing */ + NATIVE_HOST_OPCODE_UI_UNAVAILABLE = 6, /** nothing */ + NATIVE_HOST_OPCODE_HOST_IDLE = 7, /** nothing */ + NATIVE_HOST_OPCODE_INTERNAL_PLUGIN = 8, /** nothing */ + NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY = 9, /** nothing */ + NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER = 10, /** uses index, value as bool */ + NATIVE_HOST_OPCODE_REQUEST_IDLE = 11, /** nothing */ + NATIVE_HOST_OPCODE_GET_FILE_PATH = 12 /** uses ptr as string for file type */ } NativeHostDispatcherOpcode; /* ------------------------------------------------------------------------------------------------------------ diff --git a/source/includes/CarlaNative.hpp b/source/includes/CarlaNative.hpp index aeb4caeb0..60205f1a8 100644 --- a/source/includes/CarlaNative.hpp +++ b/source/includes/CarlaNative.hpp @@ -27,7 +27,7 @@ * @{ */ -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Native Plugin Class class NativePluginClass @@ -225,6 +225,13 @@ protected: pHost->dispatcher(pHost->handle, NATIVE_HOST_OPCODE_HOST_IDLE, 0, 0, nullptr, 0.0f); } + void hostRequestIdle() const + { + CARLA_SAFE_ASSERT_RETURN(pHost != nullptr,); + + pHost->dispatcher(pHost->handle, NATIVE_HOST_OPCODE_REQUEST_IDLE, 0, 0, nullptr, 0.0f); + } + void hostQueueDrawInlineDisplay() { CARLA_SAFE_ASSERT_RETURN(pHost != nullptr,); @@ -232,6 +239,17 @@ protected: pHost->dispatcher(pHost->handle, NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY, 0, 0, nullptr, 0.0f); } + const char* hostGetFilePath(const char* const filetype) const + { + CARLA_SAFE_ASSERT_RETURN(pHost != nullptr, nullptr); + + return (const char*)(uintptr_t)pHost->dispatcher(pHost->handle, + NATIVE_HOST_OPCODE_GET_FILE_PATH, + 0, 0, + (void*)const_cast(filetype), + 0.0f); + } + // ------------------------------------------------------------------- // Plugin parameter calls @@ -394,6 +412,8 @@ protected: return nullptr; } + virtual void idle() {} + // ------------------------------------------------------------------- private: @@ -521,6 +541,9 @@ public: return 0; case NATIVE_PLUGIN_OPCODE_GET_INTERNAL_HANDLE: return 0; + case NATIVE_PLUGIN_OPCODE_IDLE: + handlePtr->idle(); + return 0; } return 0; @@ -542,7 +565,7 @@ public: /**@}*/ -// --------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // -Weffc++ compat ext widget extern "C" { @@ -559,7 +582,7 @@ typedef struct _NativeInlineDisplayImageSurfaceCompat { } -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- #define PluginClassEND(ClassName) \ public: \ @@ -597,6 +620,6 @@ public: \ ClassName::_render_inline_display, \ 0, 0 -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- #endif // CARLA_NATIVE_HPP_INCLUDED diff --git a/source/includes/CarlaNativePrograms.hpp b/source/includes/CarlaNativePrograms.hpp new file mode 100644 index 000000000..da28b8a58 --- /dev/null +++ b/source/includes/CarlaNativePrograms.hpp @@ -0,0 +1,206 @@ +/* + * Carla Native Plugin API (C++) + * Copyright (C) 2012-2019 Filipe Coelho + * + * 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 CARLA_NATIVE_PROGRAMS_HPP_INCLUDED +#define CARLA_NATIVE_PROGRAMS_HPP_INCLUDED + +#include "CarlaNative.hpp" + +#include "CarlaMathUtils.hpp" +#include "CarlaMutex.hpp" + +#include "water/files/File.h" +#include "water/memory/SharedResourcePointer.h" +#include "water/text/StringArray.h" + +using water::Array; +using water::File; +using water::SharedResourcePointer; +using water::String; +using water::StringArray; + +/*! + * @defgroup CarlaNativeAPI Carla Native API + * @{ + */ + +// ----------------------------------------------------------------------- +// ... + +enum FileType { + FileNone, + FileAudio, + FileMIDI, +}; + +template +struct NativePluginPresetManager { + StringArray filenames; + + NativePluginPresetManager(const char* const paths, const char* const wildcard) + : filenames() + { + CARLA_SAFE_ASSERT_RETURN(paths != nullptr,); + CARLA_SAFE_ASSERT_RETURN(wildcard != nullptr,); + + if (paths[0] == '\0' || wildcard[0] == '\0') + return; + + const StringArray splitPaths(StringArray::fromTokens(paths, CARLA_OS_SPLIT_STR, "")); + + for (String *it = splitPaths.begin(), *end = splitPaths.end(); it != end; ++it) + { + Array results; + + if (File(*it).findChildFiles(results, File::findFiles|File::ignoreHiddenFiles, true, wildcard) > 0) + { + for (File *it2 = results.begin(), *end2 = results.end(); it2 != end2; ++it2) + filenames.add(it2->getFullPathName()); + } + } + + filenames.sort(true); + } +}; + +// ----------------------------------------------------------------------- +// Native Plugin with MIDI programs class + +template +class NativePluginWithMidiPrograms : public NativePluginClass +{ +public: + typedef SharedResourcePointer> NativeMidiPrograms; + + NativePluginWithMidiPrograms(const NativeHostDescriptor* const host, + const NativeMidiPrograms& programs, + const uint32_t numOutputs) + : NativePluginClass(host), + fRetMidiProgram(), + fRetMidiProgramName(), + fNextFilename(nullptr), + fProgramChangeMutex(), + kPrograms(programs), + kNumOutputs(numOutputs) {} + +protected: + // ------------------------------------------------------------------- + // New Plugin program calls + + virtual void setStateFromFile(const char* filename) = 0; + virtual void process2(const float** inBuffer, float** outBuffer, uint32_t frames, + const NativeMidiEvent* midiEvents, uint32_t midiEventCount) = 0; + + // ------------------------------------------------------------------- + // Plugin midi-program calls + + uint32_t getMidiProgramCount() const override + { + const NativePluginPresetManager& pm(kPrograms.get()); + return static_cast(pm.filenames.size()); + } + + const NativeMidiProgram* getMidiProgramInfo(const uint32_t uindex) const override + { + const int index = static_cast(uindex); + + const NativePluginPresetManager& pm(kPrograms.get()); + CARLA_SAFE_ASSERT_RETURN(index < pm.filenames.size(), nullptr); + + fRetMidiProgramName = File(pm.filenames.strings.getUnchecked(index)).getFileNameWithoutExtension(); + + fRetMidiProgram.bank = 0; + fRetMidiProgram.program = uindex; + fRetMidiProgram.name = fRetMidiProgramName.toRawUTF8(); + + return &fRetMidiProgram; + } + + // ------------------------------------------------------------------- + // Plugin state calls + + void setMidiProgram(const uint8_t, const uint32_t, const uint32_t program) override + { + const int iprogram = static_cast(program); + + const NativePluginPresetManager& pm(kPrograms.get()); + CARLA_SAFE_ASSERT_RETURN(iprogram < pm.filenames.size(),); + + const char* const filename(pm.filenames.strings.getUnchecked(iprogram).toRawUTF8()); + + const CarlaMutexLocker cml(fProgramChangeMutex); + + if (isOffline()) + { + setStateFromFile(filename); + } + else + { + fNextFilename = filename; + hostRequestIdle(); + } + } + + // ------------------------------------------------------------------- + // Plugin process calls + + void process(const float** const inBuffer, float** const outBuffer, uint32_t frames, + const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override + { + const CarlaMutexTryLocker cmtl(fProgramChangeMutex, isOffline()); + + if (cmtl.wasLocked()) + { + process2(inBuffer, outBuffer, frames, midiEvents, midiEventCount); + } + else + { + for (uint32_t i=0; i(numUsed + 1))) return false; new (data.elements + numUsed++) ElementType (newElement); diff --git a/source/modules/water/memory/SharedResourcePointer.h b/source/modules/water/memory/SharedResourcePointer.h index 612aaa4c8..41a5df0a0 100644 --- a/source/modules/water/memory/SharedResourcePointer.h +++ b/source/modules/water/memory/SharedResourcePointer.h @@ -3,7 +3,7 @@ This file is part of the Water library. Copyright (c) 2016 ROLI Ltd. - Copyright (C) 2017 Filipe Coelho + Copyright (C) 2017-2019 Filipe Coelho Permission is granted to use this software under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license/ @@ -94,11 +94,19 @@ public: then a shared object will be created automatically. */ SharedResourcePointer() + : sharedObject(nullptr) { initialise(); } + SharedResourcePointer(const char* const v1, const char* const v2) + : sharedObject(nullptr) + { + initialise_v2(v1, v2); + } + SharedResourcePointer (const SharedResourcePointer&) + : sharedObject(nullptr) { initialise(); } @@ -140,7 +148,7 @@ private: static SharedObjectHolder& getSharedObjectHolder() noexcept { - static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 }; + static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { nullptr }; return *reinterpret_cast (holder); } @@ -157,6 +165,17 @@ private: sharedObject = holder.sharedInstance; } + void initialise_v2(const char* const v1, const char* const v2) + { + SharedObjectHolder& holder = getSharedObjectHolder(); + const SpinLock::ScopedLockType sl (holder.lock); + + if (++(holder.refCount) == 1) + holder.sharedInstance = new SharedObjectType(v1, v2); + + sharedObject = holder.sharedInstance; + } + // There's no need to assign to a SharedResourcePointer because every // instance of the class is exactly the same! SharedResourcePointer& operator= (const SharedResourcePointer&) WATER_DELETED_FUNCTION; diff --git a/source/modules/water/threads/SpinLock.h b/source/modules/water/threads/SpinLock.h index bf9046899..a2d72b175 100644 --- a/source/modules/water/threads/SpinLock.h +++ b/source/modules/water/threads/SpinLock.h @@ -47,7 +47,7 @@ namespace water { class SpinLock { public: - inline SpinLock() noexcept {} + inline SpinLock() noexcept : lock() {} inline ~SpinLock() noexcept {} /** Acquires the lock. diff --git a/source/native-plugins/_data.cpp b/source/native-plugins/_data.cpp index 533015fc7..c87168e69 100644 --- a/source/native-plugins/_data.cpp +++ b/source/native-plugins/_data.cpp @@ -237,6 +237,7 @@ static const NativePluginDescriptor sNativePluginDescriptors[] = { |NATIVE_PLUGIN_HAS_INLINE_DISPLAY |NATIVE_PLUGIN_HAS_UI |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE + |NATIVE_PLUGIN_REQUESTS_IDLE |NATIVE_PLUGIN_USES_TIME), /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING, /* audioIns */ 0, @@ -260,6 +261,7 @@ static const NativePluginDescriptor sNativePluginDescriptors[] = { /* hints */ static_cast(NATIVE_PLUGIN_IS_RTSAFE |NATIVE_PLUGIN_HAS_UI |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE + |NATIVE_PLUGIN_REQUESTS_IDLE |NATIVE_PLUGIN_USES_STATE |NATIVE_PLUGIN_USES_TIME), /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING, diff --git a/source/native-plugins/audio-file.cpp b/source/native-plugins/audio-file.cpp index 94d752a29..d2587ae0d 100644 --- a/source/native-plugins/audio-file.cpp +++ b/source/native-plugins/audio-file.cpp @@ -12,22 +12,35 @@ * 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 + * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ -#include "CarlaNative.hpp" +#include "CarlaNativePrograms.hpp" #include "CarlaString.hpp" #include "audio-base.hpp" -#define PROGRAM_COUNT 16 +static const char* const audiofilesWildcard = +#ifdef HAVE_SNDFILE + "*.aif;*.aifc;*.aiff;*.au;*.bwf;*.flac;*.htk;*.iff;*.mat4;*.mat5;*.oga;*.ogg;" + "*.paf;*.pvf;*.pvf5;*.sd2;*.sf;*.snd;*.svx;*.vcc;*.w64;*.wav;*.xi;" +#endif +#ifdef HAVE_FFMPEG + "*.3g2;*.3gp;*.aac;*.ac3;*.amr;*.ape;*.mp2;*.mp3;*.mpc;*.wma;" +# ifndef HAVE_SNDFILE + "*.flac;*.oga;*.ogg;*.w64;*.wav;" +# endif +#endif +; + +// ----------------------------------------------------------------------- -class AudioFilePlugin : public NativePluginClass, +class AudioFilePlugin : public NativePluginWithMidiPrograms, public AbstractAudioPlayer { public: AudioFilePlugin(const NativeHostDescriptor* const host) - : NativePluginClass(host), + : NativePluginWithMidiPrograms(host, fPrograms, 2), AbstractAudioPlayer(), fLoopMode(true), fDoProcess(false), @@ -35,6 +48,7 @@ public: fMaxFrame(0), fPool(), fThread(this), + fPrograms(hostGetFilePath("audio"), audiofilesWildcard), fInlineDisplay() {} ~AudioFilePlugin() override @@ -116,8 +130,8 @@ protected: // ------------------------------------------------------------------- // Plugin process calls - void process(const float**, float** const outBuffer, const uint32_t frames, - const NativeMidiEvent*, uint32_t) override + void process2(const float**, float** const outBuffer, const uint32_t frames, + const NativeMidiEvent*, uint32_t) override { const NativeTimeInfo* const timePos(getTimeInfo()); @@ -254,6 +268,14 @@ protected: uiClosed(); } + // ------------------------------------------------------------------- + // Plugin state calls + + void setStateFromFile(const char* const filename) override + { + loadFilename(filename); + } + // ------------------------------------------------------------------- // Plugin dispatcher calls @@ -374,6 +396,8 @@ private: AudioFilePool fPool; AudioFileThread fThread; + NativeMidiPrograms fPrograms; + struct InlineDisplay : NativeInlineDisplayImageSurfaceCompat { float lastValuesL[32]; float lastValuesR[32]; @@ -454,6 +478,7 @@ static const NativePluginDescriptor audiofileDesc = { |NATIVE_PLUGIN_HAS_INLINE_DISPLAY |NATIVE_PLUGIN_HAS_UI |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE + |NATIVE_PLUGIN_REQUESTS_IDLE |NATIVE_PLUGIN_USES_TIME), /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING, /* audioIns */ 0, diff --git a/source/native-plugins/midi-file.cpp b/source/native-plugins/midi-file.cpp index ba0c0ca34..236a6b9f9 100644 --- a/source/native-plugins/midi-file.cpp +++ b/source/native-plugins/midi-file.cpp @@ -15,7 +15,7 @@ * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ -#include "CarlaNative.hpp" +#include "CarlaNativePrograms.hpp" #include "midi-base.hpp" #include "water/files/FileInputStream.h" @@ -23,15 +23,16 @@ // ----------------------------------------------------------------------- -class MidiFilePlugin : public NativePluginClass, +class MidiFilePlugin : public NativePluginWithMidiPrograms, public AbstractMidiPlayer { public: MidiFilePlugin(const NativeHostDescriptor* const host) - : NativePluginClass(host), + : NativePluginWithMidiPrograms(host, fPrograms, 0), fMidiOut(this), fNeedsAllNotesOff(false), - fWasPlayingBefore(false) {} + fWasPlayingBefore(false), + fPrograms(hostGetFilePath("midi"), "*.mid;*.midi") {} protected: // ------------------------------------------------------------------- @@ -51,7 +52,7 @@ protected: // ------------------------------------------------------------------- // Plugin process calls - void process(const float**, float**, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override + void process2(const float**, float**, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override { const NativeTimeInfo* const timePos(getTimeInfo()); @@ -116,6 +117,11 @@ protected: fMidiOut.setState(data); } + void setStateFromFile(const char* const filename) override + { + _loadMidiFile(filename); + } + // ------------------------------------------------------------------- // AbstractMidiPlayer calls @@ -140,6 +146,7 @@ private: MidiPattern fMidiOut; bool fNeedsAllNotesOff; bool fWasPlayingBefore; + NativeMidiPrograms fPrograms; void _loadMidiFile(const char* const filename) { @@ -223,6 +230,7 @@ static const NativePluginDescriptor midifileDesc = { /* hints */ static_cast(NATIVE_PLUGIN_IS_RTSAFE |NATIVE_PLUGIN_HAS_UI |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE + |NATIVE_PLUGIN_REQUESTS_IDLE |NATIVE_PLUGIN_USES_STATE |NATIVE_PLUGIN_USES_TIME), /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING, diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index 648682e41..827b437ea 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -79,6 +79,23 @@ const char* BinaryType2Str(const BinaryType type) noexcept return nullptr; } +static inline +const char* FileType2Str(const FileType type) noexcept +{ + switch (type) + { + case FILE_NONE: + return "FILE_NONE"; + case FILE_AUDIO: + return "FILE_AUDIO"; + case FILE_MIDI: + return "FILE_MIDI"; + } + + carla_stderr("CarlaBackend::FileType2Str(%i) - invalid type", type); + return nullptr; +} + static inline const char* PluginType2Str(const PluginType type) noexcept { @@ -337,6 +354,8 @@ const char* EngineOption2Str(const EngineOption option) noexcept return "ENGINE_OPTION_OSC_PORT_UDP"; case ENGINE_OPTION_OSC_PORT_TCP: return "ENGINE_OPTION_OSC_PORT_TCP"; + case ENGINE_OPTION_FILE_PATH: + return "ENGINE_OPTION_FILE_PATH"; case ENGINE_OPTION_PLUGIN_PATH: return "ENGINE_OPTION_PLUGIN_PATH"; case ENGINE_OPTION_PATH_BINARIES: