3 * Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
18 #include "CarlaHostImpl.hpp"
20 #if defined(HAVE_LIBLO) && !defined(CARLA_OS_WASM)
22 #define CARLA_ENABLE_STANDALONE_NSM
24 #define NSM_API_VERSION_MAJOR 1
25 #define NSM_API_VERSION_MINOR 2
27 // #define NSM_CLIENT_FEATURES ":switch:"
28 #define NSM_CLIENT_FEATURES ":switch:optional-gui:"
30 #include "CarlaOscUtils.hpp"
31 #include "CarlaString.hpp"
33 #include "water/files/File.h"
35 // -------------------------------------------------------------------------------------------------------------------
40 CarlaNSM(CarlaHostStandalone
& shandle
) noexcept
41 : gStandalone(shandle
),
42 fReplyAddress(nullptr),
44 fServerThread(nullptr),
49 fHasOptionalGui(false),
50 fHasServerControl(false),
52 fReadyActionOpen(true),
53 fReadyActionSave(true) {}
57 CARLA_SAFE_ASSERT(fReadyActionOpen
);
58 CARLA_SAFE_ASSERT(fReadyActionSave
);
60 if (fServerThread
!= nullptr)
62 lo_server_thread_stop(fServerThread
);
63 lo_server_thread_free(fServerThread
);
64 fServerThread
= nullptr;
68 if (fServerURL
!= nullptr)
70 std::free(fServerURL
);
74 if (fReplyAddress
!= nullptr)
76 lo_address_free(fReplyAddress
);
77 fReplyAddress
= nullptr;
81 bool announce(const uint64_t pid
, const char* const executableName
)
83 CARLA_SAFE_ASSERT_RETURN(pid
!= 0, false);
84 CARLA_SAFE_ASSERT_RETURN(executableName
!= nullptr && executableName
[0] != '\0', false);
86 const char* const NSM_URL(std::getenv("NSM_URL"));
88 if (NSM_URL
== nullptr)
91 const lo_address
nsmAddress(lo_address_new_from_url(NSM_URL
));
92 CARLA_SAFE_ASSERT_RETURN(nsmAddress
!= nullptr, false);
94 const int proto
= lo_address_get_protocol(nsmAddress
);
96 if (fServerThread
== nullptr)
98 // create new OSC server
99 fServerThread
= lo_server_thread_new_with_proto(nullptr, proto
, _osc_error_handler
);
100 CARLA_SAFE_ASSERT_RETURN(fServerThread
!= nullptr, false);
102 // register message handlers
103 lo_server_thread_add_method(fServerThread
, "/error", "sis", _error_handler
, this);
104 lo_server_thread_add_method(fServerThread
, "/reply", "ssss", _reply_handler
, this);
105 lo_server_thread_add_method(fServerThread
, "/nsm/client/open", "sss", _open_handler
, this);
106 lo_server_thread_add_method(fServerThread
, "/nsm/client/save", "", _save_handler
, this);
107 lo_server_thread_add_method(fServerThread
, "/nsm/client/session_is_loaded", "", _loaded_handler
, this);
108 lo_server_thread_add_method(fServerThread
, "/nsm/client/show_optional_gui", "", _show_gui_handler
, this);
109 lo_server_thread_add_method(fServerThread
, "/nsm/client/hide_optional_gui", "", _hide_gui_handler
, this);
110 lo_server_thread_add_method(fServerThread
, nullptr, nullptr, _broadcast_handler
, this);
112 fServer
= lo_server_thread_get_server(fServerThread
);
113 fServerURL
= lo_server_thread_get_url(fServerThread
);
116 const char* appName
= std::getenv("CARLA_NSM_NAME");
118 if (appName
== nullptr)
121 lo_send_from(nsmAddress
, fServer
, LO_TT_IMMEDIATE
, "/nsm/server/announce", "sssiii",
122 appName
, NSM_CLIENT_FEATURES
, executableName
, NSM_API_VERSION_MAJOR
, NSM_API_VERSION_MINOR
, pid
);
124 lo_address_free(nsmAddress
);
126 if (gStandalone
.engineCallback
!= nullptr)
128 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
129 CB::ENGINE_CALLBACK_NSM
,
131 CB::NSM_CALLBACK_INIT
,
132 0, 0, 0.0f
, nullptr);
138 void ready(const NsmCallbackOpcode action
)
140 CARLA_SAFE_ASSERT_RETURN(fServerThread
!= nullptr,);
144 case CB::NSM_CALLBACK_INIT
:
145 CARLA_SAFE_ASSERT_BREAK(! fStarted
);
147 lo_server_thread_start(fServerThread
);
150 case CB::NSM_CALLBACK_ERROR
:
153 case CB::NSM_CALLBACK_ANNOUNCE
:
156 case CB::NSM_CALLBACK_OPEN
:
157 fReadyActionOpen
= true;
160 case CB::NSM_CALLBACK_SAVE
:
161 fReadyActionSave
= true;
164 case CB::NSM_CALLBACK_SESSION_IS_LOADED
:
167 case CB::NSM_CALLBACK_SHOW_OPTIONAL_GUI
:
168 CARLA_SAFE_ASSERT_BREAK(fReplyAddress
!= nullptr);
169 CARLA_SAFE_ASSERT_BREAK(fServer
!= nullptr);
171 // NOTE: lo_send_from is a macro that creates local variables
172 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/nsm/client/gui_is_shown", "");
176 case CB::NSM_CALLBACK_HIDE_OPTIONAL_GUI
:
177 CARLA_SAFE_ASSERT_BREAK(fReplyAddress
!= nullptr);
178 CARLA_SAFE_ASSERT_BREAK(fServer
!= nullptr);
180 // NOTE: lo_send_from is a macro that creates local variables
181 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/nsm/client/gui_is_hidden", "");
185 case CB::NSM_CALLBACK_SET_CLIENT_NAME_ID
:
190 static CarlaNSM
& getInstance(CarlaHostStandalone
& shandle
)
192 static CarlaNSM
sInstance(shandle
);
197 int handleError(const char* const method
, const int code
, const char* const message
)
199 carla_stdout("CarlaNSM::handleError(\"%s\", %i, \"%s\")", method
, code
, message
);
201 if (gStandalone
.engineCallback
!= nullptr)
202 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
203 CB::ENGINE_CALLBACK_NSM
,
205 CB::NSM_CALLBACK_ERROR
,
216 int handleReply(const char* const method
, const char* const message
, const char* const smName
, const char* const features
,
217 const lo_message msg
)
219 CARLA_SAFE_ASSERT_RETURN(fServerThread
!= nullptr, 1);
220 carla_stdout("CarlaNSM::handleReply(\"%s\", \"%s\", \"%s\", \"%s\")", method
, message
, smName
, features
);
222 if (std::strcmp(method
, "/nsm/server/announce") == 0)
224 const lo_address
msgAddress(lo_message_get_source(msg
));
225 CARLA_SAFE_ASSERT_RETURN(msgAddress
!= nullptr, 0);
227 char* const msgURL(lo_address_get_url(msgAddress
));
228 CARLA_SAFE_ASSERT_RETURN(msgURL
!= nullptr, 0);
230 if (fReplyAddress
!= nullptr)
231 lo_address_free(fReplyAddress
);
233 fReplyAddress
= lo_address_new_from_url(msgURL
);
234 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 0);
236 fHasBroadcast
= std::strstr(features
, ":broadcast:") != nullptr;
237 fHasOptionalGui
= std::strstr(features
, ":optional-gui:") != nullptr;
238 fHasServerControl
= std::strstr(features
, ":server-control:") != nullptr;
243 // NOTE: lo_send_from is a macro that creates local variables
244 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/nsm/client/gui_is_hidden", "");
247 carla_stdout("Carla started via '%s', message: %s", smName
, message
);
249 if (gStandalone
.engineCallback
!= nullptr)
257 if (fHasServerControl
)
260 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
261 CB::ENGINE_CALLBACK_NSM
,
263 CB::NSM_CALLBACK_ANNOUNCE
,
273 carla_stdout("Got unknown NSM reply method '%s'", method
);
279 int handleOpen(const char* const projectPath
, const char* const displayName
, const char* const clientNameId
)
281 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
282 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
283 carla_stdout("CarlaNSM::handleOpen(\"%s\", \"%s\", \"%s\")", projectPath
, displayName
, clientNameId
);
285 const CarlaHostHandle handle
= (CarlaHostHandle
)&gStandalone
;
287 carla_set_engine_option(handle
, CB::ENGINE_OPTION_CLIENT_NAME_PREFIX
, 0, clientNameId
);
289 if (gStandalone
.engineCallback
!= nullptr)
291 fReadyActionOpen
= false;
292 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
293 CB::ENGINE_CALLBACK_NSM
,
295 CB::NSM_CALLBACK_SET_CLIENT_NAME_ID
,
298 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
299 CB::ENGINE_CALLBACK_NSM
,
301 CB::NSM_CALLBACK_OPEN
,
305 for (; ! fReadyActionOpen
;)
310 using namespace water
;
312 if (carla_is_engine_running(handle
))
313 carla_engine_close(handle
);
315 // TODO send error if engine failed to initialize
316 carla_engine_init(handle
, "JACK", clientNameId
);
318 fProjectPath
= projectPath
;
319 fProjectPath
+= ".carxp";
321 const String jfilename
= String(CharPointer_UTF8(fProjectPath
));
323 if (File(jfilename
).existsAsFile())
324 carla_load_project(handle
, fProjectPath
);
327 fClientNameId
= clientNameId
;
329 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/reply", "ss", "/nsm/client/open", "OK");
331 // Broadcast ourselves
334 const char* appName
= std::getenv("CARLA_NSM_NAME");
336 if (appName
== nullptr)
339 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/nsm/server/broadcast", "sssss",
340 "/non/hello", fServerURL
, appName
, CARLA_VERSION_STRING
, fClientNameId
.buffer());
348 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
349 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
350 carla_stdout("CarlaNSM::handleSave()");
352 if (gStandalone
.engineCallback
!= nullptr)
354 fReadyActionSave
= false;
355 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
356 CB::ENGINE_CALLBACK_NSM
,
358 CB::NSM_CALLBACK_SAVE
,
359 0, 0, 0.0f
, nullptr);
361 for (; ! fReadyActionSave
;)
366 CARLA_SAFE_ASSERT_RETURN(fProjectPath
.isNotEmpty(), 0);
368 const CarlaHostHandle handle
= (CarlaHostHandle
)&gStandalone
;
370 carla_save_project(handle
, fProjectPath
);
373 lo_send_from(fReplyAddress
, fServer
, LO_TT_IMMEDIATE
, "/reply", "ss", "/nsm/client/save", "OK");
378 int handleSessionIsLoaded()
380 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
381 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
382 carla_stdout("CarlaNSM::handleSessionIsLoaded()");
384 if (gStandalone
.engineCallback
!= nullptr)
385 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
386 CB::ENGINE_CALLBACK_NSM
,
388 CB::NSM_CALLBACK_SESSION_IS_LOADED
,
389 0, 0, 0.0f
, nullptr);
394 int handleShowOptionalGui()
396 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
397 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
398 carla_stdout("CarlaNSM::handleShowOptionalGui()");
400 if (gStandalone
.engineCallback
!= nullptr)
401 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
402 CB::ENGINE_CALLBACK_NSM
,
404 CB::NSM_CALLBACK_SHOW_OPTIONAL_GUI
,
405 0, 0, 0.0f
, nullptr);
410 int handleHideOptionalGui()
412 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
413 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
414 carla_stdout("CarlaNSM::handleHideOptionalGui()");
416 if (gStandalone
.engineCallback
!= nullptr)
417 gStandalone
.engineCallback(gStandalone
.engineCallbackPtr
,
418 CB::ENGINE_CALLBACK_NSM
,
420 CB::NSM_CALLBACK_HIDE_OPTIONAL_GUI
,
421 0, 0, 0.0f
, nullptr);
426 int handleBroadcast(const char* const path
, const char* const types
, lo_arg
** const argv
, const int argc
,
427 const lo_message msg
)
429 CARLA_SAFE_ASSERT_RETURN(fReplyAddress
!= nullptr, 1);
430 CARLA_SAFE_ASSERT_RETURN(fServer
!= nullptr, 1);
431 CARLA_SAFE_ASSERT_RETURN(argc
>= 0, 0);
432 carla_stdout("CarlaNSM::handleBroadcast(%s, %s, %p, %i)", path
, types
, argv
, argc
);
435 if (std::strcmp(path
, "/non/hello") == 0)
437 CARLA_SAFE_ASSERT_RETURN(argc
== 4, 0);
438 CARLA_SAFE_ASSERT_RETURN(std::strcmp(types
, "ssss") == 0, 0);
440 const char* const url
= &argv
[0]->s
;
441 //const char* const name = &argv[1]->s;
442 //const char* const version = &argv[2]->s;
443 //const char* const clientId = &argv[3]->s;
445 const lo_address
targetAddress(lo_address_new_from_url(url
));
446 CARLA_SAFE_ASSERT_RETURN(targetAddress
!= nullptr, 0);
448 lo_send_from(targetAddress
, fServer
, LO_TT_IMMEDIATE
, "/signal/hello", "ss",
449 fClientNameId
.buffer(), fServerURL
);
451 lo_address_free(targetAddress
);
456 if (std::strcmp(path
, "/signal/hello") == 0)
458 //const char* const name = &argv[0]->s;
459 const char* const url
= &argv
[1]->s
;
461 const lo_address
targetAddress(lo_address_new_from_url(url
));
462 CARLA_SAFE_ASSERT_RETURN(targetAddress
!= nullptr, 0);
464 lo_send_from(targetAddress
, fServer
, LO_TT_IMMEDIATE
, "/signal/hello", "ss",
465 fClientNameId
.buffer(), fServerURL
);
467 lo_address_free(targetAddress
);
472 if (std::strcmp(path
, "/signal/list") == 0)
474 carla_stdout("CarlaNSM::handleBroadcast - got list");
475 CARLA_SAFE_ASSERT_RETURN(carla_is_engine_running(), 0);
477 const char* prefix
= nullptr;
480 prefix
= &argv
[0]->s
;
482 const lo_address
msgAddress(lo_message_get_source(msg
));
483 CARLA_SAFE_ASSERT_RETURN(msgAddress
!= nullptr, 0);
485 for (uint32_t i
= 0, pluginCount
= carla_get_current_plugin_count(); i
< pluginCount
; ++i
)
487 const CarlaPluginInfo
* const pluginInfo(carla_get_plugin_info(i
));
488 CARLA_SAFE_ASSERT_CONTINUE(pluginInfo
!= nullptr);
490 /*const*/ CarlaString
pluginNameId(fClientNameId
+ "/" + CarlaString(pluginInfo
->name
).replace('/','_') + "/");
492 for (uint32_t j
=0, paramCount
= carla_get_parameter_count(i
); j
< paramCount
; ++j
)
494 const CarlaParameterInfo
* const paramInfo(carla_get_parameter_info(i
, j
));
495 CARLA_SAFE_ASSERT_CONTINUE(paramInfo
!= nullptr);
497 const ParameterData
* const paramData(carla_get_parameter_data(i
, j
));
498 CARLA_SAFE_ASSERT_CONTINUE(paramData
!= nullptr);
500 const ParameterRanges
* const paramRanges(carla_get_parameter_ranges(i
, j
));
501 CARLA_SAFE_ASSERT_CONTINUE(paramRanges
!= nullptr);
503 if (paramData
->type
!= CB::PARAMETER_INPUT
/*&& paramData->type != CB::PARAMETER_OUTPUT*/)
505 if ((paramData
->hints
& CB::PARAMETER_IS_ENABLED
) == 0)
507 if ((paramData
->hints
& CB::PARAMETER_IS_AUTOMATABLE
) == 0)
509 if (paramData
->hints
& CB::PARAMETER_IS_READ_ONLY
)
512 const char* const dir
= paramData
->type
== CB::PARAMETER_INPUT
? "in" : "out";
513 const CarlaString paramNameId
= pluginNameId
+ CarlaString(paramInfo
->name
).replace('/','_');
515 const float defNorm
= paramRanges
->getNormalizedValue(paramRanges
->def
);
517 if (prefix
== nullptr || std::strncmp(paramNameId
, prefix
, std::strlen(prefix
)) == 0)
519 lo_send_from(msgAddress
, fServer
, LO_TT_IMMEDIATE
, "/reply", "sssfff",
520 path
, paramNameId
.buffer(), dir
, 0.0f
, 1.0f
, defNorm
);
525 lo_send_from(msgAddress
, fServer
, LO_TT_IMMEDIATE
, "/reply", "s", path
);
530 for (int i
=0; i
<argc
; ++i
)
532 carla_stdout("%i: %s", i
+1, &argv
[i
]->s
);
542 CarlaHostStandalone
& gStandalone
;
544 lo_address fReplyAddress
;
546 lo_server_thread fServerThread
;
549 CarlaString fClientNameId
;
550 CarlaString fProjectPath
;
553 bool fHasOptionalGui
;
554 bool fHasServerControl
;
557 volatile bool fReadyActionOpen
;
558 volatile bool fReadyActionSave
;
560 #define handlePtr ((CarlaNSM*)data)
562 static void _osc_error_handler(int num
, const char* msg
, const char* path
)
564 carla_stderr2("CarlaNSM::_osc_error_handler(%i, \"%s\", \"%s\")", num
, msg
, path
);
567 static int _error_handler(const char*, const char* types
, lo_arg
** argv
, int argc
, lo_message
, void* data
)
569 CARLA_SAFE_ASSERT_RETURN(argc
== 3, 1);
570 CARLA_SAFE_ASSERT_RETURN(std::strcmp(types
, "sis") == 0, 1);
572 const char* const method
= &argv
[0]->s
;
573 const int code
= argv
[1]->i
;
574 const char* const message
= &argv
[2]->s
;
576 return handlePtr
->handleError(method
, code
, message
);
579 static int _reply_handler(const char*, const char* types
, lo_arg
** argv
, int argc
, lo_message msg
, void* data
)
581 CARLA_SAFE_ASSERT_RETURN(argc
== 4, 1);
582 CARLA_SAFE_ASSERT_RETURN(std::strcmp(types
, "ssss") == 0, 1);
584 const char* const method
= &argv
[0]->s
;
585 const char* const message
= &argv
[1]->s
;
586 const char* const smName
= &argv
[2]->s
;
587 const char* const features
= &argv
[3]->s
;
589 return handlePtr
->handleReply(method
, message
, smName
, features
, msg
);
592 static int _open_handler(const char*, const char* types
, lo_arg
** argv
, int argc
, lo_message
, void* data
)
594 CARLA_SAFE_ASSERT_RETURN(argc
== 3, 1);
595 CARLA_SAFE_ASSERT_RETURN(std::strcmp(types
, "sss") == 0, 1);
597 const char* const projectPath
= &argv
[0]->s
;
598 const char* const displayName
= &argv
[1]->s
;
599 const char* const clientNameId
= &argv
[2]->s
;
601 return handlePtr
->handleOpen(projectPath
, displayName
, clientNameId
);
604 static int _save_handler(const char*, const char*, lo_arg
**, int argc
, lo_message
, void* data
)
606 CARLA_SAFE_ASSERT_RETURN(argc
== 0, 1);
608 return handlePtr
->handleSave();
611 static int _loaded_handler(const char*, const char*, lo_arg
**, int argc
, lo_message
, void* data
)
613 CARLA_SAFE_ASSERT_RETURN(argc
== 0, 1);
615 return handlePtr
->handleSessionIsLoaded();
618 static int _show_gui_handler(const char*, const char*, lo_arg
**, int argc
, lo_message
, void* data
)
620 CARLA_SAFE_ASSERT_RETURN(argc
== 0, 1);
622 return handlePtr
->handleShowOptionalGui();
625 static int _hide_gui_handler(const char*, const char*, lo_arg
**, int argc
, lo_message
, void* data
)
627 CARLA_SAFE_ASSERT_RETURN(argc
== 0, 1);
629 return handlePtr
->handleHideOptionalGui();
632 static int _broadcast_handler(const char* path
, const char* types
, lo_arg
** argv
, int argc
, lo_message msg
, void* data
)
634 return handlePtr
->handleBroadcast(path
, types
, argv
, argc
, msg
);
639 CARLA_PREVENT_HEAP_ALLOCATION
640 CARLA_DECLARE_NON_COPYABLE(CarlaNSM
)
643 #endif // CARLA_ENABLE_STANDALONE_NSM
645 // -------------------------------------------------------------------------------------------------------------------
647 bool carla_nsm_init(CarlaHostHandle handle
, uint64_t pid
, const char* executableName
)
649 CARLA_SAFE_ASSERT_RETURN(handle
->isStandalone
, false);
651 #ifdef CARLA_ENABLE_STANDALONE_NSM
652 return CarlaNSM::getInstance(*(CarlaHostStandalone
*)handle
).announce(pid
, executableName
);
657 (void)pid
; (void)executableName
;
661 void carla_nsm_ready(CarlaHostHandle handle
, NsmCallbackOpcode action
)
663 CARLA_SAFE_ASSERT_RETURN(handle
->isStandalone
,);
665 #ifdef CARLA_ENABLE_STANDALONE_NSM
666 CarlaNSM::getInstance(*(CarlaHostStandalone
*)handle
).ready(action
);
669 return; (void)action
;
673 // -------------------------------------------------------------------------------------------------------------------