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.

guimain.cpp 18KB

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