gjackspa: pop up error window
[ng-jackspa.git] / njackspa.c
blobdc24f3fe61631c9f18b8e62f6541574da4cf34fd
1 /* njackspa.c - simple ncurses LADSPA host for the Jack Audio Connection Kit
2 * based on njconnect.c by Xj <xj@wp.pl>
3 * Copyright © 2012,2013 Xj <xj@wp.pl>
4 * Copyright © 2013 Géraud Meyer <graud@gmx.com>
6 * This file is part if ng-jackspa.
8 * ng-jackspa is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License version 2 as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along
18 * with ng-jackspa. If not, see <http://www.gnu.org/licenses/>.
21 #include <ncurses.h>
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <math.h>
26 #include "jackspa.h"
28 #define PROGRAM_NAME "njackspa"
29 #define CON_NAME "Controls"
31 #define HELP "'q'uit, '?', U/D-selection, L/R-change, 'd/D'efault, '>/<', '<n>'0%, 'u/U'pdate, e'x/X'change"
32 #define ERR_NODEFAULT "No default value"
34 #define NUM_WINDOWS 1
36 #define WCON_X 0
37 #define WCON_Y 0
38 #define WCON_W cols
39 #define WCON_H rows - 1
41 #define WHLP_X 0
42 #define WHLP_Y rows - 1
43 #define WHLP_W cols
44 #define WHLP_H 0
46 #define ERR_OUT(format, arg...) ( endwin(), fprintf(stderr, format "\n", ## arg), refresh() )
48 enum ctype {
49 VAL_FLOAT,
50 VAL_INT,
51 VAL_TOGGLE
54 typedef struct _control_t {
55 const char *name;
56 /* value in the plugin */
57 LADSPA_Data *val;
58 /* values selected in the interface */
59 LADSPA_Data sel;
60 LADSPA_Data alt_sel;
61 /* value range */
62 LADSPA_Data min;
63 LADSPA_Data max;
64 LADSPA_Data *def;
65 enum ctype type;
66 struct { LADSPA_Data fine; LADSPA_Data coarse; } inc;
67 } control_t;
69 void c_exchange(control_t *control)
71 LADSPA_Data buf;
72 buf = *control->val;
73 *control->val = control->sel;
74 control->sel = buf;
77 struct window {
78 WINDOW *window_ptr;
79 state_t *state;
80 control_t *(*controls)[];
81 unsigned long index;
82 unsigned long count;
83 bool selected;
84 int height;
85 int width;
86 const char *name;
89 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
90 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
91 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
94 int init_control(control_t *control, state_t *state, int port)
96 LADSPA_PortRangeHint hint = state->descriptor->PortRangeHints[port];
97 LADSPA_PortRangeHintDescriptor descriptor = hint.HintDescriptor;
98 LADSPA_Data lower_bound = hint.LowerBound;
99 LADSPA_Data upper_bound = hint.UpperBound;
101 control->name = state->descriptor->PortNames[port];
102 control->val = &state->control_port_values[port];
104 /* control->min, control->max */
105 if (LADSPA_IS_HINT_SAMPLE_RATE(descriptor)) {
106 int sample_rate = jack_get_sample_rate(state->jack_client);
107 lower_bound *= sample_rate;
108 upper_bound *= sample_rate;
110 if ( LADSPA_IS_HINT_BOUNDED_BELOW(descriptor) &&
111 LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor) )
113 control->min = lower_bound;
114 control->max = upper_bound;
116 else if (LADSPA_IS_HINT_BOUNDED_BELOW(descriptor)) {
117 control->min = lower_bound;
118 control->max = 1.0;
120 else if (LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor)) {
121 control->min = 0.0;
122 control->max = upper_bound;
124 else {
125 control->min = -1.0;
126 control->max = 1.0;
129 /* control->def */
130 if (LADSPA_IS_HINT_HAS_DEFAULT(descriptor)) {
131 control->def = (LADSPA_Data *)malloc(sizeof(LADSPA_Data));
132 if (!control->def)
133 return (ERR_OUT ("memory allocation error"), 1);
134 switch (descriptor & LADSPA_HINT_DEFAULT_MASK) {
135 case LADSPA_HINT_DEFAULT_MINIMUM:
136 *control->def = lower_bound;
137 break;
138 case LADSPA_HINT_DEFAULT_LOW:
139 *control->def = lower_bound * 0.75 + upper_bound * 0.25;
140 break;
141 case LADSPA_HINT_DEFAULT_MIDDLE:
142 *control->def = lower_bound * 0.5 + upper_bound * 0.5;
143 break;
144 case LADSPA_HINT_DEFAULT_HIGH:
145 *control->def = lower_bound * 0.25 + upper_bound * 0.75;
146 break;
147 case LADSPA_HINT_DEFAULT_MAXIMUM:
148 *control->def = upper_bound;
149 break;
150 case LADSPA_HINT_DEFAULT_0:
151 *control->def = 0.0;
152 break;
153 case LADSPA_HINT_DEFAULT_1:
154 *control->def = 1.0;
155 break;
156 case LADSPA_HINT_DEFAULT_100:
157 *control->def = 100.0;
158 break;
159 case LADSPA_HINT_DEFAULT_440:
160 *control->def = 440.0;
161 break;
162 default:
163 ERR_OUT ("default not found");
164 free(control->def);
165 control->def = NULL;
168 else
169 control->def = NULL;
171 /* Check the default */
172 if (control->def) {
173 if (*control->def < control->min) {
174 ERR_OUT ("default smaller than the minimum");
175 *control->def = control->min;
177 if (*control->def > control->max) {
178 ERR_OUT ("default greater than the maximum");
179 *control->def = control->max;
183 /* control->inc & Overrides */
184 if (LADSPA_IS_HINT_TOGGLED(descriptor)) {
185 control->min = 0.0;
186 control->max = 1.0;
187 control->inc.fine = 1.0;
188 control->inc.coarse = 1.0;
189 control->type = VAL_TOGGLE;
190 if (control->def) *control->def = nearbyintf(*control->def);
192 else if (LADSPA_IS_HINT_INTEGER(descriptor)) {
193 control->min = nearbyintf(control->min);
194 control->max = nearbyintf(control->max);
195 control->inc.fine = 1.0;
196 control->inc.coarse = 1.0;
197 /* set_digits(0); */
198 control->type = VAL_INT;
199 if (control->def) *control->def = nearbyintf(*control->def);
201 else {
202 control->inc.fine = (control->max - control->min) / 500;
203 control->inc.coarse = (control->max - control->min) / 50;
204 /* set_digits(2); */
205 control->type = VAL_FLOAT;
208 /* control->sel, control->val */
209 if (control->def) control->sel = *control->def;
210 else control->sel = control->min;
211 control->alt_sel = control->sel;
212 *control->val = control->sel;
214 return 0;
217 int build_controls(struct window *window, state_t *state)
219 int rc = 0;
220 unsigned long p, c;
222 window->state = state;
223 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
224 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
225 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
226 c++;
227 window->count = c;
228 window->index = 0;
229 window->controls = (control_t *(*)[])calloc(sizeof(control_t *), c);
230 if (!window->controls)
231 return (ERR_OUT ("memory allocation error"), 1);
233 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
234 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
235 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
237 (*window->controls)[c] = (control_t *)malloc(sizeof(control_t));
238 if (!(*window->controls)[c])
239 return (ERR_OUT ("memory allocation error"), 1);
240 if (init_control((*window->controls)[c++], state, p))
241 rc = 1;
244 return rc;
247 void draw_border(struct window * window_ptr)
249 int col = ( window_ptr->width - strlen(window_ptr->name) -
250 strlen(window_ptr->state->descriptor->Label) - 5 ) / 2;
251 if (col < 0) col = 0;
253 /* 0, 0 gives default characters for the vertical and horizontal lines */
254 box(window_ptr->window_ptr, 0, 0);
256 if (window_ptr->selected) {
257 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
258 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s:%s]=",
259 window_ptr->state->descriptor->Label, window_ptr->name );
260 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
262 else
263 mvwprintw( window_ptr->window_ptr, 0, col, " [%s:%s] ",
264 window_ptr->state->descriptor->Label, window_ptr->name );
267 void draw_controls(struct window* window_ptr)
269 unsigned short row, col, color, high, rows, cols;
270 unsigned long c;
271 short offset;
272 control_t *control;
273 char fmt[40];
275 getmaxyx(window_ptr->window_ptr, rows, cols);
276 offset = (long)window_ptr->index + 3 - rows; /* first displayed index */
277 if (offset < 0) offset = 0;
278 col = 1;
279 for (c = offset, row = 1; c < window_ptr->count; c++, row++) {
280 control = (*window_ptr->controls)[c];
281 high = (row == window_ptr->index - offset + 1) ?
282 (window_ptr->selected) ? 3 : 2 : 1;
283 color = (row == window_ptr->index - offset + 1) ?
284 (window_ptr->selected) ? 4 : 2 : 1;
286 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
287 snprintf(fmt, sizeof(fmt), "%%-%d.%ds", cols/3 - 1, cols/3 -1);
288 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
289 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
290 wprintw(window_ptr->window_ptr, ": ");
291 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
292 snprintf(fmt, sizeof(fmt), "%%%dF", cols/6 - 2);
293 wprintw(window_ptr->window_ptr, fmt, *control->val);
294 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
295 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
296 wprintw( window_ptr->window_ptr, " %s ",
297 (control->type == VAL_TOGGLE) ?
298 "?" : (control->type == VAL_INT) ? "i" : "f" );
299 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
300 snprintf(fmt, sizeof(fmt), "%%-%dF", cols/6 - 2);
301 wprintw(window_ptr->window_ptr, fmt, control->sel);
302 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
303 snprintf(fmt, sizeof(fmt), " [%%-%dF~%%%dF]", cols/6 - 2, cols/6 - 2);
304 wprintw(window_ptr->window_ptr, fmt, control->min, control->max);
305 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
307 wclrtoeol(window_ptr->window_ptr);
309 draw_border(window_ptr);
310 wrefresh(window_ptr->window_ptr);
313 void
314 create_window(struct window * window_ptr, int height, int width,
315 int starty, int startx, const char * name)
317 window_ptr->window_ptr = newwin(height, width, starty, startx);
318 window_ptr->selected = FALSE;
319 window_ptr->width = width;
320 window_ptr->height = height;
321 window_ptr->name = name;
322 window_ptr->index = 0;
323 window_ptr->controls = NULL;
324 window_ptr->count = 0;
325 scrollok(window_ptr->window_ptr, FALSE);
328 void
329 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
331 /* delwin(window_ptr->window_ptr); */
332 /* window_ptr->window_ptr = newwin(height, width, starty, startx); */
333 wresize(window_ptr->window_ptr, height, width);
334 mvwin(window_ptr->window_ptr, starty, startx);
335 window_ptr->width = width;
336 window_ptr->height = height;
339 void cleanup_controls(struct window* windows)
341 short i;
342 unsigned long c;
343 struct window* w = windows;
345 for (i = 0; i < NUM_WINDOWS; i++, w++) {
346 for (c = 0; c < w->count; c++)
347 free((*w->controls)[c]);
348 free(w->controls);
352 unsigned short
353 select_window(struct window* windows, int cur, int nex)
355 if (nex == cur)
356 return cur;
357 else if (nex >= NUM_WINDOWS)
358 nex = 0;
359 else if (nex < 0)
360 nex = NUM_WINDOWS - 1;
362 windows[cur].selected = FALSE;
363 windows[nex].selected = TRUE;
364 return nex;
367 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
369 unsigned short cols;
370 cols = getmaxx(w);
372 wmove(w, 0, 0);
373 wclrtoeol(w);
375 wattron(w, COLOR_PAIR(c));
376 mvwprintw(w, 0, 1, msg);
377 wattroff(w, COLOR_PAIR(c));
379 wattron(w, COLOR_PAIR(7));
380 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
381 wattroff(w, COLOR_PAIR(7));
383 wrefresh(w);
386 #define cl ((*C)[W->index])
388 int main(int argc, char *argv[])
390 unsigned short rc = 0, rows, cols, window_selection = 0;
391 struct window windows[NUM_WINDOWS];
392 struct window *W;
393 control_t *(*C)[];
394 unsigned long c;
395 LADSPA_Data val;
396 WINDOW *help_window;
397 const char *err_message = NULL;
398 bool want_refresh = FALSE, help = TRUE;
399 bool rt;
400 int k, i;
402 /* Initialize ncurses */
403 initscr();
404 curs_set(0); /* set cursor invisible */
405 noecho();
406 getmaxyx(stdscr, rows, cols);
408 if (has_colors() == FALSE) {
409 rc = -1, ERR_OUT("Your terminal does not support color");
410 goto qxit;
412 start_color();
413 init_pair(1, COLOR_CYAN, COLOR_BLACK);
414 init_pair(2, COLOR_BLACK, COLOR_WHITE);
415 init_pair(3, COLOR_BLACK, COLOR_GREEN);
416 init_pair(4, COLOR_WHITE, COLOR_BLACK);
417 init_pair(5, COLOR_BLACK, COLOR_RED);
418 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
419 init_pair(7, COLOR_BLUE, COLOR_BLACK);
421 /* Create Help Window */
422 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
423 keypad(help_window, TRUE);
424 wtimeout(help_window, 3000);
426 /* Some Jack versions are very aggressive in breaking view */
427 jack_set_info_function(suppress_jack_log);
428 jack_set_error_function(suppress_jack_log);
430 /* Initialize jack */
431 state_t state;
432 endwin();
433 if (!jackspa_init(&state, argc, argv)) {
434 rc = -1;
435 goto qxit;
437 refresh();
438 rt = (bool)jack_is_realtime(state.jack_client);
440 /* Create windows */
441 create_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME);
442 windows[window_selection].selected = TRUE;
444 /* Build controls */
445 if (build_controls(windows+0, &state)) {
446 rc = -1;
447 goto quit_no_clean;
450 loop:
451 for (i = 0; i < NUM_WINDOWS; i++) draw_controls(windows+i);
452 W = &windows[window_selection];
453 C = W->controls;
455 if (err_message) {
456 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
457 err_message = NULL;
459 else
460 draw_help(help_window, 6, help ? HELP : W->state->descriptor->Name,
461 jack_cpu_load(state.jack_client), rt);
463 switch (k = wgetch(help_window)) {
464 case '\t':
465 window_selection = select_window(windows, window_selection, window_selection+1);
466 goto loop;
467 case KEY_BTAB:
468 window_selection = select_window(windows, window_selection, window_selection-1);
469 goto loop;
470 case KEY_EXIT:
471 case 'q':
472 rc = 0; goto quit;
473 case 'r':
474 case KEY_RESIZE:
475 getmaxyx(stdscr, rows, cols);
476 wresize(help_window, WHLP_H, WHLP_W);
477 mvwin(help_window, WHLP_Y, WHLP_X);
478 resize_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X);
479 goto refresh;
480 case 'u':
481 *cl->val = cl->sel;
482 goto loop;
483 case 'U':
484 for (c = 0; c < W->count; c++)
485 *(*C)[c]->val = (*C)[c]->sel;
486 goto loop;
487 case 'x':
488 c_exchange(cl);
489 goto loop;
490 case 'X':
491 for (c = 0; c < W->count; c++)
492 c_exchange((*W->controls)[c]);
493 goto loop;
494 case KEY_BACKSPACE:
495 case 'd':
496 if (cl->def) cl->sel = *cl->def;
497 else err_message = ERR_NODEFAULT;
498 if (k == KEY_BACKSPACE) window_item_next(W);
499 goto loop;
500 case 'D':
501 for (c = 0; c < W->count; c++)
502 if ((*C)[c]->def) (*C)[c]->sel = *(*C)[c]->def;
503 else err_message = ERR_NODEFAULT;
504 goto loop;
505 case 'h':
506 case KEY_LEFT:
507 cl->sel -= cl->inc.coarse;
508 if (cl->sel < cl->min) cl->sel = cl->min;
509 goto loop;
510 case 'l':
511 case KEY_RIGHT:
512 cl->sel += cl->inc.coarse;
513 if (cl->sel > cl->max) cl->sel = cl->max;
514 goto loop;
515 case 'H':
516 cl->sel -= cl->inc.fine;
517 if (cl->sel < cl->min) cl->sel = cl->min;
518 goto loop;
519 case 'L':
520 cl->sel += cl->inc.fine;
521 if (cl->sel > cl->max) cl->sel = cl->max;
522 goto loop;
523 case '<':
524 cl->sel = cl->min;
525 goto loop;
526 case '>':
527 cl->sel = cl->max;
528 goto loop;
529 case '1':
530 case '2':
531 case '3':
532 case '4':
533 case '5':
534 case '6':
535 case '7':
536 case '8':
537 case '9':
538 val = ((LADSPA_Data)(k - 48)) / 10.0;
539 cl->sel = (1.0 - val) * cl->min + val * cl->max;
540 if (cl->type == VAL_INT || cl->type == VAL_TOGGLE)
541 cl->sel = nearbyintf(cl->sel);
542 goto loop;
543 case KEY_DOWN:
544 case 'j':
545 window_item_next(W);
546 goto loop;
547 case KEY_UP:
548 case 'k':
549 window_item_previous(W);
550 goto loop;
551 case KEY_HOME:
552 case 'g':
553 (windows+window_selection)->index = 0;
554 goto loop;
555 case KEY_END:
556 case 'G':
557 (windows+window_selection)->index = (windows+window_selection)->count - 1;
558 goto loop;
559 case '?':
560 help = !help;
562 if (!want_refresh) goto loop;
563 refresh:
564 want_refresh = FALSE;
565 for (i = 0; i < NUM_WINDOWS; i++) {
566 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
567 wclear(windows[i].window_ptr);
569 goto loop;
571 quit:
572 cleanup_controls(windows);
573 quit_no_clean:
574 /* jackspa_exit(state); */
575 jack_deactivate(state.jack_client);
576 jack_client_close(state.jack_client);
577 qxit:
578 endwin();
580 return rc;