1 // omc-learn.c
2 // LiVES (lives-exe)
3 // (c) G. Finch 2008 - 2018 <salsaman+lives@gmail.com>
4 // Released under the GPL 3 or later
5 // see file ../COPYING for licensing details
6 
7 #ifdef ENABLE_OSC
8 
9 #include "main.h"
10 #include "paramwindow.h"
11 #include "effects.h"
12 #include "interface.h"
13 #include "callbacks.h"
14 
15 #include "omc-learn.h"
16 
17 #ifdef OMC_JS_IMPL
18 #include <linux/joystick.h>
19 #endif
20 
21 #include <errno.h>
22 
23 // learn and match with an external control
24 // generally, external data is passed in as a type and a string (a sequence ascii encoded ints separated by spaces)
25 // the string will have a fixed sig(nature) which is matched against learned nodes
26 //
27 // the number of fixed values depends on the origin of the data; for example for a MIDI controller
28 // it is 2 (controller + controller number)
29 // the rest of the string is variables. These are either mapped in order to the parameters of the macro or can be filtered against
30 
31 // these types/strings are matched against OMC macros -
32 // the macros have slots for parameters which are filled in order from variables in the input
33 
34 // TODO !! - greedy matching should done - i.e. if an input sequence matches more than one macro,
35 // each of those macros will be triggered
36 // for now, only first match is acted on
37 
38 // some events are filtered out, for example MIDI_NOTE_OFF, joystick button release; this needs to be done automatically
39 
40 // TODO: we need end up with a table (struct *) like:
41 // int supertype;
42 // int ntypes;
43 // int *nfixed;
44 // int **min;
45 // int **max;
46 // boolean *uses_index;
47 // char **ignore;
48 
49 // where min/max are not known we will need to calibrate
50 
51 static OSCbuf obuf;
52 static char byarr[OSC_BUF_SIZE];
53 static lives_omc_macro_t omc_macros[N_OMC_MACROS];
54 static LiVESSList *omc_node_list;
55 static boolean omc_macros_inited = FALSE;
56 
57 static void init_omc_macros(void);
58 
59 //////////////////////////////////////////////////////////////
get_omc_macro(int idx)60 const lives_omc_macro_t *get_omc_macro(int idx) {
61   if (!omc_macros_inited) {
62     init_omc_macros();
63     omc_macros_inited = TRUE;
64     OSC_initBuffer(&obuf, OSC_BUF_SIZE, byarr);
65   }
66 
67   if (idx >= N_OMC_MACROS || !omc_macros[idx].msg) return NULL;
68 
69   return &omc_macros[idx];
70 }
71 
72 
has_devicemap(int target)73 boolean has_devicemap(int target) {
74   if (target != -1) {
75     lives_omc_match_node_t *mnode;
76     LiVESSList *slist = omc_node_list;
77     while (slist) {
78       mnode = (lives_omc_match_node_t *)slist->data;
79       if (mnode->macro == target) return TRUE;
80       slist = slist->next;
81     }
82     return FALSE;
83   }
84   return (omc_node_list != NULL);
85 }
86 
87 
omc_match_node_free(lives_omc_match_node_t * mnode)88 static void omc_match_node_free(lives_omc_match_node_t *mnode) {
89   if (mnode->nvars > 0) {
90     lives_free(mnode->offs0); lives_free(mnode->scale); lives_free(mnode->offs1);
91     lives_free(mnode->min); lives_free(mnode->max);
92     lives_free(mnode->matchp); lives_free(mnode->matchi);
93   }
94 
95   if (mnode->map) lives_free(mnode->map);
96   if (mnode->fvali) lives_free(mnode->fvali);
97   if (mnode->fvald) lives_free(mnode->fvald);
98 
99   lives_free(mnode->srch);
100 
101   lives_free(mnode);
102 }
103 
104 
remove_all_nodes(boolean every,omclearn_w * omclw)105 static void remove_all_nodes(boolean every, omclearn_w *omclw) {
106   lives_omc_match_node_t *mnode;
107   LiVESSList *slist_last = NULL, *slist_next;
108   LiVESSList *slist = omc_node_list;
109 
110   while (slist) {
111     slist_next = slist->next;
112 
113     mnode = (lives_omc_match_node_t *)slist->data;
114 
115     if (every || mnode->macro == UNMATCHED) {
116       if (slist_last) slist_last->next = slist->next;
117       else omc_node_list = slist->next;
118       omc_match_node_free(mnode);
119     } else slist_last = slist;
120     slist = slist_next;
121   }
122 
123   lives_widget_set_sensitive(omclw->clear_button, FALSE);
124   if (!slist) lives_widget_set_sensitive(omclw->del_all_button, FALSE);
125   mainw->midi_channel_lock = FALSE;
126 }
127 
128 
js_index(const char * string)129 LIVES_INLINE int js_index(const char *string) {
130   // js index, or midi channel number
131   char **array = lives_strsplit(string, " ", -1);
132   int res = atoi(array[1]);
133   lives_strfreev(array);
134   return res;
135 }
136 
137 
midi_msg_type(const char * string)138 static int midi_msg_type(const char *string) {
139   int type = atoi(string);
140 
141   if ((type & 0XF0) == 0X90) return OMC_MIDI_NOTE; // data: note, velocity
142   if ((type & 0XF0) == 0x80) return OMC_MIDI_NOTE_OFF; // data: note, velocity
143   if ((type & 0XF0) == 0xB0) return OMC_MIDI_CONTROLLER; // data: controller number, data
144   if ((type & 0XF0) == 0xC0) return OMC_MIDI_PGM_CHANGE; // data: program number
145   if ((type & 0XF0) == 0xE0) return OMC_MIDI_PITCH_BEND; // data: lsb, msb
146 
147   // other types are currently ignored:
148 
149   // 0XA0 is polyphonic aftertouch, has note and pressure
150 
151   // 0xD0 is channel aftertouch, 1 byte pressure
152 
153   // 0XF0 - 0xFF is sysex
154 
155   return 0;
156 }
157 
158 
get_nfixed(int type,const char * string)159 static int get_nfixed(int type, const char *string) {
160   int nfixed = 0;
161 
162   switch (type) {
163   case OMC_JS_BUTTON:
164     nfixed = 3; // type, index, value
165     break;
166   case OMC_JS_AXIS:
167     nfixed = 2; // type, index
168     break;
169 #ifdef OMC_MIDI_IMPL
170   case OMC_MIDI:
171     type = midi_msg_type(string);
172     return get_nfixed(type, NULL);
173   case OMC_MIDI_CONTROLLER:
174     if (prefs->midi_rcv_channel > MIDI_OMNI) nfixed = 2; // type, cnum
175     else nfixed = 3;   // type, channel, cnum
176     break;
177   case OMC_MIDI_NOTE:
178   case OMC_MIDI_NOTE_OFF:
179   case OMC_MIDI_PITCH_BEND:
180   case OMC_MIDI_PGM_CHANGE:
181     if (prefs->midi_rcv_channel > MIDI_OMNI) nfixed = 1; // type
182     else nfixed = 2; // type, channel
183     break;
184 #endif
185   }
186   return nfixed;
187 }
188 
189 
midi_index(const char * string)190 LIVES_INLINE int midi_index(const char *string) {
191   // midi controller number
192   char **array;
193   int res;
194   int nfixed = get_nfixed(OMC_MIDI_CONTROLLER, NULL);
195 
196   if (get_token_count(string, ' ') < nfixed) return -1;
197 
198   array = lives_strsplit(string, " ", -1);
199   res = atoi(array[nfixed - 1]);
200   lives_strfreev(array);
201   return res;
202 }
203 
204 #ifdef OMC_JS_IMPL
205 
206 static int js_fd;
207 
208 
209 #ifndef IS_MINGW
get_js_filename(void)210 const char *get_js_filename(void) {
211   char *js_fname;
212 
213   // OPEN DEVICE FILE
214   // first try to open /dev/input/js
215   js_fname = "/dev/input/js";
216   js_fd = open(js_fname, O_RDONLY | O_NONBLOCK);
217   if (js_fd < 0) {
218     // if it doesn't open, try to open /dev/input/js0
219     js_fname = "/dev/input/js0";
220     js_fd = open(js_fname, O_RDONLY | O_NONBLOCK);
221     if (js_fd < 0) {
222       js_fname = "/dev/js0";
223       js_fd = open(js_fname, O_RDONLY | O_NONBLOCK);
224       // if no device is found
225       if (js_fd < 0) {
226         return NULL;
227       }
228     }
229   }
230   return js_fname;
231 }
232 #endif
233 
234 
js_open(void)235 boolean js_open(void) {
236   if (!(prefs->omc_dev_opts & OMC_DEV_JS)) return TRUE;
237 
238   if (strlen(prefs->omc_js_fname)) {
239     js_fd = open(prefs->omc_js_fname, O_RDONLY | O_NONBLOCK);
240     if (js_fd < 0) return FALSE;
241   } else {
242     const char *tmp = get_js_filename();
243     if (tmp) {
244       lives_snprintf(prefs->omc_js_fname, 256, "%s", tmp);
245     }
246   }
247   if (!strlen(prefs->omc_js_fname)) return FALSE;
248 
249   mainw->ext_cntl[EXT_CNTL_JS] = TRUE;
250   d_print(_("Responding to joystick events from %s\n"), prefs->omc_js_fname);
251 
252   return TRUE;
253 }
254 
255 
js_close(void)256 void js_close(void) {
257   if (mainw->ext_cntl[EXT_CNTL_JS]) {
258     close(js_fd);
259     mainw->ext_cntl[EXT_CNTL_JS] = FALSE;
260   }
261 }
262 
263 
js_mangle(void)264 char *js_mangle(void) {
265   // get js event and process it
266   struct js_event jse;
267   size_t bytes;
268   char *ret;
269   int type = 0;
270 
271   bytes = read(js_fd, &jse, sizeof(jse));
272 
273   if (bytes != sizeof(jse)) return NULL;
274 
275   jse.type &= ~JS_EVENT_INIT; /* ignore synthetic events */
276   if (jse.type == JS_EVENT_AXIS) {
277     type = OMC_JS_AXIS;
278     if (jse.value == 0) return NULL;
279   } else if (jse.type == JS_EVENT_BUTTON) {
280     if (jse.value == 0) return NULL;
281     type = OMC_JS_BUTTON;
282   }
283 
284   ret = lives_strdup_printf("%d %d %d", type, jse.number, jse.value);
285 
286   return ret;
287 }
288 
289 #endif  // OMC_JS
290 
js_msg_type(const char * string)291 LIVES_INLINE int js_msg_type(const char *string) {
292   return atoi(string);
293 }
294 
295 
296 #ifdef OMC_MIDI_IMPL
297 
298 static int midi_fd;
299 
300 #ifndef IS_MINGW
301 
get_midi_filename(void)302 const char *get_midi_filename(void) {
303   char *midi_fname;
304 
305   // OPEN DEVICE FILE
306   midi_fname = "/dev/midi";
307   midi_fd = open(midi_fname, O_RDONLY | O_NONBLOCK);
308   if (midi_fd < 0) {
309     midi_fname = "/dev/midi0";
310     midi_fd = open(midi_fname, O_RDONLY | O_NONBLOCK);
311     if (midi_fd < 0) {
312       midi_fname = "/dev/midi1";
313       midi_fd = open(midi_fname, O_RDONLY | O_NONBLOCK);
314       if (midi_fd < 0) {
315         return NULL;
316       }
317     }
318   }
319   return midi_fname;
320 }
321 
322 #endif
323 
324 
midi_open(void)325 boolean midi_open(void) {
326   if (!(prefs->omc_dev_opts & OMC_DEV_MIDI)) return TRUE;
327 
328 #ifdef ALSA_MIDI
329   if (prefs->use_alsa_midi) {
330     d_print(_("Creating ALSA MIDI port(s)..."));
331     mainw->alsa_midi_dummy = -1;
332 
333     // Open an ALSA MIDI port
334     if (snd_seq_open(&mainw->seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) < 0) {
335       d_print_failed();
336       return FALSE;
337     }
338 
339     d_print("\n");
340 
341     snd_seq_set_client_name(mainw->seq_handle, "LiVES");
342     d_print(_("MIDI IN port..."));
343     if ((mainw->alsa_midi_port = snd_seq_create_simple_port(mainw->seq_handle, "LiVES",
344                                  SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
345                                  SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_PORT | SND_SEQ_PORT_TYPE_SOFTWARE)) < 0) {
346       snd_seq_close(mainw->seq_handle);
347       mainw->seq_handle = NULL;
348       d_print_failed();
349       return FALSE;
350     }
351     if (prefs->alsa_midi_dummy) {
352       d_print_done();
353       d_print(_("dummy MIDI OUT port..."));
354       // create dummy MIDI out if asked to. Some clients use the name for reference.
355       if ((mainw->alsa_midi_dummy = snd_seq_create_simple_port(mainw->seq_handle,
356                                     "LiVES", // some external clients read this name,
357                                     //but will actually send to the WRITE port with same name
358                                     SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, // need both
359                                     SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_PORT | SND_SEQ_PORT_TYPE_SOFTWARE)) < 0) {
360         snd_seq_delete_simple_port(mainw->seq_handle, mainw->alsa_midi_port);
361         snd_seq_close(mainw->seq_handle);
362         mainw->seq_handle = NULL;
363         d_print_failed();
364         return FALSE;
365       }
366     }
367     d_print_done();
368   } else {
369 #endif
370 
371 #ifndef IS_MINGW
372     if (strlen(prefs->omc_midi_fname)) {
373       midi_fd = open(prefs->omc_midi_fname, O_RDONLY | O_NONBLOCK);
374       if (midi_fd < 0) return FALSE;
375     } else {
376       const char *tmp = get_midi_filename();
377       if (tmp) {
378         lives_snprintf(prefs->omc_midi_fname, 256, "%s", tmp);
379       }
380     }
381     if (!strlen(prefs->omc_midi_fname)) return FALSE;
382 
383     d_print(_("Responding to MIDI events from %s\n"), prefs->omc_midi_fname);
384 #endif
385 
386 #ifdef ALSA_MIDI
387   }
388 #endif
389 
390   mainw->ext_cntl[EXT_CNTL_MIDI] = TRUE;
391 
392   return TRUE;
393 }
394 
395 
midi_close(void)396 void midi_close(void) {
397   if (mainw->ext_cntl[EXT_CNTL_MIDI]) {
398 #ifdef ALSA_MIDI
399     if (mainw->seq_handle) {
400       // close
401       snd_seq_delete_simple_port(mainw->seq_handle, mainw->alsa_midi_port);
402       if (mainw->alsa_midi_dummy >= 0) snd_seq_delete_simple_port(mainw->seq_handle, mainw->alsa_midi_dummy);
403       snd_seq_close(mainw->seq_handle);
404       mainw->seq_handle = NULL;
405     } else {
406 #endif
407       close(midi_fd);
408 
409 #ifdef ALSA_MIDI
410     }
411 #endif
412     mainw->ext_cntl[EXT_CNTL_MIDI] = FALSE;
413   }
414 }
415 
416 
get_midi_len(int msgtype)417 static int get_midi_len(int msgtype) {
418   switch (msgtype) {
419   case OMC_MIDI_CONTROLLER:
420   case OMC_MIDI_NOTE:
421   case OMC_MIDI_PITCH_BEND:
422   case OMC_MIDI_NOTE_OFF:
423     return 3;
424   case OMC_MIDI_PGM_CHANGE:
425     return 2;
426   }
427   return 0;
428 }
429 
430 
midi_mangle(void)431 char *midi_mangle(void) {
432   // get MIDI event and process it
433   char *string = NULL;
434 
435   ssize_t bytes, tot = 0, allowed = prefs->midi_rpt;
436   unsigned char midbuf[4], xbuf[4];
437   int target = 1, mtype = 0, channel;
438   boolean got_target = FALSE;
439 
440 #ifdef ALSA_MIDI
441   int npfd = 0;
442   struct pollfd *pfd = NULL;
443   snd_seq_event_t *ev;
444   int typeNumber;
445   boolean hasmore = FALSE;
446 
447   if (mainw->seq_handle) {
448     if (snd_seq_event_input_pending(mainw->seq_handle, 0) == 0) {
449       // returns number of poll descriptors
450       npfd = snd_seq_poll_descriptors_count(mainw->seq_handle, POLLIN);
451 
452       if (npfd < 1) return NULL;
453 
454       pfd = (struct pollfd *)lives_malloc(npfd * sizeof(struct pollfd));
455 
456       // fill our poll descriptors
457       snd_seq_poll_descriptors(mainw->seq_handle, pfd, npfd, POLLIN);
458     } else hasmore = TRUE; // events remaining from the last call to this function
459 
460     if (hasmore || poll(pfd, npfd, 0) > 0) {
461       do {
462         if (snd_seq_event_input(mainw->seq_handle, &ev) < 0) {
463           break; // an error occurred reading from the port
464         }
465 
466         switch (ev->type) {
467         case SND_SEQ_EVENT_CONTROLLER:
468           if (prefs->midi_rcv_channel != MIDI_OMNI && ev->data.control.channel != prefs->midi_rcv_channel) break;
469           typeNumber = 176;
470           if (prefs->midi_rcv_channel == MIDI_OMNI)
471             string = lives_strdup_printf("%d %d %u %d", typeNumber + ev->data.control.channel, ev->data.control.channel,
472                                          ev->data.control.param,
473                                          ev->data.control.value);
474           else
475             string = lives_strdup_printf("%d %u %d", typeNumber, ev->data.control.param,
476                                          ev->data.control.value);
477 
478           break;
479         case SND_SEQ_EVENT_PITCHBEND:
480           if (prefs->midi_rcv_channel != MIDI_OMNI && ev->data.control.channel != prefs->midi_rcv_channel) break;
481           typeNumber = 224;
482           if (prefs->midi_rcv_channel == MIDI_OMNI)
483             string = lives_strdup_printf("%d %d %d", typeNumber + ev->data.control.channel, ev->data.control.channel,
484                                          ev->data.control.value);
485           else
486             string = lives_strdup_printf("%d %d", typeNumber, ev->data.control.value);
487           break;
488 
489         case SND_SEQ_EVENT_NOTEON:
490           if (prefs->midi_rcv_channel != MIDI_OMNI && ev->data.note.channel != prefs->midi_rcv_channel) break;
491           typeNumber = 144;
492           if (prefs->midi_rcv_channel == MIDI_OMNI)
493             string = lives_strdup_printf("%d %d %d %d", typeNumber + ev->data.note.channel,
494                                          ev->data.note.channel, ev->data.note.note,
495                                          ev->data.note.velocity);
496           else
497             string = lives_strdup_printf("%d %d %d", typeNumber, ev->data.note.note,
498                                          ev->data.note.velocity);
499 
500           break;
501         case SND_SEQ_EVENT_NOTEOFF:
502           if (prefs->midi_rcv_channel != MIDI_OMNI && ev->data.note.channel != prefs->midi_rcv_channel) break;
503           typeNumber = 128;
504           if (prefs->midi_rcv_channel == MIDI_OMNI)
505             string = lives_strdup_printf("%d %d %d %d", typeNumber + ev->data.note.channel,
506                                          ev->data.note.channel, ev->data.note.note,
507                                          ev->data.note.off_velocity);
508           else
509             string = lives_strdup_printf("%d %d %d", typeNumber, ev->data.note.note,
510                                          ev->data.note.off_velocity);
511 
512           break;
513         case SND_SEQ_EVENT_PGMCHANGE:
514           if (prefs->midi_rcv_channel != MIDI_OMNI && ev->data.note.channel != prefs->midi_rcv_channel) break;
515           typeNumber = 192;
516           if (prefs->midi_rcv_channel == MIDI_OMNI)
517             string = lives_strdup_printf("%d %d %d", typeNumber + ev->data.note.channel,
518                                          ev->data.note.channel, ev->data.control.value);
519           else
520             string = lives_strdup_printf("%d %d", typeNumber, ev->data.control.value);
521 
522           break;
523         }
524         snd_seq_free_event(ev);
525       } while (snd_seq_event_input_pending(mainw->seq_handle, 0) > 0 && !string);
526     }
527 
528     if (pfd) lives_free(pfd);
529   } else {
530 #endif
531     if (midi_fd == -1) return NULL;
532 
533     while (tot < target) {
534       bytes = read(midi_fd, xbuf, target - tot);
535 
536       if (bytes < 1) {
537         if (--allowed < 0) return NULL;
538         continue;
539       }
540 
541       if (!got_target) {
542         char *str = lives_strdup_printf("%d", xbuf[0]);
543         target = get_midi_len((mtype = midi_msg_type(str)));
544         lives_free(str);
545       }
546 
547       //g_print("midi pip %d %02X , tg=%d\n",bytes,xbuf[0],target);
548 
549       lives_memcpy(midbuf + tot, xbuf, bytes);
550 
551       tot += bytes;
552     }
553 
554     if (mtype == 0) return NULL;
555 
556     channel = (midbuf[0] & 0x0F); // MIDI channel
557 
558     if (prefs->midi_rcv_channel != MIDI_OMNI && channel != prefs->midi_rcv_channel) return NULL; // wrong channel, ignore it
559 
560     if (prefs->midi_rcv_channel == MIDI_OMNI) {
561       // omni mode
562       if (target == 2) string = lives_strdup_printf("%u %u %u", midbuf[0], channel, midbuf[1]);
563       else if (target == 3) string = lives_strdup_printf("%u %u %u %u", midbuf[0], channel, midbuf[1], midbuf[2]);
564       else string = lives_strdup_printf("%u %u %u %u %u", midbuf[0], channel, midbuf[1], midbuf[2], midbuf[3]);
565     } else {
566       midbuf[0] &= 0xF0;
567       if (target == 2) string = lives_strdup_printf("%u %u", midbuf[0], midbuf[1]);
568       else if (target == 3) string = lives_strdup_printf("%u %u %u", midbuf[0], midbuf[1], midbuf[2]);
569       else string = lives_strdup_printf("%u %u %u %u", midbuf[0], midbuf[1], midbuf[2], midbuf[3]);
570     }
571 #ifdef ALSA_MIDI
572   }
573 #endif
574 
575   //g_print("got %s\n",string);
576 
577   return string;
578 }
579 
580 #endif //OMC_MIDI_IMPL
581 
582 
cut_string_elems(const char * string,int nelems)583 LIVES_INLINE char *cut_string_elems(const char *string, int nelems) {
584   // remove elements after nelems
585 
586   char *retval = lives_strdup(string);
587   register int i;
588   size_t slen = strlen(string);
589 
590   if (nelems < 0) return retval;
591 
592   for (i = 0; i < slen; i++) {
593     if (!strncmp((string + i), " ", 1)) {
594       if (--nelems == 0) {
595         lives_memset(retval + i, 0, 1);
596         return retval;
597       }
598     }
599   }
600   return retval;
601 }
602 
603 
omc_learn_get_pname(int type,int idx)604 static char *omc_learn_get_pname(int type, int idx) {
605   switch (type) {
606   case OMC_MIDI_CONTROLLER:
607   case OMC_MIDI_PGM_CHANGE:
608     return (_("data"));
609   case OMC_MIDI_NOTE:
610   case OMC_MIDI_NOTE_OFF:
611     if (idx == 1) return (_("velocity"));
612     return (_("note"));
613   case OMC_JS_AXIS:
614   case OMC_MIDI_PITCH_BEND:
615     return (_("value"));
616   default:
617     return (_("state"));
618   }
619 }
620 
621 
omc_learn_get_pvalue(int type,int idx,const char * string)622 static int omc_learn_get_pvalue(int type, int idx, const char *string) {
623   char **array = lives_strsplit(string, " ", -1);
624   int res;
625   int nfixed = get_nfixed(type, NULL);
626 
627   res = atoi(array[nfixed + idx]);
628   lives_strfreev(array);
629   return res;
630 }
631 
632 
cell1_edited_callback(LiVESCellRenderer * spinbutton,const char * path_string,const char * new_text,livespointer user_data)633 static void cell1_edited_callback(LiVESCellRenderer *spinbutton, const char *path_string, const char *new_text,
634                                   livespointer user_data) {
635   lives_omc_match_node_t *mnode = (lives_omc_match_node_t *)user_data;
636 
637   lives_omc_macro_t omacro = omc_macros[mnode->macro];
638 
639   int vali;
640   double vald;
641 
642   LiVESTreeIter iter;
643 
644   int row;
645 
646   int *indices;
647 
648   LiVESTreePath *tpath = lives_tree_path_new_from_string(path_string);
649 
650   if (lives_tree_path_get_depth(tpath) != 2) {
651     lives_tree_path_free(tpath);
652     return;
653   }
654 
655   indices = lives_tree_path_get_indices(tpath);
656   row = indices[1];
657 
658   lives_tree_model_get_iter(LIVES_TREE_MODEL(mnode->gtkstore2), &iter, tpath);
659 
660   lives_tree_path_free(tpath);
661 
662   if (row > (omacro.nparams - mnode->nvars)) {
663     // text, so dont alter
664     return;
665   }
666 
667   switch (omacro.ptypes[row]) {
668   case OMC_PARAM_INT:
669     vali = atoi(new_text);
670     mnode->fvali[row] = vali;
671     break;
672   case OMC_PARAM_DOUBLE:
673     vald = lives_strtod(new_text, NULL);
674     mnode->fvald[row] = vald;
675     break;
676   }
677 
678   lives_tree_store_set(mnode->gtkstore2, &iter, VALUE2_COLUMN, new_text, -1);
679 }
680 
681 
682 #if GTK_CHECK_VERSION(3, 0, 0)
rowexpand(LiVESWidget * tv,LiVESTreeIter * iter,LiVESTreePath * path,livespointer ud)683 static void rowexpand(LiVESWidget *tv, LiVESTreeIter *iter, LiVESTreePath *path, livespointer ud) {
684   lives_widget_queue_resize(tv);
685 }
686 #endif
687 
688 
omc_macro_row_add_params(lives_omc_match_node_t * mnode,int row,omclearn_w * omclw)689 static void omc_macro_row_add_params(lives_omc_match_node_t *mnode, int row, omclearn_w *omclw) {
690   lives_omc_macro_t macro = omc_macros[mnode->macro];
691 
692   LiVESCellRenderer *renderer;
693   LiVESTreeViewColumn *column;
694 
695   LiVESTreeIter iter1, iter2;
696 
697   LiVESAdjustment *adj;
698 
699   char *strval = NULL, *vname;
700   char *oldval = NULL, *final = NULL;
701 
702   int mfrom;
703   int i;
704 
705   mnode->gtkstore2 = lives_tree_store_new(OMC_NUM2_COLUMNS, LIVES_COL_TYPE_STRING, LIVES_COL_TYPE_STRING,
706                                           LIVES_COL_TYPE_OBJECT);
707 
708   if (macro.nparams == 0) return;
709 
710   lives_tree_store_append(mnode->gtkstore2, &iter1, NULL);   /* Acquire an iterator */
711   lives_tree_store_set(mnode->gtkstore2, &iter1, TITLE2_COLUMN, (_("Params.")), -1);
712 
713   for (i = 0; i < macro.nparams; i++) {
714     lives_tree_store_append(mnode->gtkstore2, &iter2, &iter1);   /* Acquire a child iterator */
715 
716     if (oldval) {
717       lives_free(oldval);
718       oldval = NULL;
719     }
720 
721     if (final) {
722       lives_free(final);
723       final = NULL;
724     }
725 
726     adj = NULL;
727 
728     if ((mfrom = mnode->map[i]) != -1) strval = (_("variable"));
729     else {
730       switch (macro.ptypes[i]) {
731       case OMC_PARAM_INT:
732         strval = lives_strdup_printf("%d", mnode->fvali[i]);
733         adj = lives_adjustment_new(mnode->fvali[i], macro.mini[i], macro.maxi[i], 1., 1., 0.);
734         break;
735       case OMC_PARAM_DOUBLE:
736         strval = lives_strdup_printf("%.*f", OMC_FP_FIX, mnode->fvald[i]);
737         adj = lives_adjustment_new(mnode->fvald[i], macro.mind[i], macro.maxd[i], 1., 1., 0.);
738         break;
739       }
740     }
741 
742     vname = macro.pname[i];
743 
744     lives_tree_store_set(mnode->gtkstore2, &iter2, TITLE2_COLUMN, vname, VALUE2_COLUMN, strval, ADJUSTMENT, adj, -1);
745   }
746 
747   lives_free(strval);
748 
749   mnode->treev2 = lives_tree_view_new_with_model(LIVES_TREE_MODEL(mnode->gtkstore2));
750 
751   if (palette->style & STYLE_1) {
752     lives_widget_set_base_color(mnode->treev2, LIVES_WIDGET_STATE_NORMAL, &palette->menu_and_bars);
753     lives_widget_set_text_color(mnode->treev2, LIVES_WIDGET_STATE_NORMAL, &palette->menu_and_bars_fore);
754   }
755 
756   renderer = lives_cell_renderer_text_new();
757   column = lives_tree_view_column_new_with_attributes(NULL,
758            renderer, LIVES_TREE_VIEW_COLUMN_TEXT, TITLE2_COLUMN, NULL);
759 
760   lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev2), column);
761 
762   renderer = lives_cell_renderer_spin_new();
763 
764   if (renderer) {
765 #ifdef GUI_GTK
766     g_object_set(renderer, "width-chars", 7, "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
767                  "editable", TRUE, "xalign", 1.0, NULL);
768 
769 #endif
770 
771     lives_signal_connect(renderer, LIVES_WIDGET_EDITED_SIGNAL, LIVES_GUI_CALLBACK(cell1_edited_callback), mnode);
772 
773     //  renderer = lives_cell_renderer_text_new ();
774     column = lives_tree_view_column_new_with_attributes(_("value"),
775              renderer, LIVES_TREE_VIEW_COLUMN_TEXT, VALUE2_COLUMN,
776              "adjustment", ADJUSTMENT, NULL);
777 
778     lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev2), column);
779   }
780 
781 #if GTK_CHECK_VERSION(3, 0, 0)
782   lives_signal_connect(LIVES_GUI_OBJECT(mnode->treev2), LIVES_WIDGET_ROW_EXPANDED_SIGNAL,
783                        LIVES_GUI_CALLBACK(rowexpand), NULL);
784 #endif
785 
786   lives_table_attach(LIVES_TABLE(omclw->table), mnode->treev2, 3, 4, row, row + 1,
787                      (LiVESAttachOptions)(LIVES_FILL | LIVES_EXPAND),
788                      (LiVESAttachOptions)(LIVES_EXPAND), 0, 0);
789 }
790 
791 
omc_learn_link_params(lives_omc_match_node_t * mnode)792 static void omc_learn_link_params(lives_omc_match_node_t *mnode) {
793   lives_omc_macro_t omc_macro = omc_macros[mnode->macro];
794   int mps = omc_macro.nparams - 1;
795   int lps = mnode->nvars - 1;
796   int i;
797 
798   if (mnode->map) lives_free(mnode->map);
799   if (mnode->fvali) lives_free(mnode->fvali);
800   if (mnode->fvald) lives_free(mnode->fvald);
801 
802   mnode->map = (int *)lives_malloc(omc_macro.nparams * sizint);
803   mnode->fvali = (int *)lives_malloc(omc_macro.nparams * sizint);
804   mnode->fvald = (double *)lives_malloc(omc_macro.nparams * sizdbl);
805 
806   if (lps > mps) lps = mps;
807 
808   if (lps >= 0) {
809     for (i = mps; i >= 0; i--) {
810       if (mnode->matchp[lps]) lps++; // variable is filtered for
811     }
812   }
813 
814   for (i = mps; i >= 0; i--) {
815     if (lps < 0 || lps >= mnode->nvars) {
816       //g_print("fixed !\n");
817       mnode->map[i] = -1;
818       if (omc_macro.ptypes[i] == OMC_PARAM_INT) mnode->fvali[i] = omc_macro.vali[i];
819       else mnode->fvald[i] = omc_macro.vald[i];
820     } else {
821       //      g_print("varied !\n");
822       if (!mnode->matchp[lps]) mnode->map[i] = lps;
823       else i++;
824     }
825     lps--;
826   }
827 }
828 
829 
on_omc_combo_entry_changed(LiVESCombo * combo,livespointer ptr)830 static void on_omc_combo_entry_changed(LiVESCombo *combo, livespointer ptr) {
831   lives_omc_match_node_t *mnode = (lives_omc_match_node_t *)ptr;
832   const char *macro_text;
833   int i, row = LIVES_POINTER_TO_INT(lives_widget_object_get_data(LIVES_WIDGET_OBJECT(combo), "row"));
834   omclearn_w *omclw = (omclearn_w *)lives_widget_object_get_data(LIVES_WIDGET_OBJECT(combo), "omclw");
835 
836   macro_text = lives_combo_get_active_text(LIVES_COMBO(combo));
837 
838   if (mnode->treev2) {
839     // remove old mapping
840     lives_widget_destroy(mnode->treev2);
841     mnode->treev2 = NULL;
842 
843     mnode->macro = -1;
844 
845     lives_free(mnode->map);
846     lives_free(mnode->fvali);
847     lives_free(mnode->fvald);
848 
849     mnode->map = mnode->fvali = NULL;
850     mnode->fvald = NULL;
851   }
852 
853   if (!strcmp(macro_text, mainw->string_constants[LIVES_STRING_CONSTANT_NONE])) {
854     return;
855   }
856 
857   for (i = 0; i < N_OMC_MACROS; i++) {
858     if (!strcmp(macro_text, omc_macros[i].macro_text)) break;
859   }
860 
861   mnode->macro = i;
862   omc_learn_link_params(mnode);
863   omc_macro_row_add_params(mnode, row, omclw);
864 }
865 
866 
cell_toggled_callback(LiVESCellRenderer * toggle,const char * path_string,livespointer user_data)867 static void cell_toggled_callback(LiVESCellRenderer *toggle, const char *path_string, livespointer user_data) {
868   lives_omc_match_node_t *mnode = (lives_omc_match_node_t *)user_data;
869   int row;
870 
871   char *txt;
872 
873   int *indices;
874 
875   LiVESTreePath *tpath = lives_tree_path_new_from_string(path_string);
876 
877   LiVESTreeIter iter;
878 
879   if (lives_tree_path_get_depth(tpath) != 2) {
880     lives_tree_path_free(tpath);
881     return;
882   }
883 
884   indices = lives_tree_path_get_indices(tpath);
885   row = indices[1];
886 
887   lives_tree_model_get_iter(LIVES_TREE_MODEL(mnode->gtkstore), &iter, tpath);
888 
889   lives_tree_path_free(tpath);
890 
891   lives_tree_model_get(LIVES_TREE_MODEL(mnode->gtkstore), &iter, VALUE_COLUMN, &txt, -1);
892 
893   if (!strcmp(txt, "-")) {
894     lives_free(txt);
895     return;
896   }
897 
898   lives_free(txt);
899 
900   mnode->matchp[row] = !(mnode->matchp[row]);
901 
902   lives_tree_store_set(mnode->gtkstore, &iter, FILTER_COLUMN, mnode->matchp[row], -1);
903 
904   omc_learn_link_params(mnode);
905 }
906 
907 
cell_edited_callback(LiVESCellRenderer * spinbutton,const char * path_string,const char * new_text,livespointer user_data)908 static void cell_edited_callback(LiVESCellRenderer *spinbutton, const char *path_string, const char *new_text,
909                                  livespointer user_data) {
910   lives_omc_match_node_t *mnode = (lives_omc_match_node_t *)user_data;
911 
912   int col = LIVES_POINTER_TO_INT(lives_widget_object_get_data(LIVES_WIDGET_OBJECT(spinbutton), "colnum"));
913 
914   int vali;
915   double vald;
916 
917   LiVESTreeIter iter;
918 
919   int row;
920 
921   int *indices;
922 
923   LiVESTreePath *tpath = lives_tree_path_new_from_string(path_string);
924 
925   if (lives_tree_path_get_depth(tpath) != 2) {
926     lives_tree_path_free(tpath);
927     return;
928   }
929 
930   indices = lives_tree_path_get_indices(tpath);
931   row = indices[1];
932 
933   lives_tree_model_get_iter(LIVES_TREE_MODEL(mnode->gtkstore), &iter, tpath);
934 
935   lives_tree_path_free(tpath);
936 
937   switch (col) {
938   case OFFS1_COLUMN:
939     vali = atoi(new_text);
940     mnode->offs0[row] = vali;
941     break;
942   case OFFS2_COLUMN:
943     vali = atoi(new_text);
944     mnode->offs1[row] = vali;
945     break;
946   case SCALE_COLUMN:
947     vald = lives_strtod(new_text, NULL);
948     mnode->scale[row] = vald;
949     break;
950   }
951 
952   lives_tree_store_set(mnode->gtkstore, &iter, col, new_text, -1);
953 }
954 
955 
create_omc_macro_combo(lives_omc_match_node_t * mnode,int row,omclearn_w * omclw)956 static LiVESWidget *create_omc_macro_combo(lives_omc_match_node_t *mnode, int row, omclearn_w *omclw) {
957   LiVESWidget *combo = lives_standard_combo_new(NULL, NULL, NULL, NULL);
958 
959   for (int i = 0; i < N_OMC_MACROS; i++) {
960     if (!omc_macros[i].msg) break;
961     lives_combo_append_text(LIVES_COMBO(combo), omc_macros[i].macro_text);
962   }
963 
964   if (mnode->macro != -1) {
965     lives_combo_set_active_index(LIVES_COMBO(combo), mnode->macro);
966   }
967 
968   lives_signal_connect_after(LIVES_WIDGET_OBJECT(combo), LIVES_WIDGET_CHANGED_SIGNAL,
969                              LIVES_GUI_CALLBACK(on_omc_combo_entry_changed), mnode);
970 
971   lives_widget_object_set_data(LIVES_WIDGET_OBJECT(combo), "row", LIVES_INT_TO_POINTER(row));
972   lives_widget_object_set_data(LIVES_WIDGET_OBJECT(combo), "omclw", (livespointer)omclw);
973 
974   return combo;
975 }
976 
977 
get_chan_string(const char * string)978 static char *get_chan_string(const char *string) {
979   char *chstr;
980   if (prefs->midi_rcv_channel == MIDI_OMNI) {
981     int chan = js_index(string);
982     // TRANSLATORS: ch is abbreviation for MIDI "channel"
983     chstr = lives_strdup_printf(_(" ch %d"), chan);
984   } else chstr = lives_strdup("");
985   return chstr;
986 }
987 
988 
omc_learner_add_row(int type,int detail,lives_omc_match_node_t * mnode,const char * string,omclearn_w * omclw)989 static void omc_learner_add_row(int type, int detail, lives_omc_match_node_t *mnode, const char *string, omclearn_w *omclw) {
990   LiVESWidget *label, *combo;
991   LiVESWidgetObject *spinadj;
992 
993   LiVESCellRenderer *renderer;
994   LiVESTreeViewColumn *column;
995 
996   LiVESTreeIter iter1, iter2;
997 
998   char *strval, *strval2, *strval3, *strval4, *vname, *valstr;
999   char *oldval = NULL, *final = NULL;
1000   char *labelt = NULL;
1001   char *chstr = NULL;
1002 
1003   int val;
1004 
1005   omclw->tbl_rows++;
1006   lives_table_resize(LIVES_TABLE(omclw->table), omclw->tbl_rows, 4);
1007 
1008   mnode->gtkstore = lives_tree_store_new(OMC_NUM_COLUMNS, LIVES_COL_TYPE_STRING, LIVES_COL_TYPE_STRING, LIVES_COL_TYPE_BOOLEAN,
1009                                          LIVES_COL_TYPE_STRING,
1010                                          LIVES_COL_TYPE_STRING, LIVES_COL_TYPE_STRING, LIVES_COL_TYPE_STRING);
1011 
1012   lives_tree_store_append(mnode->gtkstore, &iter1, NULL);   /* Acquire an iterator */
1013   lives_tree_store_set(mnode->gtkstore, &iter1, TITLE_COLUMN, (_("Vars.")), -1);
1014 
1015   for (int i = 0; i < mnode->nvars; i++) {
1016     lives_tree_store_append(mnode->gtkstore, &iter2, &iter1);   /* Acquire a child iterator */
1017 
1018     if (oldval) {
1019       lives_free(oldval);
1020       oldval = NULL;
1021     }
1022 
1023     if (final) {
1024       lives_free(final);
1025       final = NULL;
1026     }
1027 
1028     strval = lives_strdup_printf("%d - %d", mnode->min[i], mnode->max[i]);
1029     strval2 = lives_strdup_printf("%d", mnode->offs0[i]);
1030     strval3 = lives_strdup_printf("%.*f", OMC_FP_FIX, mnode->scale[i]);
1031     strval4 = lives_strdup_printf("%d", mnode->offs1[i]);
1032 
1033     if (type > 0) {
1034       vname = omc_learn_get_pname(type, i);
1035       val = omc_learn_get_pvalue(type, i, string);
1036 
1037       valstr = lives_strdup_printf("%d", val);
1038       if (!mnode->matchp[i]) {
1039         mnode->matchi[i] = val;
1040       }
1041     } else {
1042       vname = omc_learn_get_pname(-type, i);
1043       if (mnode->matchp[i]) valstr = lives_strdup_printf("%d", mnode->matchi[i]);
1044       else valstr = lives_strdup("-");
1045     }
1046 
1047     lives_tree_store_set(mnode->gtkstore, &iter2, TITLE_COLUMN, vname, VALUE_COLUMN, valstr, FILTER_COLUMN, mnode->matchp[i],
1048                          RANGE_COLUMN, strval, OFFS1_COLUMN, strval2, SCALE_COLUMN, strval3, OFFS2_COLUMN, strval4, -1);
1049 
1050     lives_free(strval); lives_free(strval2); lives_free(strval3);
1051     lives_free(strval4); lives_free(valstr); lives_free(vname);
1052   }
1053 
1054   mnode->treev1 = lives_tree_view_new_with_model(LIVES_TREE_MODEL(mnode->gtkstore));
1055 
1056   if (type < 0) type = -type;
1057 
1058   switch (type) {
1059   case OMC_MIDI_NOTE:
1060     chstr = get_chan_string(string);
1061     labelt = lives_strdup_printf(_("MIDI%s note on"), chstr);
1062     break;
1063   case OMC_MIDI_NOTE_OFF:
1064     chstr = get_chan_string(string);
1065     labelt = lives_strdup_printf(_("MIDI%s note off"), chstr);
1066     break;
1067   case OMC_MIDI_CONTROLLER:
1068     chstr = get_chan_string(string);
1069     labelt = lives_strdup_printf(_("MIDI%s controller %d"), chstr, detail);
1070     break;
1071   case OMC_MIDI_PITCH_BEND:
1072     chstr = get_chan_string(string);
1073     labelt = lives_strdup_printf(_("MIDI%s pitch bend"), chstr);
1074     break;
1075   case OMC_MIDI_PGM_CHANGE:
1076     chstr = get_chan_string(string);
1077     labelt = lives_strdup_printf(_("MIDI%s pgm change"), chstr);
1078     break;
1079   case OMC_JS_BUTTON:
1080     labelt = lives_strdup_printf(_("Joystick button %d"), detail);
1081     break;
1082   case OMC_JS_AXIS:
1083     labelt = lives_strdup_printf(_("Joystick axis %d"), detail);
1084     break;
1085   }
1086 
1087   if (chstr) lives_free(chstr);
1088 
1089   label = lives_standard_label_new(labelt);
1090 
1091   if (labelt) lives_free(labelt);
1092 
1093 #if !GTK_CHECK_VERSION(3, 0, 0)
1094   if (palette->style & STYLE_1) {
1095     lives_widget_set_fg_color(label, LIVES_WIDGET_STATE_NORMAL, &palette->black);
1096   }
1097 #endif
1098 
1099   omclw->tbl_currow++;
1100 
1101   lives_table_attach(LIVES_TABLE(omclw->table), label, 0, 1, omclw->tbl_currow, omclw->tbl_currow + 1,
1102                      (LiVESAttachOptions)(0), (LiVESAttachOptions)(0), 0, 0);
1103 
1104   // properties
1105   if (palette->style & STYLE_1) {
1106     lives_widget_set_base_color(mnode->treev1, LIVES_WIDGET_STATE_NORMAL, &palette->menu_and_bars);
1107     lives_widget_set_text_color(mnode->treev1, LIVES_WIDGET_STATE_NORMAL, &palette->menu_and_bars_fore);
1108   }
1109 
1110   renderer = lives_cell_renderer_text_new();
1111   column = lives_tree_view_column_new_with_attributes(NULL,
1112            renderer, LIVES_TREE_VIEW_COLUMN_TEXT, TITLE_COLUMN, NULL);
1113 
1114   lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1115 
1116   renderer = lives_cell_renderer_text_new();
1117   column = lives_tree_view_column_new_with_attributes(_("value"),
1118            renderer, LIVES_TREE_VIEW_COLUMN_TEXT, VALUE_COLUMN, NULL);
1119 
1120   lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1121 
1122   renderer = lives_cell_renderer_toggle_new();
1123   column = lives_tree_view_column_new_with_attributes(_("x"),
1124            renderer, "active", FILTER_COLUMN, NULL);
1125 
1126   lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1127 
1128   lives_signal_connect(renderer, LIVES_WIDGET_TOGGLED_SIGNAL, LIVES_GUI_CALLBACK(cell_toggled_callback), mnode);
1129 
1130   renderer = lives_cell_renderer_text_new();
1131   column = lives_tree_view_column_new_with_attributes(_("range"),
1132            renderer, LIVES_TREE_VIEW_COLUMN_TEXT, RANGE_COLUMN, NULL);
1133 
1134   lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1135 
1136   renderer = lives_cell_renderer_spin_new();
1137 
1138   if (renderer) {
1139     lives_widget_object_set_data(LIVES_WIDGET_OBJECT(renderer), "colnum", LIVES_UINT_TO_POINTER(OFFS1_COLUMN));
1140 
1141     spinadj = (LiVESWidgetObject *)lives_adjustment_new(0., -100000., 100000., 1., 10., 0);
1142 
1143 #ifdef GUI_GTK
1144     g_object_set(renderer, "width-chars", 7, "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
1145                  "editable", TRUE, "xalign", 1.0, "adjustment", spinadj, NULL);
1146 #endif
1147 
1148     lives_signal_connect(renderer, LIVES_WIDGET_EDITED_SIGNAL, LIVES_GUI_CALLBACK(cell_edited_callback), mnode);
1149 
1150     column = lives_tree_view_column_new_with_attributes(_("+ offset1"),
1151              renderer, LIVES_TREE_VIEW_COLUMN_TEXT, OFFS1_COLUMN, NULL);
1152 
1153     lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1154   }
1155 
1156   renderer = lives_cell_renderer_spin_new();
1157 
1158   if (renderer) {
1159     spinadj = (LiVESWidgetObject *)lives_adjustment_new(1., -100000., 100000., 1., 10., 0);
1160 
1161 #ifdef GUI_GTK
1162     g_object_set(renderer, "width-chars", 12, "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
1163                  "editable", TRUE, "xalign", 1.0, "adjustment", spinadj,
1164                  "digits", OMC_FP_FIX, NULL);
1165 #endif
1166 
1167     lives_widget_object_set_data(LIVES_WIDGET_OBJECT(renderer), "colnum", LIVES_UINT_TO_POINTER(SCALE_COLUMN));
1168     lives_signal_connect(renderer, LIVES_WIDGET_EDITED_SIGNAL, LIVES_GUI_CALLBACK(cell_edited_callback), mnode);
1169 
1170     column = lives_tree_view_column_new_with_attributes(_("* scale"),
1171              renderer, LIVES_TREE_VIEW_COLUMN_TEXT, SCALE_COLUMN, NULL);
1172     lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1173   }
1174 
1175   renderer = lives_cell_renderer_spin_new();
1176 
1177   if (renderer) {
1178     spinadj = (LiVESWidgetObject *)lives_adjustment_new(0., -100000., 100000., 1., 10., 0);
1179 
1180 #ifdef GUI_GTK
1181     g_object_set(renderer, "width-chars", 7, "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
1182                  "editable", TRUE, "xalign", 1.0, "adjustment", spinadj, NULL);
1183 #endif
1184 
1185     lives_widget_object_set_data(LIVES_WIDGET_OBJECT(renderer), "colnum", LIVES_UINT_TO_POINTER(OFFS2_COLUMN));
1186     lives_signal_connect(renderer, LIVES_WIDGET_EDITED_SIGNAL, LIVES_GUI_CALLBACK(cell_edited_callback), mnode);
1187 
1188     column = lives_tree_view_column_new_with_attributes(_("+ offset2"),
1189              renderer, LIVES_TREE_VIEW_COLUMN_TEXT, OFFS2_COLUMN, NULL);
1190     lives_tree_view_append_column(LIVES_TREE_VIEW(mnode->treev1), column);
1191   }
1192 
1193 #if LIVES_TABLE_IS_GRID
1194   lives_widget_set_size_request(mnode->treev1, -1, TREE_ROW_HEIGHT);
1195 #endif
1196 
1197   lives_table_attach(LIVES_TABLE(omclw->table), mnode->treev1, 1, 2, omclw->tbl_currow, omclw->tbl_currow + 1,
1198                      (LiVESAttachOptions)(LIVES_FILL | LIVES_EXPAND),
1199                      (LiVESAttachOptions)(LIVES_EXPAND), 0, 0);
1200 
1201 #if GTK_CHECK_VERSION(3, 0, 0)
1202   lives_signal_connect(LIVES_GUI_OBJECT(mnode->treev1), LIVES_WIDGET_ROW_EXPANDED_SIGNAL,
1203                        LIVES_GUI_CALLBACK(rowexpand), NULL);
1204 #endif
1205 
1206   combo = create_omc_macro_combo(mnode, omclw->tbl_currow, omclw);
1207 
1208   lives_table_attach(LIVES_TABLE(omclw->table), combo, 2, 3, omclw->tbl_currow, omclw->tbl_currow + 1,
1209                      (LiVESAttachOptions) 0, (LiVESAttachOptions)(0), 0, 0);
1210 
1211   if (mnode->macro == UNMATCHED) lives_widget_set_sensitive(omclw->clear_button, TRUE);
1212   lives_widget_set_sensitive(omclw->del_all_button, TRUE);
1213 }
1214 
1215 
killit(LiVESWidget * widget,livespointer user_data)1216 static void killit(LiVESWidget *widget, livespointer user_data) {
1217   lives_widget_destroy(widget);
1218 }
1219 
1220 
show_existing(omclearn_w * omclw)1221 static void show_existing(omclearn_w *omclw) {
1222   LiVESSList *slist = omc_node_list;
1223   lives_omc_match_node_t *mnode;
1224   int type, supertype;
1225   char **array, *srch;
1226   int idx;
1227 
1228   while (slist) {
1229     mnode = (lives_omc_match_node_t *)slist->data;
1230 
1231     srch = lives_strdup(mnode->srch);
1232     array = lives_strsplit(srch, " ", -1);
1233 
1234     supertype = atoi(array[0]);
1235 #ifdef OMC_MIDI_IMPL
1236     if (supertype == OMC_MIDI) {
1237       size_t blen;
1238       char *tmp;
1239 
1240       type = midi_msg_type(array[1]);
1241       if (get_token_count(srch, ' ') > (prefs->midi_rcv_channel == -1 ? 3 : 2))
1242         idx = atoi(array[prefs->midi_rcv_channel == -1 ? 3 : 2]);
1243       else idx = -1;
1244       srch = lives_strdup(mnode->srch);
1245       if (prefs->midi_rcv_channel == MIDI_OMNI) {
1246         // remove the channel if it is in the string
1247         tmp = cut_string_elems(srch, 1);
1248         blen = strlen(tmp);
1249         tmp = lives_strdup(srch + blen + 1);
1250         lives_free(srch);
1251         srch = tmp;
1252       }
1253     } else {
1254 #endif
1255       type = supertype;
1256       idx = atoi(array[1]);
1257 #ifdef OMC_MIDI_IMPL
1258     }
1259 #endif
1260     lives_strfreev(array);
1261 
1262     omc_learner_add_row(-type, idx, mnode, srch, omclw);
1263     lives_free(srch);
1264 
1265     omc_macro_row_add_params(mnode, omclw->tbl_currow, omclw);
1266 
1267     slist = slist->next;
1268   }
1269 }
1270 
1271 
clear_unmatched(LiVESButton * button,livespointer user_data)1272 static void clear_unmatched(LiVESButton *button, livespointer user_data) {
1273   omclearn_w *omclw = (omclearn_w *)user_data;
1274 
1275   // destroy everything in table
1276 
1277   lives_container_foreach(LIVES_CONTAINER(omclw->table), killit, NULL);
1278 
1279   omclw->tbl_currow = -1;
1280 
1281   remove_all_nodes(FALSE, omclw);
1282 
1283   show_existing(omclw);
1284 }
1285 
1286 
del_all(LiVESButton * button,livespointer user_data)1287 static void del_all(LiVESButton *button, livespointer user_data) {
1288   omclearn_w *omclw = (omclearn_w *)user_data;
1289 
1290   // need to use the full version here to override the default transient window
1291   if (!do_warning_dialog(_("\nClick OK to delete all entries\n"))) return;
1292 
1293   // destroy everything in table
1294   lives_container_foreach(LIVES_CONTAINER(omclw->table), killit, NULL);
1295 
1296   remove_all_nodes(TRUE, omclw);
1297 
1298   lives_widget_set_sensitive(mainw->midi_save, FALSE);
1299 }
1300 
1301 
close_learner_dialog(LiVESButton * button,livespointer user_data)1302 static void close_learner_dialog(LiVESButton *button, livespointer user_data) {
1303   mainw->cancelled = CANCEL_USER;
1304   if (has_devicemap(-1)) lives_widget_set_sensitive(mainw->midi_save, TRUE);
1305 }
1306 
1307 
create_omclearn_dialog(void)1308 static omclearn_w *create_omclearn_dialog(void) {
1309   LiVESWidget *ok_button;
1310   LiVESWidget *scrolledwindow;
1311   int winsize_h, winsize_v;
1312 
1313   omclearn_w *omclw = (omclearn_w *)lives_malloc(sizeof(omclearn_w));
1314 
1315   omclw->tbl_rows = 4;
1316   omclw->tbl_currow = -1;
1317 
1318   winsize_h = GUI_SCREEN_WIDTH - SCR_WIDTH_SAFETY;
1319   winsize_v = GUI_SCREEN_HEIGHT - SCR_HEIGHT_SAFETY;
1320 
1321   omclw->dialog = lives_standard_dialog_new(_("OMC Learner"), FALSE, winsize_h, winsize_v);
1322   lives_signal_handlers_disconnect_by_func(omclw->dialog, LIVES_GUI_CALLBACK(return_true), NULL);
1323 
1324   omclw->top_vbox = lives_dialog_get_content_area(LIVES_DIALOG(omclw->dialog));
1325 
1326   omclw->table = lives_table_new(omclw->tbl_rows, 4, FALSE);
1327 
1328   lives_table_set_col_spacings(LIVES_TABLE(omclw->table), widget_opts.packing_width * 2);
1329 
1330   scrolledwindow = lives_standard_scrolled_window_new(winsize_h, winsize_v - SCR_HEIGHT_SAFETY, omclw->table);
1331 
1332   lives_box_pack_start(LIVES_BOX(omclw->top_vbox), scrolledwindow, TRUE, TRUE, 0);
1333 
1334   omclw->clear_button = lives_dialog_add_button_from_stock(LIVES_DIALOG(omclw->dialog), LIVES_STOCK_CLEAR, _("Clear _unmatched"),
1335                         LIVES_RESPONSE_NONE);
1336 
1337   lives_signal_connect(LIVES_GUI_OBJECT(omclw->clear_button), LIVES_WIDGET_CLICKED_SIGNAL,
1338                        LIVES_GUI_CALLBACK(clear_unmatched), (livespointer)omclw);
1339 
1340   lives_widget_set_sensitive(omclw->clear_button, FALSE);
1341 
1342   omclw->del_all_button = lives_dialog_add_button_from_stock(LIVES_DIALOG(omclw->dialog), LIVES_STOCK_DELETE, _("_Delete all"),
1343                           LIVES_RESPONSE_NONE);
1344 
1345   lives_signal_connect(LIVES_GUI_OBJECT(omclw->del_all_button), LIVES_WIDGET_CLICKED_SIGNAL,
1346                        LIVES_GUI_CALLBACK(del_all), (livespointer)omclw);
1347 
1348   lives_widget_set_sensitive(omclw->del_all_button, FALSE);
1349 
1350   ok_button = lives_dialog_add_button_from_stock(LIVES_DIALOG(omclw->dialog), LIVES_STOCK_CLOSE, _("_Close Window"),
1351               LIVES_RESPONSE_OK);
1352 
1353   lives_button_grab_default_special(ok_button);
1354 
1355   lives_signal_connect(LIVES_GUI_OBJECT(ok_button), LIVES_WIDGET_CLICKED_SIGNAL,
1356                        LIVES_GUI_CALLBACK(close_learner_dialog), NULL);
1357 
1358   if (prefs->gui_monitor != 0) {
1359     lives_window_center(LIVES_WINDOW(omclw->dialog));
1360   }
1361 
1362   if (prefs->open_maximised) {
1363     lives_window_unmaximize(LIVES_WINDOW(omclw->dialog));
1364     lives_window_maximize(LIVES_WINDOW(omclw->dialog));
1365   }
1366 
1367   if (prefs->show_gui)
1368     lives_widget_show_all(omclw->dialog);
1369 
1370   return omclw;
1371 }
1372 
1373 
init_omc_macros(void)1374 static void init_omc_macros(void) {
1375   int i;
1376 
1377   for (i = 0; i < N_OMC_MACROS; i++) {
1378     omc_macros[i].macro_text = NULL;
1379     omc_macros[i].info_text = NULL;
1380     omc_macros[i].msg = NULL;
1381     omc_macros[i].nparams = 0;
1382     omc_macros[i].pname = NULL;
1383   }
1384 
1385   omc_macros[START_PLAYBACK].msg = lives_strdup("/video/play");
1386   omc_macros[START_PLAYBACK].macro_text = (_("Start video playback"));
1387 
1388   omc_macros[STOP_PLAYBACK].msg = lives_strdup("/video/stop");
1389   omc_macros[STOP_PLAYBACK].macro_text = (_("Stop video playback"));
1390 
1391   omc_macros[CLIP_SELECT].msg = lives_strdup("/clip/foreground/select");
1392   omc_macros[CLIP_SELECT].macro_text = (_("Clip select <clipnum>"));
1393   omc_macros[CLIP_SELECT].info_text = (_("Switch foreground clip to the nth valid clip"));
1394   omc_macros[CLIP_SELECT].nparams = 1;
1395 
1396   omc_macros[PLAY_FORWARDS].msg = lives_strdup("/video/play/forwards");
1397   omc_macros[PLAY_FORWARDS].macro_text = (_("Play forwards"));
1398   omc_macros[PLAY_FORWARDS].info_text = (_("Play video in a forwards direction"));
1399 
1400   omc_macros[PLAY_BACKWARDS].msg = lives_strdup("/video/play/backwards");
1401   omc_macros[PLAY_BACKWARDS].macro_text = (_("Play backwards"));
1402   omc_macros[PLAY_BACKWARDS].info_text = (_("Play video in a backwards direction"));
1403 
1404   omc_macros[REVERSE_PLAYBACK].msg = lives_strdup("/video/play/reverse");
1405   omc_macros[REVERSE_PLAYBACK].macro_text = (_("Reverse playback direction"));
1406   omc_macros[REVERSE_PLAYBACK].info_text = (_("Reverse direction of video playback"));
1407 
1408   omc_macros[PLAY_FASTER].msg = lives_strdup("/video/play/faster");
1409   omc_macros[PLAY_FASTER].macro_text = (_("Play video faster"));
1410   omc_macros[PLAY_FASTER].info_text = (_("Play video at a slightly faster rate"));
1411 
1412   omc_macros[PLAY_SLOWER].msg = lives_strdup("/video/play/slower");
1413   omc_macros[PLAY_SLOWER].macro_text = (_("Play video slower"));
1414   omc_macros[PLAY_SLOWER].info_text = (_("Play video at a slightly slower rate"));
1415 
1416   omc_macros[TOGGLE_FREEZE].msg = lives_strdup("/video/freeze/toggle");
1417   omc_macros[TOGGLE_FREEZE].macro_text = (_("Toggle video freeze"));
1418   omc_macros[TOGGLE_FREEZE].info_text = (_("Freeze video, or if already frozen, unfreeze it"));
1419 
1420   omc_macros[SET_FRAMERATE].msg = lives_strdup("/video/fps/set");
1421   omc_macros[SET_FRAMERATE].macro_text = (_("Set video framerate to <fps>"));
1422   omc_macros[SET_FRAMERATE].info_text = (_("Set the framerate of foreground clip to <(float) fps>"));
1423   omc_macros[SET_FRAMERATE].nparams = 1;
1424 
1425   omc_macros[START_RECORDING].msg = lives_strdup("/record/enable");
1426   omc_macros[START_RECORDING].macro_text = (_("Start recording"));
1427 
1428   omc_macros[STOP_RECORDING].msg = lives_strdup("/record/disable");
1429   omc_macros[STOP_RECORDING].macro_text = (_("Stop recording"));
1430 
1431   omc_macros[TOGGLE_RECORDING].msg = lives_strdup("/record/toggle");
1432   omc_macros[TOGGLE_RECORDING].macro_text = (_("Toggle recording state"));
1433 
1434   omc_macros[SWAP_FOREGROUND_BACKGROUND].msg = lives_strdup("/clip/foreground/background/swap");
1435   omc_macros[SWAP_FOREGROUND_BACKGROUND].macro_text = (_("Swap foreground and background clips"));
1436 
1437   omc_macros[RESET_EFFECT_KEYS].msg = lives_strdup("/effect_key/reset");
1438   omc_macros[RESET_EFFECT_KEYS].macro_text = (_("Reset effect keys"));
1439   omc_macros[RESET_EFFECT_KEYS].info_text = (_("Switch all effects off."));
1440 
1441   omc_macros[ENABLE_EFFECT_KEY].msg = lives_strdup("/effect_key/enable");
1442   omc_macros[ENABLE_EFFECT_KEY].macro_text = (_("Enable effect key <key>"));
1443   omc_macros[ENABLE_EFFECT_KEY].nparams = 1;
1444 
1445   omc_macros[DISABLE_EFFECT_KEY].msg = lives_strdup("/effect_key/disable");
1446   omc_macros[DISABLE_EFFECT_KEY].macro_text = (_("Disable effect key <key>"));
1447   omc_macros[DISABLE_EFFECT_KEY].nparams = 1;
1448 
1449   omc_macros[TOGGLE_EFFECT_KEY].msg = lives_strdup("/effect_key/toggle");
1450   omc_macros[TOGGLE_EFFECT_KEY].macro_text = (_("Toggle effect key <key>"));
1451   omc_macros[TOGGLE_EFFECT_KEY].nparams = 1;
1452 
1453   omc_macros[SET_PARAMETER_VALUE].msg = lives_strdup("/effect_key/nparameter/value/set");
1454   omc_macros[SET_PARAMETER_VALUE].macro_text = (_("Set parameter value <key> <pnum> = <value>"));
1455   omc_macros[SET_PARAMETER_VALUE].info_text = (_("Set <value> of pth (numerical) parameter for effect key <key>."));
1456   omc_macros[SET_PARAMETER_VALUE].nparams = 3;
1457 
1458   omc_macros[NEXT_CLIP_SELECT].msg = lives_strdup("/clip/select/next");
1459   omc_macros[NEXT_CLIP_SELECT].macro_text = (_("Switch foreground to next clip"));
1460 
1461   omc_macros[PREV_CLIP_SELECT].msg = lives_strdup("/clip/select/previous");
1462   omc_macros[PREV_CLIP_SELECT].macro_text = (_("Switch foreground to previous clip"));
1463 
1464   omc_macros[SET_FPS_RATIO].msg = lives_strdup("/video/fps/ratio/set");
1465   omc_macros[SET_FPS_RATIO].macro_text = (_("Set video framerate to ratio <fps__ratio>"));
1466   omc_macros[SET_FPS_RATIO].info_text = (_("Set the framerate ratio of the foreground clip to <(float) fps__ratio>"));
1467   omc_macros[SET_FPS_RATIO].nparams = 1;
1468 
1469   omc_macros[RETRIGGER_CLIP].msg = lives_strdup("/clip/foreground/retrigger");
1470   omc_macros[RETRIGGER_CLIP].macro_text = (_("Retrigger clip <clipnum>"));
1471   omc_macros[RETRIGGER_CLIP].info_text = lives_strdup(
1472       _("Switch foreground clip to the nth valid clip, and reset the frame number"));
1473   omc_macros[RETRIGGER_CLIP].nparams = 1;
1474 
1475   omc_macros[NEXT_MODE_CYCLE].msg = lives_strdup("/effect_key/mode/next");
1476   omc_macros[NEXT_MODE_CYCLE].macro_text = (_("Cycle to next mode for effect key <key>"));
1477   omc_macros[NEXT_MODE_CYCLE].nparams = 1;
1478 
1479   omc_macros[PREV_MODE_CYCLE].msg = lives_strdup("/effect_key/mode/previous");
1480   omc_macros[PREV_MODE_CYCLE].macro_text = (_("Cycle to previous mode for effect key <key>"));
1481   omc_macros[PREV_MODE_CYCLE].nparams = 1;
1482 
1483   omc_macros[SET_VPP_PARAMETER_VALUE].msg = lives_strdup("/video/play/parameter/value/set");
1484   omc_macros[SET_VPP_PARAMETER_VALUE].macro_text = (_("Set playback plugin parameter value <pnum> = <value>"));
1485   omc_macros[SET_VPP_PARAMETER_VALUE].info_text = (_("Set <value> of pth parameter for the playback plugin."));
1486   omc_macros[SET_VPP_PARAMETER_VALUE].nparams = 2;
1487 
1488   omc_macros[OSC_NOTIFY].msg = lives_strdup("internal"); // handled internally
1489   omc_macros[OSC_NOTIFY].macro_text = (_("Send OSC notification message"));
1490   omc_macros[OSC_NOTIFY].info_text = lives_strdup(
1491                                        _("Send LIVES_OSC_NOTIFY_USER1 notification to all listeners, with variable <value>."));
1492   omc_macros[OSC_NOTIFY].nparams = 2;
1493 
1494   for (i = 0; i < N_OMC_MACROS; i++) {
1495     if (omc_macros[i].msg) {
1496       if (omc_macros[i].nparams > 0) {
1497         omc_macros[i].ptypes = (int *)lives_malloc(omc_macros[i].nparams * sizint);
1498         omc_macros[i].mini = (int *)lives_malloc(omc_macros[i].nparams * sizint);
1499         omc_macros[i].maxi = (int *)lives_malloc(omc_macros[i].nparams * sizint);
1500         omc_macros[i].vali = (int *)lives_malloc(omc_macros[i].nparams * sizint);
1501 
1502         omc_macros[i].mind = (double *)lives_malloc(omc_macros[i].nparams * sizdbl);
1503         omc_macros[i].maxd = (double *)lives_malloc(omc_macros[i].nparams * sizdbl);
1504         omc_macros[i].vald = (double *)lives_malloc(omc_macros[i].nparams * sizdbl);
1505         omc_macros[i].pname = (char **)lives_malloc(omc_macros[i].nparams * sizeof(char *));
1506 
1507       }
1508     }
1509   }
1510 
1511   // clip select
1512   omc_macros[CLIP_SELECT].ptypes[0] = OMC_PARAM_INT;
1513   omc_macros[CLIP_SELECT].mini[0] = omc_macros[CLIP_SELECT].vali[0] = 1;
1514   omc_macros[CLIP_SELECT].maxi[0] = MAX_FILES;
1515   // TRANSLATORS: short form of "clip number"
1516   omc_macros[CLIP_SELECT].pname[0] = (_("clipnum"));
1517 
1518   // set fps (will be handled to avoid 0.)
1519   omc_macros[SET_FRAMERATE].ptypes[0] = OMC_PARAM_DOUBLE;
1520   omc_macros[SET_FRAMERATE].mind[0] = -FPS_MAX;
1521   omc_macros[SET_FRAMERATE].vald[0] = prefs->default_fps;
1522   omc_macros[SET_FRAMERATE].maxd[0] = FPS_MAX;
1523   // TRANSLATORS: short form of "frames per second"
1524   omc_macros[SET_FRAMERATE].pname[0] = (_("fps"));
1525 
1526   // effect_key enable,disable, toggle
1527   omc_macros[ENABLE_EFFECT_KEY].ptypes[0] = OMC_PARAM_INT;
1528   omc_macros[ENABLE_EFFECT_KEY].mini[0] = 1;
1529   omc_macros[ENABLE_EFFECT_KEY].vali[0] = 1;
1530   omc_macros[ENABLE_EFFECT_KEY].maxi[0] = prefs->rte_keys_virtual;
1531   // TRANSLATORS: as in keyboard key
1532   omc_macros[ENABLE_EFFECT_KEY].pname[0] = (_("key"));
1533 
1534   omc_macros[DISABLE_EFFECT_KEY].ptypes[0] = OMC_PARAM_INT;
1535   omc_macros[DISABLE_EFFECT_KEY].mini[0] = 1;
1536   omc_macros[DISABLE_EFFECT_KEY].vali[0] = 1;
1537   omc_macros[DISABLE_EFFECT_KEY].maxi[0] = prefs->rte_keys_virtual;
1538   // TRANSLATORS: as in keyboard key
1539   omc_macros[DISABLE_EFFECT_KEY].pname[0] = (_("key"));
1540 
1541   omc_macros[TOGGLE_EFFECT_KEY].ptypes[0] = OMC_PARAM_INT;
1542   omc_macros[TOGGLE_EFFECT_KEY].mini[0] = 1;
1543   omc_macros[TOGGLE_EFFECT_KEY].vali[0] = 1;
1544   omc_macros[TOGGLE_EFFECT_KEY].maxi[0] = prefs->rte_keys_virtual;
1545   // TRANSLATORS: as in keyboard key
1546   omc_macros[TOGGLE_EFFECT_KEY].pname[0] = (_("key"));
1547 
1548   // key
1549   omc_macros[SET_PARAMETER_VALUE].ptypes[0] = OMC_PARAM_INT;
1550   omc_macros[SET_PARAMETER_VALUE].mini[0] = 1;
1551   omc_macros[SET_PARAMETER_VALUE].vali[0] = 1;
1552   omc_macros[SET_PARAMETER_VALUE].maxi[0] = prefs->rte_keys_virtual;
1553   // TRANSLATORS: as in keyboard key
1554   omc_macros[SET_PARAMETER_VALUE].pname[0] = (_("key"));
1555 
1556   // param (this will be matched with numeric params)
1557   omc_macros[SET_PARAMETER_VALUE].ptypes[1] = OMC_PARAM_INT;
1558   omc_macros[SET_PARAMETER_VALUE].mini[1] = 0;
1559   omc_macros[SET_PARAMETER_VALUE].maxi[1] = 65536;
1560   omc_macros[SET_PARAMETER_VALUE].vali[1] = 0;
1561   // TRANSLATORS: short form of "parameter number"
1562   omc_macros[SET_PARAMETER_VALUE].pname[1] = (_("pnum"));
1563 
1564   // value (this will get special handling)
1565   // type conversion and auto offset/scaling will be done
1566   omc_macros[SET_PARAMETER_VALUE].ptypes[2] = OMC_PARAM_SPECIAL;
1567   omc_macros[SET_PARAMETER_VALUE].mind[2] = 0.;
1568   omc_macros[SET_PARAMETER_VALUE].maxd[2] = 0.;
1569   omc_macros[SET_PARAMETER_VALUE].vald[2] = 0.;
1570   omc_macros[SET_PARAMETER_VALUE].pname[2] = (_("value"));
1571 
1572   // set ratio fps (will be handled to avoid 0.)
1573   omc_macros[SET_FPS_RATIO].ptypes[0] = OMC_PARAM_DOUBLE;
1574   omc_macros[SET_FPS_RATIO].mind[0] = -10.;
1575   omc_macros[SET_FPS_RATIO].vald[0] = 1.;
1576   omc_macros[SET_FPS_RATIO].maxd[0] = 10.;
1577   // TRANSLATORS: short form of "frames per second"
1578   omc_macros[SET_FPS_RATIO].pname[0] = (_("fps__ratio"));
1579 
1580   // clip retrigger
1581   omc_macros[RETRIGGER_CLIP].ptypes[0] = OMC_PARAM_INT;
1582   omc_macros[RETRIGGER_CLIP].mini[0] = omc_macros[RETRIGGER_CLIP].vali[0] = 1;
1583   omc_macros[RETRIGGER_CLIP].maxi[0] = MAX_FILES;
1584   // TRANSLATORS: short form of "clip number"
1585   omc_macros[RETRIGGER_CLIP].pname[0] = (_("clipnum"));
1586 
1587   // key
1588   omc_macros[NEXT_MODE_CYCLE].ptypes[0] = OMC_PARAM_INT;
1589   omc_macros[NEXT_MODE_CYCLE].mini[0] = 1;
1590   omc_macros[NEXT_MODE_CYCLE].vali[0] = 1;
1591   omc_macros[NEXT_MODE_CYCLE].maxi[0] = prefs->rte_keys_virtual;
1592   // TRANSLATORS: as in keyboard key
1593   omc_macros[NEXT_MODE_CYCLE].pname[0] = (_("key"));
1594 
1595   // key
1596   omc_macros[PREV_MODE_CYCLE].ptypes[0] = OMC_PARAM_INT;
1597   omc_macros[PREV_MODE_CYCLE].mini[0] = 1;
1598   omc_macros[PREV_MODE_CYCLE].vali[0] = 1;
1599   omc_macros[PREV_MODE_CYCLE].maxi[0] = prefs->rte_keys_virtual;
1600   // TRANSLATORS: as in keyboard key
1601   omc_macros[PREV_MODE_CYCLE].pname[0] = (_("key"));
1602 
1603   // param
1604   omc_macros[SET_VPP_PARAMETER_VALUE].ptypes[0] = OMC_PARAM_INT;
1605   omc_macros[SET_VPP_PARAMETER_VALUE].mini[0] = 0;
1606   omc_macros[SET_VPP_PARAMETER_VALUE].maxi[0] = 128;
1607   omc_macros[SET_VPP_PARAMETER_VALUE].vali[0] = 0;
1608   // TRANSLATORS: short form of "parameter number"
1609   omc_macros[SET_VPP_PARAMETER_VALUE].pname[0] = (_("pnum"));
1610 
1611   // value (this will get special handling)
1612   // type conversion and auto offset/scaling will be done
1613   omc_macros[SET_VPP_PARAMETER_VALUE].ptypes[1] = OMC_PARAM_SPECIAL;
1614   omc_macros[SET_VPP_PARAMETER_VALUE].mind[1] = 0.;
1615   omc_macros[SET_VPP_PARAMETER_VALUE].maxd[1] = 0.;
1616   omc_macros[SET_VPP_PARAMETER_VALUE].vald[1] = 0.;
1617   omc_macros[SET_VPP_PARAMETER_VALUE].pname[1] = (_("value"));
1618 
1619   // variables for LIVES_OSC_NOTIFY_USER1
1620   omc_macros[OSC_NOTIFY].ptypes[0] = OMC_PARAM_INT;
1621   omc_macros[OSC_NOTIFY].mini[0] = 0;
1622   omc_macros[OSC_NOTIFY].vali[0] = 0;
1623   omc_macros[OSC_NOTIFY].maxi[0] = 100000;
1624   omc_macros[OSC_NOTIFY].pname[0] = (_("discrimination"));
1625 
1626   omc_macros[OSC_NOTIFY].ptypes[1] = OMC_PARAM_DOUBLE;
1627   omc_macros[OSC_NOTIFY].mini[1] = -1000000.;
1628   omc_macros[OSC_NOTIFY].vali[1] = 0.;
1629   omc_macros[OSC_NOTIFY].maxi[1] = 1000000.;
1630   omc_macros[OSC_NOTIFY].pname[1] = (_("data"));
1631 }
1632 
1633 
match_filtered_params(lives_omc_match_node_t * mnode,const char * sig,int nfixed)1634 static boolean match_filtered_params(lives_omc_match_node_t *mnode, const char *sig, int nfixed) {
1635   int i;
1636   char **array = lives_strsplit(sig, " ", -1);
1637 
1638   for (i = 0; i < mnode->nvars; i++) {
1639     if (mnode->matchp[i]) {
1640       if (mnode->matchi[i] != atoi(array[nfixed + i])) {
1641         //g_print("data mismatch %d %d %d\n",mnode->matchi[i],atoi(array[nfixed+i]),nfixed);
1642         lives_strfreev(array);
1643         return FALSE;
1644       }
1645     }
1646   }
1647   //g_print("data match\n");
1648   lives_strfreev(array);
1649   return TRUE;
1650 }
1651 
1652 
omc_match_sig(int type,int index,const char * sig)1653 static lives_omc_match_node_t *omc_match_sig(int type, int index, const char *sig) {
1654   LiVESSList *nlist = omc_node_list;
1655   char *srch, *cnodex;
1656   lives_omc_match_node_t *cnode;
1657   int nfixed;
1658 
1659   if (type == OMC_MIDI) {
1660     if (index == -1) srch = lives_strdup_printf("%d %s ", type, sig);
1661     else srch = lives_strdup_printf("%d %d %s ", type, index, sig);
1662   } else srch = lives_strdup_printf("%s ", sig);
1663 
1664   nfixed = get_nfixed(type, sig);
1665 
1666   while (nlist) {
1667     cnode = (lives_omc_match_node_t *)nlist->data;
1668     cnodex = lives_strdup_printf("%s ", cnode->srch);
1669     //g_print("cf %s and %s\n",cnode->srch,srch);
1670     if (!strncmp(cnodex, srch, strlen(cnodex))) {
1671       // got a possible match
1672       // now check the data
1673       if (match_filtered_params(cnode, sig, nfixed)) {
1674         lives_free(srch);
1675         lives_free(cnodex);
1676         return cnode;
1677       }
1678     }
1679     nlist = nlist->next;
1680     lives_free(cnodex);
1681   }
1682   lives_free(srch);
1683   return NULL;
1684 }
1685 
1686 
1687 /* not used yet */
1688 /*static char *omclearn_request_min(int type) {
1689   char *msg=NULL;
1690 
1691   switch (type) {
1692   case OMC_JS_AXIS:
1693     msg=(_("\n\nNow move the stick to the opposite position and click OK\n\n"));
1694     break;
1695   case OMC_MIDI_CONTROLLER:
1696     msg=(_("\n\nPlease set the control to its minimum value and click OK\n\n"));
1697     break;
1698   case OMC_MIDI_NOTE:
1699     msg=(_("\n\nPlease release the note\n\n"));
1700     break;
1701   }
1702 
1703   do_error_dialog(msg);
1704   if (msg!=NULL) lives_free(msg);
1705 
1706   return NULL;
1707   }*/
1708 
1709 /*
1710   LIVES_INLINE int omclearn_get_fixed_elems(const char *string1, const char *string2) {
1711   // count how many (non-space) elements match
1712   // e.g "a b c" and "a b d" returns 2
1713 
1714   // neither string may end in a space
1715 
1716   register int i;
1717 
1718   int match = 0;
1719   int stlen = MIN(strlen(string1), strlen(string2));
1720 
1721   for (i = 0; i < stlen; i++) {
1722     if (strcmp((string1 + i), (string2 + i))) return match;
1723     if (!strcmp((string1 + i), " ")) match++;
1724   }
1725 
1726   return match + 1;
1727   }
1728 */
1729 
get_nth_elem(const char * string,int idx)1730 LIVES_INLINE int get_nth_elem(const char *string, int idx) {
1731   char **array = lives_strsplit(string, " ", -1);
1732   int retval = atoi(array[idx]);
1733   lives_strfreev(array);
1734   return retval;
1735 }
1736 
1737 
lives_omc_match_node_new(int str_type,int index,const char * string,int nfixed)1738 static lives_omc_match_node_t *lives_omc_match_node_new(int str_type, int index, const char *string, int nfixed) {
1739   int i;
1740   char *tmp;
1741   char *srch_str;
1742   lives_omc_match_node_t *mnode = (lives_omc_match_node_t *)lives_malloc(sizeof(lives_omc_match_node_t));
1743 
1744   if (str_type == OMC_MIDI) {
1745     mainw->midi_channel_lock = TRUE;
1746     if (index > -1) srch_str = lives_strdup_printf("%d %d %s", str_type, index, (tmp = cut_string_elems(string,
1747                                  nfixed < 0 ? -1 : nfixed)));
1748     else srch_str = lives_strdup_printf("%d %s", str_type, (tmp = cut_string_elems(string, nfixed < 0 ? -1 : nfixed)));
1749     lives_free(tmp);
1750   } else {
1751     srch_str = lives_strdup_printf("%s", (tmp = cut_string_elems(string, nfixed < 0 ? -1 : nfixed)));
1752     lives_free(tmp);
1753   }
1754 
1755   //g_print("srch_str was %d %d .%s. %d\n", str_type, index, srch_str, nfixed);
1756 
1757   mnode->srch = srch_str;
1758   mnode->macro = -1;
1759 
1760   if (nfixed < 0) mnode->nvars = -(nfixed + 1);
1761   else mnode->nvars = get_token_count(string, ' ') - nfixed;
1762 
1763   if (mnode->nvars > 0) {
1764     mnode->offs0 = (int *)lives_malloc(mnode->nvars * sizint);
1765     mnode->scale = (double *)lives_malloc(mnode->nvars * sizdbl);
1766     mnode->offs1 = (int *)lives_malloc(mnode->nvars * sizint);
1767     mnode->min = (int *)lives_malloc(mnode->nvars * sizint);
1768     mnode->max = (int *)lives_malloc(mnode->nvars * sizint);
1769     mnode->matchp = (boolean *)lives_malloc(mnode->nvars * sizeof(boolean));
1770     mnode->matchi = (int *)lives_malloc(mnode->nvars * sizint);
1771   }
1772 
1773   for (i = 0; i < mnode->nvars; i++) {
1774     mnode->offs0[i] = mnode->offs1[i] = 0;
1775     mnode->scale[i] = 1.;
1776     mnode->matchp[i] = FALSE;
1777   }
1778 
1779   mnode->map = mnode->fvali = NULL;
1780   mnode->fvald = NULL;
1781 
1782   mnode->treev1 = mnode->treev2 = NULL;
1783   mnode->gtkstore = mnode->gtkstore2 = NULL;
1784 
1785   return mnode;
1786 }
1787 
1788 
omclearn_get_values(const char * string,int nfixed)1789 static int *omclearn_get_values(const char *string, int nfixed) {
1790   register int i, j;
1791   size_t slen, tslen;
1792   int *retvals, count = 0, nvars;
1793 
1794   slen = strlen(string);
1795 
1796   nvars = get_token_count(string, ' ') - nfixed;
1797 
1798   retvals = (int *)lives_malloc(nvars * sizint);
1799 
1800   for (i = 0; i < slen; i++) {
1801     if (!strncmp((string + i), " ", 1)) {
1802       if (--nfixed <= 0) {
1803         char *tmp = lives_strdup(string + i + 1);
1804         tslen = strlen(tmp);
1805         for (j = 0; j < tslen; j++) {
1806           if (!strncmp((tmp + j), " ", 1)) {
1807             lives_memset(tmp + j, 0, 1);
1808             retvals[count++] = atoi(tmp);
1809             lives_free(tmp);
1810             break;
1811           }
1812         }
1813         if (j == tslen) {
1814           retvals[count++] = atoi(tmp);
1815           lives_free(tmp);
1816           return retvals;
1817         }
1818         i += j;
1819       }
1820     }
1821   }
1822 
1823   // should never reach here
1824   return NULL;
1825 }
1826 
1827 
omclearn_match_control(lives_omc_match_node_t * mnode,int str_type,int index,const char * string,int nfixed,omclearn_w * omclw)1828 void omclearn_match_control(lives_omc_match_node_t *mnode, int str_type, int index, const char *string, int nfixed,
1829                             omclearn_w *omclw) {
1830   if (nfixed == -1) {
1831     // already there : allow user to update
1832     return;
1833   }
1834 
1835   if (index == -1) {
1836     index = get_nth_elem(string, 1);
1837   }
1838 
1839   // add descriptive text on left
1840   // add combo box on right
1841 
1842   omc_learner_add_row(str_type, index, mnode, string, omclw);
1843 }
1844 
1845 
omc_learn(const char * string,int str_type,int idx,omclearn_w * omclw)1846 lives_omc_match_node_t *omc_learn(const char *string, int str_type, int idx, omclearn_w *omclw) {
1847   // here we come with a string, which must be a sequence of integers
1848   // separated by single spaces
1849 
1850   // the str_type is one of JS_AXIS, JS_BUTTON, MIDI_CONTROLLER, MIDI_KEY, etc.
1851 
1852   // idx is -1, except for JS_BUTTON and JS_AXIS where it can be used
1853 
1854   // the string is first transformed into
1855   // signifier and value
1856 
1857   // next, we check if signifier is already matched to a macro
1858 
1859   // if not we allow the user to match it to any macro that has n or fewer parameters,
1860   // where n is the number of variables in string
1861 
1862   lives_omc_match_node_t *mnode;
1863 
1864   int nfixed = get_nfixed(str_type, string);
1865 
1866   switch (str_type) {
1867   case OMC_MIDI_CONTROLLER:
1868     // display controller and allow it to be matched
1869     // then request min
1870 
1871     mnode = omc_match_sig(OMC_MIDI, idx, string);
1872     //g_print("autoscale !\n");
1873 
1874     if (!mnode || mnode->macro == UNMATCHED) {
1875       mnode = lives_omc_match_node_new(OMC_MIDI, idx, string, nfixed);
1876       mnode->max[0] = 127;
1877       mnode->min[0] = 0;
1878       idx = midi_index(string);
1879       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1880       return mnode;
1881     }
1882     break;
1883   case OMC_MIDI_PGM_CHANGE:
1884     // display controller and allow it to be matched
1885 
1886     mnode = omc_match_sig(OMC_MIDI, idx, string);
1887     //g_print("autoscale !\n");
1888 
1889     if (!mnode || mnode->macro == UNMATCHED) {
1890       mnode = lives_omc_match_node_new(OMC_MIDI, idx, string, nfixed);
1891       mnode->max[0] = 127;
1892       mnode->min[0] = 0;
1893       idx = midi_index(string);
1894       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1895       return mnode;
1896     }
1897     break;
1898   case OMC_MIDI_PITCH_BEND:
1899     // display controller and allow it to be matched
1900     // then request min
1901 
1902     mnode = omc_match_sig(OMC_MIDI, idx, string);
1903     //g_print("autoscale !\n");
1904 
1905     if (!mnode || mnode->macro == UNMATCHED) {
1906       mnode = lives_omc_match_node_new(OMC_MIDI, idx, string, nfixed);
1907       mnode->max[0] = 8192;
1908       mnode->min[0] = -8192;
1909       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1910       return mnode;
1911     }
1912     break;
1913   case OMC_MIDI_NOTE:
1914   case OMC_MIDI_NOTE_OFF:
1915     // display note and allow it to be matched
1916     mnode = omc_match_sig(OMC_MIDI, idx, string);
1917 
1918     if (!mnode || mnode->macro == UNMATCHED) {
1919       mnode = lives_omc_match_node_new(OMC_MIDI, idx, string, nfixed);
1920 
1921       mnode->max[0] = 127;
1922       mnode->min[0] = 0;
1923 
1924       mnode->max[1] = 127;
1925       mnode->min[1] = 0;
1926 
1927       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1928 
1929       return mnode;
1930     }
1931     break;
1932   case OMC_JS_AXIS:
1933     // display axis and allow it to be matched
1934     // then request min
1935 
1936     mnode = omc_match_sig(str_type, idx, string);
1937 
1938     if (!mnode || mnode->macro == UNMATCHED) {
1939       mnode = lives_omc_match_node_new(str_type, idx, string, nfixed);
1940 
1941       mnode->min[0] = -128;
1942       mnode->max[0] = 128;
1943 
1944       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1945       return mnode;
1946     }
1947     break;
1948   case OMC_JS_BUTTON:
1949     // display note and allow it to be matched
1950     mnode = omc_match_sig(str_type, idx, string);
1951 
1952     if (!mnode || mnode->macro == UNMATCHED) {
1953       mnode = lives_omc_match_node_new(str_type, idx, string, nfixed);
1954       omclearn_match_control(mnode, str_type, idx, string, nfixed, omclw);
1955       return mnode;
1956     }
1957     break;
1958   default:
1959     // hmmm....
1960 
1961     break;
1962   }
1963   return NULL;
1964 }
1965 
1966 
1967 // here we process a string which is formed of (supertype) (type) [(idx)] [(values)]
1968 // eg "val_for_js js_button idx_1  1"  => "2 3 1
1969 
1970 // in learn mode we store the string + its meaning
1971 
1972 // in playback mode, we match the string with our database, and then convert/append the variables
1973 
omc_process_string(int supertype,const char * string,boolean learn,omclearn_w * omclw)1974 boolean omc_process_string(int supertype, const char *string, boolean learn, omclearn_w *omclw) {
1975   // only need to set omclw if learn is TRUE
1976 
1977   // returns TRUE if we learn new, or if we carry out an action
1978   // retruns FALSE otherwise
1979 
1980   boolean ret = FALSE;
1981   int type = -1, idx = -1;
1982   lives_omc_match_node_t *mnode;
1983 
1984   if (!string) return FALSE;
1985 
1986   if (!omc_macros_inited) {
1987     init_omc_macros();
1988     omc_macros_inited = TRUE;
1989     OSC_initBuffer(&obuf, OSC_BUF_SIZE, byarr);
1990   }
1991 
1992   switch (supertype) {
1993   case OMC_INTERNAL:
1994     supertype = type = js_msg_type(string);
1995     idx = js_index(string);
1996     break;
1997   case OMC_JS:
1998 #ifdef OMC_JS_IMPL
1999     supertype = type = js_msg_type(string);
2000     idx = js_index(string);
2001 #endif
2002     break;
2003 #ifdef OMC_MIDI_IMPL
2004   case OMC_MIDI:
2005     type = midi_msg_type(string);
2006     idx = -1;
2007 #endif
2008   }
2009   if (type > -1) {
2010     if (learn) {
2011       // pass to learner
2012       mnode = omc_learn(string, type, idx, omclw);
2013       if (mnode) {
2014         ret = TRUE;
2015         omc_node_list = lives_slist_append(omc_node_list, mnode);
2016       }
2017     } else {
2018       OSCbuf *oscbuf = omc_learner_decode(supertype, idx, string);
2019       // if not playing, the only commands we allow are:
2020       // /video/play
2021       // /clip/foreground/retrigger
2022       // and enabling a generator
2023 
2024       // basically only messages which will trigger start of playback
2025 
2026       // further checks are performed when enabling/toggling an effect to see whether it is a generator
2027 
2028       if (oscbuf && !OSC_isBufferEmpty(oscbuf)) {
2029         if (!LIVES_IS_PLAYING
2030             && strcmp(oscbuf->buffer, "/video/play")
2031             && strcmp(oscbuf->buffer, "/clip/foreground/retrigger")
2032             && strcmp(oscbuf->buffer, "/effect_key/enable")
2033             && strcmp(oscbuf->buffer, "/effect_key/toggle")
2034            ) return FALSE;
2035 
2036         lives_osc_act(oscbuf);
2037         ret = TRUE;
2038       }
2039     }
2040   }
2041   return ret;
2042 }
2043 
2044 
on_midi_learn_activate(LiVESMenuItem * menuitem,livespointer user_data)2045 void on_midi_learn_activate(LiVESMenuItem *menuitem, livespointer user_data) {
2046   omclearn_w *omclw = create_omclearn_dialog();
2047   char *string = NULL;
2048 
2049   if (!omc_macros_inited) {
2050     init_omc_macros();
2051     omc_macros_inited = TRUE;
2052     OSC_initBuffer(&obuf, OSC_BUF_SIZE, byarr);
2053   }
2054 
2055 #ifdef OMC_MIDI_IMPL
2056   if (!mainw->ext_cntl[EXT_CNTL_MIDI]) midi_open();
2057 #endif
2058 
2059 #ifdef OMC_JS_IMPL
2060   if (!mainw->ext_cntl[EXT_CNTL_JS]) js_open();
2061 #endif
2062 
2063   mainw->cancelled = CANCEL_NONE;
2064 
2065   show_existing(omclw);
2066 
2067   // read controls and notes
2068   while (mainw->cancelled == CANCEL_NONE) {
2069     // read from devices
2070 
2071 #ifdef OMC_JS_IMPL
2072     if (mainw->ext_cntl[EXT_CNTL_JS]) string = js_mangle();
2073     if (string) {
2074       omc_process_string(OMC_JS, string, TRUE, omclw);
2075       lives_free(string);
2076       string = NULL;
2077     } else {
2078 #endif
2079 
2080 #ifdef OMC_MIDI_IMPL
2081       if (mainw->ext_cntl[EXT_CNTL_MIDI]) string = midi_mangle();
2082       //#define TEST_OMC_LEARN
2083 #ifdef TEST_OMC_LEARN
2084       string = lives_strdup("176 10 0 1");
2085 #endif
2086       if (string) {
2087         omc_process_string(OMC_MIDI, string, TRUE, omclw);
2088         lives_free(string);
2089         string = NULL;
2090       }
2091 #endif
2092 
2093 #ifdef OMC_JS_IMPL
2094     }
2095 #endif
2096 
2097     lives_usleep(prefs->sleep_time);
2098 
2099     lives_widget_context_update();
2100   }
2101 
2102   remove_all_nodes(FALSE, omclw);
2103 
2104   lives_widget_destroy(omclw->dialog);
2105 
2106   mainw->cancelled = CANCEL_NONE;
2107 
2108   lives_free(omclw);
2109 }
2110 
2111 
write_fx_tag(const char * string,int nfixed,lives_omc_match_node_t * mnode,lives_omc_macro_t * omacro,char * typetags)2112 static void write_fx_tag(const char *string, int nfixed, lives_omc_match_node_t *mnode, lives_omc_macro_t *omacro,
2113                          char *typetags) {
2114   // get typetag for a filter parameter
2115 
2116   int i, j, k;
2117   int *vals = omclearn_get_values(string, nfixed);
2118   int oval0 = 1, oval1 = 0;
2119 
2120   for (i = 0; i < omacro->nparams; i++) {
2121     // get fixed val or map from
2122     j = mnode->map[i];
2123 
2124     if (j > -1) {
2125       if (i == 2) {
2126         // auto scale for fx param
2127         int ntmpls = 0, ptype, flags;
2128         int mode = rte_key_getmode(oval0);
2129         weed_plant_t *filter;
2130         weed_plant_t **ptmpls;
2131         weed_plant_t *ptmpl;
2132 
2133         if (mode == -1) return;
2134 
2135         filter = rte_keymode_get_filter(oval0, mode);
2136         ptmpls = weed_filter_get_in_paramtmpls(filter, &ntmpls);
2137 
2138         for (k = 0; k < ntmpls; k++) {
2139           ptmpl = ptmpls[k];
2140           if (weed_plant_has_leaf(ptmpl, WEED_LEAF_HOST_INTERNAL_CONNECTION)) continue;
2141           ptype = weed_paramtmpl_get_type(ptmpl);
2142           flags = weed_paramtmpl_get_flags(ptmpl);
2143           if (flags & WEED_PARAMETER_VARIABLE_SIZE) flags ^= WEED_PARAMETER_VARIABLE_SIZE;
2144           if ((ptype == WEED_PARAM_INTEGER || ptype == WEED_PARAM_FLOAT) && flags == 0 &&
2145               weed_leaf_num_elements(ptmpl, WEED_LEAF_DEFAULT) == 1) {
2146             if (oval1 == 0) {
2147               if (ptype == WEED_PARAM_INTEGER) {
2148                 // **int
2149                 lives_strappend(typetags, OSC_MAX_TYPETAGS, "i");
2150               } else {
2151                 // float
2152                 lives_strappend(typetags, OSC_MAX_TYPETAGS, "f");
2153               }
2154             }
2155             oval1--;
2156           }
2157         }
2158         lives_free(ptmpls);
2159       } else {
2160         // playback plugin params
2161         if (omacro->ptypes[i] == OMC_PARAM_INT) {
2162           int oval = myround((double)(vals[j] + mnode->offs0[j]) * mnode->scale[j]) + mnode->offs1[j];
2163           if (i == 0) oval0 = oval;
2164           if (i == 1) oval1 = oval;
2165         }
2166       }
2167     } else {
2168       if (omacro->ptypes[i] == OMC_PARAM_INT) {
2169         if (i == 0) oval0 = mnode->fvali[i];
2170         if (i == 1) oval1 = mnode->fvali[i];
2171       }
2172     }
2173   }
2174   lives_free(vals);
2175 }
2176 
2177 
omc_learner_decode(int type,int idx,const char * string)2178 OSCbuf *omc_learner_decode(int type, int idx, const char *string) {
2179   lives_omc_match_node_t *mnode = NULL;
2180   lives_omc_macro_t omacro;
2181   int *vals = NULL;
2182   double oval = 0.;
2183   int macro, nfixed = 0;
2184   int oval0 = 1, oval1 = 0;
2185   int ntmpls = 0, ptype, flags;
2186   int i, j, k;
2187 
2188   char typetags[OSC_MAX_TYPETAGS];
2189 
2190   if (type == OMC_INTERNAL) {
2191     if (idx < 0 || idx >= N_OMC_MACROS || !omc_macros[idx].msg) return NULL;
2192     macro = idx;
2193   } else {
2194     mnode = omc_match_sig(type, idx, string);
2195 
2196     if (!mnode) return NULL;
2197 
2198     macro = mnode->macro;
2199 
2200     if (macro == UNMATCHED) return NULL;
2201   }
2202 
2203   omacro = omc_macros[macro];
2204 
2205   if (!omacro.msg) return NULL;
2206 
2207   if (type != OMC_INTERNAL) nfixed = get_token_count(string, ' ') - mnode->nvars;
2208 
2209   OSC_resetBuffer(&obuf);
2210 
2211   if (macro != OSC_NOTIFY) {
2212     lives_snprintf(typetags, OSC_MAX_TYPETAGS, ",");
2213 
2214     // TODO ***: OMC_INTERNAL...we want to set param number token[2] with value token[3]
2215     // get typetags
2216     for (i = 0; i < omacro.nparams; i++) {
2217       if (omacro.ptypes[i] == OMC_PARAM_SPECIAL) {
2218         write_fx_tag(string, nfixed, mnode, &omacro, typetags);
2219       } else {
2220         if (omacro.ptypes[i] == OMC_PARAM_INT) lives_strappend(typetags, OSC_MAX_TYPETAGS, "i");
2221         else lives_strappend(typetags, OSC_MAX_TYPETAGS, "f");
2222       }
2223     }
2224     OSC_writeAddressAndTypes(&obuf, omacro.msg, typetags);
2225   }
2226 
2227   if (omacro.nparams > 0) {
2228     if (type != OMC_INTERNAL) vals = omclearn_get_values(string, nfixed);
2229 
2230     for (i = 0; i < omacro.nparams; i++) {
2231       // get fixed val or map from
2232       if (type != OMC_INTERNAL) j = mnode->map[i];
2233       else j = -1; // TODO *****, get from token[2]
2234       if (j > -1) {
2235         if (macro == SET_VPP_PARAMETER_VALUE && i == 1 && mainw->vpp && mainw->vpp->play_params &&
2236             oval0 < mainw->vpp->num_play_params) {
2237           // auto scale for playback plugin params
2238 
2239           weed_plant_t *ptmpl = weed_get_plantptr_value((weed_plant_t *)pp_get_param(mainw->vpp->play_params, oval0),
2240                                 WEED_LEAF_TEMPLATE, NULL);
2241           ptype = weed_paramtmpl_get_type(ptmpl);
2242           if ((ptype == WEED_PARAM_INTEGER || ptype == WEED_PARAM_FLOAT) &&
2243               weed_leaf_num_elements(ptmpl, WEED_LEAF_DEFAULT) == 1) {
2244             if (ptype == WEED_PARAM_INTEGER) {
2245               int omin = mnode->min[j];
2246               int omax = mnode->max[j];
2247               int mini = weed_get_int_value(ptmpl, WEED_LEAF_MIN, NULL);
2248               int maxi = weed_get_int_value(ptmpl, WEED_LEAF_MAX, NULL);
2249               oval0 = (int)((double)(vals[j] - omin) / (double)(omax - omin) * (double)(maxi - mini)) + mini;
2250               OSC_writeIntArg(&obuf, oval0);
2251             } else {
2252               // float
2253               int omin = mnode->min[j];
2254               int omax = mnode->max[j];
2255               double minf = weed_get_double_value(ptmpl, WEED_LEAF_MIN, NULL);
2256               double maxf = weed_get_double_value(ptmpl, WEED_LEAF_MAX, NULL);
2257               oval = (double)(vals[j] - omin) / (double)(omax - omin) * (maxf - minf) + minf;
2258               OSC_writeFloatArg(&obuf, (float)oval);
2259             } // end float
2260           }
2261         } else {
2262           if (macro == SET_PARAMETER_VALUE && i == 2) {
2263             // auto scale for fx param
2264             int mode = rte_key_getmode(oval0);
2265             weed_plant_t *filter;
2266             weed_plant_t **ptmpls;
2267             weed_plant_t *ptmpl;
2268 
2269             if (mode == -1) return NULL;
2270 
2271             filter = rte_keymode_get_filter(oval0, mode);
2272 
2273             ptmpls = weed_filter_get_in_paramtmpls(filter, &ntmpls);
2274             for (k = 0; k < ntmpls; k++) {
2275               ptmpl = ptmpls[k];
2276               if (weed_plant_has_leaf(ptmpl, WEED_LEAF_HOST_INTERNAL_CONNECTION)) continue;
2277               ptype = weed_paramtmpl_get_type(ptmpl);
2278               flags = weed_paramtmpl_get_flags(ptmpl);
2279               if ((ptype == WEED_PARAM_INTEGER || ptype == WEED_PARAM_FLOAT) && flags == 0 &&
2280                   weed_leaf_num_elements(ptmpl, WEED_LEAF_DEFAULT) == 1) {
2281                 if (oval1 == 0) {
2282                   if (ptype == WEED_PARAM_INTEGER) {
2283                     int omin = mnode->min[j];
2284                     int omax = mnode->max[j];
2285                     int mini = weed_get_int_value(ptmpl, WEED_LEAF_MIN, NULL);
2286                     int maxi = weed_get_int_value(ptmpl, WEED_LEAF_MAX, NULL);
2287                     int oval = (int)((double)(vals[j] - omin) / (double)(omax - omin) * (double)(maxi - mini)) + mini;
2288                     OSC_writeIntArg(&obuf, oval);
2289                   } else {
2290                     // float
2291                     int omin = mnode->min[j];
2292                     int omax = mnode->max[j];
2293                     double minf = weed_get_double_value(ptmpl, WEED_LEAF_MIN, NULL);
2294                     double maxf = weed_get_double_value(ptmpl, WEED_LEAF_MAX, NULL);
2295                     oval = (double)(vals[j] - omin) / (double)(omax - omin) * (maxf - minf) + minf;
2296                     OSC_writeFloatArg(&obuf, (float)oval);
2297                   } // end float
2298                 }
2299                 oval1--;
2300               }
2301             }
2302             lives_free(ptmpls);
2303           } else {
2304             if (omacro.ptypes[i] == OMC_PARAM_INT) {
2305               int oval;
2306               if (type != OMC_INTERNAL) oval = myround((double)(vals[j] + mnode->offs0[j]) * mnode->scale[j]) + mnode->offs1[j];
2307               else oval = 0; // TODO ****
2308               if (i == 0) oval0 = (int)oval;
2309               if (i == 1) oval1 = (int)oval;
2310               if (macro != OSC_NOTIFY) {
2311                 OSC_writeIntArg(&obuf, oval);
2312               }
2313             } else {
2314               double oval;
2315               if (type != OMC_INTERNAL) oval = (double)(vals[j] + mnode->offs0[j]) * mnode->scale[j] + (double)mnode->offs1[j];
2316               else oval = 0.; //
2317               if (macro != OSC_NOTIFY)  OSC_writeFloatArg(&obuf, oval);
2318             }
2319           }
2320         }
2321       } else {                      // use default vals
2322         if (omacro.ptypes[i] == OMC_PARAM_INT) {
2323           if (macro != OSC_NOTIFY) OSC_writeIntArg(&obuf, mnode->fvali[i]);
2324           if (type != OMC_INTERNAL) {
2325             if (i == 0) oval0 = mnode->fvali[i];
2326             if (i == 1) oval1 = mnode->fvali[i];
2327           } else {
2328             if (i == 0) oval0 = omacro.vali[i];
2329             if (i == 1) oval1 = omacro.vali[i];
2330           }
2331         } else {
2332           if (type != OMC_INTERNAL) {
2333             oval = mnode->fvald[i];
2334           } else {
2335             oval = omacro.vald[i];
2336           }
2337           if (macro != OSC_NOTIFY) OSC_writeFloatArg(&obuf, (float)oval);
2338         }
2339       }
2340     }
2341     if (vals) lives_free(vals);
2342   }
2343 
2344   if (macro == OSC_NOTIFY) {
2345     char *tmp; // send OSC notificion USER1
2346     if (prefs->show_dev_opts)
2347       g_print("sending noti\n");
2348     lives_notify(LIVES_OSC_NOTIFY_USER1, (tmp = lives_strdup_printf("%d %f", oval0, oval)));
2349     lives_free(tmp);
2350   }
2351 
2352   return &obuf;
2353 }
2354 
2355 
2356 /////////////////////////////////////
2357 
2358 /** Save device mapping to an external file
2359 */
2360 
on_devicemap_save_activate(LiVESMenuItem * menuitem,livespointer user_data)2361 void on_devicemap_save_activate(LiVESMenuItem *menuitem, livespointer user_data) {
2362   LiVESSList *slist = omc_node_list;
2363 
2364   size_t srchlen;
2365 
2366   lives_omc_match_node_t *mnode;
2367   lives_omc_macro_t omacro;
2368 
2369   char *save_file;
2370   char *devmapdir = lives_build_path(prefs->config_datadir, LIVES_DEVICEMAP_DIR, NULL);
2371 
2372   LiVESResponseType retval;
2373 
2374   int nnodes;
2375   int fd;
2376 
2377   int i;
2378 
2379   uint8_t omnimidi;
2380 
2381   save_file = choose_file(devmapdir, NULL, NULL, LIVES_FILE_CHOOSER_ACTION_SAVE, NULL, NULL);
2382   lives_free(devmapdir);
2383 
2384   if (!save_file) return;
2385   if (!*save_file) {
2386     lives_free(save_file);
2387     return;
2388   }
2389 
2390   d_print(_("Saving device mapping to file %s..."), save_file);
2391 
2392   do {
2393     retval = 0;
2394     if ((fd = open(save_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
2395       retval = do_write_failed_error_s_with_retry(save_file, lives_strerror(errno));
2396       if (retval == LIVES_RESPONSE_CANCEL) {
2397         lives_free(save_file);
2398         d_print_failed();
2399         return;
2400       }
2401     } else {
2402       THREADVAR(write_failed) = FALSE;
2403 
2404       lives_write(fd, OMC_FILE_VSTRING, strlen(OMC_FILE_VSTRING), TRUE);
2405 
2406       if (prefs->midi_rcv_channel == MIDI_OMNI) omnimidi = 1;
2407       else omnimidi = 0;
2408 
2409       lives_write(fd, &omnimidi, 1, TRUE);
2410 
2411       nnodes = lives_slist_length(omc_node_list);
2412       lives_write_le(fd, &nnodes, 4, TRUE);
2413 
2414       while (slist) {
2415         if (THREADVAR(write_failed)) break;
2416         mnode = (lives_omc_match_node_t *)slist->data;
2417         srchlen = strlen(mnode->srch);
2418 
2419         lives_write_le(fd, &srchlen, 4, TRUE);
2420         lives_write(fd, mnode->srch, srchlen, TRUE);
2421 
2422         lives_write_le(fd, &mnode->macro, 4, TRUE);
2423         lives_write_le(fd, &mnode->nvars, 4, TRUE);
2424 
2425         for (i = 0; i < mnode->nvars; i++) {
2426           if (THREADVAR(write_failed)) break;
2427           lives_write_le(fd, &mnode->offs0[i], 4, TRUE);
2428           lives_write_le(fd, &mnode->scale[i], 8, TRUE);
2429           lives_write_le(fd, &mnode->offs1[i], 4, TRUE);
2430 
2431           lives_write_le(fd, &mnode->min[i], 4, TRUE);
2432           lives_write_le(fd, &mnode->max[i], 4, TRUE);
2433 
2434           lives_write_le(fd, &mnode->matchp[i], 4, TRUE);
2435           lives_write_le(fd, &mnode->matchi[i], 4, TRUE);
2436         }
2437 
2438         omacro = omc_macros[mnode->macro];
2439 
2440         for (i = 0; i < omacro.nparams; i++) {
2441           if (THREADVAR(write_failed)) break;
2442           lives_write_le(fd, &mnode->map[i], 4, TRUE);
2443           lives_write_le(fd, &mnode->fvali[i], 4, TRUE);
2444           lives_write_le(fd, &mnode->fvald[i], 8, TRUE);
2445         }
2446         slist = slist->next;
2447       }
2448 
2449       close(fd);
2450 
2451       if (THREADVAR(write_failed)) {
2452         retval = do_write_failed_error_s_with_retry(save_file, NULL);
2453         if (retval == LIVES_RESPONSE_CANCEL) d_print_file_error_failed();
2454       }
2455     }
2456   } while (retval == LIVES_RESPONSE_RETRY);
2457 
2458   if (retval != LIVES_RESPONSE_CANCEL) d_print_done();
2459 
2460   lives_free(save_file);
2461 }
2462 
2463 
omc_node_list_free(LiVESSList * slist)2464 static void omc_node_list_free(LiVESSList *slist) {
2465   while (slist) {
2466     omc_match_node_free((lives_omc_match_node_t *)slist->data);
2467     slist = slist->next;
2468   }
2469   lives_slist_free(slist);
2470   slist = NULL;
2471 }
2472 
2473 
do_devicemap_load_error(const char * fname)2474 static void do_devicemap_load_error(const char *fname) {
2475   char *msg = lives_strdup_printf(_("\n\nError parsing file\n%s\n"), fname);
2476   do_error_dialog(msg);
2477   lives_free(msg);
2478 }
2479 
2480 
do_devicemap_version_error(const char * fname)2481 static void do_devicemap_version_error(const char *fname) {
2482   char *msg = lives_strdup_printf(_("\n\nInvalid version in file\n%s\n"), fname);
2483   do_error_dialog(msg);
2484   lives_free(msg);
2485 }
2486 
2487 
on_devicemap_load_activate(LiVESMenuItem * menuitem,livespointer user_data)2488 void on_devicemap_load_activate(LiVESMenuItem *menuitem, livespointer user_data) {
2489   lives_omc_match_node_t *mnode;
2490   lives_omc_macro_t omacro;
2491 
2492   ssize_t bytes;
2493 
2494   char tstring[512];
2495 
2496   char *load_file = NULL;
2497   char *srch;
2498 
2499   uint8_t omnimidi = 1;
2500 
2501   uint32_t srchlen, nnodes, macro, nvars, supertype;
2502   int idx = -1;
2503   int fd;
2504   int new_midi_rcv_channel = prefs->midi_rcv_channel;
2505 
2506   register int i, j;
2507 
2508 #ifdef OMC_MIDI_IMPL
2509   size_t blen;
2510   char *tmp;
2511 #endif
2512 
2513   char *devmapdir = lives_build_path(prefs->config_datadir, LIVES_DEVICEMAP_DIR, NULL);
2514 
2515   if (!user_data) load_file = choose_file(devmapdir, NULL, NULL, LIVES_FILE_CHOOSER_ACTION_OPEN, NULL, NULL);
2516   else load_file = lives_strdup((char *)user_data);
2517   lives_free(devmapdir);
2518 
2519   if (!load_file) return;
2520   if (!*load_file) {
2521     lives_free(load_file);
2522     return;
2523   }
2524 
2525   d_print(_("Loading device mapping from file %s..."), load_file);
2526 
2527   if ((fd = open(load_file, O_RDONLY)) < 0) {
2528     if (!mainw->go_away) {
2529       char *msg = lives_strdup_printf(_("\n\nUnable to open file\n%s\nError code %d\n"), load_file, errno);
2530       do_error_dialog(msg);
2531       lives_free(msg);
2532     }
2533     lives_free(load_file);
2534     d_print_failed();
2535     return;
2536   }
2537 
2538   if (!omc_macros_inited) {
2539     init_omc_macros();
2540     omc_macros_inited = TRUE;
2541     OSC_initBuffer(&obuf, OSC_BUF_SIZE, byarr);
2542   }
2543 
2544   bytes = read(fd, tstring, strlen(OMC_FILE_VSTRING));
2545   if (bytes < strlen(OMC_FILE_VSTRING)) {
2546     goto load_failed;
2547   }
2548 
2549   if (strncmp(tstring, OMC_FILE_VSTRING, strlen(OMC_FILE_VSTRING))) {
2550     if (strncmp(tstring, OMC_FILE_VSTRING_1_0, strlen(OMC_FILE_VSTRING_1_0))) {
2551       d_print_failed();
2552       if (!mainw->go_away) do_devicemap_version_error(load_file);
2553       lives_free(load_file);
2554       close(fd);
2555       return;
2556     }
2557   } else {
2558     bytes = lives_read(fd, &omnimidi, 1, TRUE);
2559     if (bytes < 1) {
2560       goto load_failed;
2561     }
2562   }
2563 
2564   bytes = lives_read_le(fd, &nnodes, 4, TRUE);
2565   if (bytes < 4) {
2566     goto load_failed;
2567   }
2568 
2569   if (omc_node_list) {
2570     omc_node_list_free(omc_node_list);
2571     omc_node_list = NULL;
2572   }
2573 
2574   for (i = 0; i < nnodes; i++) {
2575     bytes = lives_read_le(fd, &srchlen, 4, TRUE);
2576     if (bytes < 4) {
2577       goto load_failed;
2578     }
2579 
2580     srch = (char *)lives_malloc(srchlen + 1);
2581 
2582     bytes = read(fd, srch, srchlen);
2583     if (bytes < srchlen) {
2584       goto load_failed2;
2585     }
2586 
2587     lives_memset(srch + srchlen, 0, 1);
2588 
2589     bytes = lives_read_le(fd, &macro, 4, TRUE);
2590     if (bytes < sizint) {
2591       goto load_failed2;
2592     }
2593 
2594     bytes = lives_read_le(fd, &nvars, 4, TRUE);
2595     if (bytes < 4) {
2596       goto load_failed2;
2597     }
2598 
2599     supertype = atoi(srch);
2600 
2601     switch (supertype) {
2602 #ifdef OMC_JS_IMPL
2603     case OMC_JS:
2604       supertype = js_msg_type(srch);
2605     case OMC_JS_BUTTON:
2606     case OMC_JS_AXIS:
2607       idx = js_index(srch);
2608       break;
2609 #endif
2610 #ifdef OMC_MIDI_IMPL
2611     case OMC_MIDI:
2612       if (omnimidi && prefs->midi_rcv_channel > MIDI_OMNI) {
2613         new_midi_rcv_channel = MIDI_OMNI;
2614       } else if (!omnimidi && prefs->midi_rcv_channel == MIDI_OMNI) {
2615         new_midi_rcv_channel = 0;
2616       }
2617       idx = -1;
2618 
2619       // cut first value (supertype) as we will be added back in match_node_new
2620       tmp = cut_string_elems(srch, 1);
2621       blen = strlen(tmp);
2622       tmp = lives_strdup(srch + blen + 1);
2623       lives_free(srch);
2624       srch = tmp;
2625 
2626       break;
2627 #endif
2628     default:
2629       return;
2630     }
2631 
2632     mnode = lives_omc_match_node_new(supertype, idx, srch, -(nvars + 1));
2633     lives_free(srch);
2634 
2635     mnode->macro = macro;
2636 
2637     for (j = 0; j < nvars; j++) {
2638       bytes = lives_read_le(fd, &mnode->offs0[j], 4, TRUE);
2639       if (bytes < 4) {
2640         goto load_failed;
2641       }
2642       bytes = lives_read_le(fd, &mnode->scale[j], 8, TRUE);
2643       if (bytes < 8) {
2644         goto load_failed;
2645       }
2646       bytes = lives_read_le(fd, &mnode->offs1[j], 4, TRUE);
2647       if (bytes < 4) {
2648         goto load_failed;
2649       }
2650       bytes = lives_read_le(fd, &mnode->min[j], 4, TRUE);
2651       if (bytes < 4) {
2652         goto load_failed;
2653       }
2654       bytes = lives_read_le(fd, &mnode->max[j], 4, TRUE);
2655       if (bytes < 4) {
2656         goto load_failed;
2657       }
2658       bytes = lives_read_le(fd, &mnode->matchp[j], 4, TRUE);
2659       if (bytes < 4) {
2660         goto load_failed;
2661       }
2662       bytes = lives_read_le(fd, &mnode->matchi[j], 4, TRUE);
2663       if (bytes < 4) {
2664         goto load_failed;
2665       }
2666     }
2667 
2668     omacro = omc_macros[macro];
2669 
2670     mnode->map = (int *)lives_malloc(omacro.nparams * sizint);
2671     mnode->fvali = (int *)lives_malloc(omacro.nparams * sizint);
2672     mnode->fvald = (double *)lives_malloc(omacro.nparams * sizdbl);
2673 
2674     for (j = 0; j < omacro.nparams; j++) {
2675       bytes = lives_read_le(fd, &mnode->map[j], 4, TRUE);
2676       if (bytes < 4) {
2677         goto load_failed;
2678       }
2679       bytes = lives_read_le(fd, &mnode->fvali[j], 4, TRUE);
2680       if (bytes < 4) {
2681         goto load_failed;
2682       }
2683       bytes = read(fd, &mnode->fvald[j], 8);
2684       if (bytes < 8) {
2685         goto load_failed;
2686       }
2687     }
2688     omc_node_list = lives_slist_append(omc_node_list, (livespointer)mnode);
2689   }
2690 
2691   close(fd);
2692   d_print_done();
2693 
2694 #ifdef OMC_MIDI_IMPL
2695   if (!mainw->ext_cntl[EXT_CNTL_MIDI]) midi_open();
2696 #endif
2697 
2698 #ifdef OMC_JS_IMPL
2699   if (!mainw->ext_cntl[EXT_CNTL_JS]) js_open();
2700 #endif
2701 
2702   if (has_devicemap(-1)) lives_widget_set_sensitive(mainw->midi_save, TRUE);
2703 
2704   if (new_midi_rcv_channel != prefs->midi_rcv_channel) {
2705     char *dpr;
2706     if (new_midi_rcv_channel == MIDI_OMNI) dpr = (_("MIDI receive channel was set to ALL CHANNELS\n"));
2707     else dpr = lives_strdup_printf(_("MIDI receive channel was set to channel %d\n"), new_midi_rcv_channel);
2708     prefs->midi_rcv_channel = new_midi_rcv_channel;
2709     d_print(dpr);
2710     lives_free(dpr);
2711     do_warning_dialog(
2712       _("The MIDI receive channel setting was updated by the device map.\n"
2713         "Please review the setting in Preferences and adjust it if necessary.\n"));
2714   }
2715   return;
2716 
2717 load_failed2:
2718   lives_free(srch);
2719 load_failed:
2720   d_print_failed();
2721   if (!mainw->go_away) do_devicemap_load_error(load_file);
2722   lives_free(load_file);
2723   close(fd);
2724 }
2725 
2726 #endif
2727