control.h, control.c: new module providing control_init() and control_*()
[ng-jackspa.git] / njackspa.c
blobc49feb00276a5c4232744fa4e34844e8e3ab97bd
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 #define CON_NAME "Controls"
32 #define HELP "'q'uit, '?', U/D-selection, L/R-change, 'd/D'efault, '>/<', '<n>'0%, 'u/U'pdate, e'x/X'change"
33 #define ERR_NODEFAULT "No default value"
35 #define NUM_WINDOWS 1
37 #define WCON_X 0
38 #define WCON_Y 0
39 #define WCON_W cols
40 #define WCON_H rows - 1
42 #define WHLP_X 0
43 #define WHLP_Y rows - 1
44 #define WHLP_W cols
45 #define WHLP_H 0
47 #define ERR_OUT(format, arg...) ( endwin(), fprintf(stderr, format "\n", ## arg), refresh() )
49 struct window {
50 WINDOW *window_ptr;
51 state_t *state;
52 control_t *(*controls)[];
53 unsigned long index;
54 unsigned long count;
55 bool selected;
56 int height;
57 int width;
58 const char *name;
61 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
62 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
63 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
66 int build_controls(struct window *window, state_t *state)
68 int rc = 0;
69 unsigned long p, c;
71 window->state = state;
72 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
73 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
74 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
75 c++;
76 window->count = c;
77 window->index = 0;
78 window->controls = (control_t *(*)[])calloc(sizeof(control_t *), c);
79 if (!window->controls)
80 return (ERR_OUT ("memory allocation error"), 1);
82 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
83 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
84 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
86 (*window->controls)[c] = (control_t *)malloc(sizeof(control_t));
87 if (!(*window->controls)[c])
88 return (ERR_OUT ("memory allocation error"), 1);
89 if (control_init((*window->controls)[c++], state, p))
90 rc = 1;
93 return rc;
96 void draw_border(struct window * window_ptr)
98 int col = ( window_ptr->width - strlen(window_ptr->name) -
99 strlen(window_ptr->state->descriptor->Label) - 5 ) / 2;
100 if (col < 0) col = 0;
102 /* 0, 0 gives default characters for the vertical and horizontal lines */
103 box(window_ptr->window_ptr, 0, 0);
105 if (window_ptr->selected) {
106 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
107 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s:%s]=",
108 window_ptr->state->descriptor->Label, window_ptr->name );
109 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
111 else
112 mvwprintw( window_ptr->window_ptr, 0, col, " [%s:%s] ",
113 window_ptr->state->descriptor->Label, window_ptr->name );
116 void draw_controls(struct window* window_ptr)
118 unsigned short row, col, color, high, rows, cols;
119 unsigned long c;
120 short offset;
121 control_t *control;
122 char fmt[40];
124 getmaxyx(window_ptr->window_ptr, rows, cols);
125 offset = (long)window_ptr->index + 3 - rows; /* first displayed index */
126 if (offset < 0) offset = 0;
127 col = 1;
128 for (c = offset, row = 1; c < window_ptr->count; c++, row++) {
129 control = (*window_ptr->controls)[c];
130 high = (row == window_ptr->index - offset + 1) ?
131 (window_ptr->selected) ? 3 : 2 : 1;
132 color = (row == window_ptr->index - offset + 1) ?
133 (window_ptr->selected) ? 4 : 2 : 1;
135 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
136 snprintf(fmt, sizeof(fmt), "%%-%d.%ds", cols/3 - 1, cols/3 -1);
137 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
138 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
139 wprintw(window_ptr->window_ptr, ": ");
140 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
141 snprintf(fmt, sizeof(fmt), "%%%dF", cols/6 - 2);
142 wprintw(window_ptr->window_ptr, fmt, *control->val);
143 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
144 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
145 wprintw( window_ptr->window_ptr, " %s ",
146 (control->type == JACKSPA_TOGGLE) ?
147 "?" : (control->type == JACKSPA_INT) ? "i" : "f" );
148 wattron(window_ptr->window_ptr, COLOR_PAIR(high));
149 snprintf(fmt, sizeof(fmt), "%%-%dF", cols/6 - 2);
150 wprintw(window_ptr->window_ptr, fmt, control->sel);
151 wattron(window_ptr->window_ptr, COLOR_PAIR(1));
152 snprintf(fmt, sizeof(fmt), " [%%-%dF~%%%dF]", cols/6 - 2, cols/6 - 2);
153 wprintw(window_ptr->window_ptr, fmt, control->min, control->max);
154 wattroff(window_ptr->window_ptr, COLOR_PAIR(1));
156 wclrtoeol(window_ptr->window_ptr);
158 draw_border(window_ptr);
159 wrefresh(window_ptr->window_ptr);
162 void
163 create_window(struct window * window_ptr, int height, int width,
164 int starty, int startx, const char * name)
166 window_ptr->window_ptr = newwin(height, width, starty, startx);
167 window_ptr->selected = FALSE;
168 window_ptr->width = width;
169 window_ptr->height = height;
170 window_ptr->name = name;
171 window_ptr->index = 0;
172 window_ptr->controls = NULL;
173 window_ptr->count = 0;
174 scrollok(window_ptr->window_ptr, FALSE);
177 void
178 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
180 /* delwin(window_ptr->window_ptr); */
181 /* window_ptr->window_ptr = newwin(height, width, starty, startx); */
182 wresize(window_ptr->window_ptr, height, width);
183 mvwin(window_ptr->window_ptr, starty, startx);
184 window_ptr->width = width;
185 window_ptr->height = height;
188 void cleanup_controls(struct window* windows)
190 short i;
191 unsigned long c;
192 struct window* w = windows;
194 for (i = 0; i < NUM_WINDOWS; i++, w++) {
195 for (c = 0; c < w->count; c++)
196 free((*w->controls)[c]);
197 free(w->controls);
201 unsigned short
202 select_window(struct window* windows, int cur, int nex)
204 if (nex == cur)
205 return cur;
206 else if (nex >= NUM_WINDOWS)
207 nex = 0;
208 else if (nex < 0)
209 nex = NUM_WINDOWS - 1;
211 windows[cur].selected = FALSE;
212 windows[nex].selected = TRUE;
213 return nex;
216 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
218 unsigned short cols;
219 cols = getmaxx(w);
221 wmove(w, 0, 0);
222 wclrtoeol(w);
224 wattron(w, COLOR_PAIR(c));
225 mvwprintw(w, 0, 1, msg);
226 wattroff(w, COLOR_PAIR(c));
228 wattron(w, COLOR_PAIR(7));
229 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
230 wattroff(w, COLOR_PAIR(7));
232 wrefresh(w);
235 #define cl ((*C)[W->index])
237 int main(int argc, char *argv[])
239 unsigned short rc = 0, rows, cols, window_selection = 0;
240 struct window windows[NUM_WINDOWS];
241 struct window *W;
242 control_t *(*C)[];
243 unsigned long c;
244 LADSPA_Data val;
245 WINDOW *help_window;
246 const char *err_message = NULL;
247 bool want_refresh = FALSE, help = TRUE;
248 bool rt;
249 int k, i;
251 /* Initialize ncurses */
252 initscr();
253 curs_set(0); /* set cursor invisible */
254 noecho();
255 getmaxyx(stdscr, rows, cols);
257 if (has_colors() == FALSE) {
258 rc = -1, ERR_OUT("Your terminal does not support color");
259 goto qxit;
261 start_color();
262 init_pair(1, COLOR_CYAN, COLOR_BLACK);
263 init_pair(2, COLOR_BLACK, COLOR_WHITE);
264 init_pair(3, COLOR_BLACK, COLOR_GREEN);
265 init_pair(4, COLOR_WHITE, COLOR_BLACK);
266 init_pair(5, COLOR_BLACK, COLOR_RED);
267 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
268 init_pair(7, COLOR_BLUE, COLOR_BLACK);
270 /* Create Help Window */
271 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
272 keypad(help_window, TRUE);
273 wtimeout(help_window, 3000);
275 /* Some Jack versions are very aggressive in breaking view */
276 jack_set_info_function(suppress_jack_log);
277 jack_set_error_function(suppress_jack_log);
279 /* Initialize jack */
280 state_t state;
281 endwin();
282 if (!jackspa_init(&state, argc, argv)) {
283 rc = -1;
284 goto qxit;
286 refresh();
287 rt = (bool)jack_is_realtime(state.jack_client);
289 /* Create windows */
290 create_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME);
291 windows[window_selection].selected = TRUE;
293 /* Build controls */
294 if (build_controls(windows+0, &state)) {
295 rc = -1;
296 goto quit_no_clean;
299 loop:
300 for (i = 0; i < NUM_WINDOWS; i++) draw_controls(windows+i);
301 W = &windows[window_selection];
302 C = W->controls;
304 if (err_message) {
305 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
306 err_message = NULL;
308 else
309 draw_help(help_window, 6, help ? HELP : W->state->descriptor->Name,
310 jack_cpu_load(state.jack_client), rt);
312 switch (k = wgetch(help_window)) {
313 case '\t':
314 window_selection = select_window(windows, window_selection, window_selection+1);
315 goto loop;
316 case KEY_BTAB:
317 window_selection = select_window(windows, window_selection, window_selection-1);
318 goto loop;
319 case KEY_EXIT:
320 case 'q':
321 rc = 0; goto quit;
322 case 'r':
323 case KEY_RESIZE:
324 getmaxyx(stdscr, rows, cols);
325 wresize(help_window, WHLP_H, WHLP_W);
326 mvwin(help_window, WHLP_Y, WHLP_X);
327 resize_window(windows+0, WCON_H, WCON_W, WCON_Y, WCON_X);
328 goto refresh;
329 case 'u':
330 *cl->val = cl->sel;
331 goto loop;
332 case 'U':
333 for (c = 0; c < W->count; c++)
334 *(*C)[c]->val = (*C)[c]->sel;
335 goto loop;
336 case 'x':
337 control_exchange(cl);
338 goto loop;
339 case 'X':
340 for (c = 0; c < W->count; c++)
341 control_exchange((*W->controls)[c]);
342 goto loop;
343 case KEY_BACKSPACE:
344 case 'd':
345 if (cl->def) cl->sel = *cl->def;
346 else err_message = ERR_NODEFAULT;
347 if (k == KEY_BACKSPACE) window_item_next(W);
348 goto loop;
349 case 'D':
350 for (c = 0; c < W->count; c++)
351 if ((*C)[c]->def) (*C)[c]->sel = *(*C)[c]->def;
352 else err_message = ERR_NODEFAULT;
353 goto loop;
354 case 'h':
355 case KEY_LEFT:
356 cl->sel -= cl->inc.coarse;
357 if (cl->sel < cl->min) cl->sel = cl->min;
358 goto loop;
359 case 'l':
360 case KEY_RIGHT:
361 cl->sel += cl->inc.coarse;
362 if (cl->sel > cl->max) cl->sel = cl->max;
363 goto loop;
364 case 'H':
365 cl->sel -= cl->inc.fine;
366 if (cl->sel < cl->min) cl->sel = cl->min;
367 goto loop;
368 case 'L':
369 cl->sel += cl->inc.fine;
370 if (cl->sel > cl->max) cl->sel = cl->max;
371 goto loop;
372 case '<':
373 cl->sel = cl->min;
374 goto loop;
375 case '>':
376 cl->sel = cl->max;
377 goto loop;
378 case '1':
379 case '2':
380 case '3':
381 case '4':
382 case '5':
383 case '6':
384 case '7':
385 case '8':
386 case '9':
387 val = ((LADSPA_Data)(k - 48)) / 10.0;
388 cl->sel = control_rounding(cl, (1.0 - val) * cl->min + val * cl->max);
389 goto loop;
390 case KEY_DOWN:
391 case 'j':
392 window_item_next(W);
393 goto loop;
394 case KEY_UP:
395 case 'k':
396 window_item_previous(W);
397 goto loop;
398 case KEY_HOME:
399 case 'g':
400 (windows+window_selection)->index = 0;
401 goto loop;
402 case KEY_END:
403 case 'G':
404 (windows+window_selection)->index = (windows+window_selection)->count - 1;
405 goto loop;
406 case '?':
407 help = !help;
409 if (!want_refresh) goto loop;
410 refresh:
411 want_refresh = FALSE;
412 for (i = 0; i < NUM_WINDOWS; i++) {
413 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
414 wclear(windows[i].window_ptr);
416 goto loop;
418 quit:
419 cleanup_controls(windows);
420 quit_no_clean:
421 /* jackspa_exit(state); */
422 jack_deactivate(state.jack_client);
423 jack_client_close(state.jack_client);
424 qxit:
425 endwin();
427 return rc;