use the same licensing statements in all the headers
[ng-jackspa.git] / njackspa.c
blob5187006fa107adaa90f0f4355fc9bad640c78f2b
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"
27 #include "control.h"
29 #define PROGRAM_NAME "njackspa"
30 #include "interface.c"
31 #include "curses.c"
33 #define ERR_OUT(format, arg...) ( endwin(), fprintf(stderr, format "\n", ## arg), refresh() )
35 #define CON_NAME "Controls"
36 #define HELP "'q'uit, '?', 'TAB', U/D-selection, L/R-change, 'd/D'efault, '>/<', '<n>'0%, 'u/U'pdate, e'x/X'change, 'i'nput, 'N'arrow"
37 #define ERR_NODEFAULT "No default value"
38 #define ERR_INVALIDVALUE "Invalid value entered"
40 #define NUM_WINDOWS 1
42 #define WCON_X 0
43 #define WCON_Y 0
44 #define WCON_W cols
45 #define WCON_H rows - 1
47 #define WHLP_X 0
48 #define WHLP_Y rows - 1
49 #define WHLP_W cols
50 #define WHLP_H 0
52 struct window {
53 WINDOW *window_ptr;
54 state_t *state;
55 controls_t controls;
56 unsigned long count; /* number of controls */
57 unsigned long index;
58 bool selected;
59 int height;
60 int width;
61 const char *name;
62 enum { Oneline, Twolines } layout; /* state variable */
63 enum { Active, Alternative } sel; /* state variable */
66 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
67 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
68 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
71 int init_window(struct window *window, state_t *state)
73 int rc = 0;
75 rc = control_buildall(&window->count, &window->controls, state);
76 window->state = state;
77 window->index = 0;
79 return rc;
82 void draw_border(struct window * window_ptr)
84 int col = ( window_ptr->width - strlen(window_ptr->name) -
85 strlen(window_ptr->state->descriptor->Label) - 5 ) / 2;
86 if (col < 0) col = 0;
88 /* 0, 0 gives default characters for the vertical and horizontal lines */
89 box(window_ptr->window_ptr, 0, 0);
91 if (window_ptr->selected) {
92 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
93 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s:%s]=",
94 window_ptr->state->descriptor->Label, window_ptr->name );
95 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
97 else
98 mvwprintw( window_ptr->window_ptr, 0, col, " [%s:%s] ",
99 window_ptr->state->descriptor->Label, window_ptr->name );
102 void draw_controls(struct window* window_ptr)
104 int row, col, color, high, rows, cols;
105 /* start position of a control, colors, screen size */
106 unsigned long c; /* loop variable for controls */
107 long offset; /* first displayed control index */
108 control_t *control;
109 char fmt[40]; /* buffer for printf format strings */
110 int nw, vw, bw; /* computed field widths */
111 int height; /* lines for one control */
113 getmaxyx(window_ptr->window_ptr, rows, cols);
114 if (window_ptr->layout == Oneline) {
115 nw = cols-2 - (cols/6-2)*4 - 9;
116 vw = cols/6-2;
117 bw = vw;
118 height = 1;
120 else {
121 bw = cols/3-2;
122 nw = 2*bw + 4;
123 vw = cols-2 - 2*(cols/3-2) - 6;
124 height = 2;
126 offset = (long)window_ptr->index+1 + (2-rows)/height;
127 if ((rows-2)/height < 1) offset--;
128 if (offset < 0) offset = 0;
130 col = 1;
131 for (c = offset, row = 1; c < window_ptr->count && row < rows-1; c++, row++) {
132 control = (window_ptr->controls)[c];
133 high = (row-1 == (window_ptr->index - offset) * height) ?
134 (window_ptr->selected) ? 3 : 2 : 1;
135 /* currently selected widget (higlighted or normal) */
136 color = (row-1 == (window_ptr->index - offset) * height) ?
137 (window_ptr->selected) ? 4 : 2 : 1;
138 /* emphasized widget (white or normal) */
140 /* Name [nw] */
141 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
142 snprintf(fmt, sizeof(fmt), "%%-%d.%ds", nw, nw);
143 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
144 /* Separator ':' [2] */
145 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
146 wprintw(window_ptr->window_ptr, ": ");
147 /* Active value (bold) [vw] */
148 snprintf(fmt, sizeof(fmt), "%%%dF", vw);
149 if (window_ptr->sel == Active) {
150 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(high));
151 wprintw(window_ptr->window_ptr, fmt, *control->val);
152 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(high));
154 else {
155 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
156 wprintw(window_ptr->window_ptr, fmt, *control->val);
157 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
159 if (window_ptr->layout == Oneline) {
160 /* Type [3] */
161 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
162 wprintw( window_ptr->window_ptr, " %s ",
163 (control->type == JACKSPA_TOGGLE) ?
164 "?" : (control->type == JACKSPA_INT) ? "i" : "f" );
165 /* Selection (emphasized) [vw] */
166 snprintf(fmt, sizeof(fmt), "%%-%dF", vw);
167 if (window_ptr->sel == Active)
168 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
169 else
170 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
171 wprintw(window_ptr->window_ptr, fmt, control->sel);
172 /* Range [2*bw + 4] */
173 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
174 snprintf(fmt, sizeof(fmt), " [%%-%dF~%%%dF]", bw, bw);
175 wprintw(window_ptr->window_ptr, fmt, control->min, control->max);
176 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
178 else {
179 wclrtoeol(window_ptr->window_ptr);
181 row++;
182 /* Range & Type [2*bw + 6] */
183 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
184 snprintf( fmt, sizeof(fmt), " %%-%dF~%s~%%%dF ", bw,
185 (control->type == JACKSPA_TOGGLE) ?
186 "?" : (control->type == JACKSPA_INT) ? "i" : "f",
187 bw );
188 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->min, control->max);
189 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
190 /* Selection (emphasized) [vw] */
191 snprintf(fmt, sizeof(fmt), "%%%dF", vw);
192 if (window_ptr->sel == Active)
193 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
194 else
195 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
196 wprintw(window_ptr->window_ptr, fmt, control->sel);
197 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
199 wclrtoeol(window_ptr->window_ptr);
201 if (row < rows-1 && window_ptr->layout != Oneline) {
202 /* clear the unused last line */
203 wmove(window_ptr->window_ptr, row, col);
204 wclrtoeol(window_ptr->window_ptr);
207 draw_border(window_ptr);
208 wrefresh(window_ptr->window_ptr);
211 void
212 create_window(struct window * window_ptr, int height, int width,
213 int starty, int startx, const char * name)
215 window_ptr->window_ptr = newwin(height, width, starty, startx);
216 window_ptr->selected = FALSE;
217 window_ptr->width = width;
218 window_ptr->height = height;
219 window_ptr->name = name;
220 window_ptr->index = 0;
221 window_ptr->controls = NULL;
222 window_ptr->count = 0;
223 window_ptr->layout = Oneline;
224 window_ptr->sel = Alternative;
225 scrollok(window_ptr->window_ptr, FALSE);
228 void
229 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
231 /* delwin(window_ptr->window_ptr); */
232 /* window_ptr->window_ptr = newwin(height, width, starty, startx); */
233 wresize(window_ptr->window_ptr, height, width);
234 mvwin(window_ptr->window_ptr, starty, startx);
235 window_ptr->width = width;
236 window_ptr->height = height;
239 void cleanup_windows(struct window* windows)
241 short i;
242 struct window* w = windows;
244 for (i = 0; i < NUM_WINDOWS; i++, w++)
245 control_cleanupall(w->count, &w->controls);
248 unsigned short
249 select_window(struct window* windows, int cur, int nex)
251 if (nex == cur)
252 return cur;
253 else if (nex >= NUM_WINDOWS)
254 nex = 0;
255 else if (nex < 0)
256 nex = NUM_WINDOWS - 1;
258 windows[cur].selected = FALSE;
259 windows[nex].selected = TRUE;
260 return nex;
263 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
265 int cols;
266 cols = getmaxx(w);
268 wmove(w, 0, 0);
269 wclrtoeol(w);
271 wattron(w, COLOR_PAIR(c));
272 mvwprintw(w, 0, 1, msg);
273 wattroff(w, COLOR_PAIR(c));
275 wattron(w, COLOR_PAIR(7));
276 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
277 wattroff(w, COLOR_PAIR(7));
279 wrefresh(w);
282 #define cl ((*C)[W->index])
283 /* used in main() */
285 int main(int argc, char *argv[])
287 unsigned short rc = 0; /* return code */
288 int rows, cols; /* screen size */
289 struct window windows[NUM_WINDOWS];
290 unsigned short window_selection = 0; /* state variable */
291 struct window *W; /* selected window */
292 controls_t *C; /* controls of the selected window */
293 LADSPA_Data *mod; /* value of the selected window to be acted upon */
294 unsigned long c; /* loop variable for controls */
295 WINDOW *help_window;
296 const char *err_message = NULL; /* state variable */
297 bool want_refresh = FALSE, help = TRUE; /* state variables */
298 bool rt; /* jack RT capability */
299 int k, i; /* current curses character, loop variable */
300 LADSPA_Data val; /* buffering variable */
301 char input[40] = { 0 }; /* text input */
303 /* Command line options */
304 GOptionContext *context = interface_context();
305 GError *error = NULL;
306 if (!g_option_context_parse(context, &argc, &argv, &error))
307 return (ERR_OUT("option parsing failed: %s\n", error->message), -1);
309 /* Initialize ncurses */
310 initscr();
311 curs_set(0); /* set cursor invisible */
312 cbreak();
313 noecho();
314 getmaxyx(stdscr, rows, cols);
316 if (has_colors() == FALSE) {
317 rc = -1, ERR_OUT("Your terminal does not support color");
318 goto qxit;
320 start_color();
321 init_pair(1, COLOR_CYAN, COLOR_BLACK);
322 init_pair(2, COLOR_BLACK, COLOR_WHITE);
323 init_pair(3, COLOR_BLACK, COLOR_GREEN);
324 init_pair(4, COLOR_WHITE, COLOR_BLACK);
325 init_pair(5, COLOR_BLACK, COLOR_RED);
326 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
327 init_pair(7, COLOR_BLUE, COLOR_BLACK);
329 /* Create Help Window */
330 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
331 keypad(help_window, TRUE);
332 wtimeout(help_window, 3000);
334 /* Some Jack versions are very aggressive in breaking view */
335 jack_set_info_function(suppress_jack_log);
336 jack_set_error_function(suppress_jack_log);
338 /* Initialize jack */
339 state_t state;
340 endwin();
341 if (!jackspa_init(&state, argc, argv)) {
342 rc = -1;
343 goto qxit;
345 refresh();
346 rt = (bool)jack_is_realtime(state.jack_client);
348 /* Create windows */
349 create_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME);
350 windows[window_selection].selected = TRUE;
352 /* Build controls */
353 endwin();
354 if (init_window(windows+0, &state)) {
355 rc = -1;
356 goto quit_no_clean;
358 refresh();
360 loop:
361 for (i = 0; i < NUM_WINDOWS; i++) draw_controls(windows+i);
362 W = &windows[window_selection];
363 C = &W->controls;
364 mod = W->sel == Active ? cl->val : &cl->sel;
366 if (err_message) {
367 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
368 err_message = NULL;
370 else
371 draw_help(help_window, 6, help ? HELP : W->state->descriptor->Name,
372 jack_cpu_load(state.jack_client), rt);
374 switch (k = wgetch(help_window)) {
375 case KEY_EXIT:
376 case 'q':
377 rc = 0; goto quit;
378 case 'r':
379 case KEY_RESIZE:
380 getmaxyx(stdscr, rows, cols);
381 wresize(help_window, WHLP_H, WHLP_W);
382 mvwin(help_window, WHLP_Y, WHLP_X);
383 resize_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X);
384 goto refresh;
385 case 'u':
386 if (W->sel == Active)
387 cl->sel = *cl->val;
388 else
389 *cl->val = cl->sel;
390 goto loop;
391 case 'U':
392 if (W->sel == Active)
393 for (c = 0; c < W->count; c++)
394 (*C)[c]->sel = *(*C)[c]->val;
395 else
396 for (c = 0; c < W->count; c++)
397 *(*C)[c]->val = (*C)[c]->sel;
398 goto loop;
399 case 'x':
400 control_exchange(cl);
401 goto loop;
402 case 'X':
403 for (c = 0; c < W->count; c++)
404 control_exchange((W->controls)[c]);
405 goto loop;
406 case KEY_BACKSPACE:
407 case 'd':
408 if (cl->def) *mod = *cl->def;
409 else err_message = ERR_NODEFAULT;
410 if (k == KEY_BACKSPACE) window_item_next(W);
411 goto loop;
412 case 'D':
413 if (W->sel == Active) {
414 for (c = 0; c < W->count; c++)
415 if ((*C)[c]->def) *(*C)[c]->val = *(*C)[c]->def;
416 else err_message = ERR_NODEFAULT;
418 else {
419 for (c = 0; c < W->count; c++)
420 if ((*C)[c]->def) (*C)[c]->sel = *(*C)[c]->def;
421 else err_message = ERR_NODEFAULT;
423 goto loop;
424 case 'h':
425 case KEY_LEFT:
426 *mod -= cl->inc.coarse;
427 if (*mod < cl->min) *mod = cl->min;
428 goto loop;
429 case 'l':
430 case KEY_RIGHT:
431 *mod += cl->inc.coarse;
432 if (*mod > cl->max) *mod = cl->max;
433 goto loop;
434 case 'H':
435 *mod -= cl->inc.fine;
436 if (*mod < cl->min) *mod = cl->min;
437 goto loop;
438 case 'L':
439 *mod += cl->inc.fine;
440 if (*mod > cl->max) *mod = cl->max;
441 goto loop;
442 case '<':
443 *mod = cl->min;
444 goto loop;
445 case '>':
446 *mod = cl->max;
447 goto loop;
448 case '1':
449 case '2':
450 case '3':
451 case '4':
452 case '5':
453 case '6':
454 case '7':
455 case '8':
456 case '9':
457 val = ((LADSPA_Data)(k - 48)) / 10.0;
458 *mod = control_rounding(cl, (1.0 - val) * cl->min + val * cl->max);
459 goto loop;
460 case 'i':
461 case CTRL('O'):
462 curs_set(1);
463 wtextentry(help_window, input, sizeof(input));
464 if (control_set_value(mod, input, cl) < 0)
465 err_message = ERR_INVALIDVALUE;
466 curs_set(0);
467 goto loop;
468 case KEY_DOWN:
469 case 'j':
470 window_item_next(W);
471 goto loop;
472 case KEY_UP:
473 case 'k':
474 window_item_previous(W);
475 goto loop;
476 case KEY_HOME:
477 case 'g':
478 (windows+window_selection)->index = 0;
479 goto loop;
480 case KEY_END:
481 case 'G':
482 (windows+window_selection)->index = (windows+window_selection)->count - 1;
483 goto loop;
484 case '\t':
485 W->sel = W->sel == Active ? Alternative : Active;
486 goto loop;
487 case 'J':
488 W->sel = Alternative;
489 goto loop;
490 case 'K':
491 W->sel = Active;
492 goto loop;
493 case KEY_BTAB:
494 case 'N':
495 case CTRL('L'):
496 W->layout = W->layout == Oneline ? Twolines : Oneline;
497 goto refresh;
498 case '?':
499 help = !help;
501 if (!want_refresh) goto loop;
502 refresh:
503 want_refresh = FALSE;
504 for (i = 0; i < NUM_WINDOWS; i++) {
505 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
506 wclear(windows[i].window_ptr);
508 goto loop;
510 quit:
511 cleanup_windows(windows);
512 quit_no_clean:
513 jackspa_fini(&state);
514 qxit:
515 endwin();
517 return rc;