njackspa: display the plugin label and the plugin name
[ng-jackspa.git] / njackspa.c
blob8703f8faa23176de7d8e8e8da67f15f086737dd3
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"
33 #define NUM_WINDOWS 1
35 #define WCON_X 0
36 #define WCON_Y 0
37 #define WCON_W cols
38 #define WCON_H rows - 1
40 #define WHLP_X 0
41 #define WHLP_Y rows - 1
42 #define WHLP_W cols
43 #define WHLP_H 0
45 #define ERR_OUT(format, arg...) ( endwin(), fprintf(stderr, format "\n", ## arg), refresh() )
47 enum ctype {
48 VAL_FLOAT,
49 VAL_INT,
50 VAL_TOGGLE
53 typedef struct _control_t {
54 const char *name;
55 /* value in the plugin */
56 LADSPA_Data *val;
57 /* values selected in the interface */
58 LADSPA_Data sel;
59 LADSPA_Data alt_sel;
60 /* value range */
61 LADSPA_Data min;
62 LADSPA_Data max;
63 LADSPA_Data *def;
64 enum ctype type;
65 struct { LADSPA_Data fine; LADSPA_Data coarse; } inc;
66 } control_t;
68 void c_exchange(control_t *control)
70 LADSPA_Data buf;
71 buf = *control->val;
72 *control->val = control->sel;
73 control->sel = buf;
76 struct window {
77 WINDOW *window_ptr;
78 state_t *state;
79 control_t *(*controls)[];
80 unsigned long index;
81 unsigned long count;
82 bool selected;
83 int height;
84 int width;
85 const char *name;
88 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
89 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
90 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
93 int init_control(control_t *control, state_t *state, int port)
95 LADSPA_PortRangeHint hint = state->descriptor->PortRangeHints[port];
96 LADSPA_PortRangeHintDescriptor descriptor = hint.HintDescriptor;
97 LADSPA_Data lower_bound = hint.LowerBound;
98 LADSPA_Data upper_bound = hint.UpperBound;
100 control->name = state->descriptor->PortNames[port];
101 control->val = &state->control_port_values[port];
103 /* control->min, control->max */
104 if (LADSPA_IS_HINT_SAMPLE_RATE(descriptor)) {
105 int sample_rate = jack_get_sample_rate(state->jack_client);
106 lower_bound *= sample_rate;
107 upper_bound *= sample_rate;
109 if ( LADSPA_IS_HINT_BOUNDED_BELOW(descriptor) &&
110 LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor) )
112 control->min = lower_bound;
113 control->max = upper_bound;
115 else if (LADSPA_IS_HINT_BOUNDED_BELOW(descriptor)) {
116 control->min = lower_bound;
117 control->max = 1.0;
119 else if (LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor)) {
120 control->min = 0.0;
121 control->max = upper_bound;
123 else {
124 control->min = -1.0;
125 control->max = 1.0;
128 /* control->inc & Overrides */
129 if (LADSPA_IS_HINT_TOGGLED(descriptor)) {
130 control->min = 0.0;
131 control->max = 1.0;
132 control->inc.fine = 1.0;
133 control->inc.coarse = 1.0;
134 control->type = VAL_TOGGLE;
136 else if (LADSPA_IS_HINT_INTEGER(descriptor)) {
137 control->inc.fine = 1.0;
138 control->inc.coarse = 1.0;
139 /* set_digits(0); */
140 control->type = VAL_INT;
142 else {
143 control->inc.fine = (control->max - control->min) / 500;
144 control->inc.coarse = (control->max - control->min) / 50;
145 /* set_digits(2); */
146 control->type = VAL_FLOAT;
149 /* control->def */
150 if (LADSPA_IS_HINT_HAS_DEFAULT(descriptor)) {
151 control->def = (LADSPA_Data *)malloc(sizeof(LADSPA_Data));
152 if (!control->def)
153 return (ERR_OUT ("memory allocation error"), 1);
154 switch (descriptor & LADSPA_HINT_DEFAULT_MASK) {
155 case LADSPA_HINT_DEFAULT_MINIMUM:
156 *control->def = lower_bound;
157 break;
158 case LADSPA_HINT_DEFAULT_LOW:
159 *control->def = lower_bound * 0.75 + upper_bound * 0.25;
160 break;
161 case LADSPA_HINT_DEFAULT_MIDDLE:
162 *control->def = lower_bound * 0.5 + upper_bound * 0.5;
163 break;
164 case LADSPA_HINT_DEFAULT_HIGH:
165 *control->def = lower_bound * 0.25 + upper_bound * 0.75;
166 break;
167 case LADSPA_HINT_DEFAULT_MAXIMUM:
168 *control->def = upper_bound;
169 break;
170 case LADSPA_HINT_DEFAULT_0:
171 *control->def = 0.0;
172 break;
173 case LADSPA_HINT_DEFAULT_1:
174 *control->def = 1.0;
175 break;
176 case LADSPA_HINT_DEFAULT_100:
177 *control->def = 100.0;
178 break;
179 case LADSPA_HINT_DEFAULT_440:
180 *control->def = 440.0;
181 break;
182 default:
183 free(control->def);
184 control->def = NULL;
187 else
188 control->def = NULL;
190 /* control->sel, control->val */
191 if (control->def) control->sel = *control->def;
192 else control->sel = control->min;
193 control->alt_sel = control->sel;
194 *control->val = control->sel;
196 return 0;
199 int build_controls(struct window *window, state_t *state)
201 int rc = 0;
202 unsigned long p, c;
204 window->state = state;
205 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
206 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
207 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
208 c++;
209 window->count = c;
210 window->index = 0;
211 window->controls = (control_t *(*)[])calloc(sizeof(control_t *), c);
212 if (!window->controls)
213 return (ERR_OUT ("memory allocation error"), 1);
215 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
216 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
217 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
219 (*window->controls)[c] = (control_t *)malloc(sizeof(control_t));
220 if (!(*window->controls)[c])
221 return (ERR_OUT ("memory allocation error"), 1);
222 if (init_control((*window->controls)[c++], state, p))
223 rc = 1;
226 return rc;
229 void draw_border(struct window * window_ptr)
231 int col = ( window_ptr->width - strlen(window_ptr->name) -
232 strlen(window_ptr->state->descriptor->Label) - 5 ) / 2;
233 if (col < 0) col = 0;
235 /* 0, 0 gives default characters for the vertical and horizontal lines */
236 box(window_ptr->window_ptr, 0, 0);
238 if (window_ptr->selected) {
239 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
240 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s:%s]=",
241 window_ptr->state->descriptor->Label, window_ptr->name );
242 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
244 else
245 mvwprintw( window_ptr->window_ptr, 0, col, " [%s:%s] ",
246 window_ptr->state->descriptor->Label, window_ptr->name );
249 void draw_controls(struct window* window_ptr)
251 unsigned short row, col, color, high, rows, cols;
252 unsigned long c;
253 short offset;
254 control_t *control;
255 char fmt[40];
257 getmaxyx(window_ptr->window_ptr, rows, cols);
258 offset = (long)window_ptr->index + 3 - rows; /* first displayed index */
259 if (offset < 0) offset = 0;
260 col = 1;
261 for (c = offset, row = 1; c < window_ptr->count; c++, row++) {
262 control = (*window_ptr->controls)[c];
263 high = (row == window_ptr->index - offset + 1) ?
264 (window_ptr->selected) ? 3 : 2 : 1;
265 color = (row == window_ptr->index - offset + 1) ?
266 (window_ptr->selected) ? 4 : 2 : 1;
268 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
269 snprintf(fmt, sizeof(fmt), "%%-%d.%ds", cols/3 - 1, cols/3 -1);
270 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
271 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
272 wprintw(window_ptr->window_ptr, ": ");
273 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
274 snprintf(fmt, sizeof(fmt), "%%%dF", cols/6 - 2);
275 wprintw(window_ptr->window_ptr, fmt, *control->val);
276 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
277 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
278 wprintw( window_ptr->window_ptr, " %s ",
279 (control->type == VAL_TOGGLE) ?
280 "?" : (control->type == VAL_INT) ? "i" : "f" );
281 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
282 snprintf(fmt, sizeof(fmt), "%%-%dF", cols/6 - 2);
283 wprintw(window_ptr->window_ptr, fmt, control->sel);
284 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
285 snprintf(fmt, sizeof(fmt), " [%%-%dF~%%%dF]", cols/6 - 2, cols/6 - 2);
286 wprintw(window_ptr->window_ptr, fmt, control->min, control->max);
287 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
289 wclrtoeol(window_ptr->window_ptr);
291 draw_border(window_ptr);
292 wrefresh(window_ptr->window_ptr);
295 void
296 create_window(struct window * window_ptr, int height, int width,
297 int starty, int startx, const char * name)
299 window_ptr->window_ptr = newwin(height, width, starty, startx);
300 window_ptr->selected = FALSE;
301 window_ptr->width = width;
302 window_ptr->height = height;
303 window_ptr->name = name;
304 window_ptr->index = 0;
305 window_ptr->controls = NULL;
306 window_ptr->count = 0;
307 scrollok(window_ptr->window_ptr, FALSE);
310 void
311 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
313 /* delwin(window_ptr->window_ptr); */
314 /* window_ptr->window_ptr = newwin(height, width, starty, startx); */
315 wresize(window_ptr->window_ptr, height, width);
316 mvwin(window_ptr->window_ptr, starty, startx);
317 window_ptr->width = width;
318 window_ptr->height = height;
321 void cleanup_controls(struct window* windows)
323 short i;
324 unsigned long c;
325 struct window* w = windows;
327 for (i = 0; i < NUM_WINDOWS; i++, w++) {
328 for (c = 0; c < w->count; c++)
329 free((*w->controls)[c]);
330 free(w->controls);
334 unsigned short
335 select_window(struct window* windows, int cur, int nex)
337 if (nex == cur)
338 return cur;
339 else if (nex >= NUM_WINDOWS)
340 nex = 0;
341 else if (nex < 0)
342 nex = NUM_WINDOWS - 1;
344 windows[cur].selected = FALSE;
345 windows[nex].selected = TRUE;
346 return nex;
349 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
351 unsigned short cols;
352 cols = getmaxx(w);
354 wmove(w, 0, 0);
355 wclrtoeol(w);
357 wattron(w, COLOR_PAIR(c));
358 mvwprintw(w, 0, 1, msg);
359 wattroff(w, COLOR_PAIR(c));
361 wattron(w, COLOR_PAIR(7));
362 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
363 wattroff(w, COLOR_PAIR(7));
365 wrefresh(w);
368 #define cl ((*C)[W->index])
370 int main(int argc, char *argv[])
372 unsigned short rc = 0, rows, cols, window_selection = 0;
373 struct window windows[NUM_WINDOWS];
374 struct window *W;
375 control_t *(*C)[];
376 unsigned long c;
377 LADSPA_Data val;
378 WINDOW *help_window;
379 const char *err_message = NULL;
380 bool want_refresh = FALSE, help = TRUE;
381 bool rt;
382 int k, i;
384 /* Initialize ncurses */
385 initscr();
386 curs_set(0); /* set cursor invisible */
387 noecho();
388 getmaxyx(stdscr, rows, cols);
390 if (has_colors() == FALSE) {
391 rc = -1, ERR_OUT("Your terminal does not support color");
392 goto qxit;
394 start_color();
395 init_pair(1, COLOR_CYAN, COLOR_BLACK);
396 init_pair(2, COLOR_BLACK, COLOR_WHITE);
397 init_pair(3, COLOR_BLACK, COLOR_GREEN);
398 init_pair(4, COLOR_WHITE, COLOR_BLACK);
399 init_pair(5, COLOR_BLACK, COLOR_RED);
400 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
401 init_pair(7, COLOR_BLUE, COLOR_BLACK);
403 /* Create Help Window */
404 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
405 keypad(help_window, TRUE);
406 wtimeout(help_window, 3000);
408 /* Some Jack versions are very aggressive in breaking view */
409 jack_set_info_function(suppress_jack_log);
410 jack_set_error_function(suppress_jack_log);
412 /* Initialize jack */
413 state_t state;
414 endwin();
415 if (!jackspa_init(&state, argc, argv)) {
416 rc = -1;
417 goto qxit;
419 refresh();
420 rt = (bool)jack_is_realtime(state.jack_client);
422 /* Create windows */
423 create_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME);
424 windows[window_selection].selected = TRUE;
426 /* Build controls */
427 if (build_controls(windows+0, &state)) {
428 rc = -1;
429 goto quit_no_clean;
432 loop:
433 for (i = 0; i < NUM_WINDOWS; i++) draw_controls(windows+i);
434 W = &windows[window_selection];
435 C = W->controls;
437 if (err_message) {
438 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
439 err_message = NULL;
441 else
442 draw_help(help_window, 6, help ? HELP : W->state->descriptor->Name,
443 jack_cpu_load(state.jack_client), rt);
445 switch (k = wgetch(help_window)) {
446 case '\t':
447 window_selection = select_window(windows, window_selection, window_selection+1);
448 goto loop;
449 case KEY_BTAB:
450 window_selection = select_window(windows, window_selection, window_selection-1);
451 goto loop;
452 case KEY_EXIT:
453 case 'q':
454 rc = 0; goto quit;
455 case 'r':
456 case KEY_RESIZE:
457 getmaxyx(stdscr, rows, cols);
458 wresize(help_window, WHLP_H, WHLP_W);
459 mvwin(help_window, WHLP_Y, WHLP_X);
460 resize_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X);
461 goto refresh;
462 case 'u':
463 *cl->val = cl->sel;
464 goto loop;
465 case 'U':
466 for (c = 0; c < W->count; c++)
467 *(*C)[c]->val = (*C)[c]->sel;
468 goto loop;
469 case 'x':
470 c_exchange(cl);
471 goto loop;
472 case 'X':
473 for (c = 0; c < W->count; c++)
474 c_exchange((*W->controls)[c]);
475 goto loop;
476 case KEY_BACKSPACE:
477 case 'd':
478 if (cl->def) cl->sel = *cl->def;
479 if (k == KEY_BACKSPACE) window_item_next(W);
480 goto loop;
481 case 'D':
482 for (c = 0; c < W->count; c++)
483 if ((*C)[c]->def) (*C)[c]->sel = *(*C)[c]->def;
484 goto loop;
485 case 'h':
486 case KEY_LEFT:
487 cl->sel -= cl->inc.coarse;
488 if (cl->sel < cl->min) cl->sel = cl->min;
489 goto loop;
490 case 'l':
491 case KEY_RIGHT:
492 cl->sel += cl->inc.coarse;
493 if (cl->sel > cl->max) cl->sel = cl->max;
494 goto loop;
495 case 'H':
496 cl->sel -= cl->inc.fine;
497 if (cl->sel < cl->min) cl->sel = cl->min;
498 goto loop;
499 case 'L':
500 cl->sel += cl->inc.fine;
501 if (cl->sel > cl->max) cl->sel = cl->max;
502 goto loop;
503 case '<':
504 cl->sel = cl->min;
505 goto loop;
506 case '>':
507 cl->sel = cl->max;
508 goto loop;
509 case '1':
510 case '2':
511 case '3':
512 case '4':
513 case '5':
514 case '6':
515 case '7':
516 case '8':
517 case '9':
518 val = ((LADSPA_Data)(k - 48)) / 10.0;
519 cl->sel = (1.0 - val) * cl->min + val * cl->max;
520 if (cl->type == VAL_INT || cl->type == VAL_TOGGLE)
521 cl->sel = nearbyintf(cl->sel);
522 goto loop;
523 case KEY_DOWN:
524 case 'j':
525 window_item_next(W);
526 goto loop;
527 case KEY_UP:
528 case 'k':
529 window_item_previous(W);
530 goto loop;
531 case KEY_HOME:
532 case 'g':
533 (windows+window_selection)->index = 0;
534 goto loop;
535 case KEY_END:
536 case 'G':
537 (windows+window_selection)->index = (windows+window_selection)->count - 1;
538 goto loop;
539 case '?':
540 help = !help;
542 if (!want_refresh) goto loop;
543 refresh:
544 want_refresh = FALSE;
545 for (i = 0; i < NUM_WINDOWS; i++) {
546 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
547 wclear(windows[i].window_ptr);
549 goto loop;
551 quit:
552 cleanup_controls(windows);
553 quit_no_clean:
554 /* jackspa_exit(state); */
555 jack_deactivate(state.jack_client);
556 jack_client_close(state.jack_client);
557 qxit:
558 endwin();
560 return rc;