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.

499 lines
16KB

  1. /*
  2. ZynAddSubFX - a software synthesizer
  3. Connection.cpp - In-Process GUI Stubs
  4. Copyright (C) 2016 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 "Connection.h"
  11. #include "Fl_Osc_Interface.h"
  12. #include "../globals.h"
  13. #include <map>
  14. #include <cassert>
  15. #include <rtosc/rtosc.h>
  16. #include <rtosc/ports.h>
  17. #include <FL/Fl.H>
  18. #include "Fl_Osc_Tree.H"
  19. #include "common.H"
  20. #include "MasterUI.h"
  21. #include "../Misc/MiddleWare.h"
  22. #ifdef NTK_GUI
  23. #include <FL/Fl_Shared_Image.H>
  24. #include <FL/Fl_Tiled_Image.H>
  25. #include <FL/Fl_Dial.H>
  26. #include <err.h>
  27. #endif // NTK_GUI
  28. #ifndef NO_UI
  29. #include "Fl_Osc_Widget.H"
  30. #endif
  31. using namespace GUI;
  32. class MasterUI *ui;
  33. #ifdef NTK_GUI
  34. static Fl_Tiled_Image *module_backdrop;
  35. #endif
  36. int kb_shortcut_handler(int)
  37. {
  38. const bool undo_ = Fl::event_ctrl() && Fl::event_key() == 'z';
  39. const bool redo = Fl::event_ctrl() && Fl::event_key() == 'r';
  40. const bool show = Fl::event_ctrl() && Fl::event_shift() &&
  41. Fl::event_key() == 's';
  42. const bool panel = Fl::event_ctrl() && Fl::event_shift() &&
  43. Fl::event_key() == 'p';
  44. if(undo_)
  45. ui->osc->write("/undo", "");
  46. else if(redo)
  47. ui->osc->write("/redo", "");
  48. else if (show) {
  49. ui->simplemasterwindow->hide();
  50. ui->masterwindow->show();
  51. } else if (panel)
  52. ui->panelwindow->show();
  53. return undo_ || redo || show;
  54. }
  55. void
  56. set_module_parameters ( Fl_Widget *o )
  57. {
  58. #ifdef NTK_GUI
  59. o->box( FL_DOWN_FRAME );
  60. o->align( o->align() | FL_ALIGN_IMAGE_BACKDROP );
  61. o->color( FL_BLACK );
  62. o->image( module_backdrop );
  63. o->labeltype( FL_SHADOW_LABEL );
  64. if(o->parent()) {
  65. o->parent()->labeltype(FL_NO_LABEL);
  66. o->parent()->box(FL_NO_BOX);
  67. }
  68. #else
  69. o->box( FL_PLASTIC_UP_BOX );
  70. o->color( FL_CYAN );
  71. o->labeltype( FL_EMBOSSED_LABEL );
  72. #endif
  73. }
  74. ui_handle_t GUI::createUi(Fl_Osc_Interface *osc, void *exit)
  75. {
  76. #ifdef NTK_GUI
  77. fl_register_images();
  78. Fl_Dial::default_style(Fl_Dial::PIXMAP_DIAL);
  79. #ifdef CARLA_VERSION_STRING
  80. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "knob.png"))
  81. Fl_Dial::default_image(img);
  82. #else
  83. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/knob.png"))
  84. Fl_Dial::default_image(img);
  85. #endif
  86. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/knob.png"))
  87. Fl_Dial::default_image(img);
  88. else
  89. errx(1, "ERROR: Cannot find pixmaps/knob.png");
  90. #ifdef CARLA_VERSION_STRING
  91. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/window_backdrop.png"))
  92. Fl::scheme_bg(new Fl_Tiled_Image(img));
  93. #else
  94. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/window_backdrop.png"))
  95. Fl::scheme_bg(new Fl_Tiled_Image(img));
  96. #endif
  97. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/window_backdrop.png"))
  98. Fl::scheme_bg(new Fl_Tiled_Image(img));
  99. else
  100. errx(1, "ERROR: Cannot find pixmaps/window_backdrop.png");
  101. #ifdef CARLA_VERSION_STRING
  102. if(Fl_Shared_Image *img = Fl_Shared_Image::get(gUiPixmapPath + "/module_backdrop.png"))
  103. module_backdrop = new Fl_Tiled_Image(img);
  104. #else
  105. if(Fl_Shared_Image *img = Fl_Shared_Image::get(PIXMAP_PATH "/module_backdrop.png"))
  106. module_backdrop = new Fl_Tiled_Image(img);
  107. #endif
  108. else if(Fl_Shared_Image *img = Fl_Shared_Image::get(SOURCE_DIR "/pixmaps/module_backdrop.png"))
  109. module_backdrop = new Fl_Tiled_Image(img);
  110. else
  111. errx(1, "ERROR: Cannot find pixmaps/module_backdrop");
  112. Fl::background(50, 50, 50);
  113. Fl::background2(70, 70, 70);
  114. Fl::foreground(255, 255, 255);
  115. #endif
  116. //Fl_Window *midi_win = new Fl_Double_Window(400, 400, "Midi connections");
  117. //Fl_Osc_Tree *tree = new Fl_Osc_Tree(0,0,400,400);
  118. //midi_win->resizable(tree);
  119. //tree->root_ports = &Master::ports;
  120. //tree->osc = osc;
  121. //midi_win->show();
  122. Fl::add_handler(kb_shortcut_handler);
  123. return (void*) (ui = new MasterUI((int*)exit, osc));
  124. }
  125. void GUI::destroyUi(ui_handle_t ui)
  126. {
  127. delete static_cast<MasterUI*>(ui);
  128. }
  129. #define BEGIN(x) {x,":non-realtime\0",NULL,[](const char *m, rtosc::RtData &d){ \
  130. MasterUI *ui = static_cast<MasterUI*>(d.obj); \
  131. rtosc_arg_t a0 = {0}, a1 = {0}; \
  132. if(rtosc_narguments(m) > 0) \
  133. a0 = rtosc_argument(m,0); \
  134. if(rtosc_narguments(m) > 1) \
  135. a1 = rtosc_argument(m,1); \
  136. (void)ui;(void)a1;(void)a0;
  137. #define END }},
  138. struct uiPorts {
  139. static rtosc::Ports ports;
  140. };
  141. //DSL based ports
  142. rtosc::Ports uiPorts::ports = {
  143. BEGIN("show:i") {
  144. ui->showUI(a0.i);
  145. } END
  146. BEGIN("alert:s") {
  147. fl_alert("%s",a0.s);
  148. } END
  149. BEGIN("alert-reload:i") {
  150. int res = fl_choice("Old autosave found, do you want to reload?",
  151. "Delete", "Reload", "Ignore");
  152. // 0 1 2
  153. if(1==res) {
  154. d.reply("/reload_auto_save", "i", a0.i);
  155. ui->refresh_master_ui();
  156. ui->updatepanel();
  157. } else if(0==res) {
  158. d.reply("/delete_auto_save", "i", a0.i);
  159. }
  160. } END
  161. BEGIN("session-type:s") {
  162. if(strcmp(a0.s,"LASH"))
  163. return;
  164. ui->sm_indicator1->value(1);
  165. ui->sm_indicator2->value(1);
  166. ui->sm_indicator1->tooltip("LASH");
  167. ui->sm_indicator2->tooltip("LASH");
  168. } END
  169. BEGIN("save-master:s") {
  170. ui->do_save_master(a0.s);
  171. } END
  172. BEGIN("load-master:s") {
  173. ui->do_load_master(a0.s);
  174. } END
  175. BEGIN("vu-meter:bb") {
  176. if(a0.b.len == sizeof(vuData) &&
  177. a1.b.len == sizeof(float)*NUM_MIDI_PARTS) {
  178. //Refresh the primary VU meters
  179. ui->simplemastervu->update((vuData*)a0.b.data);
  180. ui->mastervu->update((vuData*)a0.b.data);
  181. float *partvu = (float*)a1.b.data;
  182. for(int i=0; i<NUM_MIDI_PARTS; ++i)
  183. ui->panellistitem[i]->partvu->update(partvu[i]);
  184. }
  185. } END
  186. BEGIN("close-ui") {
  187. ui->close();
  188. } END
  189. };
  190. //very tiny rtdata ext
  191. class RtDataUI: public rtosc::RtData {
  192. public:
  193. RtDataUI(Fl_Osc_Interface *osc_)
  194. :osc(osc_)
  195. {}
  196. void reply(const char *path, const char *args, ...) override
  197. {
  198. va_list va;
  199. va_start(va,args);
  200. char buf[2048];
  201. rtosc_vmessage(buf,sizeof(buf),path,args,va);
  202. osc->writeRaw(buf);
  203. va_end(va);
  204. }
  205. Fl_Osc_Interface *osc;
  206. };
  207. void GUI::raiseUi(ui_handle_t gui, const char *message)
  208. {
  209. if(!gui)
  210. return;
  211. MasterUI *mui = (MasterUI*)gui;
  212. if(string("/damage") == message && rtosc_type(message, 0) == 's') {
  213. string damage_str = rtosc_argument(message,0).s;
  214. int npart = -1;
  215. if(sscanf(damage_str.c_str(), "/part%d", &npart) == 1 && damage_str.size() < 10) {
  216. if(mui->npartcounter->value()-1 == npart) {
  217. mui->partui->showparameters(0,-1);
  218. mui->npartcounter->do_callback();
  219. }
  220. }
  221. mui->osc->damage(rtosc_argument(message,0).s);
  222. }
  223. mui->osc->tryLink(message);
  224. //printf("got message for UI '%s'\n", message);
  225. char buffer[1024];
  226. memset(buffer, 0, sizeof(buffer));
  227. RtDataUI d(mui->osc);
  228. d.loc = buffer;
  229. d.loc_size = 1024;
  230. d.obj = gui;
  231. uiPorts::ports.dispatch(message+1, d);
  232. }
  233. void GUI::raiseUi(ui_handle_t gui, const char *dest, const char *args, ...)
  234. {
  235. char buffer[1024];
  236. va_list va;
  237. va_start(va,args);
  238. if(rtosc_vmessage(buffer,1024,dest,args,va))
  239. raiseUi(gui, buffer);
  240. va_end(va);
  241. }
  242. void GUI::tickUi(ui_handle_t)
  243. {
  244. Fl::wait(0.02f);
  245. }
  246. /******************************************************************************
  247. * OSC Interface For User Interface *
  248. * *
  249. * This is a largely out of date section of code *
  250. * Most type specific write methods are no longer used *
  251. * See UI/Fl_Osc_* to see what is actually used in this interface *
  252. ******************************************************************************/
  253. class UI_Interface:public Fl_Osc_Interface
  254. {
  255. public:
  256. UI_Interface(MiddleWare *impl_)
  257. :impl(impl_)
  258. {}
  259. void requestValue(string s) override
  260. {
  261. assert(s!="/Psysefxvol-1/part0");
  262. //Fl_Osc_Interface::requestValue(s);
  263. char *tmp = strdup(s.c_str());
  264. s = rtosc::Ports::collapsePath(tmp);
  265. free(tmp);
  266. if(impl->activeUrl() != "GUI") {
  267. impl->transmitMsg("/echo", "ss", "OSC_URL", "GUI");
  268. impl->activeUrl("GUI");
  269. }
  270. impl->transmitMsg(s.c_str(),"");
  271. }
  272. void write(string s, const char *args, ...) override
  273. {
  274. char *tmp = strdup(s.c_str());
  275. s = rtosc::Ports::collapsePath(tmp);
  276. free(tmp);
  277. va_list va;
  278. va_start(va, args);
  279. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  280. ////fprintf(stderr, ".");
  281. //fprintf(stderr, "write(%s:%s)\n", s.c_str(), args);
  282. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  283. impl->transmitMsg_va(s.c_str(), args, va);
  284. va_end(va);
  285. }
  286. void writeRaw(const char *msg) override
  287. {
  288. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  289. ////fprintf(stderr, ".");
  290. //fprintf(stderr, "rawWrite(%s:%s)\n", msg, rtosc_argument_string(msg));
  291. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  292. impl->transmitMsg(rtosc::Ports::collapsePath((char*)msg));
  293. }
  294. void writeValue(string s, string ss) override
  295. {
  296. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  297. //fprintf(stderr, "writevalue<string>(%s,%s)\n", s.c_str(),ss.c_str());
  298. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  299. write(s, "s", ss.c_str());
  300. }
  301. void writeValue(string s, char c) override
  302. {
  303. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  304. //fprintf(stderr, "writevalue<char>(%s,%d)\n", s.c_str(),c);
  305. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  306. write(s, "c", c);
  307. }
  308. void writeValue(string s, float f) override
  309. {
  310. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 4 + 30, 0 + 40);
  311. //fprintf(stderr, "writevalue<float>(%s,%f)\n", s.c_str(),f);
  312. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  313. write(s, "f", f);
  314. }
  315. void createLink(string s, class Fl_Osc_Widget*w) override
  316. {
  317. char *tmp = strdup(s.c_str());
  318. s = rtosc::Ports::collapsePath(tmp);
  319. free(tmp);
  320. assert(s.length() != 0);
  321. Fl_Osc_Interface::createLink(s,w);
  322. assert(!strstr(s.c_str(), "/part0/kit-1"));
  323. map.insert(std::pair<string,Fl_Osc_Widget*>(s,w));
  324. }
  325. void renameLink(string old, string newer, Fl_Osc_Widget *w) override
  326. {
  327. //fprintf(stdout, "renameLink('%s','%s',%p)\n",
  328. // old.c_str(), newer.c_str(), w);
  329. removeLink(old, w);
  330. createLink(newer, w);
  331. }
  332. void removeLink(string s, class Fl_Osc_Widget*w) override
  333. {
  334. char *tmp = strdup(s.c_str());
  335. s = rtosc::Ports::collapsePath(tmp);
  336. free(tmp);
  337. for(auto i = map.begin(); i != map.end(); ++i) {
  338. if(i->first == s && i->second == w) {
  339. map.erase(i);
  340. break;
  341. }
  342. }
  343. //printf("[%d] removing '%s' (%p)...\n", map.size(), s.c_str(), w);
  344. }
  345. virtual void removeLink(class Fl_Osc_Widget *w) override
  346. {
  347. bool processing = true;
  348. while(processing)
  349. {
  350. //Verify Iterator invalidation sillyness
  351. processing = false;//Exit if no new elements are found
  352. for(auto i = map.begin(); i != map.end(); ++i) {
  353. if(i->second == w) {
  354. //printf("[%d] removing '%s' (%p)...\n", map.size()-1,
  355. // i->first.c_str(), w);
  356. map.erase(i);
  357. processing = true;
  358. break;
  359. }
  360. }
  361. }
  362. }
  363. //A very simplistic implementation of a UI agnostic refresh method
  364. virtual void damage(const char *path) override
  365. {
  366. #ifndef NO_UI
  367. //printf("\n\nDamage(\"%s\")\n", path);
  368. std::set<Fl_Osc_Widget*> to_update;
  369. for(auto pair:map) {
  370. if(strstr(pair.first.c_str(), path)) {
  371. auto *tmp = dynamic_cast<Fl_Widget*>(pair.second);
  372. if(!tmp || tmp->visible_r()) {
  373. to_update.insert(pair.second);
  374. }
  375. }
  376. }
  377. for(auto elm:to_update)
  378. elm->update();
  379. #endif
  380. }
  381. void tryLink(const char *msg) override
  382. {
  383. //DEBUG
  384. //if(strcmp(msg, "/vu-meter"))//Ignore repeated message
  385. // printf("trying the link for a '%s'<%s>\n", msg, rtosc_argument_string(msg));
  386. const char *handle = strrchr(msg,'/');
  387. if(handle)
  388. ++handle;
  389. int found_count = 0;
  390. auto range = map.equal_range(msg);
  391. for(auto itr = range.first; itr != range.second; ++itr) {
  392. auto widget = itr->second;
  393. found_count++;
  394. const char *arg_str = rtosc_argument_string(msg);
  395. //Always provide the raw message
  396. widget->OSC_raw(msg);
  397. if(!strcmp(arg_str, "b")) {
  398. widget->OSC_value(rtosc_argument(msg,0).b.len,
  399. rtosc_argument(msg,0).b.data,
  400. handle);
  401. } else if(!strcmp(arg_str, "c")) {
  402. widget->OSC_value((char)rtosc_argument(msg,0).i,
  403. handle);
  404. } else if(!strcmp(arg_str, "s")) {
  405. widget->OSC_value((const char*)rtosc_argument(msg,0).s,
  406. handle);
  407. } else if(!strcmp(arg_str, "i")) {
  408. widget->OSC_value((int)rtosc_argument(msg,0).i,
  409. handle);
  410. } else if(!strcmp(arg_str, "f")) {
  411. widget->OSC_value((float)rtosc_argument(msg,0).f,
  412. handle);
  413. } else if(!strcmp(arg_str, "T") || !strcmp(arg_str, "F")) {
  414. widget->OSC_value((bool)rtosc_argument(msg,0).T, handle);
  415. }
  416. }
  417. if(found_count == 0
  418. && strcmp(msg, "/vu-meter")
  419. && strcmp(msg, "undo_change")
  420. && !strstr(msg, "parameter")
  421. && !strstr(msg, "Prespoint")) {
  422. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 1, 7 + 30, 0 + 40);
  423. //fprintf(stderr, "Unknown widget '%s'\n", msg);
  424. //fprintf(stderr, "%c[%d;%d;%dm", 0x1B, 0, 7 + 30, 0 + 40);
  425. }
  426. };
  427. void dumpLookupTable(void)
  428. {
  429. if(!map.empty()) {
  430. printf("Leaked controls:\n");
  431. for(auto i = map.begin(); i != map.end(); ++i) {
  432. printf("Known control '%s' (%p)...\n", i->first.c_str(), i->second);
  433. }
  434. }
  435. }
  436. private:
  437. std::multimap<string,Fl_Osc_Widget*> map;
  438. MiddleWare *impl;
  439. };
  440. Fl_Osc_Interface *GUI::genOscInterface(MiddleWare *mw)
  441. {
  442. return new UI_Interface(mw);
  443. }