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 *)&note_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