1 /* robtk stepseq gui
2 *
3 * Copyright 2016 Robin Gareus <robin@gareus.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <math.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #include "src/stepseq.h"
27 #include "gui/velocity_button.h"
28 #include "gui/custom_knob.h"
29
30 #define RTK_URI SEQ_URI
31 #define RTK_GUI "ui"
32
33 #ifndef MAX
34 #define MAX(A,B) ((A) > (B)) ? (A) : (B)
35 #endif
36
37 #ifndef MIN
38 #define MIN(A,B) ((A) < (B)) ? (A) : (B)
39 #endif
40
41 typedef struct {
42 LV2UI_Write_Function write;
43 LV2UI_Controller controller;
44
45 PangoFontDescription* font[2];
46
47 RobWidget* rw; // top-level container
48 RobWidget* ctbl; // control element table
49
50 RobTkVBtn* btn_grid[N_NOTES * N_STEPS];
51 RobTkSelect* sel_note[N_NOTES];
52 RobTkLbl* lbl_note[N_NOTES];
53 RobTkPBtn* btn_clear[N_NOTES + N_STEPS + 1];
54 RobTkCBtn* btn_sync;
55 RobTkSelect* sel_drum;
56 RobTkSelect* sel_mchn;
57 RobTkCnob* spn_div;
58 RobTkCnob* spn_bpm;
59 RobTkCnob* spn_swing;
60 RobTkPBtn* btn_panic;
61 RobTkSep* sep_h0;
62 RobTkLbl* lbl_chn;
63 RobTkLbl* lbl_div;
64 RobTkLbl* lbl_bpm;
65 RobTkLbl* lbl_swg;
66
67 cairo_pattern_t* swg_bg;
68 cairo_surface_t* bpm_bg;
69 cairo_surface_t* div_bg;
70
71 float user_bpm;
72 bool disable_signals;
73 const char* nfo;
74 } SeqUI;
75
76 ///////////////////////////////////////////////////////////////////////////////
77
78 static const char* mdrums[] = {
79 "Kick 2", // "Bass Drum 2" #35
80 "Kick 1", // "Bass Drum 1"
81 "RimShot", // "Side Stick/Rimshot"
82 "Snare 1", // "Snare Drum 1"
83 "Clap", // "Hand Clap"
84 "Snare 2", // "Snare Drum 2"
85 "Low Tom 2", // "Low Tom 2"
86 "HH Closed", // "Closed Hi-hat"
87 "Low Tom 1", // "Low Tom 1"
88 "HH Pedal", // "Pedal Hi-hat"
89 "Mid Tom 2", // "Mid Tom 2"
90 "HH Open", // "Open Hi-hat"
91 "Mid Tom 1", // "Mid Tom 1"
92 "Hi Tom 2", // "High Tom 2"
93 "Crash 1", // "Crash Cymbal 1"
94 "Hi Tom 1", // "High Tom 1"
95 "Ride 1", // "Ride Cymbal 1"
96 "China Cym", // "Chinese Cymbal"
97 "Ride Bell", // "Ride Bell"
98 "Tambour.", // "Tambourine"
99 "Splash", // "Splash Cymbal"
100 "Cowbell", // "Cowbell"
101 "Crash 2", // "Crash Cymbal 2"
102 "Slap", // "Vibra Slap"
103 "Ride 2", // "Ride Cymbal 2"
104 "Bongo Hi", // "High Bongo"
105 "Bongo Lo", // "Low Bongo"
106 "Conga Mt.", // "Mute High Conga"
107 "Conga Op.", // "Open High Conga"
108 "Conga Low", // "Low Conga"
109 "Timbale H", // "High Timbale"
110 "Timbale L", // "Low Timbale"
111 "Agogo H", // "High Agogô"
112 "Agogo L", // "Low Agogô"
113 "Cabasa", // "Cabasa"
114 "Maracas", // "Maracas"
115 "Whistle S", // "Short Whistle"
116 "Whistle L", // "Long Whistle"
117 "Guiro S", // "Short Güiro"
118 "Guiro L", // "Long Güiro"
119 "Claves", // "Claves"
120 "Woodblk H", // "High Wood Block"
121 "Woodblk L", // "Low Wood Block"
122 "Cuica Mt.", // "Mute Cuíca"
123 "Cuica Op.", // "Open Cuíca"
124 "Tri. Mt.", // "Mute Triangle"
125 "Tri. Op." // "Open Triangle"
126 };
127
128 static const char* avldrums[] = {
129 "KickDrum", // 36
130 "SnareSide",
131 "Snare",
132 "HandClap",
133 "SnareEdge",
134 "Floor Tom",
135 "HH Closed",
136 "FTom Edge",
137 "HH Pedal",
138 "TomCtr",
139 "HH Semi",
140 "Tom Edge",
141 "HH Swish",
142 "Crash 1",
143 "Crash 1Ck",
144 "Ride Tip",
145 "Ride Chk",
146 "Ride Bell",
147 "Tambour.",
148 "Splash",
149 "Cowbell",
150 "Crash 2",
151 "Crash 2Ck",
152 "Ride Shnk",
153 "Crash 3",
154 "Maracas" // 61
155 };
156
157 static const char* notename[] = {
158 "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B"
159 };
160
161 /* factory default mappings, nd2 user manual p.10, nd3 user manual p.12 */
162 static const char* norddrum[] = {
163 "Channel 1", // 60
164 notename[61%12],
165 "Channel 2",
166 notename[63%12],
167 "Channel 3",
168 "Channel 4",
169 notename[66%12],
170 "Channel 5",
171 notename[68%12],
172 "Channel 6" // 69
173 };
174
175 ///////////////////////////////////////////////////////////////////////////////
176
177 struct MyGimpImage {
178 unsigned int width;
179 unsigned int height;
180 unsigned int bytes_per_pixel;
181 unsigned char pixel_data[];
182 };
183
184 /* load gimp-exported .c image into cairo surface */
img2surf(struct MyGimpImage const * img,cairo_surface_t ** ds)185 static void img2surf (struct MyGimpImage const* img, cairo_surface_t** ds) {
186 unsigned int x,y;
187 unsigned char* d;
188 cairo_surface_t* s;
189 int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, img->width);
190
191 d = (unsigned char *) malloc (stride * img->height);
192 s = cairo_image_surface_create_for_data(d,
193 CAIRO_FORMAT_ARGB32, img->width, img->height, stride);
194
195 cairo_surface_flush (s);
196 for (y = 0; y < img->height; ++y) {
197 const int y0 = y * stride;
198 const int ys = y * img->width * img->bytes_per_pixel;
199 for (x = 0; x < img->width; ++x) {
200 const int xs = x * img->bytes_per_pixel;
201 const int xd = x * 4;
202
203 if (img->bytes_per_pixel == 3) {
204 d[y0 + xd + 3] = 0xff;
205 } else {
206 d[y0 + xd + 3] = img->pixel_data[ys + xs + 3]; // A
207 }
208 d[y0 + xd + 2] = img->pixel_data[ys + xs]; // R
209 d[y0 + xd + 1] = img->pixel_data[ys + xs + 1]; // G
210 d[y0 + xd + 0] = img->pixel_data[ys + xs + 2]; // B
211 }
212 }
213 cairo_surface_mark_dirty (s);
214
215 *ds = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, img->width, img->height);
216 cairo_t *cr = cairo_create (*ds);
217 cairo_set_source_surface (cr, s, 0, 0);
218 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
219 cairo_paint (cr);
220 cairo_destroy (cr);
221 free (d);
222 }
223
224 ///////////////////////////////////////////////////////////////////////////////
225
226 static const float c_dlf[4] = {0.8, 0.8, 0.8, 1.0}; // dial faceplate fg
227
228 #include "gui/bpmwheel.h"
229 #include "gui/divisions.h"
230
231 /*** knob faceplates ***/
prepare_faceplates(SeqUI * ui)232 static void prepare_faceplates (SeqUI* ui)
233 {
234 float w = ui->spn_bpm->w_width * 2.f;
235 float h = ui->spn_bpm->w_height * 2.f;
236 ui->bpm_bg = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
237 cairo_t* cr = cairo_create (ui->bpm_bg);
238 draw_wheel (cr, w, h);
239 cairo_destroy (cr);
240
241 h = ui->spn_swing->w_height;
242 ui->swg_bg = cairo_pattern_create_linear (0.0, 0.0, 3.0, h);
243 cairo_pattern_add_color_stop_rgba (ui->swg_bg, 0.00, 1.0, 0.6, 0.2, 1.0);
244 cairo_pattern_add_color_stop_rgba (ui->swg_bg, 0.33, 1.0, 0.6, 0.2, 1.0);
245 cairo_pattern_add_color_stop_rgba (ui->swg_bg, 1.00, 0.5, 0.3, 0.1, 1.0);
246
247 img2surf((struct MyGimpImage const *)¬e_image, &ui->div_bg);
248 }
249
250 #if 0
251 static void display_annotation (SeqUI* ui, RobTkCnob* d, cairo_t* cr, const char* txt) {
252 int tw, th;
253 cairo_save (cr);
254 PangoLayout* pl = pango_cairo_create_layout (cr);
255 pango_layout_set_font_description (pl, ui->font[0]);
256 pango_layout_set_text (pl, txt, -1);
257 pango_layout_get_pixel_size (pl, &tw, &th);
258 cairo_translate (cr, d->w_width / 2, d->w_height - 2);
259 cairo_translate (cr, -tw / 2.0, -th);
260 cairo_set_source_rgba (cr, .0, .0, .0, .7);
261 rounded_rectangle (cr, -1, -1, tw+3, th+1, 3);
262 cairo_fill (cr);
263 CairoSetSouerceRGBA (c_wht);
264 pango_cairo_show_layout (cr, pl);
265 g_object_unref (pl);
266 cairo_restore (cr);
267 cairo_new_path (cr);
268 }
269
270 static void cnob_annotation_bpm (RobTkCnob* d, cairo_t* cr, void* data) {
271 SeqUI* ui = (SeqUI*)data;
272 char txt[16];
273 snprintf (txt, 16, "%5.1f BPM", d->cur);
274 display_annotation (ui, d, cr, txt);
275 }
276 #endif
277 ///////////////////////////////////////////////////////////////////////////////
278
draw_swing_text(SeqUI * ui,cairo_t * cr,const char * txt)279 static void draw_swing_text (SeqUI* ui, cairo_t* cr, const char* txt) {
280 int tw, th;
281 float c_fg[4]; get_color_from_theme(0, c_fg);
282 PangoLayout* pl = pango_cairo_create_layout (cr);
283 pango_layout_set_font_description (pl, ui->font[0]);
284 cairo_save (cr);
285 CairoSetSouerceRGBA(c_fg);
286 pango_layout_set_text (pl, txt, -1);
287 pango_layout_get_pixel_size (pl, &tw, &th);
288 cairo_translate (cr, -tw / 2.0, -th / 2.0);
289 pango_cairo_layout_path (cr, pl);
290 cairo_fill (cr);
291 cairo_restore (cr);
292 g_object_unref (pl);
293 }
294
cnob_expose_swing(RobTkCnob * d,cairo_t * cr,void * data)295 static void cnob_expose_swing (RobTkCnob* d, cairo_t* cr, void* data) {
296 SeqUI* ui = (SeqUI*)data;
297 float c_bg[4]; get_color_from_theme(1, c_bg);
298
299 float w = d->w_width;
300 float h = d->w_height;
301
302 const float v_min = d->min;
303 const float v_max = d->max;
304 const float v_cur = d->cur;
305
306 rounded_rectangle (cr, 1.5, 1.5, w - 3, h - 3, 5);
307 // TODO gradient
308 cairo_set_source_rgba (cr, SHADE_RGB(c_bg, 0.75), 1.0);
309 cairo_fill_preserve (cr);
310 cairo_set_line_width (cr, 1.0);
311 cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
312 cairo_stroke_preserve (cr);
313 cairo_clip (cr);
314
315 float vh = h * (v_cur - v_min) / (v_max - v_min);
316 cairo_rectangle (cr, 0, h - vh, w, vh);
317 cairo_set_source (cr, ui->swg_bg);
318 cairo_fill (cr);
319
320 for (int r = 2 * C_RAD; r > 0; --r) {
321 const float alpha = 0.1 - 0.1 * r / (2 * C_RAD + 1.f);
322 cairo_set_line_width (cr, r);
323 cairo_set_source_rgba (cr, .0, .0, .0, alpha);
324 cairo_move_to(cr, 0, 1.5);
325 cairo_rel_line_to(cr, d->w_width, 0);
326 cairo_stroke(cr);
327 cairo_move_to(cr, 1.5, 0);
328 cairo_rel_line_to(cr, 0, d->w_height);
329 cairo_stroke(cr);
330 }
331
332 cairo_save (cr);
333 cairo_translate (cr, w * .5, h * .5);
334
335 if (rint(30 * v_cur) == 0.0) {
336 draw_swing_text (ui, cr, "1:1");
337 }
338 else if (rint(30 * v_cur) == 6.0) {
339 draw_swing_text (ui, cr, "3:2");
340 }
341 else if (rint(30 * v_cur) == 10.0) {
342 draw_swing_text (ui, cr, "2:1");
343 }
344 else if (rint(30 * v_cur) == 15.0) {
345 draw_swing_text (ui, cr, "3:1");
346 }
347
348 cairo_restore (cr);
349
350 rounded_rectangle (cr, 1.5, 1.5, w - 3, h - 3, 5);
351 cairo_set_line_width (cr, 1.0);
352 cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
353 cairo_stroke (cr);
354 }
355
cnob_expose_div(RobTkCnob * d,cairo_t * cr,void * data)356 static void cnob_expose_div (RobTkCnob* d, cairo_t* cr, void* data) {
357 SeqUI* ui = (SeqUI*)data;
358 float c[4]; get_color_from_theme(1, c);
359
360 float w = d->w_width;
361 float h = d->w_height;
362
363 rounded_rectangle (cr, 1.5, 1.5, w - 3, h - 3, C_RAD);
364 // TODO gradient
365 cairo_set_source_rgba (cr, SHADE_RGB(c, 0.75), 1.0);
366 cairo_fill_preserve (cr);
367 cairo_set_line_width (cr, 1.0);
368 cairo_set_source_rgba (cr, .0, .0, .0, 1.0);
369 cairo_stroke_preserve (cr);
370 cairo_clip (cr);
371
372 for (int r = 2 * C_RAD; r > 0; --r) {
373 const float alpha = 0.1 - 0.1 * r / (2 * C_RAD + 1.f);
374 cairo_set_line_width (cr, r);
375 cairo_set_source_rgba (cr, .0, .0, .0, alpha);
376 cairo_move_to(cr, 0, 1.5);
377 cairo_rel_line_to(cr, d->w_width, 0);
378 cairo_stroke(cr);
379 cairo_move_to(cr, 1.5, 0);
380 cairo_rel_line_to(cr, 0, d->w_height);
381 cairo_stroke(cr);
382 }
383
384 cairo_save (cr);
385 cairo_scale (cr, 0.5, 0.5);
386 cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
387 cairo_set_source_surface(cr, ui->div_bg, -60 * rintf(d->cur), 0);
388 cairo_paint (cr);
389 cairo_restore (cr);
390 }
391
cnob_expose_bpm(RobTkCnob * d,cairo_t * cr,void * data)392 static void cnob_expose_bpm (RobTkCnob* d, cairo_t* cr, void* data) {
393 SeqUI* ui = (SeqUI*)data;
394
395 PangoLayout* pl = pango_cairo_create_layout (cr);
396 pango_layout_set_font_description (pl, ui->font[1]);
397
398 float w = d->w_width;
399 float h = d->w_height;
400
401 cairo_save (cr);
402 cairo_scale (cr, 0.5, 0.5);
403 cairo_set_source_surface(cr, ui->bpm_bg, 0, 0);
404 cairo_paint (cr);
405
406 cairo_restore (cr);
407
408 cairo_translate (cr, 0, 1);
409 wheel_path (cr, w, h - 2);
410 cairo_clip (cr);
411 cairo_save (cr);
412 cairo_translate (cr, w * .5f, h * .5f);
413
414 const float v_min = d->min;
415 const float v_max = d->max;
416 const float v_cur = d->cur;
417
418 for (int s = 0; s < 5; ++s) {
419 char txt[7];
420 float v = v_cur + s - 2;
421 if (floor(v) < v_min || floor(v) > v_max) {
422 continue;
423 }
424 sprintf (txt, "%.0f", floor(v));
425 float off = s / (4.f);
426 off -= fmodf (v_cur, 1.0) / 4.f;
427 if (off < .05 || off > .95) {
428 continue;
429 }
430 draw_bpm_value (cr, pl, txt, d->w_height, off);
431 }
432 cairo_restore (cr);
433 g_object_unref (pl);
434 // keep clip (for overlay)
435 }
436
set_note_txt(SeqUI * ui,int n)437 static void set_note_txt (SeqUI* ui, int n) {
438 const int mn = rintf (robtk_select_get_value (ui->sel_note[n]));
439 const int dm = rintf (robtk_select_get_value (ui->sel_drum));
440
441 if (dm == 1 && mn >= 35 && mn <= 81) {
442 /* general midi */
443 robtk_lbl_set_text (ui->lbl_note[n], mdrums[mn-35]);
444 } else if (dm == 2 && mn >= 36 && mn <= 61) {
445 /* avldrums */
446 robtk_lbl_set_text (ui->lbl_note[n], avldrums[mn-36]);
447 } else if (dm == 3 && mn >= 60 && mn <= 69) {
448 /* nord drum 2 & 3 */
449 robtk_lbl_set_text (ui->lbl_note[n], norddrum[mn-60]);
450 } else {
451 char txt[16];
452 sprintf (txt, "%-2s%d ", notename[mn%12], -1 + mn / 12);
453 robtk_lbl_set_text (ui->lbl_note[n], txt);
454 }
455 }
456 ///////////////////////////////////////////////////////////////////////////////
457
458 /*** knob & button callbacks ****/
459
cb_mchn(RobWidget * w,void * handle)460 static bool cb_mchn (RobWidget* w, void* handle) {
461 SeqUI* ui = (SeqUI*)handle;
462 if (ui->disable_signals) return TRUE;
463 const float val = robtk_select_get_value (ui->sel_mchn);
464 ui->write (ui->controller, PORT_CHN, sizeof (float), 0, (const void*) &val);
465 return TRUE;
466 }
467
cb_div(RobWidget * w,void * handle)468 static bool cb_div (RobWidget* w, void* handle) {
469 SeqUI* ui = (SeqUI*)handle;
470 if (ui->disable_signals) return TRUE;
471 const float val = robtk_cnob_get_value (ui->spn_div);
472 ui->write (ui->controller, PORT_DIVIDER, sizeof (float), 0, (const void*) &val);
473 return TRUE;
474 }
475
cb_bpm(RobWidget * w,void * handle)476 static bool cb_bpm (RobWidget* w, void* handle) {
477 char txt[31];
478 SeqUI* ui = (SeqUI*)handle;
479 if (ui->disable_signals) return TRUE;
480 const float val = robtk_cnob_get_value (ui->spn_bpm);
481 ui->user_bpm = val;
482 ui->write (ui->controller, PORT_BPM, sizeof (float), 0, (const void*) &val);
483 snprintf(txt, 31, "%.1f BPM", val);
484 robtk_lbl_set_text (ui->lbl_bpm, txt);
485 return TRUE;
486 }
487
cb_swing(RobWidget * w,void * handle)488 static bool cb_swing (RobWidget* w, void* handle) {
489 SeqUI* ui = (SeqUI*)handle;
490 if (ui->disable_signals) return TRUE;
491 const float val = robtk_cnob_get_value (ui->spn_swing);
492 ui->write (ui->controller, PORT_SWING, sizeof (float), 0, (const void*) &val);
493 return TRUE;
494 }
495
cb_note(RobWidget * w,void * handle)496 static bool cb_note (RobWidget* w, void* handle) {
497 SeqUI* ui = (SeqUI*)handle;
498 int n;
499 memcpy (&n, w->name, sizeof(int)); // hack alert
500 if (ui->disable_signals) return TRUE;
501 const float val = robtk_select_get_value (ui->sel_note[n]);
502 ui->write (ui->controller, PORT_NOTES + n, sizeof (float), 0, (const void*) &val);
503 set_note_txt (ui, n);
504 return TRUE;
505 }
506
cb_sync(RobWidget * w,void * handle)507 static bool cb_sync (RobWidget* w, void* handle) {
508 SeqUI* ui = (SeqUI*)handle;
509 if (ui->disable_signals) return TRUE;
510 const float val = robtk_cbtn_get_active (ui->btn_sync) ? 1 : 0;
511 ui->write (ui->controller, PORT_SYNC, sizeof (float), 0, (const void*) &val);
512 return TRUE;
513 }
514
cb_drum(RobWidget * w,void * handle)515 static bool cb_drum (RobWidget* w, void* handle) {
516 SeqUI* ui = (SeqUI*)handle;
517 for (int n = 0; n < N_NOTES; ++n) {
518 set_note_txt (ui, n);
519 }
520 if (ui->disable_signals) return TRUE;
521 const float val = robtk_select_get_value (ui->sel_drum);
522 ui->write (ui->controller, PORT_DRUM, sizeof (float), 0, (const void*) &val);
523 return TRUE;
524 }
525
cb_grid(RobWidget * w,void * handle)526 static bool cb_grid (RobWidget* w, void* handle) {
527 SeqUI* ui = (SeqUI*)handle;
528 int g;
529 memcpy (&g, w->name, sizeof(int)); // hack alert
530 if (ui->disable_signals) return TRUE;
531 const float val = robtk_vbtn_get_value (ui->btn_grid[g]);
532 ui->write (ui->controller, PORT_NOTES + N_NOTES + g, sizeof (float), 0, (const void*) &val);
533 return TRUE;
534 }
535
cb_btn_panic_press(RobWidget * w,void * handle)536 static bool cb_btn_panic_press (RobWidget *w, void* handle) {
537 SeqUI* ui = (SeqUI*)handle;
538 float val = 1;
539 ui->write (ui->controller, PORT_PANIC, sizeof (float), 0, (const void*) &val);
540 return TRUE;
541 }
542
cb_btn_panic_release(RobWidget * w,void * handle)543 static bool cb_btn_panic_release (RobWidget *w, void* handle) {
544 SeqUI* ui = (SeqUI*)handle;
545 float val = 0;
546 ui->write (ui->controller, PORT_PANIC, sizeof (float), 0, (const void*) &val);
547 return TRUE;
548 }
549
cb_btn_reset(RobWidget * w,void * handle)550 static bool cb_btn_reset (RobWidget *w, void* handle) {
551 SeqUI* ui = (SeqUI*)handle;
552 if (ui->disable_signals) return TRUE;
553 int g;
554 memcpy (&g, w->name, sizeof(int)); // hack alert
555 if (g < N_NOTES) {
556 // row 'g'
557 for (uint32_t p = 0; p < N_STEPS; ++p) {
558 uint32_t po = N_STEPS * g + p;
559 robtk_vbtn_set_value (ui->btn_grid[po], 0);
560 }
561 } else if (g < N_NOTES + N_STEPS) {
562 // column 'g'
563 for (uint32_t p = 0; p < N_NOTES; ++p) {
564 uint32_t po = (g - N_NOTES) + N_STEPS * p;
565 robtk_vbtn_set_value (ui->btn_grid[po], 0);
566 }
567 } else {
568 for (uint32_t p = 0; p < N_NOTES * N_STEPS; ++p) {
569 robtk_vbtn_set_value (ui->btn_grid[p], 0);
570 }
571 }
572 return TRUE;
573 }
574
575 ///////////////////////////////////////////////////////////////////////////////
576
577
toplevel(SeqUI * ui,void * const top)578 static RobWidget* toplevel (SeqUI* ui, void* const top) {
579 /* main widget: layout */
580 ui->rw = rob_hbox_new (FALSE, 2);
581 robwidget_make_toplevel (ui->rw, top);
582 robwidget_toplevel_enable_scaling (ui->rw);
583
584 ui->font[0] = pango_font_description_from_string ("Sans 12px");
585 ui->font[1] = pango_font_description_from_string ("Sans 17px");
586
587 ui->ctbl = rob_table_new (/*rows*/N_NOTES + 2, /*cols*/ N_STEPS + 2, FALSE);
588
589 #define GSL_W(PTR) robtk_select_widget (PTR)
590
591 for (uint32_t n = 0; n < N_NOTES; ++n) {
592 ui->lbl_note[n] = robtk_lbl_new ("##|G#-8|#");
593 robtk_lbl_set_min_geometry (ui->lbl_note[n], ui->lbl_note[n]->w_width, ui->lbl_note[n]->w_height);
594 robtk_lbl_set_alignment (ui->lbl_note[n], .5, 0);
595 ui->sel_note[n] = robtk_select_new ();
596 for (uint32_t mn = 0; mn < 128; ++mn) {
597 char txt[8];
598 sprintf (txt, "%d", mn);
599 robtk_select_add_item (ui->sel_note[n], mn, txt);
600 // hack alert -- should add a .data field to Robwidget
601 memcpy (ui->sel_note[n]->rw->name, &n, sizeof(int));
602 }
603 robtk_select_set_callback (ui->sel_note[n], cb_note, ui);
604
605 rob_table_attach (ui->ctbl, GSL_W (ui->sel_note[n]), 0, 1, 2*n, 2*n + 1, 2, 0, RTK_SHRINK, RTK_SHRINK);
606 rob_table_attach (ui->ctbl, robtk_lbl_widget (ui->lbl_note[n]), 0, 1, 2*n + 1, 2*n + 2, 2, 0, RTK_SHRINK, RTK_EXANDF);
607
608 for (uint32_t s = 0; s < N_STEPS; ++s) {
609 uint32_t g = n * N_STEPS + s;
610 ui->btn_grid[g] = robtk_vbtn_new ();
611 rob_table_attach (ui->ctbl, robtk_vbtn_widget (ui->btn_grid[g]), 1 + s, 2 + s, 2*n, 2*n + 2, 0, 0, RTK_SHRINK, RTK_SHRINK);
612 // hack alert -- should add a .data field to Robwidget
613 memcpy (ui->btn_grid[g]->rw->name, &g, sizeof(int));
614 robtk_vbtn_set_callback (ui->btn_grid[g], cb_grid, ui);
615 }
616
617 ui->btn_clear[n] = robtk_pbtn_new ("C");
618 robtk_pbtn_set_callback_up (ui->btn_clear[n], cb_btn_reset, ui);
619 robtk_pbtn_set_alignment (ui->btn_clear[n], .5, .5);
620 rob_table_attach (ui->ctbl, robtk_pbtn_widget (ui->btn_clear[n]), N_STEPS + 1, N_STEPS + 2, 2*n, 2*n + 2, 2, 2, RTK_EXANDF, RTK_SHRINK);
621 // hack alert -- should add a .data field to Robwidget
622 memcpy (ui->btn_clear[n]->rw->name, &n, sizeof(int));
623 }
624
625 for (uint32_t s = 0; s <= N_STEPS; ++s) {
626 int n = N_NOTES + s;
627 ui->btn_clear[n] = robtk_pbtn_new ("C");
628 robtk_pbtn_set_callback_up (ui->btn_clear[n], cb_btn_reset, ui);
629 robtk_pbtn_set_alignment (ui->btn_clear[n], .5, .5);
630 rob_table_attach (ui->ctbl, robtk_pbtn_widget (ui->btn_clear[n]), s + 1, s + 2, 2 * N_NOTES, 2 * N_NOTES + 1, 2, 2, RTK_SHRINK, RTK_SHRINK);
631 // hack alert -- should add a .data field to Robwidget
632 memcpy (ui->btn_clear[n]->rw->name, &n, sizeof(int));
633 }
634
635 ui->sep_h0 = robtk_sep_new(TRUE);
636 rob_table_attach (ui->ctbl, robtk_sep_widget(ui->sep_h0), 0, N_STEPS + 2, 2 * N_NOTES + 1, 2 * N_NOTES + 2, 4, 8, RTK_EXANDF, RTK_SHRINK);
637
638 int cr = 2 + 2 * N_NOTES;
639
640 /* sync */
641 ui->btn_sync = robtk_cbtn_new ("Host Sync", GBT_LED_LEFT, false);
642 robtk_cbtn_set_callback (ui->btn_sync, cb_sync, ui);
643
644 /* sync */
645 ui->sel_drum = robtk_select_new ();
646 robtk_select_add_item (ui->sel_drum, 0, "Chromatic");
647 robtk_select_add_item (ui->sel_drum, 1, "GM/GS Drums");
648 robtk_select_add_item (ui->sel_drum, 2, "AVL Drums");
649 robtk_select_add_item (ui->sel_drum, 3, "Nord Drum");
650 robtk_select_set_default_item (ui->sel_drum, 0);
651 robtk_select_set_callback (ui->sel_drum, cb_drum, ui);
652
653 /* beat divicer */
654 ui->spn_div = robtk_cnob_new (0, 9, 1, 30, 42);
655 robtk_cnob_set_callback (ui->spn_div, cb_div, ui);
656 robtk_cnob_set_value (ui->spn_div, 3.f);
657 robtk_cnob_set_default (ui->spn_div, 3.f);
658 robtk_cnob_expose_callback (ui->spn_div, cnob_expose_div, ui);
659 robtk_cnob_set_scroll_mult (ui->spn_div, 1.0);
660
661 /* bpm */
662 ui->spn_bpm = robtk_cnob_new (40, 208, 0.1, 64, 44);
663 robtk_cnob_set_callback (ui->spn_bpm, cb_bpm, ui);
664 robtk_cnob_set_value (ui->spn_bpm, 120.f);
665 robtk_cnob_set_default (ui->spn_bpm, 120.f);
666 robtk_cnob_expose_callback (ui->spn_bpm, cnob_expose_bpm, ui);
667 robtk_cnob_set_scroll_mult (ui->spn_bpm, 10.0);
668 ui->user_bpm = 120;
669
670 /* schwing */
671 ui->spn_swing = robtk_cnob_new (0, 0.5, 1.f / 30.f, 30, 42);
672 robtk_cnob_set_callback (ui->spn_swing, cb_swing, ui);
673 robtk_cnob_set_value (ui->spn_swing, 0.f);
674 robtk_cnob_set_default (ui->spn_swing, 0.f);
675 robtk_cnob_expose_callback (ui->spn_swing, cnob_expose_swing, ui);
676 robtk_cnob_set_scroll_mult (ui->spn_swing, 1.0);
677 //float swing_detents[4] = {0, 0.2, 1.0 / 3.0, 0.5};
678 //robtk_cnob_set_detents (ui->spn_swing, 4, swing_detents);
679
680 /* midi channel */
681 ui->sel_mchn = robtk_select_new ();
682 for (int mc = 0; mc < 16; ++mc) {
683 char buf[8];
684 snprintf (buf, 8, "%d", mc + 1);
685 robtk_select_add_item (ui->sel_mchn, mc, buf);
686 }
687 robtk_select_set_default_item (ui->sel_mchn, 0);
688 robtk_select_set_callback (ui->sel_mchn, cb_mchn, ui);
689
690 /* panic */
691 ui->btn_panic = robtk_pbtn_new ("Panic");
692 robtk_pbtn_set_callback_down (ui->btn_panic, cb_btn_panic_press, ui);
693 robtk_pbtn_set_callback_up (ui->btn_panic, cb_btn_panic_release, ui);
694 robtk_pbtn_set_alignment(ui->btn_panic, 0.5, 0.5);
695
696 /* labels */
697 ui->lbl_chn = robtk_lbl_new ("Midi Chn.");
698 ui->lbl_div = robtk_lbl_new ("Step");
699 ui->lbl_bpm = robtk_lbl_new ("888.8 BPM");
700 ui->lbl_swg = robtk_lbl_new ("Swing");
701
702 /* Layout */
703
704 rob_table_attach (ui->ctbl, robtk_cbtn_widget (ui->btn_sync), 0, 2, cr + 0, cr + 1, 2, 0, RTK_EXANDF, RTK_SHRINK);
705 rob_table_attach (ui->ctbl, GSL_W (ui->sel_drum), 0, 2, cr + 1, cr + 2, 2, 0, RTK_EXANDF, RTK_SHRINK);
706
707 rob_table_attach (ui->ctbl, robtk_cnob_widget (ui->spn_div), 3, 4, cr + 0, cr + 2, 0, 0, RTK_EXANDF, RTK_SHRINK);
708 rob_table_attach (ui->ctbl, robtk_cnob_widget (ui->spn_bpm), 4, 6, cr + 0, cr + 2, 0, 0, RTK_SHRINK, RTK_SHRINK);
709 rob_table_attach (ui->ctbl, robtk_cnob_widget (ui->spn_swing), 6, 7, cr + 0, cr + 2, 0, 0, RTK_EXANDF, RTK_SHRINK);
710
711 rob_table_attach (ui->ctbl, robtk_lbl_widget (ui->lbl_div), 3, 4, cr + 2, cr + 3, 0, 0, RTK_EXANDF, RTK_SHRINK);
712 rob_table_attach (ui->ctbl, robtk_lbl_widget (ui->lbl_bpm), 4, 6, cr + 2, cr + 3, 0, 0, RTK_EXANDF, RTK_SHRINK);
713 rob_table_attach (ui->ctbl, robtk_lbl_widget (ui->lbl_swg), 6, 7, cr + 2, cr + 3, 0, 0, RTK_EXANDF, RTK_SHRINK);
714
715 rob_table_attach (ui->ctbl, robtk_pbtn_widget (ui->btn_panic), 8, 10, cr + 0, cr + 1, 2, 0, RTK_EXANDF, RTK_SHRINK);
716 rob_table_attach (ui->ctbl, GSL_W (ui->sel_mchn), 8, 10, cr + 1, cr + 2, 2, 0, RTK_EXANDF, RTK_SHRINK);
717 rob_table_attach (ui->ctbl, robtk_lbl_widget (ui->lbl_chn), 8, 10, cr + 2, cr + 3, 0, 0, RTK_EXANDF, RTK_SHRINK);
718
719 /* top-level packing */
720 rob_hbox_child_pack (ui->rw, ui->ctbl, FALSE, TRUE);
721
722 prepare_faceplates (ui);
723
724 return ui->rw;
725 }
726
gui_cleanup(SeqUI * ui)727 static void gui_cleanup (SeqUI* ui) {
728 pango_font_description_free (ui->font[0]);
729 pango_font_description_free (ui->font[1]);
730
731 for (uint32_t n = 0; n < N_NOTES; ++n) {
732 robtk_select_destroy (ui->sel_note[n]);
733 robtk_lbl_destroy (ui->lbl_note[n]);
734 for (uint32_t s = 0; s < N_STEPS; ++s) {
735 uint32_t g = n * N_STEPS + s;
736 robtk_vbtn_destroy (ui->btn_grid[g]);
737 }
738 }
739
740 for (uint32_t p = 0; p < N_NOTES + N_STEPS + 1; ++p) {
741 robtk_pbtn_destroy (ui->btn_clear[p]);
742 }
743
744 robtk_cbtn_destroy (ui->btn_sync);
745 robtk_select_destroy (ui->sel_drum);
746 robtk_select_destroy (ui->sel_mchn);
747 robtk_cnob_destroy (ui->spn_div);
748 robtk_cnob_destroy (ui->spn_bpm);
749 robtk_cnob_destroy (ui->spn_swing);
750 robtk_pbtn_destroy (ui->btn_panic);
751 robtk_sep_destroy (ui->sep_h0);
752 robtk_lbl_destroy (ui->lbl_chn);
753 robtk_lbl_destroy (ui->lbl_div);
754 robtk_lbl_destroy (ui->lbl_bpm);
755 robtk_lbl_destroy (ui->lbl_swg);
756
757 cairo_surface_destroy (ui->bpm_bg);
758 cairo_pattern_destroy (ui->swg_bg);
759 cairo_surface_destroy (ui->div_bg);
760
761 rob_table_destroy (ui->ctbl);
762 rob_box_destroy (ui->rw);
763 }
764
765 /******************************************************************************
766 * RobTk + LV2
767 */
768
769 #define LVGL_RESIZEABLE
770
ui_enable(LV2UI_Handle handle)771 static void ui_enable (LV2UI_Handle handle) { }
ui_disable(LV2UI_Handle handle)772 static void ui_disable (LV2UI_Handle handle) { }
773
774 static enum LVGLResize
plugin_scale_mode(LV2UI_Handle handle)775 plugin_scale_mode (LV2UI_Handle handle)
776 {
777 return LVGL_LAYOUT_TO_FIT;
778 }
779
780 static LV2UI_Handle
instantiate(void * const ui_toplevel,const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,RobWidget ** widget,const LV2_Feature * const * features)781 instantiate (
782 void* const ui_toplevel,
783 const LV2UI_Descriptor* descriptor,
784 const char* plugin_uri,
785 const char* bundle_path,
786 LV2UI_Write_Function write_function,
787 LV2UI_Controller controller,
788 RobWidget** widget,
789 const LV2_Feature* const* features)
790 {
791 SeqUI* ui = (SeqUI*) calloc (1, sizeof (SeqUI));
792 if (!ui) {
793 return NULL;
794 }
795
796 if (strcmp (plugin_uri, RTK_URI)) {
797 free (ui);
798 return NULL;
799 }
800
801 ui->nfo = robtk_info (ui_toplevel);
802 ui->write = write_function;
803 ui->controller = controller;
804
805 ui->disable_signals = true;
806 *widget = toplevel (ui, ui_toplevel);
807 ui->disable_signals = false;
808 return ui;
809 }
810
811 static void
cleanup(LV2UI_Handle handle)812 cleanup (LV2UI_Handle handle)
813 {
814 SeqUI* ui = (SeqUI*)handle;
815 gui_cleanup (ui);
816 free (ui);
817 }
818
819 /* receive information from DSP */
820 static void
port_event(LV2UI_Handle handle,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)821 port_event (LV2UI_Handle handle,
822 uint32_t port_index,
823 uint32_t buffer_size,
824 uint32_t format,
825 const void* buffer)
826 {
827 SeqUI* ui = (SeqUI*)handle;
828 if (format != 0 || port_index <= PORT_MIDI_OUT) return;
829
830 const float v = *(float*)buffer;
831 ui->disable_signals = true;
832
833 switch (port_index) {
834 case PORT_SYNC:
835 robtk_cbtn_set_active (ui->btn_sync, v > 0);
836 break;
837 case PORT_BPM:
838 ui->user_bpm = v;
839 if (ui->spn_bpm->sensitive) {
840 char txt[31];
841 snprintf(txt, 31, "%.1f BPM", v);
842 robtk_lbl_set_text (ui->lbl_bpm, txt);
843 robtk_cnob_set_value (ui->spn_bpm, v); // TODO check sensitivity -- PORT_HOSTBPM
844 }
845 break;
846 case PORT_HOSTBPM:
847 if (v <= 0) {
848 robtk_cnob_set_sensitive (ui->spn_bpm, true);
849 // restore user-set BPM (display)
850 port_event (handle, PORT_BPM, buffer_size, 0, &ui->user_bpm);
851 } else {
852 char txt[31];
853 robtk_cnob_set_sensitive (ui->spn_bpm, false);
854 robtk_cnob_set_value (ui->spn_bpm, v);
855 snprintf(txt, 31, "%.1f BPM", v);
856 robtk_lbl_set_text (ui->lbl_bpm, txt);
857 }
858 if (v != 0) {
859 // [potential] host sync
860 robtk_cbtn_set_color_on (ui->btn_sync, .3, .8, .1);
861 robtk_cbtn_set_color_off (ui->btn_sync, .1, .3, .1);
862 }
863 break;
864 case PORT_DIVIDER:
865 robtk_cnob_set_value (ui->spn_div, v);
866 break;
867 case PORT_CHN:
868 robtk_select_set_value (ui->sel_mchn, v);
869 break;
870 case PORT_DRUM:
871 robtk_select_set_value (ui->sel_drum, v);
872 break;
873 case PORT_SWING:
874 robtk_cnob_set_value (ui->spn_swing, v);
875 break;
876 case PORT_PANIC:
877 break;
878 case PORT_STEP:
879 {
880 unsigned int step = rintf (v - 1.f);
881 for (uint32_t p = 0; p < N_NOTES * N_STEPS; ++p) {
882 robtk_vbtn_set_highlight (ui->btn_grid[p], (p % N_STEPS) == step);
883 }
884 }
885 break;
886 default:
887 if (port_index < PORT_NOTES + N_NOTES) {
888 int n = port_index - PORT_NOTES;
889 robtk_select_set_item (ui->sel_note[n], rintf(v));
890 set_note_txt (ui, n);
891 }
892 else if (port_index < PORT_NOTES + N_NOTES + N_NOTES * N_STEPS) {
893 int g = port_index - PORT_NOTES - N_NOTES;
894 robtk_vbtn_set_value (ui->btn_grid[g], v);
895 }
896 break;
897 }
898
899 ui->disable_signals = false;
900 }
901
902 static const void*
extension_data(const char * uri)903 extension_data (const char* uri)
904 {
905 return NULL;
906 }
907