njackspa: [bug] fix the fields' widths
[ng-jackspa.git] / njackspa.c
blobf922d46c80f47c3335be6daebca63a659164f113
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"
32 #define ERR_OUT(format, arg...) ( endwin(), fprintf(stderr, format "\n", ## arg), refresh() )
34 #define CON_NAME "Controls"
35 #define HELP "'q'uit, '?', 'TAB', U/D-selection, L/R-change, 'd/D'efault, '>/<', '<n>'0%, 'u/U'pdate, e'x/X'change"
36 #define ERR_NODEFAULT "No default value"
38 #define NUM_WINDOWS 1
40 #define WCON_X 0
41 #define WCON_Y 0
42 #define WCON_W cols
43 #define WCON_H rows - 1
45 #define WHLP_X 0
46 #define WHLP_Y rows - 1
47 #define WHLP_W cols
48 #define WHLP_H 0
50 struct window {
51 WINDOW *window_ptr;
52 state_t *state;
53 controls_t controls;
54 unsigned long count; /* number of controls */
55 unsigned long index;
56 bool selected;
57 int height;
58 int width;
59 const char *name;
60 enum { Active, Alternative } sel; /* state variable */
63 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
64 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
65 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
68 int init_window(struct window *window, state_t *state)
70 int rc = 0;
72 rc = control_buildall(&window->count, &window->controls, state);
73 window->state = state;
74 window->index = 0;
76 return rc;
79 void draw_border(struct window * window_ptr)
81 int col = ( window_ptr->width - strlen(window_ptr->name) -
82 strlen(window_ptr->state->descriptor->Label) - 5 ) / 2;
83 if (col < 0) col = 0;
85 /* 0, 0 gives default characters for the vertical and horizontal lines */
86 box(window_ptr->window_ptr, 0, 0);
88 if (window_ptr->selected) {
89 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
90 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s:%s]=",
91 window_ptr->state->descriptor->Label, window_ptr->name );
92 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
94 else
95 mvwprintw( window_ptr->window_ptr, 0, col, " [%s:%s] ",
96 window_ptr->state->descriptor->Label, window_ptr->name );
99 void draw_controls(struct window* window_ptr)
101 int row, col, color, high, rows, cols;
102 /* start position of a control, colors, screen size */
103 unsigned long c; /* loop variable for controls */
104 long offset; /* first displayed control index */
105 control_t *control;
106 char fmt[40]; /* buffer for printf format strings */
107 int w; /* computed field width */
109 getmaxyx(window_ptr->window_ptr, rows, cols);
110 offset = (long)window_ptr->index + 3-rows;
111 if (offset < 0) offset = 0;
112 col = 1;
113 for (c = offset, row = 1; c < window_ptr->count; c++, row++) {
114 control = (window_ptr->controls)[c];
115 high = (row == window_ptr->index - offset + 1) ?
116 (window_ptr->selected) ? 3 : 2 : 1;
117 /* currently selected widget (higlighted or normal) */
118 color = (row == window_ptr->index - offset + 1) ?
119 (window_ptr->selected) ? 4 : 2 : 1;
120 /* emphasized widget (white or normal) */
122 /* Name [ramaining space] */
123 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
124 w = cols-2 - (cols/6-2)*4 - 9;
125 snprintf(fmt, sizeof(fmt), "%%-%d.%ds", w, w);
126 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
127 /* Separator ':' [2] */
128 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
129 wprintw(window_ptr->window_ptr, ": ");
130 /* Active value (bold) [c/6-2] */
131 snprintf(fmt, sizeof(fmt), "%%%dF", cols/6-2);
132 if (window_ptr->sel == Active) {
133 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(high));
134 wprintw(window_ptr->window_ptr, fmt, *control->val);
135 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(high));
137 else {
138 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
139 wprintw(window_ptr->window_ptr, fmt, *control->val);
140 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
142 /* Type [3] */
143 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
144 wprintw( window_ptr->window_ptr, " %s ",
145 (control->type == JACKSPA_TOGGLE) ?
146 "?" : (control->type == JACKSPA_INT) ? "i" : "f" );
147 /* Selection (emphasized) [c/6-2] */
148 snprintf(fmt, sizeof(fmt), "%%-%dF", cols/6-2);
149 if (window_ptr->sel == Active)
150 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
151 else
152 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
153 wprintw(window_ptr->window_ptr, fmt, control->sel);
154 /* Range [2* (c/6-2) + 4] */
155 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
156 snprintf(fmt, sizeof(fmt), " [%%-%dF~%%%dF]", cols/6-2, cols/6-2);
157 wprintw(window_ptr->window_ptr, fmt, control->min, control->max);
158 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
160 wclrtoeol(window_ptr->window_ptr);
162 draw_border(window_ptr);
163 wrefresh(window_ptr->window_ptr);
166 void
167 create_window(struct window * window_ptr, int height, int width,
168 int starty, int startx, const char * name)
170 window_ptr->window_ptr = newwin(height, width, starty, startx);
171 window_ptr->selected = FALSE;
172 window_ptr->width = width;
173 window_ptr->height = height;
174 window_ptr->name = name;
175 window_ptr->index = 0;
176 window_ptr->controls = NULL;
177 window_ptr->count = 0;
178 window_ptr->sel = Alternative;
179 scrollok(window_ptr->window_ptr, FALSE);
182 void
183 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
185 /* delwin(window_ptr->window_ptr); */
186 /* window_ptr->window_ptr = newwin(height, width, starty, startx); */
187 wresize(window_ptr->window_ptr, height, width);
188 mvwin(window_ptr->window_ptr, starty, startx);
189 window_ptr->width = width;
190 window_ptr->height = height;
193 void cleanup_windows(struct window* windows)
195 short i;
196 struct window* w = windows;
198 for (i = 0; i < NUM_WINDOWS; i++, w++)
199 control_cleanupall(w->count, &w->controls);
202 unsigned short
203 select_window(struct window* windows, int cur, int nex)
205 if (nex == cur)
206 return cur;
207 else if (nex >= NUM_WINDOWS)
208 nex = 0;
209 else if (nex < 0)
210 nex = NUM_WINDOWS - 1;
212 windows[cur].selected = FALSE;
213 windows[nex].selected = TRUE;
214 return nex;
217 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
219 int cols;
220 cols = getmaxx(w);
222 wmove(w, 0, 0);
223 wclrtoeol(w);
225 wattron(w, COLOR_PAIR(c));
226 mvwprintw(w, 0, 1, msg);
227 wattroff(w, COLOR_PAIR(c));
229 wattron(w, COLOR_PAIR(7));
230 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
231 wattroff(w, COLOR_PAIR(7));
233 wrefresh(w);
236 #define cl ((*C)[W->index])
237 /* used in main() */
239 int main(int argc, char *argv[])
241 unsigned short rc = 0; /* return code */
242 int rows, cols; /* screen size */
243 struct window windows[NUM_WINDOWS];
244 unsigned short window_selection = 0; /* state variable */
245 struct window *W; /* selected window */
246 controls_t *C; /* controls of the selected window */
247 LADSPA_Data *mod; /* value of the selected window to be acted upon */
248 unsigned long c; /* loop variable for controls */
249 LADSPA_Data val; /* buffering variable */
250 WINDOW *help_window;
251 const char *err_message = NULL; /* state variable */
252 bool want_refresh = FALSE, help = TRUE; /* state variables */
253 bool rt; /* jack RT capability */
254 int k, i; /* current curses character, loop variable */
256 /* Command line options */
257 GOptionContext *context = interface_context();
258 GError *error = NULL;
259 if (!g_option_context_parse(context, &argc, &argv, &error))
260 return (ERR_OUT("option parsing failed: %s\n", error->message), -1);
262 /* Initialize ncurses */
263 initscr();
264 curs_set(0); /* set cursor invisible */
265 noecho();
266 getmaxyx(stdscr, rows, cols);
268 if (has_colors() == FALSE) {
269 rc = -1, ERR_OUT("Your terminal does not support color");
270 goto qxit;
272 start_color();
273 init_pair(1, COLOR_CYAN, COLOR_BLACK);
274 init_pair(2, COLOR_BLACK, COLOR_WHITE);
275 init_pair(3, COLOR_BLACK, COLOR_GREEN);
276 init_pair(4, COLOR_WHITE, COLOR_BLACK);
277 init_pair(5, COLOR_BLACK, COLOR_RED);
278 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
279 init_pair(7, COLOR_BLUE, COLOR_BLACK);
281 /* Create Help Window */
282 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
283 keypad(help_window, TRUE);
284 wtimeout(help_window, 3000);
286 /* Some Jack versions are very aggressive in breaking view */
287 jack_set_info_function(suppress_jack_log);
288 jack_set_error_function(suppress_jack_log);
290 /* Initialize jack */
291 state_t state;
292 endwin();
293 if (!jackspa_init(&state, argc, argv)) {
294 rc = -1;
295 goto qxit;
297 refresh();
298 rt = (bool)jack_is_realtime(state.jack_client);
300 /* Create windows */
301 create_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME);
302 windows[window_selection].selected = TRUE;
304 /* Build controls */
305 endwin();
306 if (init_window(windows+0, &state)) {
307 rc = -1;
308 goto quit_no_clean;
310 refresh();
312 loop:
313 for (i = 0; i < NUM_WINDOWS; i++) draw_controls(windows+i);
314 W = &windows[window_selection];
315 C = &W->controls;
316 mod = W->sel == Active ? cl->val : &cl->sel;
318 if (err_message) {
319 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
320 err_message = NULL;
322 else
323 draw_help(help_window, 6, help ? HELP : W->state->descriptor->Name,
324 jack_cpu_load(state.jack_client), rt);
326 switch (k = wgetch(help_window)) {
327 case KEY_EXIT:
328 case 'q':
329 rc = 0; goto quit;
330 case 'r':
331 case KEY_RESIZE:
332 getmaxyx(stdscr, rows, cols);
333 wresize(help_window, WHLP_H, WHLP_W);
334 mvwin(help_window, WHLP_Y, WHLP_X);
335 resize_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X);
336 goto refresh;
337 case 'u':
338 if (W->sel == Active)
339 cl->sel = *cl->val;
340 else
341 *cl->val = cl->sel;
342 goto loop;
343 case 'U':
344 if (W->sel == Active)
345 for (c = 0; c < W->count; c++)
346 (*C)[c]->sel = *(*C)[c]->val;
347 else
348 for (c = 0; c < W->count; c++)
349 *(*C)[c]->val = (*C)[c]->sel;
350 goto loop;
351 case 'x':
352 control_exchange(cl);
353 goto loop;
354 case 'X':
355 for (c = 0; c < W->count; c++)
356 control_exchange((W->controls)[c]);
357 goto loop;
358 case KEY_BACKSPACE:
359 case 'd':
360 if (cl->def) *mod = *cl->def;
361 else err_message = ERR_NODEFAULT;
362 if (k == KEY_BACKSPACE) window_item_next(W);
363 goto loop;
364 case 'D':
365 if (W->sel == Active) {
366 for (c = 0; c < W->count; c++)
367 if ((*C)[c]->def) *(*C)[c]->val = *(*C)[c]->def;
368 else err_message = ERR_NODEFAULT;
370 else {
371 for (c = 0; c < W->count; c++)
372 if ((*C)[c]->def) (*C)[c]->sel = *(*C)[c]->def;
373 else err_message = ERR_NODEFAULT;
375 goto loop;
376 case 'h':
377 case KEY_LEFT:
378 *mod -= cl->inc.coarse;
379 if (*mod < cl->min) *mod = cl->min;
380 goto loop;
381 case 'l':
382 case KEY_RIGHT:
383 *mod += cl->inc.coarse;
384 if (*mod > cl->max) *mod = cl->max;
385 goto loop;
386 case 'H':
387 *mod -= cl->inc.fine;
388 if (*mod < cl->min) *mod = cl->min;
389 goto loop;
390 case 'L':
391 *mod += cl->inc.fine;
392 if (*mod > cl->max) *mod = cl->max;
393 goto loop;
394 case '<':
395 *mod = cl->min;
396 goto loop;
397 case '>':
398 *mod = cl->max;
399 goto loop;
400 case '1':
401 case '2':
402 case '3':
403 case '4':
404 case '5':
405 case '6':
406 case '7':
407 case '8':
408 case '9':
409 val = ((LADSPA_Data)(k - 48)) / 10.0;
410 *mod = control_rounding(cl, (1.0 - val) * cl->min + val * cl->max);
411 goto loop;
412 case KEY_DOWN:
413 case 'j':
414 window_item_next(W);
415 goto loop;
416 case KEY_UP:
417 case 'k':
418 window_item_previous(W);
419 goto loop;
420 case KEY_HOME:
421 case 'g':
422 (windows+window_selection)->index = 0;
423 goto loop;
424 case KEY_END:
425 case 'G':
426 (windows+window_selection)->index = (windows+window_selection)->count - 1;
427 goto loop;
428 case '\t':
429 case KEY_BTAB:
430 W->sel = W->sel == Active ? Alternative : Active;
431 goto loop;
432 case 'J':
433 W->sel = Alternative;
434 goto loop;
435 case 'K':
436 W->sel = Active;
437 goto loop;
438 case '?':
439 help = !help;
441 if (!want_refresh) goto loop;
442 refresh:
443 want_refresh = FALSE;
444 for (i = 0; i < NUM_WINDOWS; i++) {
445 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
446 wclear(windows[i].window_ptr);
448 goto loop;
450 quit:
451 cleanup_windows(windows);
452 quit_no_clean:
453 /* jackspa_exit(state); */
454 jack_deactivate(state.jack_client);
455 jack_client_close(state.jack_client);
456 qxit:
457 endwin();
459 return rc;