1
// SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
2 // SPDX-License-Identifier: GPL-2.0-or-later
5 # error This file should not be compiled if not building bridge
8 #include "CarlaEngine.hpp"
10 #include "CarlaUtils.h"
12 #include "CarlaBackendUtils.hpp"
13 #include "CarlaJuceUtils.hpp"
14 #include "CarlaMainLoop.hpp"
15 #include "CarlaTimeUtils.hpp"
17 #include "CarlaMIDI.h"
20 # include "CarlaMacUtils.hpp"
29 # define SCHED_RESET_ON_FORK 0x40000000
38 # include <X11/Xlib.h>
41 #include "water/files/File.h"
42 #include "water/misc/Time.h"
45 #include "jackbridge/JackBridge.hpp"
47 using CARLA_BACKEND_NAMESPACE::CarlaEngine
;
48 using CARLA_BACKEND_NAMESPACE::EngineCallbackOpcode
;
49 using CARLA_BACKEND_NAMESPACE::EngineCallbackOpcode2Str
;
50 using CARLA_BACKEND_NAMESPACE::runMainLoopOnce
;
52 using water::CharPointer_UTF8
;
56 // -------------------------------------------------------------------------
58 static bool gIsInitiated
= false;
59 static volatile bool gCloseBridge
= false;
60 static volatile bool gCloseSignal
= false;
61 static volatile bool gSaveNow
= false;
63 #if defined(CARLA_OS_UNIX)
64 static void closeSignalHandler(int) noexcept
68 static void saveSignalHandler(int) noexcept
72 #elif defined(CARLA_OS_WIN)
73 static LONG WINAPI
winExceptionFilter(_EXCEPTION_POINTERS
*)
75 return EXCEPTION_EXECUTE_HANDLER
;
78 static BOOL WINAPI
winSignalHandler(DWORD dwCtrlType
) noexcept
80 if (dwCtrlType
== CTRL_C_EVENT
)
89 static void initSignalHandler()
91 #if defined(CARLA_OS_UNIX)
93 carla_zeroStruct(sig
);
95 sig
.sa_handler
= closeSignalHandler
;
96 sig
.sa_flags
= SA_RESTART
;
97 sigemptyset(&sig
.sa_mask
);
98 sigaction(SIGTERM
, &sig
, nullptr);
99 sigaction(SIGINT
, &sig
, nullptr);
101 sig
.sa_handler
= saveSignalHandler
;
102 sig
.sa_flags
= SA_RESTART
;
103 sigemptyset(&sig
.sa_mask
);
104 sigaction(SIGUSR1
, &sig
, nullptr);
105 #elif defined(CARLA_OS_WIN)
106 SetConsoleCtrlHandler(winSignalHandler
, TRUE
);
107 SetErrorMode(SEM_NOGPFAULTERRORBOX
);
108 SetUnhandledExceptionFilter(winExceptionFilter
);
112 // -------------------------------------------------------------------------
114 static String gProjectFilename
;
115 static CarlaHostHandle gHostHandle
;
119 carla_engine_idle(gHostHandle
);
125 if (gProjectFilename
.isNotEmpty())
127 if (! carla_save_plugin_state(gHostHandle
, 0, gProjectFilename
.toRawUTF8()))
128 carla_stderr("Plugin preset save failed, error was:\n%s", carla_get_last_error(gHostHandle
));
133 // -------------------------------------------------------------------------
135 class CarlaBridgePlugin
138 CarlaBridgePlugin(const bool useBridge
, const char* const clientName
, const char* const audioPoolBaseName
,
139 const char* const rtClientBaseName
, const char* const nonRtClientBaseName
, const char* const nonRtServerBaseName
)
144 CARLA_ASSERT(clientName
!= nullptr && clientName
[0] != '\0');
145 carla_debug("CarlaBridgePlugin::CarlaBridgePlugin(%s, \"%s\", %s, %s, %s, %s)",
146 bool2str(useBridge
), clientName
, audioPoolBaseName
, rtClientBaseName
, nonRtClientBaseName
, nonRtServerBaseName
);
148 carla_set_engine_callback(gHostHandle
, callback
, this);
152 carla_engine_init_bridge(gHostHandle
,
159 else if (std::getenv("CARLA_BRIDGE_DUMMY") != nullptr)
161 carla_engine_init(gHostHandle
, "Dummy", clientName
);
165 carla_engine_init(gHostHandle
, "JACK", clientName
);
168 fEngine
= carla_get_engine_from_handle(gHostHandle
);
173 carla_debug("CarlaBridgePlugin::~CarlaBridgePlugin()");
175 if (fEngine
!= nullptr && ! fUsingExec
)
176 carla_engine_close(gHostHandle
);
179 bool isOk() const noexcept
181 return (fEngine
!= nullptr);
184 // ---------------------------------------------------------------------
186 void exec(const bool useBridge
)
188 fUsingBridge
= useBridge
;
191 const bool testing
= std::getenv("CARLA_BRIDGE_TESTING") != nullptr;
193 if (! useBridge
&& ! testing
)
195 const CarlaPluginInfo
* const pInfo
= carla_get_plugin_info(gHostHandle
, 0);
196 CARLA_SAFE_ASSERT_RETURN(pInfo
!= nullptr,);
198 gProjectFilename
= CharPointer_UTF8(pInfo
->name
);
199 gProjectFilename
+= ".carxs";
201 if (! File::isAbsolutePath(gProjectFilename
))
202 gProjectFilename
= File::getCurrentWorkingDirectory().getChildFile(gProjectFilename
).getFullPathName();
204 if (File(gProjectFilename
.toRawUTF8()).existsAsFile())
206 if (carla_load_plugin_state(gHostHandle
, 0, gProjectFilename
.toRawUTF8()))
207 carla_stdout("Plugin state loaded successfully");
209 carla_stderr("Plugin state load failed, error was:\n%s", carla_get_last_error(gHostHandle
));
213 carla_stdout("Previous plugin state in '%s' is non-existent, will start from default state",
214 gProjectFilename
.toRawUTF8());
220 int64_t timeToEnd
= 0;
224 timeToEnd
= water::Time::currentTimeMillis() + 5 * 1000;
225 fEngine
->transportPlay();
228 for (; runMainLoopOnce() && ! gCloseBridge
;)
231 #if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)
232 // MacOS and Win32 have event-loops to run, so minimize sleep time
237 if (testing
&& timeToEnd
- water::Time::currentTimeMillis() < 0)
239 if (gCloseSignal
&& ! fUsingBridge
)
243 carla_engine_close(gHostHandle
);
246 // ---------------------------------------------------------------------
249 void handleCallback(const EngineCallbackOpcode action
,
251 const int, const int, const float, const char* const)
253 CARLA_BACKEND_USE_NAMESPACE
;
257 case ENGINE_CALLBACK_ENGINE_STOPPED
:
258 case ENGINE_CALLBACK_PLUGIN_REMOVED
:
259 case ENGINE_CALLBACK_QUIT
:
260 gCloseBridge
= gCloseSignal
= true;
263 case ENGINE_CALLBACK_UI_STATE_CHANGED
:
264 if (gIsInitiated
&& value1
!= 1 && ! fUsingBridge
)
265 gCloseBridge
= gCloseSignal
= true;
274 CarlaEngine
* fEngine
;
279 static void callback(void* ptr
, EngineCallbackOpcode action
, unsigned int pluginId
,
280 int value1
, int value2
, int value3
,
281 float valuef
, const char* valueStr
)
283 carla_debug("CarlaBridgePlugin::callback(%p, %i:%s, %i, %i, %i, %i, %f, \"%s\")",
284 ptr
, action
, EngineCallbackOpcode2Str(action
),
285 pluginId
, value1
, value2
, value3
, static_cast<double>(valuef
), valueStr
);
287 // ptr must not be null
288 CARLA_SAFE_ASSERT_RETURN(ptr
!= nullptr,);
290 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
291 // pluginId must be 0 (first), except for patchbay things
292 if (action
< CARLA_BACKEND_NAMESPACE::ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED
||
293 action
> CARLA_BACKEND_NAMESPACE::ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED
)
296 CARLA_SAFE_ASSERT_UINT_RETURN(pluginId
== 0, pluginId
,);
299 return ((CarlaBridgePlugin
*)ptr
)->handleCallback(action
, value1
, value2
, value3
, valuef
, valueStr
);
302 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaBridgePlugin
)
305 // -------------------------------------------------------------------------
307 int main(int argc
, char* argv
[])
309 // ---------------------------------------------------------------------
310 // Check argument count
312 if (argc
!= 4 && argc
!= 5)
314 carla_stdout("usage: %s <type> <filename> <label> [uniqueId]", argv
[0]);
318 #if defined(CARLA_OS_WIN) && defined(BUILDING_CARLA_FOR_WINE)
319 // ---------------------------------------------------------------------
320 // Test if bridge is working
322 if (! jackbridge_is_ok())
324 carla_stderr("A JACK or Wine library is missing, cannot continue");
329 // ---------------------------------------------------------------------
332 const char* const stype
= argv
[1];
333 const char* filename
= argv
[2];
334 const char* label
= argv
[3];
335 const int64_t uniqueId
= (argc
== 5) ? static_cast<int64_t>(std::atoll(argv
[4])) : 0;
337 if (filename
[0] == '\0' || std::strcmp(filename
, "(none)") == 0)
340 if (label
[0] == '\0' || std::strcmp(label
, "(none)") == 0)
343 // ---------------------------------------------------------------------
346 CARLA_BACKEND_NAMESPACE::BinaryType btype
= CARLA_BACKEND_NAMESPACE::BINARY_NATIVE
;
348 if (const char* const binaryTypeStr
= std::getenv("CARLA_BRIDGE_PLUGIN_BINARY_TYPE"))
349 btype
= CARLA_BACKEND_NAMESPACE::getBinaryTypeFromString(binaryTypeStr
);
351 if (btype
== CARLA_BACKEND_NAMESPACE::BINARY_NONE
)
353 carla_stderr("Invalid binary type '%i'", btype
);
357 // ---------------------------------------------------------------------
360 CARLA_BACKEND_NAMESPACE::PluginType itype
= CARLA_BACKEND_NAMESPACE::getPluginTypeFromString(stype
);
362 if (itype
== CARLA_BACKEND_NAMESPACE::PLUGIN_NONE
)
364 carla_stderr("Invalid plugin type '%s'", stype
);
368 // ---------------------------------------------------------------------
371 const File
file(filename
!= nullptr ? filename
: "");
373 // ---------------------------------------------------------------------
376 const char* name(std::getenv("CARLA_CLIENT_NAME"));
378 if (name
!= nullptr && (name
[0] == '\0' || std::strcmp(name
, "(none)") == 0))
381 // ---------------------------------------------------------------------
384 const char* const shmIds(std::getenv("ENGINE_BRIDGE_SHM_IDS"));
386 const bool useBridge
= (shmIds
!= nullptr);
388 // ---------------------------------------------------------------------
391 char audioPoolBaseName
[6+1];
392 char rtClientBaseName
[6+1];
393 char nonRtClientBaseName
[6+1];
394 char nonRtServerBaseName
[6+1];
398 CARLA_SAFE_ASSERT_RETURN(std::strlen(shmIds
) == 6*4, 1);
399 std::strncpy(audioPoolBaseName
, shmIds
+6*0, 6);
400 std::strncpy(rtClientBaseName
, shmIds
+6*1, 6);
401 std::strncpy(nonRtClientBaseName
, shmIds
+6*2, 6);
402 std::strncpy(nonRtServerBaseName
, shmIds
+6*3, 6);
403 audioPoolBaseName
[6] = '\0';
404 rtClientBaseName
[6] = '\0';
405 nonRtClientBaseName
[6] = '\0';
406 nonRtServerBaseName
[6] = '\0';
407 jackbridge_parent_deathsig(false);
411 audioPoolBaseName
[0] = '\0';
412 rtClientBaseName
[0] = '\0';
413 nonRtClientBaseName
[0] = '\0';
414 nonRtServerBaseName
[0] = '\0';
418 // ---------------------------------------------------------------------
421 CarlaString clientName
;
427 else if (itype
== CARLA_BACKEND_NAMESPACE::PLUGIN_LV2
)
430 CARLA_SAFE_ASSERT_RETURN(label
!= nullptr && label
[0] != '\0', 1);
432 // LV2 URI is not usable as client name, create a usable name from URI
433 CarlaString
label2(label
);
435 // truncate until last valid char
436 for (std::size_t i
=label2
.length()-1; i
!= 0; --i
)
438 if (! std::isalnum(label2
[i
]))
441 label2
.truncate(i
+1);
445 // get last used separator
447 std::size_t septmp
, sep
= 0;
449 septmp
= label2
.rfind('#', &found
)+1;
450 if (found
&& septmp
> sep
)
453 septmp
= label2
.rfind('/', &found
)+1;
454 if (found
&& septmp
> sep
)
457 septmp
= label2
.rfind('=', &found
)+1;
458 if (found
&& septmp
> sep
)
461 septmp
= label2
.rfind(':', &found
)+1;
462 if (found
&& septmp
> sep
)
465 // make name starting from the separator and first valid char
466 const char* name2
= label2
.buffer() + sep
;
467 for (; *name2
!= '\0' && ! std::isalnum(*name2
); ++name2
) {}
472 else if (label
!= nullptr)
478 clientName
= file
.getFileNameWithoutExtension().toRawUTF8();
481 // if we still have no client name by now, use a dummy one
482 if (clientName
.isEmpty())
483 clientName
= "carla-plugin";
486 clientName
.toBasic();
488 // ---------------------------------------------------------------------
491 const void* extraStuff
= nullptr;
493 if (itype
== CARLA_BACKEND_NAMESPACE::PLUGIN_SF2
)
495 if (label
== nullptr)
498 if (std::strstr(label
, " (16 outs)") != nullptr)
502 // ---------------------------------------------------------------------
503 // Initialize OS features
505 const bool dummy
= std::getenv("CARLA_BRIDGE_DUMMY") != nullptr;
506 const bool testing
= std::getenv("CARLA_BRIDGE_TESTING") != nullptr;
509 CARLA_BACKEND_NAMESPACE::initStandaloneApplication();
513 OleInitialize(nullptr);
514 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED
);
515 # ifndef __WINPTHREADS_VERSION
516 // (non-portable) initialization of statically linked pthread library
517 pthread_win32_process_attach_np();
518 pthread_win32_thread_attach_np();
523 if (std::getenv("DISPLAY") != nullptr)
527 // ---------------------------------------------------------------------
528 // Set ourselves with high priority
530 if (!dummy
&& !testing
)
532 #ifdef CARLA_OS_LINUX
533 // reset scheduler to normal mode
534 struct sched_param sparam
;
535 carla_zeroStruct(sparam
);
536 sched_setscheduler(0, SCHED_OTHER
|SCHED_RESET_ON_FORK
, &sparam
);
538 // try niceness first, if it fails, try SCHED_RR
541 sparam
.sched_priority
= (sched_get_priority_max(SCHED_RR
) + sched_get_priority_min(SCHED_RR
*7)) / 8;
543 if (sparam
.sched_priority
> 0)
545 if (sched_setscheduler(0, SCHED_RR
|SCHED_RESET_ON_FORK
, &sparam
) < 0)
547 CarlaString
error(std::strerror(errno
));
548 carla_stderr("Failed to set high priority, error %i: %s", errno
, error
.buffer());
555 if (! SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS
))
556 carla_stderr("Failed to set high priority.");
560 // ---------------------------------------------------------------------
561 // Listen for ctrl+c or sigint/sigterm events
565 // ---------------------------------------------------------------------
566 // Init plugin bridge
571 gHostHandle
= carla_standalone_host_init();
573 CarlaBridgePlugin
bridge(useBridge
, clientName
,
574 audioPoolBaseName
, rtClientBaseName
, nonRtClientBaseName
, nonRtServerBaseName
);
578 carla_stderr("Failed to init engine, error was:\n%s", carla_get_last_error(gHostHandle
));
582 if (! useBridge
&& ! testing
)
585 if (std::getenv("DISPLAY") != nullptr)
587 carla_set_engine_option(gHostHandle
,
588 CARLA_BACKEND_NAMESPACE::ENGINE_OPTION_FRONTEND_UI_SCALE
,
589 static_cast<int>(carla_get_desktop_scale_factor()*1000+0.5),
593 // -----------------------------------------------------------------
596 if (carla_add_plugin(gHostHandle
,
598 file
.getFullPathName().toRawUTF8(), name
, label
, uniqueId
, extraStuff
,
599 CARLA_BACKEND_NAMESPACE::PLUGIN_OPTIONS_NULL
))
605 carla_set_active(gHostHandle
, 0, true);
606 carla_set_engine_option(gHostHandle
, CARLA_BACKEND_NAMESPACE::ENGINE_OPTION_PLUGINS_ARE_STANDALONE
, 1, nullptr);
608 if (const CarlaPluginInfo
* const pluginInfo
= carla_get_plugin_info(gHostHandle
, 0))
610 if (itype
== CARLA_BACKEND_NAMESPACE::PLUGIN_INTERNAL
&& (std::strcmp(label
, "audiofile") == 0 || std::strcmp(label
, "midifile") == 0))
613 carla_set_custom_data(gHostHandle
, 0,
614 CARLA_BACKEND_NAMESPACE::CUSTOM_DATA_TYPE_STRING
,
615 "file", file
.getFullPathName().toRawUTF8());
617 else if (pluginInfo
->hints
& CARLA_BACKEND_NAMESPACE::PLUGIN_HAS_CUSTOM_UI
)
620 if (std::getenv("DISPLAY") != nullptr)
623 carla_show_custom_ui(gHostHandle
, 0, true);
626 // on standalone usage, enable everything that makes sense
627 const uint optsAvailable
= pluginInfo
->optionsAvailable
;
628 if (optsAvailable
& CARLA_BACKEND_NAMESPACE::PLUGIN_OPTION_FIXED_BUFFERS
)
629 carla_set_option(gHostHandle
, 0, CARLA_BACKEND_NAMESPACE::PLUGIN_OPTION_FIXED_BUFFERS
, true);
633 bridge
.exec(useBridge
);
639 const char* const lastError(carla_get_last_error(gHostHandle
));
640 carla_stderr("Plugin failed to load, error was:\n%s", lastError
);
644 // do a single idle so that we can send error message to server
648 // kill ourselves now if we can't load plugin in bridge mode
649 ::kill(::getpid(), SIGKILL
);
656 #ifndef __WINPTHREADS_VERSION
657 pthread_win32_thread_detach_np();
658 pthread_win32_process_detach_np();