diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index b03443940..91ab594b4 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -1332,7 +1332,7 @@ void CarlaEngine::callback(const bool sendHost, const bool sendOsc, if (sendOsc) { #if defined(HAVE_LIBLO) && ! defined(BUILD_BRIDGE) - if (pData->osc.isControlRegistered()) + if (pData->osc.isControlRegisteredForTCP()) { switch (action) { @@ -1823,7 +1823,7 @@ void CarlaEngine::setOption(const EngineOption option, const int value, const ch bool CarlaEngine::isOscControlRegistered() const noexcept { # ifdef HAVE_LIBLO - return pData->osc.isControlRegistered(); + return pData->osc.isControlRegisteredForTCP(); # else return false; # endif diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 19270bdad..256ff98c1 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -238,8 +238,11 @@ protected: } else if (std::strcmp(msg, "patchbay_refresh") == 0) { + bool external; + CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(external), true); + try { - ok = fEngine->patchbayRefresh(false); + ok = fEngine->patchbayRefresh(external); } CARLA_SAFE_EXCEPTION("patchbayRefresh"); } else if (std::strcmp(msg, "transport_play") == 0) diff --git a/source/backend/engine/CarlaEngineOsc.cpp b/source/backend/engine/CarlaEngineOsc.cpp index 1a239499d..4056fe37f 100644 --- a/source/backend/engine/CarlaEngineOsc.cpp +++ b/source/backend/engine/CarlaEngineOsc.cpp @@ -183,6 +183,9 @@ void CarlaEngineOsc::close() noexcept CARLA_SAFE_ASSERT(fServerUDP != nullptr); carla_debug("CarlaEngineOsc::close()"); + if (fControlDataTCP.target != nullptr) + sendExit(); + fName.clear(); if (fServerTCP != nullptr) diff --git a/source/backend/engine/CarlaEngineOsc.hpp b/source/backend/engine/CarlaEngineOsc.hpp index 402e00059..54c045fe4 100644 --- a/source/backend/engine/CarlaEngineOsc.hpp +++ b/source/backend/engine/CarlaEngineOsc.hpp @@ -79,9 +79,14 @@ public: // ------------------------------------------------------------------- - bool isControlRegistered() const noexcept + bool isControlRegisteredForTCP() const noexcept { - return (fControlDataTCP.target != nullptr); + return fControlDataTCP.target != nullptr; + } + + bool isControlRegisteredForUDP() const noexcept + { + return fControlDataUDP.target != nullptr; } // ------------------------------------------------------------------- @@ -95,11 +100,13 @@ public: void sendPluginPortCount(const CarlaPlugin* const plugin) const noexcept; void sendPluginParameterInfo(const CarlaPlugin* const plugin, const uint32_t parameterId) const noexcept; void sendPluginInternalParameterValues(const CarlaPlugin* const plugin) const noexcept; - void sendRuntimeInfo() const noexcept; + void sendPing() const noexcept; + void sendExit() const noexcept; // ------------------------------------------------------------------- // UDP + void sendRuntimeInfo() const noexcept; void sendParameterValue(const uint pluginId, const uint32_t index, const float value) const noexcept; void sendPeaks(const uint pluginId, const float peaks[4]) const noexcept; @@ -125,6 +132,8 @@ private: int handleMsgRegister(const bool isTCP, const int argc, const lo_arg* const* const argv, const char* const types); int handleMsgUnregister(const bool isTCP, const int argc, const lo_arg* const* const argv, const char* const types); + int handleMsgControl(const char* const method, + const int argc, const lo_arg* const* const argv, const char* const types); // Internal methods int handleMsgSetActive(CARLA_ENGINE_OSC_HANDLE_ARGS); diff --git a/source/backend/engine/CarlaEngineOscHandlers.cpp b/source/backend/engine/CarlaEngineOscHandlers.cpp index 24db2b083..b8c2d29e2 100644 --- a/source/backend/engine/CarlaEngineOscHandlers.cpp +++ b/source/backend/engine/CarlaEngineOscHandlers.cpp @@ -57,12 +57,19 @@ int CarlaEngineOsc::handleMessage(const bool isTCP, const char* const path, cons if (std::strcmp(path, "/unregister") == 0) return handleMsgUnregister(isTCP, argc, argv, types); + if (std::strncmp(path, "/ctrl/", 6) == 0) + { + CARLA_SAFE_ASSERT_RETURN(isTCP, 1); + return handleMsgControl(path + 6, argc, argv, types); + } + const std::size_t nameSize(fName.length()); // Check if message is for this client if (std::strlen(path) <= nameSize || std::strncmp(path+1, fName, nameSize) != 0) { - carla_stderr("CarlaEngineOsc::handleMessage() - message not for this client -> '%s' != '/%s/'", path, fName.buffer()); + carla_stderr("CarlaEngineOsc::handleMessage() - message not for this client -> '%s' != '/%s/'", + path, fName.buffer()); return 1; } @@ -213,29 +220,28 @@ int CarlaEngineOsc::handleMsgRegister(const bool isTCP, oscData.owner = carla_strdup_safe(url); oscData.path = carla_strdup_free(lo_url_get_path(url)); - oscData.source = lo_address_new_with_proto(isTCP ? LO_TCP : LO_UDP, host, port); oscData.target = target; - for (uint i=0, count=fEngine->getCurrentPluginCount(); i < count; ++i) + if (isTCP) { - CarlaPlugin* const plugin(fEngine->getPluginUnchecked(i)); - CARLA_SAFE_ASSERT_CONTINUE(plugin != nullptr); - - fEngine->callback(false, true, ENGINE_CALLBACK_PLUGIN_ADDED, i, 0, 0, 0, 0.0f, plugin->getName()); - } + const EngineOptions& opts(fEngine->getOptions()); - const EngineOptions& opts(fEngine->getOptions()); + fEngine->callback(false, true, + ENGINE_CALLBACK_ENGINE_STARTED, 0, + opts.processMode, + opts.transportMode, + static_cast(fEngine->getBufferSize()), + static_cast(fEngine->getSampleRate()), + fEngine->getCurrentDriverName()); - fEngine->callback(false, true, - ENGINE_CALLBACK_ENGINE_STARTED, 0, - opts.processMode, - opts.transportMode, - static_cast(fEngine->getBufferSize()), - static_cast(fEngine->getSampleRate()), - fEngine->getCurrentDriverName()); + for (uint i=0, count=fEngine->getCurrentPluginCount(); i < count; ++i) + { + CarlaPlugin* const plugin(fEngine->getPluginUnchecked(i)); + CARLA_SAFE_ASSERT_CONTINUE(plugin != nullptr); - // TODO - // fEngine->patchbayRefresh(); + fEngine->callback(false, true, ENGINE_CALLBACK_PLUGIN_ADDED, i, 0, 0, 0, 0.0f, plugin->getName()); + } + } } lo_address_free(addr); @@ -269,6 +275,137 @@ int CarlaEngineOsc::handleMsgUnregister(const bool isTCP, return 0; } +int CarlaEngineOsc::handleMsgControl(const char* const method, + const int argc, const lo_arg* const* const argv, const char* const types) +{ + carla_debug("CarlaEngineOsc::handleMsgControl()"); + CARLA_SAFE_ASSERT_RETURN(method != nullptr && method[0] != '\0', 0); + CARLA_SAFE_ASSERT_RETURN(types != nullptr, 0); + + if (fControlDataTCP.owner == nullptr) + { + carla_stderr("OSC backend is not registered yet, control failed"); + return 0; + } + + carla_stdout("OSC control for '%s'", method); + +// "patchbay_refresh", +// "transport_play", +// "transport_pause", +// "transport_relocate", +// "transport_bpm", + + + /**/ if (std::strcmp(method, "clear_engine_xruns") == 0) + { + fEngine->clearXruns(); + } + else if (std::strcmp(method, "cancel_engine_action") == 0) + { + fEngine->setActionCanceled(true); + } +// "patchbay_connect", +// "patchbay_disconnect", + else if (std::strcmp(method, "patchbay_connect") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 4, argc, 0); + CARLA_SAFE_ASSERT_RETURN(types[0] == 'i', 0); + CARLA_SAFE_ASSERT_RETURN(types[1] == 'i', 0); + CARLA_SAFE_ASSERT_RETURN(types[2] == 'i', 0); + CARLA_SAFE_ASSERT_RETURN(types[3] == 'i', 0); + + const int32_t i0 = argv[0]->i; + CARLA_SAFE_ASSERT_RETURN(i0 >= 0, 0); + + const int32_t i1 = argv[1]->i; + CARLA_SAFE_ASSERT_RETURN(i1 >= 0, 0); + + const int32_t i2 = argv[2]->i; + CARLA_SAFE_ASSERT_RETURN(i2 >= 0, 0); + + const int32_t i3 = argv[3]->i; + CARLA_SAFE_ASSERT_RETURN(i3 >= 0, 0); + + fEngine->patchbayConnect(static_cast(i0), + static_cast(i1), + static_cast(i2), + static_cast(i3)); + } + else if (std::strcmp(method, "patchbay_disconnect") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 1, argc, 0); + CARLA_SAFE_ASSERT_RETURN(types[0] == 'i', 0); + + const int32_t i = argv[0]->i; + CARLA_SAFE_ASSERT_RETURN(i >= 0, 0); + + fEngine->patchbayDisconnect(static_cast(i)); + } + else if (std::strcmp(method, "patchbay_refresh") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 1, argc, 0); + CARLA_SAFE_ASSERT_RETURN(types[0] == 'i', 0); + + const int32_t i = argv[0]->i; + fEngine->patchbayRefresh(i != 0); + } + else if (std::strcmp(method, "transport_play") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 0, argc, 0); + fEngine->transportPlay(); + } + else if (std::strcmp(method, "transport_pause") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 0, argc, 0); + fEngine->transportPause(); + } + else if (std::strcmp(method, "transport_bpm") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 1, argc, 0); + CARLA_SAFE_ASSERT_RETURN(types[0] == 'f', 0); + + const double f = argv[0]->f; + CARLA_SAFE_ASSERT_RETURN(f >= 0.0, 0); + + fEngine->transportBPM(f); + } + else if (std::strcmp(method, "transport_relocate") == 0) + { + CARLA_SAFE_ASSERT_INT_RETURN(argc == 1, argc, 0); + uint64_t frame; + + /**/ if (types[0] == 'i') + { + const int32_t i = argv[0]->i; + CARLA_SAFE_ASSERT_RETURN(i >= 0, 0); + frame = static_cast(i); + } + else if (types[0] == 'h') + { + const int64_t h = argv[0]->h; + CARLA_SAFE_ASSERT_RETURN(h >= 0, 0); + frame = static_cast(h); + } + else + { + carla_stderr2("Wrong OSC type used for '%s'", method); + return 0; + } + + fEngine->transportRelocate(frame); + } + +// #"add_plugin", +// "remove_plugin", +// "remove_all_plugins", +// "rename_plugin", +// "clone_plugin", +// "replace_plugin", +// "switch_plugins", + return 0; +} + // ----------------------------------------------------------------------- int CarlaEngineOsc::handleMsgSetActive(CARLA_ENGINE_OSC_HANDLE_ARGS) diff --git a/source/backend/engine/CarlaEngineOscSend.cpp b/source/backend/engine/CarlaEngineOscSend.cpp index e699420dd..23f7dd032 100644 --- a/source/backend/engine/CarlaEngineOscSend.cpp +++ b/source/backend/engine/CarlaEngineOscSend.cpp @@ -33,8 +33,9 @@ void CarlaEngineOsc::sendCallback(const EngineCallbackOpcode action, const uint { CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); - carla_debug("CarlaEngineOsc::sendCallback(%i:%s, %i, %i, %i, %i, %f, \"%s\")", - action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valuef, valueStr); + carla_stdout("CarlaEngineOsc::sendCallback(%i:%s, %i, %i, %i, %i, %f, \"%s\")", + action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, + static_cast(valuef), valueStr); static const char* const kFakeNullString = "(null)"; @@ -51,7 +52,7 @@ void CarlaEngineOsc::sendPluginInit(const uint pluginId, const char* const plugi CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); CARLA_SAFE_ASSERT_RETURN(pluginName != nullptr && pluginName[0] != '\0',); - carla_debug("CarlaEngineOsc::sendadd_plugin_start(%i, \"%s\")", pluginId, pluginName); + carla_stdout("CarlaEngineOsc::sendPluginInit(%i, \"%s\")", pluginId, pluginName); char targetPath[std::strlen(fControlDataTCP.path)+18]; std::strcpy(targetPath, fControlDataTCP.path); @@ -64,7 +65,7 @@ void CarlaEngineOsc::sendPluginInfo(const CarlaPlugin* const plugin) const noexc CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); CARLA_SAFE_ASSERT_RETURN(plugin != nullptr,); - carla_debug("CarlaEngineOsc::sendPluginInfo(%p)", plugin); + carla_stdout("CarlaEngineOsc::sendPluginInfo(%p)", plugin); char bufName[STR_MAX+1], bufLabel[STR_MAX+1], bufMaker[STR_MAX+1], bufCopyright[STR_MAX+1]; carla_zeroChars(bufName, STR_MAX+1); @@ -94,7 +95,7 @@ void CarlaEngineOsc::sendPluginPortCount(const CarlaPlugin* const plugin) const CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); CARLA_SAFE_ASSERT_RETURN(plugin != nullptr,); - carla_debug("CarlaEngineOsc::sendPluginInfo(%p)", plugin); + carla_stdout("CarlaEngineOsc::sendPluginInfo(%p)", plugin); uint32_t paramIns, paramOuts; plugin->getParameterCountInfo(paramIns, paramOuts); @@ -235,20 +236,43 @@ void CarlaEngineOsc::sendset_midi_program_data(const uint pluginId, const uint32 // ----------------------------------------------------------------------- -// FIXME use UDP +void CarlaEngineOsc::sendPing() const noexcept +{ + CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); -void CarlaEngineOsc::sendRuntimeInfo() const noexcept + char targetPath[std::strlen(fControlDataTCP.path)+6]; + std::strcpy(targetPath, fControlDataTCP.path); + std::strcat(targetPath, "/ping"); + try_lo_send(fControlDataTCP.target, targetPath, ""); +} + +void CarlaEngineOsc::sendExit() const noexcept { CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); + carla_debug("CarlaEngineOsc::sendExit()"); + + char targetPath[std::strlen(fControlDataTCP.path)+6]; + std::strcpy(targetPath, fControlDataTCP.path); + std::strcat(targetPath, "/exit"); + try_lo_send(fControlDataTCP.target, targetPath, ""); +} + +// ----------------------------------------------------------------------- + +void CarlaEngineOsc::sendRuntimeInfo() const noexcept +{ + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.path != nullptr && fControlDataUDP.path[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.target != nullptr,); carla_debug("CarlaEngineOsc::sendRuntimeInfo()"); const EngineTimeInfo timeInfo(fEngine->getTimeInfo()); - char targetPath[std::strlen(fControlDataTCP.path)+18]; - std::strcpy(targetPath, fControlDataTCP.path); + char targetPath[std::strlen(fControlDataUDP.path)+18]; + std::strcpy(targetPath, fControlDataUDP.path); std::strcat(targetPath, "/runtime"); - try_lo_send(fControlDataTCP.target, targetPath, "fiihiiif", + try_lo_send(fControlDataUDP.target, targetPath, "fiihiiif", static_cast(fEngine->getDSPLoad()), static_cast(fEngine->getTotalXruns()), timeInfo.playing ? 1 : 0, @@ -261,13 +285,13 @@ void CarlaEngineOsc::sendRuntimeInfo() const noexcept void CarlaEngineOsc::sendParameterValue(const uint pluginId, const uint32_t index, const float value) const noexcept { - CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); - CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.path != nullptr && fControlDataUDP.path[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.target != nullptr,); - char targetPath[std::strlen(fControlDataTCP.path)+21]; - std::strcpy(targetPath, fControlDataTCP.path); - std::strcat(targetPath, "/param_fixme"); - try_lo_send(fControlDataTCP.target, targetPath, "iif", + char targetPath[std::strlen(fControlDataUDP.path)+21]; + std::strcpy(targetPath, fControlDataUDP.path); + std::strcat(targetPath, "/param"); + try_lo_send(fControlDataUDP.target, targetPath, "iif", static_cast(pluginId), index, static_cast(value)); @@ -275,13 +299,13 @@ void CarlaEngineOsc::sendParameterValue(const uint pluginId, const uint32_t inde void CarlaEngineOsc::sendPeaks(const uint pluginId, const float peaks[4]) const noexcept { - CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.path != nullptr && fControlDataTCP.path[0] != '\0',); - CARLA_SAFE_ASSERT_RETURN(fControlDataTCP.target != nullptr,); + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.path != nullptr && fControlDataUDP.path[0] != '\0',); + CARLA_SAFE_ASSERT_RETURN(fControlDataUDP.target != nullptr,); - char targetPath[std::strlen(fControlDataTCP.path)+11]; - std::strcpy(targetPath, fControlDataTCP.path); + char targetPath[std::strlen(fControlDataUDP.path)+11]; + std::strcpy(targetPath, fControlDataUDP.path); std::strcat(targetPath, "/peaks"); - try_lo_send(fControlDataTCP.target, targetPath, "iffff", static_cast(pluginId), + try_lo_send(fControlDataUDP.target, targetPath, "iffff", static_cast(pluginId), static_cast(peaks[0]), static_cast(peaks[1]), static_cast(peaks[2]), diff --git a/source/backend/engine/CarlaEngineThread.cpp b/source/backend/engine/CarlaEngineThread.cpp index f71e2a092..7030e15b7 100644 --- a/source/backend/engine/CarlaEngineThread.cpp +++ b/source/backend/engine/CarlaEngineThread.cpp @@ -19,6 +19,8 @@ #include "CarlaEngineInternal.hpp" #include "CarlaPlugin.hpp" +#include "water/misc/Time.h" + CARLA_BACKEND_START_NAMESPACE // ----------------------------------------------------------------------- @@ -49,7 +51,8 @@ void CarlaEngineThread::run() noexcept float value; #if defined(HAVE_LIBLO) && ! defined(BUILD_BRIDGE) - CarlaEngineOsc& engineOsc(kEngine->pData->osc); + // int64_t lastPingTime = 0; + const CarlaEngineOsc& engineOsc(kEngine->pData->osc); #endif // thread must do something... @@ -58,16 +61,14 @@ void CarlaEngineThread::run() noexcept for (; (kIsAlwaysRunning || kEngine->isRunning()) && ! shouldThreadExit();) { #if defined(HAVE_LIBLO) && ! defined(BUILD_BRIDGE) - const bool oscRegisted = kEngine->isOscControlRegistered(); + const bool oscRegistedForUDP = engineOsc.isControlRegisteredForUDP(); #else - const bool oscRegisted = false; + const bool oscRegistedForUDP = false; #endif #if defined(HAVE_LIBLO) && !defined(BUILD_BRIDGE) if (kIsPlugin) engineOsc.idle(); - if (oscRegisted) - engineOsc.sendRuntimeInfo(); #endif for (uint i=0, count = kEngine->getCurrentPluginCount(); i < count; ++i) @@ -90,7 +91,7 @@ void CarlaEngineThread::run() noexcept // ----------------------------------------------------------- // Post-poned events - if (oscRegisted || updateUI) + if (oscRegistedForUDP || updateUI) { // ------------------------------------------------------- // Update parameter outputs @@ -104,7 +105,7 @@ void CarlaEngineThread::run() noexcept #if defined(HAVE_LIBLO) && ! defined(BUILD_BRIDGE) // Update OSC engine client - if (oscRegisted) + if (oscRegistedForUDP) engineOsc.sendParameterValue(i, j, value); #endif // Update UI @@ -124,11 +125,29 @@ void CarlaEngineThread::run() noexcept // ----------------------------------------------------------- // Update OSC control client peaks - if (oscRegisted) + if (oscRegistedForUDP) engineOsc.sendPeaks(i, kEngine->getPeaks(i)); #endif } +#if defined(HAVE_LIBLO) && !defined(BUILD_BRIDGE) + if (oscRegistedForUDP) + engineOsc.sendRuntimeInfo(); + + /* + if (engineOsc.isControlRegisteredForTCP()) + { + const int64_t timeNow = water::Time::currentTimeMillis(); + + if (timeNow - lastPingTime > 1000) + { + engineOsc.sendPing(); + lastPingTime = timeNow; + } + } + */ +#endif + carla_msleep(25); } diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index 63c753b20..b8ca9d333 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -3034,8 +3034,7 @@ class CarlaHostPlugin(CarlaHostMeta): return self.sendMsgAndSetError(["patchbay_disconnect", connectionId]) def patchbay_refresh(self, external): - # don't send external param, never used in plugins - return self.sendMsgAndSetError(["patchbay_refresh"]) + return self.sendMsgAndSetError(["patchbay_refresh", external]) def transport_play(self): self.sendMsg(["transport_play"]) @@ -3047,7 +3046,7 @@ class CarlaHostPlugin(CarlaHostMeta): self.sendMsg(["transport_bpm", bpm]) def transport_relocate(self, frame): - self.sendMsg(["transport_relocate"]) + self.sendMsg(["transport_relocate", frame]) def get_current_transport_frame(self): return self.fTransportInfo['frame'] @@ -3313,6 +3312,15 @@ class CarlaHostPlugin(CarlaHostMeta): self._reset_info(info) self.fPluginsInfo.append(info) + def _allocateAsNeeded(self, pluginId): + if pluginId < len(self.fPluginsInfo): + return + + for i in range(pluginId + 1 - len(self.fPluginsInfo)): + info = PluginStoreInfo() + self._reset_info(info) + self.fPluginsInfo.append(info) + def _reset_info(self, info): info.pluginInfo = PyCarlaPluginInfo.copy() info.pluginRealName = "" @@ -3352,6 +3360,7 @@ class CarlaHostPlugin(CarlaHostMeta): self.fPluginsInfo[pluginId].internalValues[abs(paramIndex)-2] = float(value) def _set_audioCountInfo(self, pluginId, info): + #self._allocateAsNeeded(pluginId) self.fPluginsInfo[pluginId].audioCountInfo = info def _set_midiCountInfo(self, pluginId, info): @@ -3455,7 +3464,8 @@ class CarlaHostPlugin(CarlaHostMeta): self.fPluginsInfo[pluginId].customData[cdIndex] = data def _set_peaks(self, pluginId, in1, in2, out1, out2): - self.fPluginsInfo[pluginId].peaks = [in1, in2, out1, out2] + if pluginId < len(self.fPluginsInfo): + self.fPluginsInfo[pluginId].peaks = [in1, in2, out1, out2] def _switchPlugins(self, pluginIdA, pluginIdB): tmp = self.fPluginsInfo[pluginIdA] diff --git a/source/frontend/carla_control.py b/source/frontend/carla_control.py index aa5d37956..25452f672 100755 --- a/source/frontend/carla_control.py +++ b/source/frontend/carla_control.py @@ -44,8 +44,12 @@ class CarlaHostOSC(CarlaHostQtPlugin): def __init__(self): CarlaHostQtPlugin.__init__(self) - self.lo_target = None - self.lo_target_name = "" + self.lo_server_tcp = None + self.lo_server_udp = None + self.lo_target_tcp = None + self.lo_target_udp = None + self.lo_target_tcp_name = "" + self.lo_target_udp_name = "" # ------------------------------------------------------------------- @@ -61,87 +65,96 @@ class CarlaHostOSC(CarlaHostQtPlugin): method = lines.pop(0) if method == "set_engine_option": + print(method, lines) return True - if self.lo_target is None: - return self.printAndReturnError("lo_target is None") - if self.lo_target_name is None: - return self.printAndReturnError("lo_target_name is None") - - if method not in ( - #"set_option", - "set_active", - "set_drywet", - "set_volume", - "set_balance_left", - "set_balance_right", - "set_panning", - #"set_ctrl_channel", - "set_parameter_value", - "set_parameter_midi_channel", - "set_parameter_midi_cc", - "set_program", - "set_midi_program", - #"set_custom_data", - #"set_chunk_data", - #"prepare_for_save", - #"reset_parameters", - #"randomize_parameters", - "send_midi_note" - ): - return self.printAndReturnError("invalid method '%s'" % method) - - pluginId = lines.pop(0) - - args = [] - - if method == "send_midi_note": - channel, note, velocity = lines - - if velocity: - method = "note_on" - args = [channel, note, velocity] - else: - method = "note_off" - args = [channel, note] + if self.lo_target_tcp is None: + return self.printAndReturnError("lo_target_tcp is None") + if self.lo_target_tcp_name is None: + return self.printAndReturnError("lo_target_tcp_name is None") + + if method in ("clear_engine_xruns", + "cancel_engine_action", + #"load_file", + #"load_project", + #"save_project", + #"clear_project_filename", + "patchbay_connect", + "patchbay_disconnect", + "patchbay_refresh", + "transport_play", + "transport_pause", + "transport_bpm", + "transport_relocate", + #"add_plugin", + "remove_plugin", + "remove_all_plugins", + "rename_plugin", + "clone_plugin", + #"replace_plugin", + "switch_plugins", + #"load_plugin_state", + #"save_plugin_state", + ): + path = "/ctrl/" + method + + elif method in (#"set_option", + "set_active", + "set_drywet", + "set_volume", + "set_balance_left", + "set_balance_right", + "set_panning", + #"set_ctrl_channel", + "set_parameter_value", + "set_parameter_midi_channel", + "set_parameter_midi_cc", + "set_program", + "set_midi_program", + #"set_custom_data", + #"set_chunk_data", + #"prepare_for_save", + #"reset_parameters", + #"randomize_parameters", + "send_midi_note" + ): + pluginId = lines.pop(0) + path = "/%s/%i/%s" % (self.lo_target_tcp_name, pluginId, method) else: - for line in lines: - if isinstance(line, bool): - args.append(int(line)) - else: - args.append(line) - - path = "/%s/%i/%s" % (self.lo_target_name, pluginId, method) + return self.printAndReturnError("invalid method '%s'" % method) - print(path, args) + args = [int(line) if isinstance(line, bool) else line for line in lines] + #print(path, args) - lo_send(self.lo_target, path, *args) + lo_send(self.lo_target_tcp, path, *args) return True # ------------------------------------------------------------------- def engine_init(self, driverName, clientName): - return self.lo_target is not None + print("engine_init", self.lo_target_tcp is not None) + return self.lo_target_tcp is not None def engine_close(self): + print("engine_close") return True def engine_idle(self): return def is_engine_running(self): - return self.lo_target is not None + return self.lo_target_tcp is not None def set_engine_about_to_close(self): return -# ------------------------------------------------------------------------------------------------------------ +# --------------------------------------------------------------------------------------------------------------------- # OSC Control server -class CarlaControlServer(Server): - def __init__(self, host, mode): - Server.__init__(self, 8998 + int(random()*9000), mode) +class CarlaControlServerTCP(Server): + def __init__(self, host): + Server.__init__(self, proto=LO_TCP) self.host = host @@ -152,36 +165,32 @@ class CarlaControlServer(Server): pass def getFullURL(self): - return "%scarla-control" % self.get_url() + return "%sctrl" % self.get_url() - @make_method('/carla-control/cb', 'iiiiifs') + @make_method('/ctrl/cb', 'iiiiifs') def carla_cb(self, path, args): - print(path, args) self.fReceivedMsgs = True action, pluginId, value1, value2, value3, valuef, valueStr = args self.host._setViaCallback(action, pluginId, value1, value2, value3, valuef, valueStr) engineCallback(self.host, action, pluginId, value1, value2, value3, valuef, valueStr) - @make_method('/carla-control/init', 'is') # FIXME set name in add method + @make_method('/ctrl/init', 'is') # FIXME set name in add method def carla_init(self, path, args): - print(path, args) self.fReceivedMsgs = True pluginId, pluginName = args self.host._add(pluginId) self.host._set_pluginInfoUpdate(pluginId, {'name': pluginName}) - @make_method('/carla-control/ports', 'iiiiiiii') + @make_method('/ctrl/ports', 'iiiiiiii') def carla_ports(self, path, args): - print(path, args) self.fReceivedMsgs = True pluginId, audioIns, audioOuts, midiIns, midiOuts, paramIns, paramOuts, paramTotal = args self.host._set_audioCountInfo(pluginId, {'ins': audioIns, 'outs': audioOuts}) self.host._set_midiCountInfo(pluginId, {'ins': midiOuts, 'outs': midiOuts}) self.host._set_parameterCountInfo(pluginId, paramTotal, {'ins': paramIns, 'outs': paramOuts}) - @make_method('/carla-control/info', 'iiiihssss') + @make_method('/ctrl/info', 'iiiihssss') def carla_info(self, path, args): - print(path, args) self.fReceivedMsgs = True pluginId, type_, category, hints, uniqueId, realName, label, maker, copyright = args optsAvail = optsEnabled = 0x0 # FIXME @@ -207,9 +216,8 @@ class CarlaControlServer(Server): self.host._set_pluginInfoUpdate(pluginId, pinfo) self.host._set_pluginRealName(pluginId, realName) - @make_method('/carla-control/param', 'iiiiiissfffffff') + @make_method('/ctrl/param', 'iiiiiissfffffff') def carla_param(self, path, args): - print(path, args) self.fReceivedMsgs = True ( pluginId, paramId, type_, hints, midiChan, midiCC, name, unit, @@ -248,9 +256,8 @@ class CarlaControlServer(Server): self.host._set_parameterValue(pluginId, paramId, value) - @make_method('/carla-control/iparams', 'ifffffff') + @make_method('/ctrl/iparams', 'ifffffff') def carla_iparams(self, path, args): - print(path, args) self.fReceivedMsgs = True pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan = args self.host._set_internalValue(pluginId, PARAMETER_ACTIVE, active) @@ -261,72 +268,97 @@ class CarlaControlServer(Server): self.host._set_internalValue(pluginId, PARAMETER_PANNING, pan) self.host._set_internalValue(pluginId, PARAMETER_CTRL_CHANNEL, ctrlChan) - @make_method('/carla-control/set_program_count', 'ii') - def set_program_count_callback(self, path, args): - print(path, args) + #@make_method('/ctrl/set_program_count', 'ii') + #def set_program_count_callback(self, path, args): + #print(path, args) + #self.fReceivedMsgs = True + #pluginId, count = args + #self.host._set_programCount(pluginId, count) + + #@make_method('/ctrl/set_midi_program_count', 'ii') + #def set_midi_program_count_callback(self, path, args): + #print(path, args) + #self.fReceivedMsgs = True + #pluginId, count = args + #self.host._set_midiProgramCount(pluginId, count) + + #@make_method('/ctrl/set_program_name', 'iis') + #def set_program_name_callback(self, path, args): + #print(path, args) + #self.fReceivedMsgs = True + #pluginId, progId, progName = args + #self.host._set_programName(pluginId, progId, progName) + + #@make_method('/ctrl/set_midi_program_data', 'iiiis') + #def set_midi_program_data_callback(self, path, args): + #print(path, args) + #self.fReceivedMsgs = True + #pluginId, midiProgId, bank, program, name = args + #self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name}) + + #@make_method('/note_on', 'iiii') + + @make_method('/ctrl/exit', '') + def carla_exit(self, path, args): self.fReceivedMsgs = True - pluginId, count = args - self.host._set_programCount(pluginId, count) + #self.host.lo_target_tcp = None + self.host.QuitCallback.emit() - @make_method('/carla-control/set_midi_program_count', 'ii') - def set_midi_program_count_callback(self, path, args): - print(path, args) + @make_method('/ctrl/exit-error', 's') + def carla_exit_error(self, path, args): self.fReceivedMsgs = True - pluginId, count = args - self.host._set_midiProgramCount(pluginId, count) + error, = args + self.host.lo_target_tcp = None + self.host.QuitCallback.emit() + self.host.ErrorCallback.emit(error) - @make_method('/carla-control/set_program_name', 'iis') - def set_program_name_callback(self, path, args): - print(path, args) + @make_method(None, None) + def fallback(self, path, args): + print("ControlServerTCP::fallback(\"%s\") - unknown message, args =" % path, args) self.fReceivedMsgs = True - pluginId, progId, progName = args - self.host._set_programName(pluginId, progId, progName) - @make_method('/carla-control/set_midi_program_data', 'iiiis') - def set_midi_program_data_callback(self, path, args): - print(path, args) - self.fReceivedMsgs = True - pluginId, midiProgId, bank, program, name = args - self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name}) +# --------------------------------------------------------------------------------------------------------------------- + +class CarlaControlServerUDP(Server): + def __init__(self, host): + Server.__init__(self, proto=LO_UDP) + + self.host = host - #@make_method('/carla-control/note_on', 'iiii') + def idle(self): + self.fReceivedMsgs = False + + while self.recv(0) and self.fReceivedMsgs: + pass - @make_method('/carla-control/runtime', 'fiihiiif') + def getFullURL(self): + return "%sctrl" % self.get_url() + + @make_method('/ctrl/runtime', 'fiihiiif') def carla_runtime(self, path, args): self.fReceivedMsgs = True load, xruns, playing, frame, bar, beat, tick, bpm = args self.host._set_runtime_info(load, xruns) self.host._set_transport(bool(playing), frame, bar, beat, tick, bpm) - @make_method('/carla-control/param_fixme', 'iif') + @make_method('/ctrl/param', 'iif') def carla_param_fixme(self, path, args): self.fReceivedMsgs = True pluginId, paramId, paramValue = args self.host._set_parameterValue(pluginId, paramId, paramValue) - @make_method('/carla-control/peaks', 'iffff') + @make_method('/ctrl/peaks', 'iffff') def carla_peaks(self, path, args): self.fReceivedMsgs = True pluginId, in1, in2, out1, out2 = args self.host._set_peaks(pluginId, in1, in2, out1, out2) - #@make_method('/carla-control/note_on', 'iiii') - - @make_method('/carla-control/exit-error', 's') - def set_exit_error_callback(self, path, args): - print(path, args) - self.fReceivedMsgs = True - error, = args - self.host.lo_target = None - self.host.QuitCallback.emit() - self.host.ErrorCallback.emit(error) - @make_method(None, None) def fallback(self, path, args): - print("ControlServer::fallback(\"%s\") - unknown message, args =" % path, args) + print("ControlServerUDP::fallback(\"%s\") - unknown message, args =" % path, args) self.fReceivedMsgs = True -# ------------------------------------------------------------------------------------------------------------ +# --------------------------------------------------------------------------------------------------------------------- # Main Window class HostWindowOSC(HostWindow): @@ -339,11 +371,6 @@ class HostWindowOSC(HostWindow): host = CarlaHostPlugin() self.host = host - # ---------------------------------------------------------------------------------------------------- - # Internal stuff - - self.fOscServer = None - # ---------------------------------------------------------------------------------------------------- # Connect actions to functions @@ -356,32 +383,36 @@ class HostWindowOSC(HostWindow): if oscAddr: QTimer.singleShot(0, self.connectOsc) - def connectOsc(self, addr = None): - if addr is not None: - self.fOscAddress = addr + def connectOsc(self, addrTCP = None, addrUDP = None): + if addrTCP is not None: + self.fOscAddressTCP = addrTCP + if addrUDP is not None: + self.fOscAddressUDP = addrUDP - lo_protocol = LO_UDP if self.fOscAddress.startswith("osc.udp") else LO_TCP - lo_target_name = self.fOscAddress.rsplit("/", 1)[-1] + lo_target_tcp_name = self.fOscAddressTCP.rsplit("/", 1)[-1] + lo_target_udp_name = self.fOscAddressUDP.rsplit("/", 1)[-1] err = None - print("Connecting to \"%s\" as '%s'..." % (self.fOscAddress, lo_target_name)) try: - lo_target = Address(self.fOscAddress) - self.fOscServer = CarlaControlServer(self.host, lo_protocol) - lo_send(lo_target, "/register", self.fOscServer.getFullURL()) + lo_target_tcp = Address(self.fOscAddressTCP) + lo_server_tcp = CarlaControlServerTCP(self.host) + lo_send(lo_target_tcp, "/register", lo_server_tcp.getFullURL()) + print(lo_server_tcp.getFullURL()) + + lo_target_udp = Address(self.fOscAddressUDP) + lo_server_udp = CarlaControlServerUDP(self.host) + lo_send(lo_target_udp, "/register", lo_server_udp.getFullURL()) + print(lo_server_udp.getFullURL()) except AddressError as e: err = e except OSError as e: err = e except: - err = { 'args': [] } + err = Exception() if err is not None: - del self.fOscServer - self.fOscServer = None - fullError = self.tr("Failed to connect to the Carla instance.") if len(err.args) > 0: @@ -400,53 +431,72 @@ class HostWindowOSC(HostWindow): QMessageBox.Ok) return - self.host.lo_target = lo_target - self.host.lo_target_name = lo_target_name + self.host.lo_server_tcp = lo_server_tcp + self.host.lo_target_tcp = lo_target_tcp + self.host.lo_target_tcp_name = lo_target_tcp_name + + self.host.lo_server_udp = lo_server_udp + self.host.lo_target_udp = lo_target_udp + self.host.lo_target_udp_name = lo_target_udp_name + self.ui.act_file_refresh.setEnabled(True) self.startTimers() def disconnectOsc(self): self.killTimers() - - if self.host.lo_target is not None: - try: - lo_send(self.host.lo_target, "/unregister", self.fOscServer.getFullURL()) - except: - pass - - if self.fOscServer is not None: - del self.fOscServer - self.fOscServer = None - + self.unregister() self.removeAllPlugins() - self.host.lo_target = None - self.host.lo_target_name = "" self.ui.act_file_refresh.setEnabled(False) # -------------------------------------------------------------------------------------------------------- - # Timers - def startTimers(self): - if self.fIdleTimerOSC == 0: - self.fIdleTimerOSC = self.startTimer(20) + def unregister(self): + if self.host.lo_server_tcp is not None: + if self.host.lo_target_tcp is not None: + try: + lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL()) + except: + pass + self.host.lo_target_tcp = None + + while self.host.lo_server_tcp.recv(0): + pass + #self.host.lo_server_tcp.free() + self.host.lo_server_tcp = None + + if self.host.lo_server_udp is not None: + if self.host.lo_target_udp is not None: + try: + lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL()) + except: + pass + self.host.lo_target_udp = None + + while self.host.lo_server_udp.recv(0): + pass + #self.host.lo_server_udp.free() + self.host.lo_server_udp = None - HostWindow.startTimers(self) + self.host.lo_target_tcp_name = "" + self.host.lo_target_udp_name = "" - def restartTimersIfNeeded(self): - if self.fIdleTimerOSC != 0: - self.killTimer(self.fIdleTimerOSC) - self.fIdleTimerOSC = self.startTimer(20) + # -------------------------------------------------------------------------------------------------------- + # Timers - HostWindow.restartTimersIfNeeded(self) + def idleFast(self): + HostWindow.idleFast(self) - def killTimers(self): - if self.fIdleTimerOSC != 0: - self.killTimer(self.fIdleTimerOSC) - self.fIdleTimerOSC = 0 + if self.host.lo_server_tcp is not None: + self.host.lo_server_tcp.idle() + else: + self.disconnectOsc() - HostWindow.killTimers(self) + if self.host.lo_server_udp is not None: + self.host.lo_server_udp.idle() + else: + self.disconnectOsc() # -------------------------------------------------------------------------------------------------------- @@ -458,12 +508,15 @@ class HostWindowOSC(HostWindow): def loadSettings(self, firstTime): settings = HostWindow.loadSettings(self, firstTime) - self.fOscAddress = settings.value("RemoteAddress", "osc.tcp://127.0.0.1:22752/Carla", type=str) + self.fOscAddressTCP = settings.value("RemoteAddressTCP", "osc.tcp://127.0.0.1:22752/Carla", type=str) + self.fOscAddressUDP = settings.value("RemoteAddressUDP", "osc.udp://127.0.0.1:22752/Carla", type=str) def saveSettings(self): settings = HostWindow.saveSettings(self) - if self.fOscAddress: - settings.setValue("RemoteAddress", self.fOscAddress) + if self.fOscAddressTCP: + settings.setValue("RemoteAddressTCP", self.fOscAddressTCP) + if self.fOscAddressUDP: + settings.setValue("RemoteAddressUDP", self.fOscAddressUDP) # -------------------------------------------------------------------------------------------------------- @@ -472,7 +525,7 @@ class HostWindowOSC(HostWindow): dialog = QInputDialog(self) dialog.setInputMode(QInputDialog.TextInput) dialog.setLabelText(self.tr("Address:")) - dialog.setTextValue(self.fOscAddress or "osc.tcp://127.0.0.1:22752/Carla") + dialog.setTextValue(self.fOscAddressTCP or "osc.tcp://127.0.0.1:22752/Carla") dialog.setWindowTitle(self.tr("Carla Control - Connect")) dialog.resize(400,1) @@ -488,45 +541,50 @@ class HostWindowOSC(HostWindow): self.disconnectOsc() if addr: - self.connectOsc(addr) + self.connectOsc(addr.replace("osc.udp:", "osc.tcp:"), addr.replace("osc.tcp:", "osc.udp:")) @pyqtSlot() def slot_fileRefresh(self): - if self.host.lo_target is None or self.fOscServer is None: + if None in (self.host.lo_server_tcp, self.host.lo_server_udp, self.host.lo_target_tcp, self.host.lo_target_udp): return - self.killTimers() + lo_send(self.host.lo_target_udp, "/unregister", self.host.lo_server_udp.getFullURL()) + while self.host.lo_server_udp.recv(0): + pass + #self.host.lo_server_udp.free() + + lo_send(self.host.lo_target_tcp, "/unregister", self.host.lo_server_tcp.getFullURL()) + while self.host.lo_server_tcp.recv(0): + pass + #self.host.lo_server_tcp.free() + self.removeAllPlugins() - lo_send(self.host.lo_target, "/unregister", self.fOscServer.getFullURL()) - lo_send(self.host.lo_target, "/register", self.fOscServer.getFullURL()) + self.host.lo_server_tcp = CarlaControlServerTCP(self.host) + self.host.lo_server_udp = CarlaControlServerUDP(self.host) - self.startTimers() + try: + lo_send(self.host.lo_target_tcp, "/register", self.host.lo_server_tcp.getFullURL()) + except: + self.disconnectOsc() + return + + try: + lo_send(self.host.lo_target_udp, "/register", self.host.lo_server_udp.getFullURL()) + except: + self.disconnectOsc() + return @pyqtSlot() def slot_handleQuitCallback(self): - HostWindow.slot_handleQuitCallback(self) self.disconnectOsc() + HostWindow.slot_handleQuitCallback(self) # -------------------------------------------------------------------------------------------------------- - def timerEvent(self, event): - if event.timerId() == self.fIdleTimerOSC: - self.fOscServer.idle() - - if self.host.lo_target is None: - self.disconnectOsc() - - HostWindow.timerEvent(self, event) - def closeEvent(self, event): self.killTimers() - - if self.host.lo_target is not None and self.fOscServer is not None: - try: - lo_send(self.host.lo_target, "/unregister", self.fOscServer.getFullURL()) - except: - pass + self.unregister() HostWindow.closeEvent(self, event) diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index 19babc5f4..86097a7ab 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -128,7 +128,6 @@ class HostWindow(QMainWindow): # Internal stuff self.fIdleTimerNull = self.startTimer(1000) # keep application signals alive - self.fIdleTimerOSC = 0 self.fIdleTimerFast = 0 self.fIdleTimerSlow = 0 @@ -147,7 +146,8 @@ class HostWindow(QMainWindow): self.fLastTransportState = False self.fBufferSize = 0 self.fSampleRate = 0.0 - self.fOscAddress = "" + self.fOscAddressTCP = "" + self.fOscAddressUDP = "" if MACOS: self.fMacClosingHelper = True @@ -577,7 +577,8 @@ class HostWindow(QMainWindow): host.nsm_ready(NSM_CALLBACK_INIT) return - QTimer.singleShot(0, self.slot_engineStart) + if not host.isControl: + QTimer.singleShot(0, self.slot_engineStart) # -------------------------------------------------------------------------------------------------------- # Setup @@ -1262,8 +1263,13 @@ class HostWindow(QMainWindow): @pyqtSlot(int, str) def slot_handlePluginAddedCallback(self, pluginId, pluginName): - pitem = self.ui.listWidget.createItem(pluginId) + if pluginId != self.fPluginCount: + print("ERROR: pluginAdded mismatch Id:", pluginId, self.fPluginCount) + pitem = self.getPluginItem(pluginId) + pitem.recreateWidget() + return + pitem = self.ui.listWidget.createItem(pluginId) self.fPluginList.append(pitem) self.fPluginCount += 1 diff --git a/source/includes/CarlaDefines.h b/source/includes/CarlaDefines.h index 1422a4142..2249c8d4e 100644 --- a/source/includes/CarlaDefines.h +++ b/source/includes/CarlaDefines.h @@ -175,6 +175,10 @@ #define CARLA_SAFE_ASSERT_CONTINUE(cond) if (! (cond)) { carla_safe_assert(#cond, __FILE__, __LINE__); continue; } #define CARLA_SAFE_ASSERT_RETURN(cond, ret) if (! (cond)) { carla_safe_assert(#cond, __FILE__, __LINE__); return ret; } +#define CARLA_SAFE_ASSERT_INT_BREAK(cond, value) if (! (cond)) { carla_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value); break; } +#define CARLA_SAFE_ASSERT_INT_CONTINUE(cond, value) if (! (cond)) { carla_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); continue; } +#define CARLA_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (! (cond)) { carla_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); return ret; } + #define CARLA_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (! (cond)) { carla_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); break; } #define CARLA_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (! (cond)) { carla_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); continue; } #define CARLA_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (! (cond)) { carla_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); return ret; }