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.

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