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