1 /* rexima 1.4 - a curses-based (and command-line) mixer for Linux.
2 * Copyright (C) 1996-2003 Russell Marks.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <curses.h>
25 #include <sys/ioctl.h>
26 #include <sys/soundcard.h>
27
28
29 #define REXIMA_VER "1.4"
30
31
32 #define DEV_Y_START 3
33 #define DEV_X_START 4
34 #define DEV_X_DEVEND (DEV_X_START+9)
35 #define DEV_X_BAR (DEV_X_DEVEND+3)
36 #define DEV_X_PCNT (DEV_X_BAR+55)
37 #define DEV_X_REC (DEV_X_PCNT+5)
38
39
40 char dev_labels[SOUND_MIXER_NRDEVICES][80]=SOUND_DEVICE_LABELS;
41 char dev_names [SOUND_MIXER_NRDEVICES][80]=SOUND_DEVICE_NAMES;
42 int dev_line[SOUND_MIXER_NRDEVICES];
43
44
45 char *mixerdev="/dev/mixer";
46
47
48 /* equivalents of optopt, opterr, optind, and optarg */
49 int optnopt=0,optnerr=0,optnind=1;
50 char *optnarg=NULL;
51
52 /* holds offset in current argv[] value */
53 static int optnpos=1;
54
55
56 /* This routine assumes that the caller is pretty sane and doesn't
57 * try passing an invalid 'optstring' or varying argc/argv.
58 */
getoptn(int argc,char * argv[],char * optstring)59 int getoptn(int argc,char *argv[],char *optstring)
60 {
61 char *ptr;
62
63 /* check for end of arg list */
64 if(optnind==argc || *(argv[optnind])!='-' || strlen(argv[optnind])<=1)
65 return(-1);
66
67 if((ptr=strchr(optstring,argv[optnind][optnpos]))==NULL)
68 return('?'); /* error: unknown option */
69 else
70 {
71 optnopt=*ptr;
72 if(ptr[1]==':')
73 {
74 if(optnind==argc-1) return(':'); /* error: missing option */
75 optnarg=argv[optnind+1];
76 optnpos=1;
77 optnind+=2;
78 return(optnopt); /* return early, avoiding the normal increment */
79 }
80 }
81
82 /* now increment position ready for next time.
83 * no checking is done for the end of args yet - this is done on
84 * the next call.
85 */
86 optnpos++;
87 if(optnpos>strlen(argv[optnind]))
88 {
89 optnpos=1;
90 optnind++;
91 }
92
93 return(optnopt); /* return the found option */
94 }
95
96
97
die(char * str)98 void die(char *str)
99 {
100 fprintf(stderr,"rexima: couldn't %s.\n",str);
101 exit(1);
102 }
103
104
init(int * mixfd,int * existmask,int * canrecmask,int * isrecmask,int * stereomask)105 void init(int *mixfd,int *existmask,int *canrecmask,int *isrecmask,
106 int *stereomask)
107 {
108 if(((*mixfd)=open(mixerdev,O_RDWR))<0) die("open mixer device");
109 if(ioctl(*mixfd,SOUND_MIXER_READ_DEVMASK,existmask)==-1) die("ioctl");
110 if(ioctl(*mixfd,SOUND_MIXER_READ_RECMASK,canrecmask)==-1) die("ioctl");
111 if(ioctl(*mixfd,SOUND_MIXER_READ_RECSRC,isrecmask)==-1) die("ioctl");
112 /* this looks like a `recent' addition [in 1996 maybe ;-)] so be lenient */
113 if(ioctl(*mixfd,SOUND_MIXER_READ_STEREODEVS,stereomask)==-1) *stereomask=0;
114 }
115
116
init_term()117 void init_term()
118 {
119 initscr(); cbreak(); noecho();
120 keypad(stdscr,TRUE);
121 }
122
123
uninit(int mixfd)124 void uninit(int mixfd)
125 {
126 clear(); refresh();
127 echo(); nocbreak(); endwin();
128 putchar('\n');
129 close(mixfd);
130 }
131
132
drawsel(int new,int old)133 void drawsel(int new,int old)
134 {
135 if(new!=old)
136 {
137 if(old>=0)
138 {
139 mvaddstr(DEV_Y_START+dev_line[old],DEV_X_START-3," ");
140 mvaddstr(DEV_Y_START+dev_line[old],DEV_X_DEVEND," ");
141 }
142
143 if(new>=0)
144 {
145 mvaddstr(DEV_Y_START+dev_line[new],DEV_X_START-3,"->");
146 mvaddstr(DEV_Y_START+dev_line[new],DEV_X_DEVEND,"<-");
147 }
148 }
149
150 /* this'll be the last thing before a refresh, so... */
151 move(DEV_Y_START+dev_line[new],0);
152 }
153
154
mixer_getlevel_stereo(int mixfd,int dev)155 int mixer_getlevel_stereo(int mixfd,int dev)
156 {
157 int level=0;
158
159 ioctl(mixfd,MIXER_READ(dev),&level);
160 return(level);
161 }
162
163
mixer_getlevel(int mixfd,int dev)164 int mixer_getlevel(int mixfd,int dev)
165 {
166 return(mixer_getlevel_stereo(mixfd,dev)&255);
167 }
168
169
mixer_setlevel_stereo(int mixfd,int dev,int left,int right)170 void mixer_setlevel_stereo(int mixfd,int dev,int left,int right)
171 {
172 left+=256*right;
173 ioctl(mixfd,MIXER_WRITE(dev),&left);
174 }
175
176
mixer_setlevel(int mixfd,int dev,int level)177 void mixer_setlevel(int mixfd,int dev,int level)
178 {
179 mixer_setlevel_stereo(mixfd,dev,level,level);
180 }
181
182
mixer_change(int mixfd,int dev,int add)183 void mixer_change(int mixfd,int dev,int add)
184 {
185 int level=mixer_getlevel(mixfd,dev)+add;
186
187 if(level<0) level=0;
188 if(level>100) level=100;
189 mixer_setlevel(mixfd,dev,level);
190 }
191
192
mixer_rectoggle(int mixfd,int dev,int * isrecmask)193 void mixer_rectoggle(int mixfd,int dev,int *isrecmask)
194 {
195 (*isrecmask)^=(1<<dev);
196 ioctl(mixfd,SOUND_MIXER_WRITE_RECSRC,isrecmask);
197 }
198
199
mixer_recset(int mixfd,int dev,int * isrecmask,int on)200 void mixer_recset(int mixfd,int dev,int *isrecmask,int on)
201 {
202 if(on)
203 (*isrecmask)|= (1<<dev);
204 else
205 (*isrecmask)&=~(1<<dev);
206 ioctl(mixfd,SOUND_MIXER_WRITE_RECSRC,isrecmask);
207 }
208
209
drawlevel(int dev,int level)210 void drawlevel(int dev,int level)
211 {
212 char buf[60];
213 int f;
214
215 /* sanity check */
216 if(level<0) level=0;
217 if(level>100) level=100;
218
219 memset(buf,'=',51);
220 buf[51]=0;
221 buf[level/2]='|';
222 for(f=level/2+1;f<51;f++)
223 buf[f]='-';
224
225 mvaddstr(DEV_Y_START+dev_line[dev],DEV_X_BAR+1,buf);
226
227 sprintf(buf,"%3d%%",level);
228 mvaddstr(DEV_Y_START+dev_line[dev],DEV_X_PCNT,buf);
229 }
230
231
drawrec(int dev,int on)232 void drawrec(int dev,int on)
233 {
234 mvaddch(DEV_Y_START+dev_line[dev],DEV_X_REC+1,on?'R':' ');
235 }
236
237
setupframe(int existmask,int canrecmask,int * firstdevp,int * lastdevp)238 void setupframe(int existmask,int canrecmask,int *firstdevp,int *lastdevp)
239 {
240 int f;
241 int offset=0;
242
243 mvaddstr(0,36,"rexima");
244 mvaddstr(DEV_Y_START-1,DEV_X_BAR+1,
245 "min . . . . : . . . . max");
246
247 /* we know existmask is non-zero by now (see main()) */
248 for(f=0;f<SOUND_MIXER_NRDEVICES;f++)
249 {
250 if(existmask&(1<<f))
251 {
252 if(offset==0) *firstdevp=f;
253 *lastdevp=f;
254 dev_line[f]=offset++;
255 mvaddstr(DEV_Y_START+dev_line[f],DEV_X_START,dev_labels[f]);
256 mvaddch(DEV_Y_START+dev_line[f],DEV_X_BAR,'[');
257 mvaddch(DEV_Y_START+dev_line[f],DEV_X_BAR+52,']');
258 if(canrecmask&(1<<f))
259 mvaddstr(DEV_Y_START+dev_line[f],DEV_X_REC,"[ ]");
260 }
261 }
262
263 mvaddstr(LINES-1,2,
264 "< move/alter with hjkl/cursors; space to toggle rec src; "
265 "Esc/q/x to quit >");
266 }
267
268
269
usage_help(int existmask)270 void usage_help(int existmask)
271 {
272 int f,count;
273
274 puts(
275 "rexima " REXIMA_VER " - copyright (c) 1996-2003 Russell Marks.\n"
276 "\n"
277 "usage: rexima [-hv] [-d mixer_device_file]\n"
278 "\t\t [device <level | offset | left,right | <rec | norec>>\n"
279 "\t\t [device ...]]\n"
280 "\n"
281 " -d specify mixer device file to use (ordinarily /dev/mixer).\n"
282 " -h give this usage help.\n"
283 " -v show current mixer settings.\n");
284 printf(
285 " device a device to set the level of. %s\n",mixerdev);
286 printf("\t\twill allow the levels of these devices to be set:\n\n\t\t");
287
288 count=0;
289 for(f=0;f<SOUND_MIXER_NRDEVICES;f++)
290 {
291 if(existmask&(1<<f))
292 printf("%s ",dev_names[f]),count++;
293 if(count>=7) printf("\n\t\t"),count=0;
294 }
295
296 puts(
297 "\n\n"
298 " level level to set specified device to.\n"
299 " offset amount to change level by (e.g. `-3', `+12').\n"
300 " left,right set (stereo) device's level with independent left/right values.\n"
301 " rec | norec `rec' makes device a recording source, `norec' makes it, well,\n"
302 " not a recording source. :-)\n"
303 "\n"
304 "If invoked without any args (with the exception of `-d'), rexima runs\n"
305 "interactively.");
306 }
307
308
309 /* on entry, we know argc>=2 */
cmdline_main(int argc,char * argv[])310 void cmdline_main(int argc,char *argv[])
311 {
312 int mixfd;
313 int existmask,canrecmask,isrecmask,stereomask;
314 int f,tmp,l,r;
315 int found;
316 char *ptr;
317 int done=0,want_usage=0,want_levels=0;
318
319 do
320 switch(getoptn(argc,argv,"d:hvV"))
321 {
322 case 'd': /* mixer device */
323 if((mixerdev=malloc(strlen(optnarg)+1))==NULL)
324 die("allocate memory");
325 strcpy(mixerdev,optnarg);
326 break;
327 case 'h':
328 want_usage=1;
329 break;
330 case 'v': case 'V': /* show levels */
331 want_levels=1;
332 break;
333 case '?':
334 switch(optnopt)
335 {
336 case 'd':
337 fprintf(stderr,"rexima: "
338 "the `-d' option needs a mixer device to be specified.\n");
339 break;
340 default:
341 fprintf(stderr,"rexima: option `%c' not recognised.\n",optnopt);
342 }
343 exit(1);
344 case -1:
345 done=1;
346 }
347 while(!done);
348
349
350 if(want_usage)
351 {
352 init(&mixfd,&existmask,&canrecmask,&isrecmask,&stereomask);
353
354 usage_help(existmask);
355
356 exit(0);
357 }
358
359 if(want_levels)
360 {
361 init(&mixfd,&existmask,&canrecmask,&isrecmask,&stereomask);
362
363 for(f=0;f<SOUND_MIXER_NRDEVICES;f++)
364 if(existmask&(1<<f))
365 {
366 tmp=mixer_getlevel_stereo(mixfd,f);
367 l=(tmp&255); r=((tmp>>8)&255);
368 printf("%s\t%3d",dev_names[f],l);
369 if(stereomask&(1<<f)) printf(",%3d",r);
370 if(canrecmask&(1<<f))
371 printf("\t[%c]",(isrecmask&(1<<f))?'R':' ');
372 putchar('\n');
373 }
374
375 exit(0);
376 }
377
378 /* if there aren't any "device, setting" pairs, return (without init). */
379 if(optnind>=argc)
380 return;
381
382 /* otherwise, init - we have one or more pairs to deal with. */
383
384 init(&mixfd,&existmask,&canrecmask,&isrecmask,&stereomask);
385
386 while(optnind<argc)
387 {
388 /* lookup argv[1] in dev_names[] */
389 found=0;
390 for(f=0;f<SOUND_MIXER_NRDEVICES;f++)
391 if(existmask&(1<<f) && strcmp(argv[optnind],dev_names[f])==0)
392 {
393 found=1;
394 break;
395 }
396
397 if(!found)
398 {
399 fprintf(stderr,"rexima: unavailable or unknown device `%s'.\n",argv[optnind]);
400 exit(1);
401 }
402
403 if(argc-optnind<2)
404 {
405 fprintf(stderr,"rexima: no setting specified for device `%s'.\n",argv[optnind]);
406 exit(1);
407 }
408
409 optnind++;
410
411 tmp=f; /* save dev no. */
412
413 /* setting can be mono, stereo, or rec/norec.
414 * first see if it's rec or norec:
415 */
416 if(strcmp(argv[optnind],"rec")==0 || strcmp(argv[optnind],"norec")==0)
417 {
418 if(!(canrecmask&(1<<f)))
419 {
420 fprintf(stderr,"rexima: can't set rec/norec on device `%s'.\n",
421 argv[optnind-1]);
422 exit(1);
423 }
424
425 mixer_recset(mixfd,tmp,&isrecmask,(argv[optnind][0]=='r'));
426 }
427 else
428 {
429 int offset_sign=0;
430
431 /* see if it's an offset */
432 if(argv[optnind][0]=='+')
433 offset_sign=1;
434 if(argv[optnind][0]=='-')
435 offset_sign=-1;
436
437 /* be sure to puke if level contains funny chars */
438 for(f=(offset_sign?1:0);f<strlen(argv[optnind]);f++)
439 if(strchr("0123456789, \t",argv[optnind][f])==NULL)
440 {
441 fprintf(stderr,"rexima: invalid level for device `%s'.\n",
442 argv[optnind-1]);
443 exit(1);
444 }
445
446 /* now check for stereo */
447 if((ptr=strchr(argv[optnind],','))!=NULL)
448 {
449 if(offset_sign)
450 {
451 fprintf(stderr,"rexima: level offset must be mono, not stereo.\n");
452 exit(1);
453 }
454
455 l=atoi(argv[optnind]);
456 r=atoi(ptr+1);
457 mixer_setlevel_stereo(mixfd,tmp,l,r);
458 }
459 else
460 {
461 /* must be mono; default to 2 for an offset with no size specified. */
462 if(offset_sign)
463 mixer_change(mixfd,tmp,
464 (strlen(argv[optnind])<2)?
465 2*offset_sign:atoi(argv[optnind]));
466 else
467 mixer_setlevel(mixfd,tmp,atoi(argv[optnind]));
468 }
469 }
470
471 optnind++;
472 }
473
474 exit(0);
475 }
476
477
478
main(int argc,char * argv[])479 int main(int argc,char *argv[])
480 {
481 int quit=0;
482 int cursel; /* currently selected device */
483 int oldsel;
484 int mixfd;
485 int key;
486 int existmask,canrecmask,isrecmask,stereomask;
487 int firstdev,lastdev;
488 int f;
489
490 if(argc>1)
491 cmdline_main(argc,argv); /* returns (without init) if we're interactive */
492
493 init(&mixfd,&existmask,&canrecmask,&isrecmask,&stereomask);
494
495 /* later things assume existmask is non-zero, so... */
496 if(!existmask)
497 fprintf(stderr,"rexima: mixer has no devices!\n"),exit(1);
498
499 init_term();
500 setupframe(existmask,canrecmask,&firstdev,&lastdev);
501 cursel=firstdev;
502
503 for(f=0;f<SOUND_MIXER_NRDEVICES;f++)
504 if(existmask&(1<<f))
505 {
506 drawlevel(f,mixer_getlevel(mixfd,f));
507 if(canrecmask&(1<<f))
508 drawrec(f,isrecmask&(1<<f));
509 }
510
511 drawsel(cursel,-1);
512 refresh();
513
514 while(!quit)
515 {
516 oldsel=cursel;
517
518 key=getch();
519
520 switch(key)
521 {
522 case 'q': case 'x': case 27:
523 quit=1; break;
524 case 'k': case KEY_UP:
525 do
526 cursel--;
527 while(cursel>=firstdev && !(existmask&(1<<cursel)));
528 if(cursel<firstdev) cursel=lastdev;
529 break;
530 case 'j': case KEY_DOWN:
531 do
532 cursel++;
533 while(cursel<=lastdev && !(existmask&(1<<cursel)));
534 if(cursel>lastdev) cursel=firstdev;
535 break;
536 case 'h': case 'H': case KEY_LEFT: case '-':
537 if(existmask&(1<<cursel))
538 {
539 mixer_change(mixfd,cursel,(key=='H')?-1:-2);
540 drawlevel(cursel,mixer_getlevel(mixfd,cursel));
541 }
542 break;
543 case 'l': case 'L': case KEY_RIGHT: case '+': case '=':
544 if(existmask&(1<<cursel))
545 {
546 mixer_change(mixfd,cursel,(key=='L')?1:2);
547 drawlevel(cursel,mixer_getlevel(mixfd,cursel));
548 }
549 break;
550 case ' ':
551 if(canrecmask&(1<<cursel))
552 {
553 mixer_rectoggle(mixfd,cursel,&isrecmask);
554 drawrec(cursel,isrecmask&(1<<cursel));
555 }
556 break;
557 case 'R'-0x40: case 'L'-0x40: /* ^R and ^L */
558 clearok(curscr,TRUE);
559 break;
560 }
561
562 drawsel(cursel,oldsel);
563
564 refresh();
565 }
566
567 uninit(mixfd);
568 exit(0);
569 }
570