njackspa.c: new ncurses interface based on njconnect.c version 1.2
[ng-jackspa.git] / njackspa.c
blob2ffd982dce3b614231ab8a98b2bb82a46ab7fe70
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 "jackspa.h"
27 #define PROGRAM_NAME "njackspa"
28 #define CON_NAME_A "Values"
29 #define CON_NAME_M "Switches"
31 #define ERR_UPDATE "Connection failed"
32 #define ERR_DISCONNECT "Disconnection failed"
33 #define HELP "'q'uit, U/D-selection, L/R-change, 'd/D'efault, '<n>'0%, 'u/U'pdate, e'x/X'change"
35 #define WOUT_X 0
36 #define WOUT_Y 0
37 #define WOUT_W cols / 2
38 #define WOUT_H rows / 2
40 #define WIN_X cols / 2
41 #define WIN_Y 0
42 #define WIN_W cols - cols / 2
43 #define WIN_H rows / 2
45 #define WCON_X 0
46 #define WCON_Y rows / 2
47 #define WCON_W cols
48 #define WCON_H rows - rows / 2 - 1
50 #define WHLP_X 0
51 #define WHLP_Y rows - 1
52 #define WHLP_W cols
53 #define WHLP_H 0
55 #define MSG_OUT(format, arg...) printf(format "\n", ## arg)
56 #define ERR_OUT(format, arg...) fprintf(stderr, format "\n", ## arg)
58 enum WinType {
59 WIN_PORTS,
60 WIN_CONNECTIONS
63 enum ctype {
64 VAL_FLOAT,
65 VAL_INT,
66 VAL_TOGGLE
69 typedef struct _control_t {
70 const char *name;
71 /* value in the plugin */
72 LADSPA_Data *val;
73 /* values selected in the interface */
74 LADSPA_Data sel;
75 LADSPA_Data alt_sel;
76 /* value range */
77 LADSPA_Data min;
78 LADSPA_Data max;
79 LADSPA_Data *def;
80 enum ctype type;
81 struct { LADSPA_Data fine; LADSPA_Data coarse; } inc;
82 } control_t;
84 struct window {
85 WINDOW *window_ptr;
86 state_t *state;
87 //JSlist *controls;
88 control_t *(*controls)[];
89 unsigned long index;
90 unsigned long count;
91 bool selected;
92 int height;
93 int width;
94 const char *name;
95 enum WinType type;
99 void window_item_next(struct window* w) { if (w->index < w->count - 1) w->index++; }
100 void window_item_previous(struct window* w) { if (w->index > 0) w->index--; }
101 void suppress_jack_log(const char *msg) { ; /* Just suppress Jack SPAM here ;-) */ }
103 int init_control(control_t *control, state_t *state, int port)
105 LADSPA_PortRangeHint hint = state->descriptor->PortRangeHints[port];
106 LADSPA_PortRangeHintDescriptor descriptor = hint.HintDescriptor;
107 LADSPA_Data lower_bound = hint.LowerBound;
108 LADSPA_Data upper_bound = hint.UpperBound;
110 control->name = state->descriptor->PortNames[port];
111 control->val = &state->control_port_values[port];
113 if (LADSPA_IS_HINT_SAMPLE_RATE(descriptor)) {
114 int sample_rate = jack_get_sample_rate(state->jack_client);
115 lower_bound *= sample_rate;
116 upper_bound *= sample_rate;
118 if ( LADSPA_IS_HINT_BOUNDED_BELOW(descriptor) &&
119 LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor) )
121 control->min = lower_bound;
122 control->max = upper_bound;
124 else if (LADSPA_IS_HINT_BOUNDED_BELOW(descriptor)) {
125 control->min = lower_bound;
126 control->max = 1.0;
128 else if (LADSPA_IS_HINT_BOUNDED_ABOVE(descriptor)) {
129 control->min = 0.0;
130 control->max = upper_bound;
132 else {
133 control->min = -1.0;
134 control->max = 1.0;
137 if (LADSPA_IS_HINT_TOGGLED(descriptor)) {
138 control->min = 0.0;
139 control->max = 1.0;
140 control->inc.fine = 1.0;
141 control->inc.coarse = 1.0;
142 control->type = VAL_TOGGLE;
144 else if (LADSPA_IS_HINT_INTEGER(descriptor)) {
145 control->inc.fine = 1.0;
146 control->inc.coarse = 1.0;
147 //set_digits(0);
148 control->type = VAL_INT;
150 else {
151 control->inc.fine = 0.05;
152 control->inc.coarse = 0.1;
153 //set_digits(2);
154 control->type = VAL_FLOAT;
157 control->sel = control->min;
158 if (LADSPA_IS_HINT_HAS_DEFAULT(descriptor)) {
159 control->def = (LADSPA_Data *)malloc(sizeof(LADSPA_Data));
160 if (!control->def) {
161 ERR_OUT ("memory allocation error");
162 return 1;
164 switch (descriptor & LADSPA_HINT_DEFAULT_MASK) {
165 case LADSPA_HINT_DEFAULT_MINIMUM:
166 *control->def = lower_bound;
167 break;
168 case LADSPA_HINT_DEFAULT_LOW:
169 *control->def = lower_bound * 0.75 + upper_bound * 0.25;
170 break;
171 case LADSPA_HINT_DEFAULT_MIDDLE:
172 *control->def = lower_bound * 0.5 + upper_bound * 0.5;
173 break;
174 case LADSPA_HINT_DEFAULT_HIGH:
175 *control->def = lower_bound * 0.25 + upper_bound * 0.75;
176 break;
177 case LADSPA_HINT_DEFAULT_MAXIMUM:
178 *control->def = upper_bound;
179 break;
180 case LADSPA_HINT_DEFAULT_0:
181 *control->def = 0.0;
182 break;
183 case LADSPA_HINT_DEFAULT_1:
184 *control->def = 1.0;
185 break;
186 case LADSPA_HINT_DEFAULT_100:
187 *control->def = 100.0;
188 break;
189 case LADSPA_HINT_DEFAULT_440:
190 *control->def = 440.0;
191 break;
192 default:
193 free(control->def);
194 control->def = NULL;
196 control->sel = *control->def;
198 else
199 control->def = NULL;
201 control->alt_sel = control->sel;
202 *control->val = control->sel;
204 return 0;
207 int build_controls(struct window *window, state_t *state)
209 int rc = 0;
210 unsigned long p, c;
212 window->state = state;
213 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
214 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
215 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
216 c++;
217 window->count = c;
218 window->index = 0;
219 window->controls = (control_t *(*)[])calloc(sizeof(control_t *), c);
220 if (!window->controls) {
221 ERR_OUT ("memory allocation error");
222 return 1;
225 for (p = 0, c = 0; p < state->descriptor->PortCount; p++)
226 if ( LADSPA_IS_PORT_INPUT(state->descriptor->PortDescriptors[p]) &&
227 LADSPA_IS_PORT_CONTROL(state->descriptor->PortDescriptors[p]) )
229 (*window->controls)[c] = (control_t *)malloc(sizeof(control_t));
230 if (!(*window->controls)[c]) {
231 ERR_OUT ("memory allocation error");
232 return 1;
234 if (init_control((*window->controls)[c++], state, p))
235 rc = 1;
238 return rc;
241 void draw_border(struct window * window_ptr)
243 int col = (window_ptr->width - strlen(window_ptr->name) - 4)/2;
244 if (col < 0) col = 0;
246 /* 0, 0 gives default characters for the vertical and horizontal lines */
247 box(window_ptr->window_ptr, 0, 0);
249 if (window_ptr->selected) {
250 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
251 mvwprintw( window_ptr->window_ptr, 0, col, "=[%s]=", window_ptr->name);
252 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(4));
253 } else {
254 mvwprintw( window_ptr->window_ptr, 0, col, " [%s] ", window_ptr->name);
258 void draw_controls(struct window* window_ptr)
260 unsigned short row, col, color, rows, cols;
261 unsigned long c;
262 short offset;
263 control_t *control;
264 char fmt[40];
266 row = col = 1;
267 getmaxyx(window_ptr->window_ptr, rows, cols);
268 offset = (long)window_ptr->index + 3 - rows; // first displayed index
269 if (offset < 0) offset = 0;
270 for (c = offset; c < window_ptr->count; c++) {
271 color = (row == window_ptr->index - offset + 1) ? (window_ptr->selected) ? 3 : 2 : 1;
272 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
274 switch (window_ptr->type) {
275 case WIN_PORTS:
276 break;
277 case WIN_CONNECTIONS:
278 control = (*window_ptr->controls)[c];
279 snprintf(fmt, sizeof(fmt), "%%-%d.%ds: ", cols/3 - 2, cols/3 - 2);
280 mvwprintw(window_ptr->window_ptr, row, col, fmt, control->name);
281 wattron(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
282 snprintf(fmt, sizeof(fmt), "%%%dF", cols/6 - 2);
283 wprintw(window_ptr->window_ptr, fmt, *control->val);
284 wattroff(window_ptr->window_ptr, WA_BOLD|COLOR_PAIR(color));
285 wattron(window_ptr->window_ptr, COLOR_PAIR(color));
286 snprintf(fmt, sizeof(fmt), " x %%-%dF [%%-%dF %%%dF]", cols/6 - 2, cols/6 - 2, cols/6 - 2);
287 wprintw(window_ptr->window_ptr, fmt,
288 control->sel, control->min, control->max);
289 break;
290 default:
291 ERR_OUT("Unknown WinType");
293 wattroff(window_ptr->window_ptr, COLOR_PAIR(color));
294 wclrtoeol(window_ptr->window_ptr);
295 row++;
297 draw_border(window_ptr);
298 wrefresh(window_ptr->window_ptr);
301 void
302 create_window(struct window * window_ptr, int height, int width,
303 int starty, int startx, const char * name, enum WinType type)
305 window_ptr->window_ptr = newwin(height, width, starty, startx);
306 window_ptr->selected = FALSE;
307 window_ptr->width = width;
308 window_ptr->height = height;
309 window_ptr->name = name;
310 window_ptr->index = 0;
311 window_ptr->type = type;
312 window_ptr->controls = NULL;
313 window_ptr->count = 0;
314 // scrollok(window_ptr->window_ptr, TRUE);
317 void
318 resize_window(struct window * window_ptr, int height, int width, int starty, int startx)
320 // delwin(window_ptr->window_ptr);
321 // window_ptr->window_ptr = newwin(height, width, starty, startx);
322 wresize(window_ptr->window_ptr, height, width);
323 mvwin(window_ptr->window_ptr, starty, startx);
324 window_ptr->width = width;
325 window_ptr->height = height;
328 void cleanup_controls(struct window* windows)
330 short i;
331 unsigned long c;
332 struct window* w = windows;
334 for(i = 0; i < 3; i++, w++)
335 if (w->type == WIN_CONNECTIONS) {
336 for (c = 0; c < w->count; c++)
337 free((*w->controls)[c]);
338 free(w->controls);
342 unsigned short
343 select_window(struct window* windows, int cur, int nex)
345 if (nex == cur)
346 return cur;
347 else if (nex > 2)
348 nex = 0;
349 else if (nex < 0)
350 nex = 2;
352 windows[cur].selected = FALSE;
353 windows[nex].selected = TRUE;
354 return nex;
357 void draw_help(WINDOW* w, int c, const char* msg, float dsp_load, bool rt)
359 unsigned short cols;
360 cols = getmaxx(w);
362 wmove(w, 0, 0);
363 wclrtoeol(w);
365 wattron(w, COLOR_PAIR(c));
366 mvwprintw(w, 0, 1, msg);
367 wattroff(w, COLOR_PAIR(c));
369 wattron(w, COLOR_PAIR(7));
370 mvwprintw(w, 0, cols-12, "DSP:%4.2f%s", dsp_load, rt ? "@RT" : "!RT" );
371 wattroff(w, COLOR_PAIR(7));
373 wrefresh(w);
377 int main(int argc, char *argv[])
379 unsigned short i, rc = 0, rows, cols, window_selection = 0;
380 struct window windows[3];
381 WINDOW *help_window;
382 const char *err_message = NULL;
383 const char *PortsType = JACK_DEFAULT_MIDI_TYPE;
384 bool want_refresh = FALSE;
385 bool rt;
387 /* Initialize ncurses */
388 initscr();
389 curs_set(0); /* set cursor invisible */
390 noecho();
391 getmaxyx(stdscr, rows, cols);
393 if (has_colors() == FALSE) {
394 ERR_OUT("Your terminal does not support color");
395 rc = -1;
396 goto qxit;
398 start_color();
399 init_pair(1, COLOR_CYAN, COLOR_BLACK);
400 init_pair(2, COLOR_BLACK, COLOR_WHITE);
401 init_pair(3, COLOR_BLACK, COLOR_GREEN);
402 init_pair(4, COLOR_WHITE, COLOR_BLACK);
403 init_pair(5, COLOR_BLACK, COLOR_RED);
404 init_pair(6, COLOR_YELLOW, COLOR_BLACK);
405 init_pair(7, COLOR_BLUE, COLOR_BLACK);
407 /* Create Help Window */
408 help_window = newwin(WHLP_H, WHLP_W, WHLP_Y, WHLP_X);
409 keypad(help_window, TRUE);
410 wtimeout(help_window, 3000);
412 /* Some Jack versions are very aggressive in breaking view */
413 jack_set_info_function(suppress_jack_log);
414 jack_set_error_function(suppress_jack_log);
416 /* Initialize jack */
417 state_t state;
418 endwin();
419 if (!jackspa_init(&state, argc, argv)) {
420 rc = -1;
421 goto qxit;
423 refresh();
424 rt = (bool)jack_is_realtime(state.jack_client);
426 /* Create windows */
427 create_window(windows, WOUT_H, WOUT_W, WOUT_Y, WOUT_Y, "Output Ports", WIN_PORTS);
428 create_window(windows+1, WIN_H, WIN_W, WIN_Y, WIN_X, "Input Ports", WIN_PORTS);
429 create_window(windows+2, WCON_H, WCON_W, WCON_Y, WCON_X, CON_NAME_M, WIN_CONNECTIONS);
430 windows[window_selection].selected = TRUE;
432 /* Build ports */
433 if (build_controls(windows+2, &state)) {
434 rc = -1;
435 goto quit_no_clean;
438 loop:
439 for (i = 0; i < 3; i++) draw_controls(windows+i);
441 if (err_message) {
442 draw_help(help_window, 5, err_message, jack_cpu_load(state.jack_client), rt);
443 err_message = NULL;
445 else
446 draw_help(help_window, 6, HELP, jack_cpu_load(state.jack_client), rt);
448 switch (wgetch(help_window)) {
449 case '\t':
450 window_selection = select_window(windows, window_selection, window_selection+1);
451 goto loop;
452 case KEY_BTAB:
453 window_selection = select_window(windows, window_selection, window_selection-1);
454 goto loop;
455 case 'a':
456 windows[2].name = CON_NAME_A;
457 PortsType = JACK_DEFAULT_AUDIO_TYPE;
458 goto refresh;
459 case 'm':
460 windows[2].name = CON_NAME_M;
461 PortsType = JACK_DEFAULT_MIDI_TYPE;
462 goto refresh;
463 case 'q':
464 rc = 0; goto quit;
465 case 'r':
466 case KEY_RESIZE:
467 getmaxyx(stdscr, rows, cols);
468 wresize(help_window, WHLP_H, WHLP_W);
469 mvwin(help_window, WHLP_Y, WHLP_X);
470 resize_window(windows, WOUT_H, WOUT_W, WOUT_Y, WOUT_X);
471 resize_window(windows+1, WIN_H, WIN_W, WIN_Y, WIN_X);
472 resize_window(windows+2, WCON_H, WCON_W, WCON_Y, WCON_X);
473 goto refresh;
474 case 'u':
475 err_message = ERR_UPDATE;
476 goto loop;
477 case 'x':
478 err_message = ERR_UPDATE;
479 goto loop;
480 case KEY_DOWN:
481 window_item_next(windows+window_selection);
482 goto loop;
483 case KEY_UP:
484 window_item_previous(windows+window_selection);
485 goto loop;
486 case KEY_LEFT:
487 window_selection = select_window(windows, window_selection, 0);
488 goto loop;
489 case KEY_RIGHT:
490 window_selection = select_window(windows, window_selection, 1);
491 goto loop;
493 if (! want_refresh) goto loop;
494 refresh:
495 want_refresh = FALSE;
497 for(i = 0; i < 3; i++) {
498 if (windows[i].index > windows[i].count - 1) windows[i].index = 0;
499 wclear(windows[i].window_ptr);
502 goto loop;
503 quit:
504 cleanup_controls(windows);
505 quit_no_clean:
506 //jackspa_exit(state);
507 jack_deactivate(state.jack_client);
508 jack_client_close(state.jack_client);
509 qxit:
510 endwin();
512 return rc;