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.

608 lines
18KB

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