1 /*
2 
3     TiMidity -- Experimental MIDI to WAVE converter
4     Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 
20     ncurs_c.c
21 
22     */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <stdarg.h>
28 
29 #include <ncurses.h>
30 
31 #include "config.h"
32 #include "common.h"
33 #include "instrum.h"
34 #include "playmidi.h"
35 #include "output.h"
36 #include "controls.h"
37 
38 static void ctl_refresh(void);
39 static void ctl_help_mode(void);
40 static void ctl_total_time(int tt);
41 static void ctl_master_volume(int mv);
42 static void ctl_file_name(char *name);
43 static void ctl_current_time(int ct);
44 static void ctl_note(int v);
45 static void ctl_program(int ch, int val);
46 static void ctl_volume(int channel, int val);
47 static void ctl_expression(int channel, int val);
48 static void ctl_panning(int channel, int val);
49 static void ctl_sustain(int channel, int val);
50 static void ctl_pitch_bend(int channel, int val);
51 static void ctl_reset(void);
52 static int ctl_open(int using_stdin, int using_stdout);
53 static void ctl_close(void);
54 static int ctl_read(int32 *valp);
55 static int cmsg(int type, int verbosity_level, char *fmt, ...);
56 
57 /**********************************************/
58 /* export the interface functions */
59 
60 #define ctl ncurses_control_mode
61 
62 ControlMode ctl=
63 {
64   "ncurses interface", 'n',
65   1,0,0,
66   ctl_open,dumb_pass_playing_list, ctl_close, ctl_read, cmsg,
67   ctl_refresh, ctl_reset, ctl_file_name, ctl_total_time, ctl_current_time,
68   ctl_note,
69   ctl_master_volume, ctl_program, ctl_volume,
70   ctl_expression, ctl_panning, ctl_sustain, ctl_pitch_bend
71 };
72 
73 
74 /***********************************************************************/
75 /* foreground/background checks disabled since switching to curses */
76 /* static int in_foreground=1; */
77 static int ctl_helpmode=0;
78 
79 static WINDOW *dftwin=0, *msgwin=0;
80 
_ctl_refresh(void)81 static void _ctl_refresh(void)
82 {
83   wmove(dftwin, 0,0);
84   wrefresh(dftwin);
85 }
86 
ctl_refresh(void)87 static void ctl_refresh(void)
88 {
89   if (ctl.trace_playing)
90     _ctl_refresh();
91 }
92 
ctl_help_mode(void)93 static void ctl_help_mode(void)
94 {
95   static WINDOW *helpwin;
96   if (ctl_helpmode)
97     {
98       ctl_helpmode=0;
99       delwin(helpwin);
100       touchwin(dftwin);
101       _ctl_refresh();
102     }
103   else
104     {
105       ctl_helpmode=1;
106       /* And here I thought the point of curses was that you could put
107 	 stuff on windows without having to worry about whether this
108 	 one is overlapping that or the other way round... */
109       helpwin=newwin(2,COLS,0,0);
110       wattron(helpwin, A_REVERSE);
111       werase(helpwin);
112       waddstr(helpwin,
113 	      "V/Up=Louder    b/Left=Skip back      "
114 	      "n/Next=Next file      r/Home=Restart file");
115       wmove(helpwin, 1,0);
116       waddstr(helpwin,
117 	      "v/Down=Softer  f/Right=Skip forward  "
118 	      "p/Prev=Previous file  q/End=Quit program");
119       wrefresh(helpwin);
120     }
121 }
122 
ctl_total_time(int tt)123 static void ctl_total_time(int tt)
124 {
125   int mins, secs=tt/play_mode->rate;
126   mins=secs/60;
127   secs-=mins*60;
128 
129   wmove(dftwin, 4,6+6+3);
130   wattron(dftwin, A_BOLD);
131   wprintw(dftwin, "%3d:%02d", mins, secs);
132   wattroff(dftwin, A_BOLD);
133   _ctl_refresh();
134 }
135 
ctl_master_volume(int mv)136 static void ctl_master_volume(int mv)
137 {
138   wmove(dftwin, 4,COLS-5);
139   wattron(dftwin, A_BOLD);
140   wprintw(dftwin, "%03d %%", mv);
141   wattroff(dftwin, A_BOLD);
142   _ctl_refresh();
143 }
144 
ctl_file_name(char * name)145 static void ctl_file_name(char *name)
146 {
147   wmove(dftwin, 3,6);
148   wclrtoeol(dftwin );
149   wattron(dftwin, A_BOLD);
150   waddstr(dftwin, name);
151   wattroff(dftwin, A_BOLD);
152   _ctl_refresh();
153 }
154 
ctl_current_time(int ct)155 static void ctl_current_time(int ct)
156 {
157   int i,v;
158   int mins, secs=ct/play_mode->rate;
159   if (!ctl.trace_playing)
160     return;
161 
162   mins=secs/60;
163   secs-=mins*60;
164   wmove(dftwin, 4,6);
165   wattron(dftwin, A_BOLD);
166   wprintw(dftwin, "%3d:%02d", mins, secs);
167   v=0;
168   i=voices;
169   while (i--)
170     if (voice[i].status!=VOICE_FREE) v++;
171   wmove(dftwin, 4,48);
172   wprintw(dftwin, "%2d", v);
173   wattroff(dftwin, A_BOLD);
174   _ctl_refresh();
175 }
176 
ctl_note(int v)177 static void ctl_note(int v)
178 {
179   int xl;
180   if (!ctl.trace_playing)
181     return;
182   xl=voice[v].note%(COLS-24);
183   wmove(dftwin, 8+voice[v].channel,xl+3);
184   switch(voice[v].status)
185     {
186     case VOICE_DIE:
187       waddch(dftwin, ',');
188       break;
189     case VOICE_FREE:
190       waddch(dftwin, '.');
191       break;
192     case VOICE_ON:
193       wattron(dftwin, A_BOLD);
194       waddch(dftwin, '0'+(10*voice[v].velocity)/128);
195       wattroff(dftwin, A_BOLD);
196       break;
197     case VOICE_OFF:
198     case VOICE_SUSTAINED:
199       waddch(dftwin, '0'+(10*voice[v].velocity)/128);
200       break;
201     }
202 }
203 
ctl_program(int ch,int val)204 static void ctl_program(int ch, int val)
205 {
206   if (!ctl.trace_playing)
207     return;
208   wmove(dftwin, 8+ch, COLS-20);
209   if (ISDRUMCHANNEL(ch))
210     {
211       wattron(dftwin, A_BOLD);
212       wprintw(dftwin, "%03d", val);
213       wattroff(dftwin, A_BOLD);
214     }
215   else
216     wprintw(dftwin, "%03d", val);
217 }
218 
ctl_volume(int channel,int val)219 static void ctl_volume(int channel, int val)
220 {
221   if (!ctl.trace_playing)
222     return;
223   wmove(dftwin, 8+channel, COLS-16);
224   wprintw(dftwin, "%3d", (val*100)/127);
225 }
226 
ctl_expression(int channel,int val)227 static void ctl_expression(int channel, int val)
228 {
229   if (!ctl.trace_playing)
230     return;
231   wmove(dftwin, 8+channel, COLS-12);
232   wprintw(dftwin, "%3d", (val*100)/127);
233 }
234 
ctl_panning(int channel,int val)235 static void ctl_panning(int channel, int val)
236 {
237   if (!ctl.trace_playing)
238     return;
239   wmove(dftwin, 8+channel, COLS-8);
240   if (val==NO_PANNING)
241     waddstr(dftwin, "   ");
242   else if (val<5)
243     waddstr(dftwin, " L ");
244   else if (val>123)
245     waddstr(dftwin, " R ");
246   else if (val>60 && val<68)
247     waddstr(dftwin, " C ");
248   else
249     {
250       /* wprintw(dftwin, "%+02d", (100*(val-64))/64); */
251       val = (100*(val-64))/64; /* piss on curses */
252       if (val<0)
253 	{
254 	  waddch(dftwin, '-');
255 	  val=-val;
256 	}
257       else waddch(dftwin, '+');
258       wprintw(dftwin, "%02d", val);
259     }
260 }
261 
ctl_sustain(int channel,int val)262 static void ctl_sustain(int channel, int val)
263 {
264   if (!ctl.trace_playing)
265     return;
266   wmove(dftwin, 8+channel, COLS-4);
267   if (val) waddch(dftwin, 'S');
268   else waddch(dftwin, ' ');
269 }
270 
ctl_pitch_bend(int channel,int val)271 static void ctl_pitch_bend(int channel, int val)
272 {
273   if (!ctl.trace_playing)
274     return;
275   wmove(dftwin, 8+channel, COLS-2);
276   if (val>0x2000) waddch(dftwin, '+');
277   else if (val<0x2000) waddch(dftwin, '-');
278   else waddch(dftwin, ' ');
279 }
280 
ctl_reset(void)281 static void ctl_reset(void)
282 {
283   int i,j;
284   if (!ctl.trace_playing)
285     return;
286   for (i=0; i<16; i++)
287     {
288       wmove(dftwin, 8+i, 3);
289       for (j=0; j<COLS-24; j++)
290 	waddch(dftwin, '.');
291       ctl_program(i, channel[i].program);
292       ctl_volume(i, channel[i].volume);
293       ctl_expression(i, channel[i].expression);
294       ctl_panning(i, channel[i].panning);
295       ctl_sustain(i, channel[i].sustain);
296       ctl_pitch_bend(i, channel[i].pitchbend);
297     }
298   _ctl_refresh();
299 }
300 
301 /***********************************************************************/
302 
303 /* #define CURSED_REDIR_HACK */
304 
305 #ifdef CURSED_REDIR_HACK
306 static SCREEN *oldscr;
307 #endif
308 
ctl_open(int using_stdin,int using_stdout)309 static int ctl_open(int using_stdin, int using_stdout)
310 {
311   int i;
312 #ifdef CURSED_REDIR_HACK
313   FILE *infp=stdin, *outfp=stdout;
314   SCREEN *dftscr;
315 
316   /* This doesn't work right */
317   if (using_stdin && using_stdout)
318     {
319       infp=outfp=stderr;
320       fflush(stderr);
321       setvbuf(stderr, 0, _IOFBF, BUFSIZ);
322     }
323   else if (using_stdout)
324     {
325       outfp=stderr;
326       fflush(stderr);
327       setvbuf(stderr, 0, _IOFBF, BUFSIZ);
328     }
329   else if (using_stdin)
330     {
331       infp=stdout;
332       fflush(stdout);
333       setvbuf(stdout, 0, _IOFBF, BUFSIZ);
334     }
335 
336   dftscr=newterm(0, outfp, infp);
337   if (!dftscr)
338     return -1;
339   oldscr=set_term(dftscr);
340   /* dftwin=stdscr; */
341 #else
342   initscr();
343 #endif
344 
345   cbreak();
346   noecho();
347   nonl();
348   nodelay(stdscr, 1);
349   scrollok(stdscr, 0);
350   idlok(stdscr, 1);
351   keypad(stdscr, TRUE);
352   ctl.opened=1;
353 
354   if (ctl.trace_playing)
355     dftwin=stdscr;
356   else
357     dftwin=newwin(6,COLS,0,0);
358 
359   werase(dftwin);
360   wmove(dftwin, 0,0);
361   waddstr(dftwin, "TiMidity v" TIMID_VERSION);
362   wmove(dftwin, 0,COLS-52);
363   waddstr(dftwin, "(C) 1995 Tuukka Toivonen <toivonen@clinet.fi>");
364   wmove(dftwin, 1,0);
365   waddstr(dftwin, "Press 'h' for help with keys, or 'q' to quit.");
366   wmove(dftwin, 3,0);
367   waddstr(dftwin, "File:");
368   wmove(dftwin, 4,0);
369   if (ctl.trace_playing)
370     {
371       waddstr(dftwin, "Time:");
372       wmove(dftwin, 4,6+6+1);
373       waddch(dftwin, '/');
374       wmove(dftwin, 4,40);
375       wprintw(dftwin, "Voices:    / %d", voices);
376     }
377   else
378     {
379       waddstr(dftwin, "Playing time:");
380     }
381   wmove(dftwin, 4,COLS-20);
382   waddstr(dftwin, "Master volume:");
383   wmove(dftwin, 5,0);
384   for (i=0; i<COLS; i++)
385     waddch(dftwin, '_');
386   if (ctl.trace_playing)
387     {
388       wmove(dftwin, 6,0);
389       waddstr(dftwin, "Ch");
390       wmove(dftwin, 6,COLS-20);
391       waddstr(dftwin, "Prg Vol Exp Pan S B");
392       wmove(dftwin, 7,0);
393       for (i=0; i<COLS; i++)
394 	waddch(dftwin, '-');
395       for (i=0; i<16; i++)
396 	{
397 	  wmove(dftwin, 8+i, 0);
398 	  wprintw(dftwin, "%02d", i+1);
399 	}
400     }
401   else
402     {
403       msgwin=newwin(LINES-6,COLS,6,0);
404       werase(msgwin);
405       scrollok(msgwin, 1);
406       wrefresh(msgwin);
407     }
408   _ctl_refresh();
409 
410   return 0;
411 }
412 
ctl_close(void)413 static void ctl_close(void)
414 {
415   if (ctl.opened)
416     {
417       endwin();
418       ctl.opened=0;
419     }
420 }
421 
ctl_read(int32 * valp)422 static int ctl_read(int32 *valp)
423 {
424   int c;
425   while ((c=getch())!=ERR)
426     {
427       switch(c)
428 	{
429 	case 'h':
430 	case '?':
431 	case KEY_F(1):
432 	  ctl_help_mode();
433 	  return RC_NONE;
434 
435 	case 'V':
436 	case KEY_UP:
437 	  *valp=10;
438 	  return RC_CHANGE_VOLUME;
439 	case 'v':
440 	case KEY_DOWN:
441 	  *valp=-10;
442 	  return RC_CHANGE_VOLUME;
443 	case 'q':
444 	case KEY_END:
445 	  return RC_QUIT;
446 	case 'n':
447 	case KEY_NPAGE:
448 	  return RC_NEXT;
449 	case 'p':
450 	case KEY_PPAGE:
451 	  return RC_REALLY_PREVIOUS;
452 	case 'r':
453 	case KEY_HOME:
454 	  return RC_RESTART;
455 
456 	case 'f':
457 	case KEY_RIGHT:
458 	  *valp=play_mode->rate;
459 	  return RC_FORWARD;
460 	case 'b':
461 	case KEY_LEFT:
462 	  *valp=play_mode->rate;
463 	  return RC_BACK;
464 	  /* case ' ':
465 	     return RC_TOGGLE_PAUSE; */
466 	}
467     }
468   return RC_NONE;
469 }
470 
cmsg(int type,int verbosity_level,char * fmt,...)471 static int cmsg(int type, int verbosity_level, char *fmt, ...)
472 {
473   va_list ap;
474   if ((type==CMSG_TEXT || type==CMSG_INFO || type==CMSG_WARNING) &&
475       ctl.verbosity<verbosity_level)
476     return 0;
477   va_start(ap, fmt);
478   if (!ctl.opened)
479     {
480       vfprintf(stderr, fmt, ap);
481       fprintf(stderr, "\n");
482     }
483   else if (ctl.trace_playing)
484     {
485       switch(type)
486 	{
487 	  /* Pretty pointless to only have one line for messages, but... */
488 	case CMSG_WARNING:
489 	case CMSG_ERROR:
490 	case CMSG_FATAL:
491 	  wmove(dftwin, 2,0);
492 	  wclrtoeol(dftwin);
493 	  wattron(dftwin, A_REVERSE);
494 	  vwprintw(dftwin, fmt, ap);
495 	  wattroff(dftwin, A_REVERSE);
496 	  _ctl_refresh();
497 	  if (type==CMSG_WARNING)
498 	    sleep(1); /* Don't you just _HATE_ it when programs do this... */
499 	  else
500 	    sleep(2);
501 	  wmove(dftwin, 2,0);
502 	  wclrtoeol(dftwin);
503 	  _ctl_refresh();
504 	  break;
505 	}
506     }
507   else
508     {
509       switch(type)
510 	{
511 	default:
512 	  vwprintw(msgwin, fmt, ap);
513 	  wprintw(msgwin, "\n");
514 	  wrefresh(msgwin);
515 	  break;
516 
517 	case CMSG_WARNING:
518 	  wattron(msgwin, A_BOLD);
519 	  vwprintw(msgwin, fmt, ap);
520 	  wprintw(msgwin, "\n");
521 	  wattroff(msgwin, A_BOLD);
522 	  wrefresh(msgwin);
523 	  break;
524 
525 	case CMSG_ERROR:
526 	case CMSG_FATAL:
527 	  wattron(msgwin, A_REVERSE);
528 	  vwprintw(msgwin, fmt, ap);
529 	  wprintw(msgwin, "\n");
530 	  wattroff(msgwin, A_REVERSE);
531 	  wrefresh(msgwin);
532 	  if (type==CMSG_FATAL)
533 	    sleep(2);
534 	  break;
535 	}
536     }
537 
538   va_end(ap);
539   return 0;
540 }
541