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"
40static char *defaultDeviceName = DEVICE_NAME;
41extern void Snack_WriteLog(char *s);
42extern 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
49static int mfd = 0;
50
51static struct MixerLink mixerLinks[SOUND_MIXER_NRDEVICES][2];
52
53static int littleEndian = 0;
54
55static int minNumChan = 1;
56
57int
58SnackAudioOpen(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
219int
220SnackAudioClose(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
233long
234SnackAudioPause(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
244void
245SnackAudioResume(ADesc *A)
246{
247  A->time = A->time + SnackCurrentTime() - A->timep;
248}
249
250void
251SnackAudioFlush(ADesc *A)
252{
253  if (A->mode == RECORD) {
254  } else {
255    ioctl(A->afd, SNDCTL_DSP_RESET, 0);
256  }
257}
258
259static char zeroBlock[16];
260
261void
262SnackAudioPost(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
278int
279SnackAudioRead(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
322int
323SnackAudioWrite(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
351int
352SnackAudioReadable(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
365int
366SnackAudioWriteable(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
375long
376SnackAudioPlayed(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
397void
398SnackAudioInit()
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
463void
464SnackAudioFree()
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
488void
489ASetRecGain(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
503void
504ASetPlayGain(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
514int
515AGetRecGain()
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
532int
533AGetPlayGain()
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
545int
546SnackAudioGetEncodings(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
565void
566SnackAudioGetRates(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
584int
585SnackAudioMaxNumberChannels(char *device)
586{
587  return(2);
588}
589
590int
591SnackAudioMinNumberChannels(char *device)
592{
593  return(minNumChan);
594}
595
596void
597SnackMixerGetInputJackLabels(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
616void
617SnackMixerGetOutputJackLabels(char *buf, int n)
618{
619  buf[0] = '\0';
620}
621
622void
623SnackMixerGetInputJack(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
641int
642SnackMixerSetInputJack(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
675void
676SnackMixerGetOutputJack(char *buf, int n)
677{
678  buf[0] = '\0';
679}
680
681void
682SnackMixerSetOutputJack(char *jack, char *status)
683{
684}
685
686static int dontTrace = 0;
687
688static char *
689JackVarProc(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
750void
751SnackMixerLinkJacks(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
785void
786SnackMixerGetChannelLabels(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
804void
805SnackMixerGetVolume(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
837void
838SnackMixerSetVolume(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
866static char *
867VolumeVarProc(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
897void
898SnackMixerLinkVolume(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
935void
936SnackMixerUpdateVars(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
966void
967SnackMixerGetLineLabels(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
986int
987SnackGetOutputDevices(char **arr, int n)
988{
989  return SnackGetInputDevices(arr, n);
990}
991
992int
993SnackGetInputDevices(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
1013int
1014SnackGetMixerDevices(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