+ DSP primitives: fix a rather stupid bug in clamping functions
[calf.git] / src / dssigui.cpp
blob4ec9cc2aaf55ed9050842c9532239ad2b1f5c323
1 /* Calf DSP Library utility application.
2 * DSSI GUI application.
3 * Copyright (C) 2007 Krzysztof Foltman
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
20 #include <assert.h>
21 #include <getopt.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <config.h>
25 #include <calf/giface.h>
26 #include <calf/gui.h>
27 #include <calf/main_win.h>
28 #include <calf/osctl.h>
29 #include <calf/osctlnet.h>
30 #include <calf/osctlserv.h>
32 using namespace std;
33 using namespace dsp;
34 using namespace calf_plugins;
35 using namespace osctl;
37 #define debug_printf(...)
39 #if 0
40 void osctl_test()
42 string sdata = string("\000\000\000\003123\000test\000\000\000\000\000\000\000\001\000\000\000\002", 24);
43 osc_stream is(sdata);
44 vector<osc_data> data;
45 is.read("bsii", data);
46 assert(is.pos == sdata.length());
47 assert(data.size() == 4);
48 assert(data[0].type == osc_blob);
49 assert(data[1].type == osc_string);
50 assert(data[2].type == osc_i32);
51 assert(data[3].type == osc_i32);
52 assert(data[0].strval == "123");
53 assert(data[1].strval == "test");
54 assert(data[2].i32val == 1);
55 assert(data[3].i32val == 2);
56 osc_stream os("");
57 os.write(data);
58 assert(os.buffer == sdata);
59 osc_server srv;
60 srv.bind("0.0.0.0", 4541);
62 osc_client cli;
63 cli.bind("0.0.0.0", 0);
64 cli.set_addr("0.0.0.0", 4541);
65 if (!cli.send("/blah", data))
66 g_error("Could not send the OSC message");
68 g_main_loop_run(g_main_loop_new(NULL, FALSE));
70 #endif
72 struct cairo_params
74 enum { HAS_COLOR = 1, HAS_WIDTH = 2 };
75 uint32_t flags;
76 float r, g, b, a;
77 float line_width;
79 cairo_params()
80 : flags(0)
81 , r(0.f)
82 , g(0.f)
83 , b(0.f)
84 , a(1.f)
85 , line_width(1)
90 struct graph_item: public cairo_params
92 float data[128];
95 struct dot_item: public cairo_params
97 float x, y;
98 int32_t size;
101 struct gridline_item: public cairo_params
103 float pos;
104 int32_t vertical;
105 std::string text;
108 struct param_line_graphs
110 vector<graph_item *> graphs;
111 vector<dot_item *> dots;
112 vector<gridline_item *> gridlines;
114 void clear();
117 void param_line_graphs::clear()
119 for (size_t i = 0; i < graphs.size(); i++)
120 delete graphs[i];
121 graphs.clear();
123 for (size_t i = 0; i < dots.size(); i++)
124 delete dots[i];
125 dots.clear();
127 for (size_t i = 0; i < gridlines.size(); i++)
128 delete gridlines[i];
129 gridlines.clear();
133 struct plugin_proxy: public plugin_ctl_iface, public plugin_metadata_proxy, public line_graph_iface
135 osc_client *client;
136 bool send_osc;
137 plugin_gui *gui;
138 map<string, string> cfg_vars;
139 int param_count;
140 float *params;
141 map<int, param_line_graphs> graphs;
142 bool update_graphs;
144 plugin_proxy(plugin_metadata_iface *md)
145 : plugin_metadata_proxy(md)
147 client = NULL;
148 send_osc = false;
149 update_graphs = true;
150 gui = NULL;
151 param_count = md->get_param_count();
152 params = new float[param_count];
153 for (int i = 0; i < param_count; i++)
154 params[i] = get_param_props(i)->def_value;
156 virtual float get_param_value(int param_no) {
157 if (param_no < 0 || param_no >= param_count)
158 return 0;
159 return params[param_no];
161 virtual void set_param_value(int param_no, float value) {
162 if (param_no < 0 || param_no >= param_count)
163 return;
164 update_graphs = true;
165 params[param_no] = value;
166 if (send_osc)
168 osc_inline_typed_strstream str;
169 str << (uint32_t)(param_no + get_param_port_offset()) << value;
170 client->send("/control", str);
173 virtual bool activate_preset(int bank, int program) {
174 if (send_osc) {
175 osc_inline_typed_strstream str;
176 str << (uint32_t)(bank) << (uint32_t)(program);
177 client->send("/program", str);
178 return false;
180 return false;
182 virtual float get_level(unsigned int port) { return 0.f; }
183 virtual void execute(int command_no) {
184 if (send_osc) {
185 stringstream ss;
186 ss << command_no;
188 osc_inline_typed_strstream str;
189 str << "ExecCommand" << ss.str();
190 client->send("/configure", str);
192 str.clear();
193 str << "ExecCommand" << "";
194 client->send("/configure", str);
197 char *configure(const char *key, const char *value) {
198 // do not store temporary vars in presets
199 if (strncmp(key, "OSC:", 4))
200 cfg_vars[key] = value;
201 osc_inline_typed_strstream str;
202 str << key << value;
203 client->send("/configure", str);
204 return NULL;
206 void send_configures(send_configure_iface *sci) {
207 for (map<string, string>::iterator i = cfg_vars.begin(); i != cfg_vars.end(); i++)
208 sci->send_configure(i->first.c_str(), i->second.c_str());
210 virtual line_graph_iface *get_line_graph_iface() { return this; }
211 virtual bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context);
212 virtual bool get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context);
213 virtual bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context);
214 void update_cairo_context(cairo_iface *context, cairo_params &item);
217 bool plugin_proxy::get_graph(int index, int subindex, float *data, int points, cairo_iface *context)
219 if (!graphs.count(index))
220 return false;
221 param_line_graphs &g = graphs[index];
222 if (subindex < (int)g.graphs.size())
224 float *sdata = g.graphs[subindex]->data;
225 for (int i = 0; i < points; i++) {
226 float pos = i * 127.0 / points;
227 int ipos = i * 127 / points;
228 data[i] = sdata[ipos] + (sdata[ipos + 1] - sdata[ipos]) * (pos-ipos);
230 update_cairo_context(context, *g.graphs[subindex]);
231 return true;
233 return false;
236 bool plugin_proxy::get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context)
238 if (!graphs.count(index))
239 return false;
240 param_line_graphs &g = graphs[index];
241 if (subindex < (int)g.dots.size())
243 dot_item &item = *g.dots[subindex];
244 x = item.x;
245 y = item.y;
246 size = item.size;
247 update_cairo_context(context, item);
248 return true;
250 return false;
253 bool plugin_proxy::get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context)
255 if (!graphs.count(index))
256 return false;
257 param_line_graphs &g = graphs[index];
258 if (subindex < (int)g.gridlines.size())
260 gridline_item &item = *g.gridlines[subindex];
261 pos = item.pos;
262 vertical = item.vertical != 0;
263 legend = item.text;
264 update_cairo_context(context, item);
265 return true;
267 return false;
270 void plugin_proxy::update_cairo_context(cairo_iface *context, cairo_params &item)
272 if (item.flags & cairo_params::HAS_COLOR)
273 context->set_source_rgba(item.r, item.g, item.b, item.a);
274 if (item.flags & cairo_params::HAS_WIDTH)
275 context->set_line_width(item.line_width);
278 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
280 GMainLoop *mainloop;
282 static bool osc_debug = false;
284 struct dssi_osc_server: public osc_server, public osc_message_sink<osc_strstream>
286 plugin_proxy *plugin;
287 main_window *main_win;
288 plugin_gui_window *window;
289 string effect_name, title;
290 osc_client cli;
291 bool in_program, enable_dump;
292 vector<plugin_preset> presets;
293 /// Timeout callback source ID
294 int source_id;
295 bool osc_link_active;
297 dssi_osc_server()
298 : plugin(NULL)
299 , main_win(new main_window)
300 , window(new plugin_gui_window(main_win))
302 sink = this;
303 source_id = 0;
304 osc_link_active = false;
307 static void on_destroy(GtkWindow *window, dssi_osc_server *self)
309 debug_printf("on_destroy, have to send \"exiting\"\n");
310 bool result = self->cli.send("/exiting");
311 debug_printf("result = %d\n", result ? 1 : 0);
312 g_main_loop_quit(mainloop);
313 // eliminate a warning with empty debug_printf
314 result = false;
317 void create_window()
319 plugin = NULL;
320 vector<plugin_metadata_iface *> plugins;
321 get_all_plugins(plugins);
322 for (unsigned int i = 0; i < plugins.size(); i++)
324 if (!strcmp(plugins[i]->get_id(), effect_name.c_str()))
326 plugin = new plugin_proxy(plugins[i]);
327 break;
330 if (!plugin)
332 fprintf(stderr, "Unknown plugin: %s\n", effect_name.c_str());
333 exit(1);
335 plugin->client = &cli;
336 plugin->send_osc = true;
337 ((main_window *)window->main)->conditions.insert("dssi");
338 ((main_window *)window->main)->conditions.insert("directlink");
339 window->create(plugin, title.c_str(), effect_name.c_str());
340 plugin->gui = window->gui;
341 gtk_signal_connect(GTK_OBJECT(window->toplevel), "destroy", G_CALLBACK(on_destroy), this);
342 vector<plugin_preset> tmp_presets;
343 get_builtin_presets().get_for_plugin(presets, effect_name.c_str());
344 get_user_presets().get_for_plugin(tmp_presets, effect_name.c_str());
345 presets.insert(presets.end(), tmp_presets.begin(), tmp_presets.end());
346 source_id = g_timeout_add_full(G_PRIORITY_LOW, 1000/30, on_idle, this, NULL);
349 static gboolean on_idle(void *self)
351 dssi_osc_server *self_ptr = (dssi_osc_server *)(self);
352 if (self_ptr->osc_link_active)
353 self_ptr->send_osc_update();
354 return TRUE;
357 void set_osc_update(bool enabled);
358 void send_osc_update();
360 virtual void receive_osc_message(std::string address, std::string args, osc_strstream &buffer);
361 void unmarshal_line_graph(osc_strstream &buffer);
364 void dssi_osc_server::set_osc_update(bool enabled)
366 osc_link_active = enabled;
367 osc_inline_typed_strstream data;
368 data << "OSC:FEEDBACK_URI";
369 data << (enabled ? get_uri() : "");
370 cli.send("/configure", data);
373 void dssi_osc_server::send_osc_update()
375 static int serial_no = 0;
376 osc_inline_typed_strstream data;
377 data << "OSC:UPDATE";
378 data << calf_utils::i2s(serial_no++);
379 cli.send("/configure", data);
382 void dssi_osc_server::unmarshal_line_graph(osc_strstream &buffer)
384 uint32_t cmd;
386 do {
387 buffer >> cmd;
388 if (cmd == LGI_GRAPH)
390 uint32_t param;
391 buffer >> param;
392 param_line_graphs &graphs = plugin->graphs[param];
394 graphs.clear();
395 cairo_params params;
397 do {
398 buffer >> cmd;
399 if (cmd == LGI_SET_RGBA)
401 params.flags |= cairo_params::HAS_COLOR;
402 buffer >> params.r >> params.g >> params.b >> params.a;
404 else
405 if (cmd == LGI_SET_WIDTH)
407 params.flags |= cairo_params::HAS_WIDTH;
408 buffer >> params.line_width;
410 else
411 if (cmd == LGI_SUBGRAPH)
413 buffer >> param; // ignore number of points
414 graph_item *gi = new graph_item;
415 (cairo_params &)*gi = params;
416 for (int i = 0; i < 128; i++)
417 buffer >> gi->data[i];
418 graphs.graphs.push_back(gi);
419 params.flags = 0;
421 else
422 if (cmd == LGI_DOT)
424 dot_item *di = new dot_item;
425 (cairo_params &)*di = params;
426 buffer >> di->x >> di->y >> di->size;
427 graphs.dots.push_back(di);
428 params.flags = 0;
430 else
431 if (cmd == LGI_LEGEND)
433 gridline_item *li = new gridline_item;
434 (cairo_params &)*li = params;
435 buffer >> li->pos >> li->vertical >> li->text;
436 graphs.gridlines.push_back(li);
437 params.flags = 0;
439 else
440 break;
441 } while(1);
444 else
445 break;
446 } while(1);
449 void dssi_osc_server::receive_osc_message(std::string address, std::string args, osc_strstream &buffer)
451 if (osc_debug)
452 dump.receive_osc_message(address, args, buffer);
453 if (address == prefix + "/update" && args == "s")
455 string str;
456 buffer >> str;
457 debug_printf("UPDATE: %s\n", str.c_str());
458 set_osc_update(true);
459 send_osc_update();
460 return;
462 else if (address == prefix + "/quit")
464 set_osc_update(false);
465 debug_printf("QUIT\n");
466 g_main_loop_quit(mainloop);
467 return;
469 else if (address == prefix + "/configure"&& args == "ss")
471 string key, value;
472 buffer >> key >> value;
473 // do not store temporary vars in presets
474 if (strncmp(key.c_str(), "OSC:", 4))
475 plugin->cfg_vars[key] = value;
476 // XXXKF perhaps this should be queued !
477 window->gui->refresh();
478 return;
480 else if (address == prefix + "/program"&& args == "ii")
482 uint32_t bank, program;
484 buffer >> bank >> program;
486 unsigned int nr = bank * 128 + program;
487 debug_printf("PROGRAM %d\n", nr);
488 if (nr == 0)
490 bool sosc = plugin->send_osc;
491 plugin->send_osc = false;
492 int count = plugin->get_param_count();
493 for (int i =0 ; i < count; i++)
494 plugin->set_param_value(i, plugin->get_param_props(i)->def_value);
495 plugin->send_osc = sosc;
496 window->gui->refresh();
497 // special handling for default preset
498 return;
500 nr--;
501 if (nr >= presets.size())
502 return;
503 bool sosc = plugin->send_osc;
504 plugin->send_osc = false;
505 presets[nr].activate(plugin);
506 plugin->send_osc = sosc;
507 window->gui->refresh();
509 // cli.send("/update", data);
510 return;
512 else if (address == prefix + "/control" && args == "if")
514 uint32_t port;
515 float val;
517 buffer >> port >> val;
519 int idx = port - plugin->get_param_port_offset();
520 debug_printf("CONTROL %d %f\n", idx, val);
521 bool sosc = plugin->send_osc;
522 plugin->send_osc = false;
523 window->gui->set_param_value(idx, val);
524 plugin->send_osc = sosc;
525 if (plugin->get_param_props(idx)->flags & PF_PROP_GRAPH)
526 plugin->update_graphs = true;
527 return;
529 else if (address == prefix + "/show")
531 set_osc_update(true);
533 gtk_widget_show_all(GTK_WIDGET(window->toplevel));
534 return;
536 else if (address == prefix + "/hide")
538 set_osc_update(false);
540 gtk_widget_hide(GTK_WIDGET(window->toplevel));
541 return;
543 else if (address == prefix + "/lineGraph")
545 unmarshal_line_graph(buffer);
546 if (plugin->update_graphs) {
547 // updates graphs that are only redrawn on startup and parameter changes
548 // (the OSC message may come a while after the parameter has been changed,
549 // so the redraw triggered by parameter change usually shows stale values)
550 window->gui->refresh();
551 plugin->update_graphs = false;
553 return;
555 else
556 printf("Unknown OSC address: %s\n", address.c_str());
559 //////////////////////////////////////////////////////////////////////////////////
561 void help(char *argv[])
563 printf("GTK+ user interface for Calf DSSI plugins\nSyntax: %s [--help] [--version] <osc-url> <so-file> <plugin-label> <instance-name>\n", argv[0]);
566 static struct option long_options[] = {
567 {"help", 0, 0, 'h'},
568 {"version", 0, 0, 'v'},
569 {"debug", 0, 0, 'd'},
570 {0,0,0,0},
573 int main(int argc, char *argv[])
575 char *debug_var = getenv("OSC_DEBUG");
576 if (debug_var && atoi(debug_var))
577 osc_debug = true;
579 gtk_init(&argc, &argv);
580 while(1) {
581 int option_index;
582 int c = getopt_long(argc, argv, "dhv", long_options, &option_index);
583 if (c == -1)
584 break;
585 switch(c) {
586 case 'd':
587 osc_debug = true;
588 break;
589 case 'h':
590 help(argv);
591 return 0;
592 case 'v':
593 printf("%s\n", PACKAGE_STRING);
594 return 0;
597 if (argc - optind != 4)
599 help(argv);
600 exit(0);
603 try {
604 get_builtin_presets().load_defaults(true);
605 get_user_presets().load_defaults(false);
607 catch(calf_plugins::preset_exception &e)
609 fprintf(stderr, "Error while loading presets: %s\n", e.what());
610 exit(1);
613 dssi_osc_server srv;
614 srv.prefix = "/dssi/"+string(argv[optind + 1]) + "/" + string(argv[optind + 2]);
615 for (char *p = argv[optind + 2]; *p; p++)
616 *p = tolower(*p);
617 srv.effect_name = argv[optind + 2];
618 srv.title = argv[optind + 3];
620 srv.bind();
621 srv.create_window();
623 mainloop = g_main_loop_new(NULL, FALSE);
625 srv.cli.bind();
626 srv.cli.set_url(argv[optind]);
628 debug_printf("URI = %s\n", srv.get_uri().c_str());
630 string data_buf, type_buf;
631 osc_inline_typed_strstream data;
632 data << srv.get_uri();
633 if (!srv.cli.send("/update", data))
635 g_error("Could not send the initial update message via OSC to %s", argv[optind]);
636 return 1;
639 g_main_loop_run(mainloop);
640 if (srv.source_id)
641 g_source_remove(srv.source_id);
643 srv.set_osc_update(false);
644 debug_printf("exited\n");
646 return 0;