Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

638 lines
19KB

  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. guimain.cpp - Main file of synthesizer GUI
  4. Copyright (C) 2015 Mark McCurry
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. */
  10. #include <rtosc/thread-link.h>
  11. #include <lo/lo.h>
  12. #include <string>
  13. #include <thread>
  14. //GUI System
  15. #include "Connection.h"
  16. #include "NSM.H"
  17. #include <sys/stat.h>
  18. GUI::ui_handle_t gui = 0;
  19. const char *embedId = NULL;
  20. #if USE_NSM
  21. NSM_Client *nsm = NULL;
  22. #endif
  23. lo_server server;
  24. std::string sendtourl;
  25. /*
  26. * Program exit
  27. */
  28. void exitprogram()
  29. {
  30. GUI::destroyUi(gui);
  31. }
  32. int Pexitprogram=0;
  33. #include "Connection.h"
  34. #include "Fl_Osc_Interface.h"
  35. #include "../globals.h"
  36. #include <map>
  37. #include <cassert>
  38. #include <rtosc/rtosc.h>
  39. #include <rtosc/ports.h>
  40. #include <FL/Fl.H>
  41. #include "Fl_Osc_Tree.H"
  42. #include "common.H"
  43. #include "MasterUI.h"
  44. #ifdef NTK_GUI
  45. #include <FL/Fl_Shared_Image.H>
  46. #include <FL/Fl_Tiled_Image.H>
  47. #include <FL/Fl_Dial.H>
  48. #include <FL/x.H>
  49. #include <err.h>
  50. #endif // NTK_GUI
  51. #ifndef NO_UI
  52. #include "Fl_Osc_Widget.H"
  53. #endif
  54. using namespace GUI;
  55. class MasterUI *ui=0;
  56. // exceptionally extension of the namespace outside the core
  57. namespace zyncarla
  58. {
  59. bool isPlugin = false;
  60. bool fileexists(const char *filename)
  61. {
  62. struct stat tmp;
  63. int result = stat(filename, &tmp);
  64. if(result >= 0)
  65. return true;
  66. return false;
  67. }
  68. }
  69. #ifdef NTK_GUI
  70. static Fl_Tiled_Image *module_backdrop;
  71. #endif
  72. int kb_shortcut_handler(int)
  73. {
  74. const bool undo_ = Fl::event_ctrl() && Fl::event_key() == 'z';
  75. const bool redo = Fl::event_ctrl() && Fl::event_key() == 'r';
  76. const bool show = Fl::event_ctrl() && Fl::event_shift() &&
  77. Fl::event_key() == 's';
  78. const bool panel = Fl::event_ctrl() && Fl::event_shift() &&
  79. Fl::event_key() == 'p';
  80. if(undo_)
  81. ui->osc->write("/undo", "");
  82. else if(redo)
  83. ui->osc->write("/redo", "");
  84. else if (show) {
  85. ui->simplemasterwindow->hide();
  86. ui->masterwindow->show();
  87. }
  88. else if (panel)
  89. ui->panelwindow->show();
  90. return undo_ || redo || show;
  91. }
  92. void
  93. set_module_parameters ( Fl_Widget *o )
  94. {
  95. #ifdef NTK_GUI
  96. o->box( FL_DOWN_FRAME );
  97. o->align( o->align() | FL_ALIGN_IMAGE_BACKDROP );
  98. o->color( FL_BLACK );
  99. o->image( module_backdrop );
  100. o->labeltype( FL_SHADOW_LABEL );
  101. if(o->parent()) {
  102. o->parent()->labeltype(FL_NO_LABEL);
  103. o->parent()->box(FL_NO_BOX);
  104. }
  105. #else
  106. o->box( FL_PLASTIC_UP_BOX );
  107. o->color( FL_CYAN );
  108. o->labeltype( FL_EMBOSSED_LABEL );
  109. #endif
  110. }
  111. ui_handle_t GUI::createUi(Fl_Osc_Interface *osc, void *exit)
  112. {
  113. #ifdef NTK_GUI
  114. fl_register_images();
  115. Fl_Dial::default_style(Fl_Dial::PIXMAP_DIAL);
  116. #ifdef CARLA_VERSION_STRING
  117. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/knob.png"))
  118. Fl_Dial::default_image(img);
  119. #else
  120. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/knob.png"))
  121. Fl_Dial::default_image(img);
  122. #endif
  123. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/knob.png"))
  124. Fl_Dial::default_image(img);
  125. else
  126. errx(1, "ERROR: Cannot find pixmaps/knob.png");
  127. #ifdef CARLA_VERSION_STRING
  128. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/window_backdrop.png"))
  129. Fl::scheme_bg(new Fl_Tiled_Image(img));
  130. #else
  131. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/window_backdrop.png"))
  132. Fl::scheme_bg(new Fl_Tiled_Image(img));
  133. #endif
  134. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/window_backdrop.png"))
  135. Fl::scheme_bg(new Fl_Tiled_Image(img));
  136. else
  137. errx(1, "ERROR: Cannot find pixmaps/window_backdrop.png");
  138. #ifdef CARLA_VERSION_STRING
  139. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/module_backdrop.png"))
  140. module_backdrop = new Fl_Tiled_Image(img);
  141. #else
  142. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/module_backdrop.png"))
  143. module_backdrop = new Fl_Tiled_Image(img);
  144. #endif
  145. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/module_backdrop.png"))
  146. module_backdrop = new Fl_Tiled_Image(img);
  147. else
  148. errx(1, "ERROR: Cannot find pixmaps/module_backdrop");
  149. Fl::background(50, 50, 50);
  150. Fl::background2(70, 70, 70);
  151. Fl::foreground(255, 255, 255);
  152. #endif
  153. //Fl_Window *midi_win = new Fl_Double_Window(400, 400, "Midi connections");
  154. //Fl_Osc_Tree *tree = new Fl_Osc_Tree(0,0,400,400);
  155. //midi_win->resizable(tree);
  156. //tree->root_ports = &Master::ports;
  157. //tree->osc = osc;
  158. //midi_win->show();
  159. Fl::add_handler(kb_shortcut_handler);
  160. ui = new MasterUI((int*)exit, osc);
  161. if (embedId != NULL)
  162. {
  163. if (long long winId = atoll(embedId))
  164. {
  165. // running as plugin
  166. isPlugin = true;
  167. MasterUI::menu_mastermenu[11].hide(); // file -> nio settings
  168. MasterUI::menu_mastermenu[26].deactivate(); // misc -> switch interface mode
  169. #ifdef NTK_GUI
  170. if (winId != 1)
  171. {
  172. MasterUI::menu_mastermenu[13].hide(); // file -> exit
  173. fl_embed(ui->masterwindow, winId);
  174. }
  175. #endif
  176. ui->masterwindow->show();
  177. }
  178. }
  179. return (void*) ui;
  180. }
  181. void GUI::destroyUi(ui_handle_t ui)
  182. {
  183. delete static_cast<MasterUI*>(ui);
  184. }
  185. #define BEGIN(x) {x,":non-realtime\0",NULL,[](const char *m, rtosc::RtData d){ \
  186. MasterUI *ui = static_cast<MasterUI*>(d.obj); \
  187. rtosc_arg_t a0 = {0}, a1 = {0}; \
  188. if(rtosc_narguments(m) > 0) \
  189. a0 = rtosc_argument(m,0); \
  190. if(rtosc_narguments(m) > 1) \
  191. a1 = rtosc_argument(m,1); \
  192. (void)ui;(void)a1;(void)a0;
  193. #define END }},
  194. struct uiPorts {
  195. static rtosc::Ports ports;
  196. };
  197. //DSL based ports
  198. rtosc::Ports uiPorts::ports = {
  199. BEGIN("show:i") {
  200. ui->showUI(a0.i);
  201. } END
  202. BEGIN("alert:s") {
  203. fl_alert("%s",a0.s);
  204. } END
  205. BEGIN("session-type:s") {
  206. if(strcmp(a0.s,"LASH"))
  207. return;
  208. ui->sm_indicator1->value(1);
  209. ui->sm_indicator2->value(1);
  210. ui->sm_indicator1->tooltip("LASH");
  211. ui->sm_indicator2->tooltip("LASH");
  212. } END
  213. BEGIN("save-master:s") {
  214. ui->do_save_master(a0.s);
  215. } END
  216. BEGIN("load-master:s") {
  217. ui->do_load_master(a0.s);
  218. } END
  219. BEGIN("vu-meter:bb") {
  220. #ifdef DEBUG
  221. printf("Vu meter handler...\n");
  222. #endif
  223. if(a0.b.len == sizeof(vuData) &&
  224. a1.b.len == sizeof(float)*NUM_MIDI_PARTS) {
  225. #ifdef DEBUG
  226. printf("Normal behavior...\n");
  227. #endif
  228. //Refresh the primary VU meters
  229. ui->simplemastervu->update((vuData*)a0.b.data);
  230. ui->mastervu->update((vuData*)a0.b.data);
  231. float *partvu = (float*)a1.b.data;
  232. for(int i=0; i<NUM_MIDI_PARTS; ++i)
  233. ui->panellistitem[i]->partvu->update(partvu[i]);
  234. }
  235. } END
  236. BEGIN("close-ui") {
  237. ui->close();
  238. } END
  239. };
  240. void GUI::raiseUi(ui_handle_t gui, const char *message)
  241. {
  242. if(!gui)
  243. return;
  244. MasterUI *mui = (MasterUI*)gui;
  245. mui->osc->tryLink(message);
  246. #ifdef DEBUG
  247. printf("got message for UI '%s:%s'\n", message, rtosc_argument_string(message));
  248. #endif
  249. char buffer[1024];
  250. memset(buffer, 0, sizeof(buffer));
  251. rtosc::RtData d;
  252. d.loc = buffer;
  253. d.loc_size = 1024;
  254. d.obj = gui;
  255. uiPorts::ports.dispatch(message+1, d);
  256. }
  257. void GUI::raiseUi(ui_handle_t gui, const char *dest, const char *args, ...)
  258. {
  259. char buffer[1024];
  260. va_list va;
  261. va_start(va,args);
  262. if(rtosc_vmessage(buffer,1024,dest,args,va))
  263. raiseUi(gui, buffer);
  264. va_end(va);
  265. }
  266. void GUI::tickUi(ui_handle_t)
  267. {
  268. Fl::wait(0.02f);
  269. }
  270. /******************************************************************************
  271. * OSC Interface For User Interface *
  272. * *
  273. * This is a largely out of date section of code *
  274. * Most type specific write methods are no longer used *
  275. * See UI/Fl_Osc_* to see what is actually used in this interface *
  276. ******************************************************************************/
  277. class UI_Interface:public Fl_Osc_Interface
  278. {
  279. public:
  280. UI_Interface()
  281. {}
  282. void transmitMsg(const char *path, const char *args, ...)
  283. {
  284. char buffer[1024];
  285. va_list va;
  286. va_start(va,args);
  287. if(rtosc_vmessage(buffer,1024,path,args,va))
  288. transmitMsg(buffer);
  289. else
  290. fprintf(stderr, "Error in transmitMsg(...)\n");
  291. va_end(va);
  292. }
  293. void transmitMsg(const char *rtmsg)
  294. {
  295. //Send to known url
  296. if(!sendtourl.empty()) {
  297. lo_message msg = lo_message_deserialise((void*)rtmsg,
  298. rtosc_message_length(rtmsg, rtosc_message_length(rtmsg,-1)), NULL);
  299. lo_address addr = lo_address_new_from_url(sendtourl.c_str());
  300. lo_send_message(addr, rtmsg, msg);
  301. }
  302. }
  303. void requestValue(string s) override
  304. {
  305. //printf("Request Value '%s'\n", s.c_str());
  306. assert(s!="/Psysefxvol-1/part0");
  307. //Fl_Osc_Interface::requestValue(s);
  308. /*
  309. if(impl->activeUrl() != "GUI") {
  310. impl->transmitMsg("/echo", "ss", "OSC_URL", "GUI");
  311. impl->activeUrl("GUI");
  312. }*/
  313. transmitMsg(s.c_str(),"");
  314. }
  315. void write(string s, const char *args, ...) override
  316. {
  317. char buffer[4096];
  318. va_list va;
  319. va_start(va, args);
  320. rtosc_vmessage(buffer, sizeof(buffer), s.c_str(), args, va);
  321. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  322. ////fprintf(stderr, ".");
  323. //fprintf(stderr, "write(%s:%s)\n", s.c_str(), args);
  324. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  325. transmitMsg(buffer);
  326. va_end(va);
  327. }
  328. void writeRaw(const char *msg) override
  329. {
  330. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  331. ////fprintf(stderr, ".");
  332. //fprintf(stderr, "rawWrite(%s:%s)\n", msg, rtosc_argument_string(msg));
  333. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  334. transmitMsg(msg);
  335. }
  336. void writeValue(string s, string ss) override
  337. {
  338. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  339. //fprintf(stderr, "writevalue<string>(%s,%s)\n", s.c_str(),ss.c_str());
  340. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  341. transmitMsg(s.c_str(), "s", ss.c_str());
  342. }
  343. void writeValue(string s, char c) override
  344. {
  345. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  346. //fprintf(stderr, "writevalue<char>(%s,%d)\n", s.c_str(),c);
  347. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  348. transmitMsg(s.c_str(), "c", c);
  349. }
  350. void writeValue(string s, float f) override
  351. {
  352. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  353. //fprintf(stderr, "writevalue<float>(%s,%f)\n", s.c_str(),f);
  354. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  355. transmitMsg(s.c_str(), "f", f);
  356. }
  357. void createLink(string s, class Fl_Osc_Widget*w) override
  358. {
  359. assert(s.length() != 0);
  360. Fl_Osc_Interface::createLink(s,w);
  361. assert(!strstr(s.c_str(), "/part0/kit-1"));
  362. map.insert(std::pair<string,Fl_Osc_Widget*>(s,w));
  363. }
  364. void renameLink(string old, string newer, Fl_Osc_Widget *w) override
  365. {
  366. fprintf(stdout, "renameLink('%s','%s',%p)\n",
  367. old.c_str(), newer.c_str(), w);
  368. removeLink(old, w);
  369. createLink(newer, w);
  370. }
  371. void removeLink(string s, class Fl_Osc_Widget*w) override
  372. {
  373. for(auto i = map.begin(); i != map.end(); ++i) {
  374. if(i->first == s && i->second == w) {
  375. map.erase(i);
  376. break;
  377. }
  378. }
  379. //printf("[%d] removing '%s' (%p)...\n", map.size(), s.c_str(), w);
  380. }
  381. virtual void removeLink(class Fl_Osc_Widget *w) override
  382. {
  383. bool processing = true;
  384. while(processing)
  385. {
  386. //Verify Iterator invalidation sillyness
  387. processing = false;//Exit if no new elements are found
  388. for(auto i = map.begin(); i != map.end(); ++i) {
  389. if(i->second == w) {
  390. //printf("[%d] removing '%s' (%p)...\n", map.size()-1,
  391. // i->first.c_str(), w);
  392. map.erase(i);
  393. processing = true;
  394. break;
  395. }
  396. }
  397. }
  398. }
  399. //A very simplistic implementation of a UI agnostic refresh method
  400. virtual void damage(const char *path) override
  401. {
  402. //printf("\n\nDamage(\"%s\")\n", path);
  403. for(auto pair:map) {
  404. if(strstr(pair.first.c_str(), path)) {
  405. auto *tmp = dynamic_cast<Fl_Widget*>(pair.second);
  406. //if(tmp)
  407. // printf("%x, %d %d [%s]\n", (int)pair.second, tmp->visible_r(), tmp->visible(), pair.first.c_str());
  408. //else
  409. // printf("%x, (NULL)[%s]\n", (int)pair.second,pair.first.c_str());
  410. if(!tmp || tmp->visible_r())
  411. pair.second->update();
  412. }
  413. }
  414. }
  415. void tryLink(const char *msg) override
  416. {
  417. //DEBUG
  418. //if(strcmp(msg, "/vu-meter"))//Ignore repeated message
  419. // printf("trying the link for a '%s'<%s>\n", msg, rtosc_argument_string(msg));
  420. const char *handle = strrchr(msg,'/');
  421. if(handle)
  422. ++handle;
  423. int found_count = 0;
  424. auto range = map.equal_range(msg);
  425. for(auto itr = range.first; itr != range.second; ++itr) {
  426. auto widget = itr->second;
  427. found_count++;
  428. const char *arg_str = rtosc_argument_string(msg);
  429. //Always provide the raw message
  430. widget->OSC_raw(msg);
  431. if(!strcmp(arg_str, "b")) {
  432. widget->OSC_value(rtosc_argument(msg,0).b.len,
  433. rtosc_argument(msg,0).b.data,
  434. handle);
  435. } else if(!strcmp(arg_str, "c")) {
  436. widget->OSC_value((char)rtosc_argument(msg,0).i,
  437. handle);
  438. } else if(!strcmp(arg_str, "s")) {
  439. widget->OSC_value((const char*)rtosc_argument(msg,0).s,
  440. handle);
  441. } else if(!strcmp(arg_str, "i")) {
  442. widget->OSC_value((int)rtosc_argument(msg,0).i,
  443. handle);
  444. } else if(!strcmp(arg_str, "f")) {
  445. widget->OSC_value((float)rtosc_argument(msg,0).f,
  446. handle);
  447. } else if(!strcmp(arg_str, "T") || !strcmp(arg_str, "F")) {
  448. widget->OSC_value((bool)rtosc_argument(msg,0).T, handle);
  449. }
  450. }
  451. if(found_count == 0
  452. && strcmp(msg, "/vu-meter")
  453. && strcmp(msg, "undo_change")
  454. && !strstr(msg, "parameter")
  455. && !strstr(msg, "Prespoint")) {
  456. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40);
  457. //fprintf(stderr, "Unknown widget '%s'\n", msg);
  458. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  459. }
  460. };
  461. void dumpLookupTable(void)
  462. {
  463. if(!map.empty()) {
  464. printf("Leaked controls:\n");
  465. for(auto i = map.begin(); i != map.end(); ++i) {
  466. printf("Known control '%s' (%p)...\n", i->first.c_str(), i->second);
  467. }
  468. }
  469. }
  470. private:
  471. std::multimap<string,Fl_Osc_Widget*> map;
  472. };
  473. Fl_Osc_Interface *GUI::genOscInterface(MiddleWare *)
  474. {
  475. return new UI_Interface();
  476. }
  477. rtosc::ThreadLink lo_buffer(4096*2, 1000);
  478. static void liblo_error_cb(int i, const char *m, const char *loc)
  479. {
  480. fprintf(stderr, "liblo :-( %d-%s@%s\n",i,m,loc);
  481. }
  482. static int handler_function(const char *path, const char *types, lo_arg **argv,
  483. int argc, lo_message msg, void *user_data)
  484. {
  485. (void) types;
  486. (void) argv;
  487. (void) argc;
  488. (void) user_data;
  489. char buffer[8192];
  490. memset(buffer, 0, sizeof(buffer));
  491. size_t size = sizeof(buffer);
  492. assert(lo_message_length(msg, path) <= sizeof(buffer));
  493. lo_message_serialise(msg, path, buffer, &size);
  494. assert(size <= sizeof(buffer));
  495. lo_buffer.raw_write(buffer);
  496. return 0;
  497. }
  498. void watch_lo(void)
  499. {
  500. while(server && Pexitprogram == 0)
  501. lo_server_recv_noblock(server, 100);
  502. }
  503. const char *help_message =
  504. "zynaddsubfx-ext-gui [options] uri - Connect to remote ZynAddSubFX\n"
  505. " --help print this help message\n"
  506. " --no-uri run without a remote ZynAddSubFX\n"
  507. " --embed window ID [Internal Flag For Embedding Windows]\n"
  508. "\n"
  509. " example: zynaddsubfx-ext-gui osc.udp://localhost:1234/\n"
  510. " This will connect to a running zynaddsubfx instance on the same\n"
  511. " machine on port 1234.\n";
  512. #ifndef CARLA_VERSION_STRING
  513. int main(int argc, char *argv[])
  514. {
  515. const char *uri = NULL;
  516. const char *title = NULL;
  517. bool help = false;
  518. bool no_uri = false;
  519. for(int i=1; i<argc; ++i) {
  520. if(!strcmp("--help", argv[i]))
  521. help = true;
  522. else if(!strcmp("--no-uri", argv[i]))
  523. no_uri = true;
  524. else if(!strcmp("--embed", argv[i]))
  525. embedId = argv[++i];
  526. else if(!strcmp("--title", argv[i]))
  527. title = argv[++i];
  528. else
  529. uri = argv[i];
  530. }
  531. if(uri == NULL && no_uri == false)
  532. help = true;
  533. if(help) {
  534. puts(help_message);
  535. return 1;
  536. }
  537. //Startup Liblo Link
  538. if(uri) {
  539. server = lo_server_new_with_proto(NULL, LO_UDP, liblo_error_cb);
  540. lo_server_add_method(server, NULL, NULL, handler_function, 0);
  541. sendtourl = uri;
  542. }
  543. fprintf(stderr, "ext client running on %d\n", lo_server_get_port(server));
  544. std::thread lo_watch(watch_lo);
  545. gui = GUI::createUi(new UI_Interface(), &Pexitprogram);
  546. if (title != NULL)
  547. GUI::raiseUi(gui, "/ui/title", "s", title);
  548. GUI::raiseUi(gui, "/show", "i", 1);
  549. while(Pexitprogram == 0) {
  550. GUI::tickUi(gui);
  551. while(lo_buffer.hasNext())
  552. raiseUi(gui, lo_buffer.read());
  553. }
  554. exitprogram();
  555. lo_watch.join();
  556. return 0;
  557. }
  558. #endif