1 /*
2  * Copyright (C) 1997-2004 Kare Sjolander <kare@speech.kth.se>
3  *
4  * This file is part of the Snack Sound Toolkit.
5  * The latest version can be found at http://www.speech.kth.se/snack/
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 
22 #include "tcl.h"
23 #include "jkAudIO.h"
24 #include "jkSound.h"
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <sys/ioctl.h>
29 #ifdef __NetBSD__
30  #include <soundcard.h>
31 #else /* OSS default */
32  #include <sys/soundcard.h>
33 #endif
34 #include <string.h>
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <glob.h>
38 #define DEVICE_NAME "/dev/dsp"
39 #define MIXER_NAME  "/dev/mixer"
40 static char *defaultDeviceName = DEVICE_NAME;
41 extern void Snack_WriteLog(char *s);
42 extern void Snack_WriteLogInt(char *s, int n);
43 
44 #ifndef min
45 #define min(a,b) ((a)<(b)?(a):(b))
46 #define max(a,b) ((a)>(b)?(a):(b))
47 #endif
48 
49 static int mfd = 0;
50 
51 static struct MixerLink mixerLinks[SOUND_MIXER_NRDEVICES][2];
52 
53 static int littleEndian = 0;
54 
55 static int minNumChan = 1;
56 
57 int
58 SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq,
59 	       int nchannels, int encoding)
60 {
61   int format;
62   int nformat;
63   int channels;
64   int speed;
65   int mask;
66   /*  int frag = 0x7fff0010;*/
67 
68   if (A->debug > 1) Snack_WriteLog("  Enter SnackAudioOpen\n");
69 
70   if (device == NULL) {
71     device = defaultDeviceName;
72   }
73   if (strlen(device) == 0) {
74     device = defaultDeviceName;
75   }
76 
77   /* Test if the device is not locked by another process. This is
78    * just a crude workaround to avoid complete lockup of snack. It is
79    * not perfect since it is theoretically possible that another program
80    * locks the device between the close() and open() calls below. */
81   A->afd = open(device, O_WRONLY|O_NONBLOCK);
82   if(A->afd == -1) {
83     Tcl_AppendResult(interp, "Could not gain access to ", device, " for writing.",NULL);
84     return TCL_ERROR;
85   }
86   close(A->afd);
87 
88   A->mode = mode;
89   switch (mode) {
90   case RECORD:
91     if ((A->afd = open(device, O_RDONLY, 0)) == -1) {
92       Tcl_AppendResult(interp, "Could not open ", device, " for read.",
93 		       NULL);
94       return TCL_ERROR;
95     }
96     break;
97 
98   case PLAY:
99     if ((A->afd = open(device, O_WRONLY, 0)) == -1) {
100       Tcl_AppendResult(interp, "Could not open ", device, " for write.",
101 		       NULL);
102       return TCL_ERROR;
103     }
104     break;
105   }
106 
107   fcntl(A->afd, F_SETFD, FD_CLOEXEC);
108 
109   /*  if (ioctl(A->afd, SNDCTL_DSP_SETFRAGMENT, &frag)) return(-1);*/
110 
111   if (ioctl(A->afd, SNDCTL_DSP_GETFMTS, &mask) == -1) {
112     close(A->afd);
113     Tcl_AppendResult(interp, "Failed getting formats.", NULL);
114     return TCL_ERROR;
115   }
116 
117   A->convert = 0;
118 
119   switch (encoding) {
120   case LIN16:
121     if (littleEndian) {
122       format = AFMT_S16_LE;
123     } else {
124       format = AFMT_S16_BE;
125     }
126     A->bytesPerSample = sizeof(short);
127     break;
128 #ifdef AFMT_S32_LE
129   case LIN24:
130     if (littleEndian) {
131       format = AFMT_S32_LE;
132     } else {
133       format = AFMT_S32_BE;
134     }
135     A->bytesPerSample = sizeof(int);
136     break;
137 #endif
138   case ALAW:
139     if (mask & AFMT_A_LAW) {
140       format = AFMT_A_LAW;
141       A->bytesPerSample = sizeof(char);
142     } else {
143       if (littleEndian) {
144 	format = AFMT_S16_LE;
145       } else {
146 	format = AFMT_S16_BE;
147       }
148       A->bytesPerSample = sizeof(short);
149       A->convert = ALAW;
150     }
151     break;
152   case MULAW:
153     if (mask & AFMT_MU_LAW) {
154       format = AFMT_MU_LAW;
155       A->bytesPerSample = sizeof(char);
156     } else {
157       if (littleEndian) {
158 	format = AFMT_S16_LE;
159       } else {
160 	format = AFMT_S16_BE;
161       }
162       A->bytesPerSample = sizeof(short);
163       A->convert = MULAW;
164     }
165     break;
166   case LIN8OFFSET:
167     format = AFMT_U8;
168     A->bytesPerSample = sizeof(char);
169     break;
170   case LIN8:
171     format = AFMT_S8;
172     A->bytesPerSample = sizeof(char);
173     break;
174   }
175 
176   nformat = format;
177   if (ioctl(A->afd, SNDCTL_DSP_SETFMT, &format) == -1
178       || format != nformat) {
179     close(A->afd);
180     Tcl_AppendResult(interp, "Failed setting format.", NULL);
181     return TCL_ERROR;
182   }
183 
184   A->nChannels = nchannels;
185   channels = nchannels;
186   if (ioctl(A->afd, SNDCTL_DSP_CHANNELS, &channels) == -1
187       || channels != nchannels) {
188     close(A->afd);
189     Tcl_AppendResult(interp, "Failed setting number of channels.", NULL);
190     return TCL_ERROR;
191   }
192 
193   speed = freq;
194   if (ioctl(A->afd, SNDCTL_DSP_SPEED, &speed) == -1
195       || abs(speed - freq) > freq / 100) {
196     close(A->afd);
197     Tcl_AppendResult(interp, "Failed setting sample frequency.", NULL);
198     return TCL_ERROR;
199   }
200 
201   /*  A->count = 0;*/
202   A->frag_size = 0;
203   if (ioctl(A->afd, SNDCTL_DSP_GETBLKSIZE, &A->frag_size) == -1) {
204     close(A->afd);
205     Tcl_AppendResult(interp, "Failed getting fragment size.", NULL);
206     return TCL_ERROR;
207   }
208   /*    printf("Frag size: %d\n",  A->frag_size);*/
209   A->time = SnackCurrentTime();
210   A->timep = 0.0;
211   A->freq = freq;
212   A->warm = 0;
213 
214   if (A->debug > 1) Snack_WriteLogInt("  Exit SnackAudioOpen", A->frag_size);
215 
216   return TCL_OK;
217 }
218 
219 int
220 SnackAudioClose(ADesc *A)
221 {
222   if (A->debug > 1) Snack_WriteLog("  Enter SnackAudioClose\n");
223 
224   /*  A->count = 0;*/
225 
226   close(A->afd);
227 
228   if (A->debug > 1) Snack_WriteLog("  Exit SnackAudioClose\n");
229 
230   return(0);
231 }
232 
233 long
234 SnackAudioPause(ADesc *A)
235 {
236   long res = SnackAudioPlayed(A);
237 
238   A->timep = SnackCurrentTime();
239   ioctl(A->afd, SNDCTL_DSP_RESET, 0);
240 
241   return(res);
242 }
243 
244 void
245 SnackAudioResume(ADesc *A)
246 {
247   A->time = A->time + SnackCurrentTime() - A->timep;
248 }
249 
250 void
251 SnackAudioFlush(ADesc *A)
252 {
253   if (A->mode == RECORD) {
254   } else {
255     ioctl(A->afd, SNDCTL_DSP_RESET, 0);
256   }
257 }
258 
259 static char zeroBlock[16];
260 
261 void
262 SnackAudioPost(ADesc *A)
263 {
264   if (A->debug > 1) Snack_WriteLog("  Enter SnackAudioPost\n");
265 
266   if (A->warm == 1) {
267     int i;
268     for (i = 0; i < A->frag_size / (A->bytesPerSample * A->nChannels); i++) {
269       write(A->afd, zeroBlock, A->bytesPerSample * A->nChannels);
270     }
271     A->warm = 2;
272     ioctl(A->afd, SNDCTL_DSP_POST, 0);
273   }
274 
275   if (A->debug > 1) Snack_WriteLog("  Exit SnackAudioPost\n");
276 }
277 
278 int
279 SnackAudioRead(ADesc *A, void *buf, int nFrames)
280 {
281   int n = 2;
282 
283   if (A->debug > 1) Snack_WriteLogInt("  Enter SnackAudioRead", nFrames);
284 
285 
286   while (nFrames > n * 2) n *= 2;
287   nFrames = n;
288 
289   if (A->convert) {
290     int n = 0, i, res;
291     short s[2];
292 
293     for (i = 0; i < nFrames * A->nChannels; i += A->nChannels) {
294       res = read(A->afd, &s, A->nChannels * sizeof(short));
295       if (res <= 0) return(n / (A->bytesPerSample * A->nChannels));
296       if (A->convert == ALAW) {
297 	((unsigned char *)buf)[i] = Snack_Lin2Alaw(s[0]);
298 	if (A->nChannels == 2) {
299 	  ((unsigned char *)buf)[i+1] = Snack_Lin2Alaw(s[1]);
300 	}
301       } else {
302 	((unsigned char *)buf)[i] = Snack_Lin2Mulaw(s[0]);
303 	if (A->nChannels == 2) {
304 	  ((unsigned char *)buf)[i+1] = Snack_Lin2Mulaw(s[1]);
305 	}
306       }
307       n += res;
308     }
309 
310     return(n / (A->bytesPerSample * A->nChannels));
311   } else {
312     int n = read(A->afd, (unsigned char *)buf, nFrames * A->bytesPerSample * A->nChannels);
313 
314     if (n > 0) n /= (A->bytesPerSample * A->nChannels);
315 
316     if (A->debug > 1) Snack_WriteLogInt("  Exit SnackAudioRead", n);
317 
318     return(n);
319   }
320 }
321 
322 int
323 SnackAudioWrite(ADesc *A, void *buf, int nFrames)
324 {
325   if (A->warm == 0) A->warm = 1;
326 
327   if (A->convert) {
328     int n = 0, i, res;
329     short s;
330 
331     for (i = 0; i < nFrames * A->nChannels; i++) {
332       if (A->convert == ALAW) {
333 	s = Snack_Alaw2Lin(((unsigned char *)buf)[i]);
334       } else {
335 	s = Snack_Mulaw2Lin(((unsigned char *)buf)[i]);
336       }
337       res = write(A->afd, &s, sizeof(short));
338       if (res <= 0) return(n / (A->bytesPerSample * A->nChannels));
339       n += res;
340     }
341 
342     return(n / (A->bytesPerSample * A->nChannels));
343   } else {
344     int n = write(A->afd, buf, nFrames * A->bytesPerSample * A->nChannels);
345     if (n > 0) n /= (A->bytesPerSample * A->nChannels);
346 
347     return(n);
348   }
349 }
350 
351 int
352 SnackAudioReadable(ADesc *A)
353 {
354   audio_buf_info info;
355 
356   if (A->debug > 1) Snack_WriteLog("  Enter SnackAudioReadable\n");
357 
358   ioctl(A->afd, SNDCTL_DSP_GETISPACE, &info);
359   if (info.bytes > 60*44100*4) info.bytes = 0;
360   if (A->debug > 1) Snack_WriteLogInt("  Exit SnackAudioReadable", info.bytes);
361 
362   return (info.bytes / (A->bytesPerSample * A->nChannels));
363 }
364 
365 int
366 SnackAudioWriteable(ADesc *A)
367 {
368   audio_buf_info info;
369 
370   ioctl(A->afd, SNDCTL_DSP_GETOSPACE, &info);
371 
372   return (info.bytes / (A->bytesPerSample * A->nChannels));
373 }
374 
375 long
376 SnackAudioPlayed(ADesc *A)
377 {
378   /*
379   count_info info;
380   int res = 0;
381 
382   if (A->warm) {
383     ioctl(A->afd, SNDCTL_DSP_GETOPTR, &info);
384     if (info.bytes > 0 || A->warm == 2) {
385       res = (A->freq * (SnackCurrentTime() - A->time) +.5);
386       A->warm = 2;
387     }
388   }
389   */
390   long res;
391 
392   res = (A->freq * (SnackCurrentTime() - A->time) +.5);
393 
394   return(res);
395 }
396 
397 void
398 SnackAudioInit()
399 {
400   union {
401     char c[sizeof(short)];
402     short s;
403   } order;
404   int afd, format, channels, nchannels;
405   /*
406   int i, n;
407   char *arr[MAX_NUM_DEVICES];
408   */
409 
410   /* Compute the byte order of this machine. */
411 
412   order.s = 1;
413   if (order.c[0] == 1) {
414     littleEndian = 1;
415   }
416 
417   if ((mfd = open(MIXER_NAME, O_RDWR, 0)) == -1) {
418     fprintf(stderr, "Unable to open mixer %s\n", MIXER_NAME);
419   }
420   /*
421   n = SnackGetOutputDevices(arr, MAX_NUM_DEVICES);
422   for (i = 0; i < n; i++) {
423     printf("Trying %s %d\n",arr[i], open(arr[i], O_WRONLY, 0));
424     if ((afd = open(arr[i], O_WRONLY, 0)) != -1) {
425       defaultDeviceName = arr[i];
426       printf("accepting %s %d\n",defaultDeviceName,afd);
427       break;
428     }
429   }
430   */
431 
432   if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) {
433     defaultDeviceName = "/dev/sound/dsp";
434     if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) {
435       return;
436     }
437   }
438   close(afd);
439 
440   /* Determine minimum number of channels supported. */
441 
442   if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) {
443     return;
444   }
445 
446   if (littleEndian) {
447     format = AFMT_S16_LE;
448   } else {
449     format = AFMT_S16_BE;
450   }
451   if (ioctl(afd, SNDCTL_DSP_SETFMT, &format) == -1) {
452     close(afd);
453     return;
454   }
455   channels = nchannels = 1;
456   if (ioctl(afd, SNDCTL_DSP_CHANNELS, &channels) == -1
457       || channels != nchannels) {
458     minNumChan = channels;
459   }
460   close(afd);
461 }
462 
463 void
464 SnackAudioFree()
465 {
466   int i, j;
467 
468   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
469     for (j = 0; j < 2; j++) {
470       if (mixerLinks[i][j].mixer != NULL) {
471 	ckfree(mixerLinks[i][j].mixer);
472       }
473       if (mixerLinks[i][j].mixerVar != NULL) {
474 	ckfree(mixerLinks[i][j].mixerVar);
475       }
476     }
477     if (mixerLinks[i][0].jack != NULL) {
478       ckfree(mixerLinks[i][0].jack);
479     }
480     if (mixerLinks[i][0].jackVar != NULL) {
481       ckfree((char *)mixerLinks[i][0].jackVar);
482     }
483   }
484 
485   close(mfd);
486 }
487 
488 void
489 ASetRecGain(int gain)
490 {
491   int g = min(max(gain, 0), 100);
492   int recsrc = 0;
493 
494   g = g * 256 + g;
495   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc);
496   if (recsrc & SOUND_MASK_LINE) {
497     ioctl(mfd, SOUND_MIXER_WRITE_LINE, &g);
498   } else {
499     ioctl(mfd, SOUND_MIXER_WRITE_MIC, &g);
500   }
501 }
502 
503 void
504 ASetPlayGain(int gain)
505 {
506   int g = min(max(gain, 0), 100);
507   int pcm_gain = 25700;
508 
509   g = g * 256 + g;
510   ioctl(mfd, SOUND_MIXER_WRITE_VOLUME, &g);
511   ioctl(mfd, SOUND_MIXER_WRITE_PCM, &pcm_gain);
512 }
513 
514 int
515 AGetRecGain()
516 {
517   int g = 0, left, right, recsrc = 0;
518 
519   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc);
520   if (recsrc & SOUND_MASK_LINE) {
521     ioctl(mfd, SOUND_MIXER_READ_LINE, &g);
522   } else {
523     ioctl(mfd, SOUND_MIXER_READ_MIC, &g);
524   }
525   left  =  g & 0xff;
526   right = (g & 0xff00) / 256;
527   g = (left + right) / 2;
528 
529   return(g);
530 }
531 
532 int
533 AGetPlayGain()
534 {
535   int g = 0, left, right;
536 
537   ioctl(mfd, SOUND_MIXER_READ_VOLUME, &g);
538   left  =  g & 0xff;
539   right = (g & 0xff00) / 256;
540   g = (left + right) / 2;
541 
542   return(g);
543 }
544 
545 int
546 SnackAudioGetEncodings(char *device)
547 {
548   int afd, mask;
549 
550   if ((afd = open(DEVICE_NAME, O_WRONLY, 0)) == -1) {
551     return(0);
552   }
553   if (ioctl(afd, SNDCTL_DSP_GETFMTS, &mask) == -1) {
554     return(0);
555   }
556   close(afd);
557 
558   if (mask & AFMT_S16_LE || mask & AFMT_S16_BE) {
559     return(LIN16);
560   } else {
561     return(0);
562   }
563 }
564 
565 void
566 SnackAudioGetRates(char *device, char *buf, int n)
567 {
568   int afd, freq, pos= 0, i;
569   int f[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 };
570 
571   if ((afd = open(DEVICE_NAME, O_WRONLY, 0)) == -1) {
572     buf[0] = '\0';
573     return;
574   }
575   for (i = 0; i < 8; i++) {
576     freq = f[i];
577     if (ioctl(afd, SNDCTL_DSP_SPEED, &freq) == -1) break;
578     if (abs(f[i] - freq) > freq / 100) continue;
579     pos += sprintf(&buf[pos], "%d ", freq);
580   }
581   close(afd);
582 }
583 
584 int
585 SnackAudioMaxNumberChannels(char *device)
586 {
587   return(2);
588 }
589 
590 int
591 SnackAudioMinNumberChannels(char *device)
592 {
593   return(minNumChan);
594 }
595 
596 void
597 SnackMixerGetInputJackLabels(char *buf, int n)
598 {
599   char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
600   int i, recMask, pos = 0;
601 
602   if (mfd != -1) {
603     ioctl(mfd, SOUND_MIXER_READ_RECMASK, &recMask);
604     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
605       if ((1 << i) & recMask) {
606 	pos += sprintf(&buf[pos], "%s", jackLabels[i]);
607 	pos += sprintf(&buf[pos], " ");
608       }
609     }
610   } else {
611     buf[0] = '\0';
612   }
613   buf[n-1] = '\0';
614 }
615 
616 void
617 SnackMixerGetOutputJackLabels(char *buf, int n)
618 {
619   buf[0] = '\0';
620 }
621 
622 void
623 SnackMixerGetInputJack(char *buf, int n)
624 {
625   char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
626   int i, recSrc = 0, pos = 0;
627 
628   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
629   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
630     if ((1 << i) & recSrc) {
631       pos += sprintf(&buf[pos], "%s", jackLabels[i]);
632       while (isspace(buf[pos-1])) pos--;
633       pos += sprintf(&buf[pos], " ");
634     }
635   }
636   if(isspace(buf[pos-1])) pos--;
637   buf[pos] = '\0';
638   /*printf("SnackMixerGetInputJack %x, %s\n", recSrc, buf);*/
639 }
640 
641 int
642 SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status)
643 {
644   char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
645   int i, recSrc = 0, currSrc;
646 
647   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
648     if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) {
649       recSrc = 1 << i;
650       break;
651     }
652   }
653 
654   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &currSrc);
655 
656 /*  printf("SnackMixerSetInputJack1 %x %s %s\n", currSrc, jack, status);*/
657 
658   if (strcmp(status, "1") == 0) {
659     recSrc |= currSrc;
660   } else {
661     recSrc = (currSrc & ~recSrc);
662   }
663 /*  printf("SnackMixerSetInputJack2 %x\n", recSrc);*/
664 
665   if (ioctl(mfd, SOUND_MIXER_WRITE_RECSRC, &recSrc) == -1) {
666     return 1;
667   } else {
668     ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
669 /*    printf("SnackMixerSetInputJack3 %x\n", recSrc);*/
670     return 0;
671   }
672   return 1;
673 }
674 
675 void
676 SnackMixerGetOutputJack(char *buf, int n)
677 {
678   buf[0] = '\0';
679 }
680 
681 void
682 SnackMixerSetOutputJack(char *jack, char *status)
683 {
684 }
685 
686 static int dontTrace = 0;
687 
688 static char *
689 JackVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
690 	    CONST84 char *name2, int flags)
691 {
692   MixerLink *mixLink = (MixerLink *) clientData;
693   char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
694   int i, recSrc = 0, status = 0;
695   CONST84 char *stringValue;
696   Tcl_Obj *obj, *var;
697 
698   if (dontTrace) return (char *) NULL;
699 
700   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
701 /*printf("JackVarProc %x %s %s\n", recSrc, name1, name2);*/
702   if (flags & TCL_TRACE_UNSETS) {
703     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
704       for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
705 	if (strncasecmp(mixLink->jack, jackLabels[i], strlen(mixLink->jack))
706 	    == 0) {
707 	  if ((1 << i) & recSrc) {
708 	    status = 1;
709 	  } else {
710 	    status = 0;
711 	  }
712 	  break;
713 	}
714       }
715       obj = Tcl_NewIntObj(status);
716       var = Tcl_NewStringObj(mixLink->jackVar, -1);
717       Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
718       Tcl_TraceVar(interp, mixLink->jackVar,
719 		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
720 		   JackVarProc, mixLink);
721     }
722     return (char *) NULL;
723   }
724 
725   stringValue = Tcl_GetVar(interp, mixLink->jackVar, TCL_GLOBAL_ONLY);
726   if (stringValue != NULL) {
727     SnackMixerSetInputJack(interp, mixLink->jack, stringValue);
728   }
729 
730   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
731 /*printf("JackVarProc2 %x\n", recSrc);*/
732   dontTrace = 1;
733   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
734     if (mixerLinks[i][0].jackVar != NULL) {
735       if ((1 << i) & recSrc) {
736 	status = 1;
737       } else {
738 	status = 0;
739       }
740       obj = Tcl_NewIntObj(status);
741       var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1);
742       Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY |TCL_PARSE_PART1);
743     }
744   }
745   dontTrace = 0;
746 
747   return (char *) NULL;
748 }
749 
750 void
751 SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var)
752 {
753   char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
754   int i, recSrc = 0, status;
755   CONST84 char *value;
756 
757   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
758 
759   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
760     if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) {
761       if ((1 << i) & recSrc) {
762 	status = 1;
763       } else {
764 	status = 0;
765       }
766       mixerLinks[i][0].jack = SnackStrDup(jack);
767       mixerLinks[i][0].jackVar = SnackStrDup(Tcl_GetStringFromObj(var, NULL));
768       value = Tcl_GetVar(interp, mixerLinks[i][0].jackVar, TCL_GLOBAL_ONLY);
769       if (value != NULL) {
770 	SnackMixerSetInputJack(interp, mixerLinks[i][0].jack, value);
771       } else {
772 	Tcl_Obj *obj = Tcl_NewIntObj(status);
773 	Tcl_ObjSetVar2(interp, var, NULL, obj,
774 		       TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
775 
776       }
777       Tcl_TraceVar(interp, mixerLinks[i][0].jackVar,
778 		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
779 		   JackVarProc, (ClientData) &mixerLinks[i][0]);
780       break;
781     }
782   }
783 }
784 
785 void
786 SnackMixerGetChannelLabels(char *line, char *buf, int n)
787 {
788   char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
789   int i, devMask;
790 
791   ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask);
792   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
793     if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
794       if (devMask & (1 << i)) {
795 	sprintf(buf, "Left Right");
796       } else {
797 	sprintf(buf, "Mono");
798       }
799       break;
800     }
801   }
802 }
803 
804 void
805 SnackMixerGetVolume(char *line, int channel, char *buf, int n)
806 {
807   char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
808   int i, vol = 0, devMask, isStereo = 0, left, right;
809 
810   buf[0] = '\0';
811 
812   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
813     if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
814       ioctl(mfd, MIXER_READ(i), &vol);
815       ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask);
816       if (devMask & (1 << i)) {
817 	isStereo = 1;
818       }
819       break;
820     }
821   }
822   left  =  vol & 0xff;
823   right = (vol & 0xff00) >> 8;
824   if (isStereo) {
825     if (channel == 0) {
826       sprintf(buf, "%d", left);
827     } else if (channel == 1) {
828       sprintf(buf, "%d", right);
829     } else if (channel == -1) {
830       sprintf(buf, "%d", (left + right)/2);
831     }
832   } else {
833     sprintf(buf, "%d", left);
834   }
835 }
836 
837 void
838 SnackMixerSetVolume(char *line, int channel, int volume)
839 {
840   char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
841   int tmp = min(max(volume, 0), 100), i, oldVol = 0;
842   int vol = (tmp << 8) + tmp;
843 
844   if (channel == 0) {
845     vol = tmp;
846   }
847   if (channel == 1) {
848     vol = tmp << 8;
849   }
850 
851   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
852     if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
853       ioctl(mfd, MIXER_READ(i), &oldVol);
854       if (channel == 0) {
855 	vol = (oldVol & 0xff00) | (vol & 0x00ff);
856       }
857       if (channel == 1) {
858 	vol = (vol & 0xff00) | (oldVol & 0x00ff);
859       }
860       ioctl(mfd, MIXER_WRITE(i), &vol);
861       break;
862     }
863   }
864 }
865 
866 static char *
867 VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
868 	      CONST84 char *name2, int flags)
869 {
870   MixerLink *mixLink = (MixerLink *) clientData;
871   CONST84 char *stringValue;
872 
873   if (flags & TCL_TRACE_UNSETS) {
874     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
875       Tcl_Obj *obj, *var;
876       char tmp[VOLBUFSIZE];
877 
878       SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE);
879       obj = Tcl_NewIntObj(atoi(tmp));
880       var = Tcl_NewStringObj(mixLink->mixerVar, -1);
881       Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
882       Tcl_TraceVar(interp, mixLink->mixerVar,
883 		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
884 		   VolumeVarProc, mixLink);
885     }
886     return (char *) NULL;
887   }
888 
889   stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY);
890   if (stringValue != NULL) {
891     SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue));
892   }
893 
894   return (char *) NULL;
895 }
896 
897 void
898 SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n,
899 		     Tcl_Obj *CONST objv[])
900 {
901   char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
902   int i, j, channel;
903   CONST84 char *value;
904   char tmp[VOLBUFSIZE];
905 
906   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
907     if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
908       for (j = 0; j < n; j++) {
909 	if (n == 1) {
910 	  channel = -1;
911 	} else {
912 	  channel = j;
913 	}
914 	mixerLinks[i][j].mixer = SnackStrDup(line);
915 	mixerLinks[i][j].mixerVar = SnackStrDup(Tcl_GetStringFromObj(objv[j+3],NULL));
916 	mixerLinks[i][j].channel = j;
917 	value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY);
918 	if (value != NULL) {
919 	  SnackMixerSetVolume(line, channel, atoi(value));
920 	} else {
921 	  Tcl_Obj *obj;
922 	  SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE);
923 	  obj = Tcl_NewIntObj(atoi(tmp));
924 	  Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj,
925 			 TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
926 	}
927 	Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar,
928 		     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
929 		     VolumeVarProc, (ClientData) &mixerLinks[i][j]);
930       }
931     }
932   }
933 }
934 
935 void
936 SnackMixerUpdateVars(Tcl_Interp *interp)
937 {
938   int i, j, recSrc, status;
939   char tmp[VOLBUFSIZE];
940   Tcl_Obj *obj, *var;
941 
942   ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc);
943   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
944     for (j = 0; j < 2; j++) {
945       if (mixerLinks[i][j].mixerVar != NULL) {
946 	SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel,
947 			    tmp, VOLBUFSIZE);
948 	obj = Tcl_NewIntObj(atoi(tmp));
949 	var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1);
950 	Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1);
951       }
952     }
953     if (mixerLinks[i][0].jackVar != NULL) {
954       if ((1 << i) & recSrc) {
955 	status = 1;
956       } else {
957 	status = 0;
958       }
959       obj = Tcl_NewIntObj(status);
960       var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1);
961       Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
962     }
963   }
964 }
965 
966 void
967 SnackMixerGetLineLabels(char *buf, int n)
968 {
969   char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
970   int i, devMask, pos = 0;
971 
972   if (mfd != -1) {
973     ioctl(mfd, SOUND_MIXER_READ_DEVMASK, &devMask);
974     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
975       if ((1 << i) & devMask && pos < n-8) {
976 	pos += sprintf(&buf[pos], "%s", mixLabels[i]);
977 	pos += sprintf(&buf[pos], " ");
978       }
979     }
980   } else {
981     buf[0] = '\0';
982   }
983   buf[n-1] = '\0';
984 }
985 
986 int
987 SnackGetOutputDevices(char **arr, int n)
988 {
989   return SnackGetInputDevices(arr, n);
990 }
991 
992 int
993 SnackGetInputDevices(char **arr, int n)
994 {
995   int i, j = 0;
996   glob_t globt;
997 
998   glob("/dev/dsp*", 0, NULL, &globt);
999   glob("/dev/audio*", GLOB_APPEND, NULL, &globt);
1000   glob("/dev/sound/dsp*", GLOB_APPEND, NULL, &globt);
1001   glob("/dev/sound/audio*", GLOB_APPEND, NULL, &globt);
1002 
1003   for (i = 0; i < globt.gl_pathc; i++) {
1004     if (j < n) {
1005       arr[j++] = (char *) SnackStrDup(globt.gl_pathv[i]);
1006     }
1007   }
1008   globfree(&globt);
1009 
1010   return(j);
1011 }
1012 
1013 int
1014 SnackGetMixerDevices(char **arr, int n)
1015 {
1016   int i, j = 0;
1017   glob_t globt;
1018 
1019   glob("/dev/mixer*", 0, NULL, &globt);
1020   glob("/dev/sound/mixer*", GLOB_APPEND, NULL, &globt);
1021 
1022   for (i = 0; i < globt.gl_pathc; i++) {
1023     if (j < n) {
1024       arr[j++] = (char *) SnackStrDup(globt.gl_pathv[i]);
1025     }
1026   }
1027   globfree(&globt);
1028 
1029   return(j);
1030 }
1031