1/*
2 * Copyright (C) 1997-2002 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 <stdlib.h>
29
30static char *audioDev;
31#define DEF_AUDIO "/dev/audio"
32#define ENV_AUDIO "AUDIODEV"
33
34extern void Snack_WriteLog(char *s);
35extern void Snack_WriteLogInt(char *s, int n);
36
37#ifndef min
38#define min(a,b) ((a)<(b)?(a):(b))
39#define max(a,b) ((a)>(b)?(a):(b))
40#endif
41
42int ctlfd = 0;
43
44#define SNACK_NUMBER_MIXERS 2
45#define SNACK_NUMBER_JACKS 5
46#define SNACK_NUMBER_OUTJACKS 3
47
48struct MixerLink mixerLinks[SNACK_NUMBER_JACKS][2];
49
50int
51SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq,
52	       int nchannels, int encoding)
53{
54  ioctl(ctlfd, AUDIO_GETINFO, &A->ainfo);
55
56  A->mode = mode;
57  switch (mode) {
58  case RECORD:
59    if ((A->afd = open(audioDev, O_RDONLY, 0)) < 0) {
60      Tcl_AppendResult(interp, "Couldn't open ", audioDev, " for read.",
61		       NULL);
62      return TCL_ERROR;
63    }
64    break;
65
66  case PLAY:
67    if ((A->afd = open(audioDev, O_WRONLY, 0)) < 0) {
68      Tcl_AppendResult(interp, "Couldn't open ", audioDev, " for write.",
69		       NULL);
70      return TCL_ERROR;
71    }
72    fcntl(A->afd, F_SETFL, O_NONBLOCK);
73    break;
74  }
75
76  fcntl(A->afd, F_SETFD, FD_CLOEXEC);
77
78  A->ainfo.play.sample_rate = freq;
79  A->ainfo.play.channels = nchannels;
80  A->ainfo.record.sample_rate = freq;
81  A->ainfo.record.channels = nchannels;
82  A->nChannels = nchannels;
83  A->convert = 0;
84  A->convBuf = NULL;
85  A->convSize = 0;
86
87  switch (encoding) {
88  case LIN16:
89    A->ainfo.play.encoding    = AUDIO_ENCODING_LINEAR;
90    A->ainfo.record.encoding  = AUDIO_ENCODING_LINEAR;
91    A->ainfo.play.precision   = 16;
92    A->ainfo.record.precision = 16;
93    A->bytesPerSample         = sizeof(short);
94    break;
95  case ALAW:
96    if (nchannels == 1) {
97      A->ainfo.play.encoding    = AUDIO_ENCODING_ALAW;
98      A->ainfo.record.encoding  = AUDIO_ENCODING_ALAW;
99      A->ainfo.play.precision   = 8;
100      A->ainfo.record.precision = 8;
101      A->bytesPerSample         = sizeof(char);
102    } else {
103      A->ainfo.play.encoding    = AUDIO_ENCODING_LINEAR;
104      A->ainfo.record.encoding  = AUDIO_ENCODING_LINEAR;
105      A->ainfo.play.precision   = 16;
106      A->ainfo.record.precision = 16;
107      A->bytesPerSample         = sizeof(short);
108      A->convert                = ALAW;
109    }
110    break;
111  case MULAW:
112    if (nchannels == 1) {
113      A->ainfo.play.encoding    = AUDIO_ENCODING_ULAW;
114      A->ainfo.record.encoding  = AUDIO_ENCODING_ULAW;
115      A->ainfo.play.precision   = 8;
116      A->ainfo.record.precision = 8;
117      A->bytesPerSample         = sizeof(char);
118    } else {
119      A->ainfo.play.encoding    = AUDIO_ENCODING_LINEAR;
120      A->ainfo.record.encoding  = AUDIO_ENCODING_LINEAR;
121      A->ainfo.play.precision   = 16;
122      A->ainfo.record.precision = 16;
123      A->bytesPerSample         = sizeof(short);
124      A->convert                = MULAW;
125    }
126    break;
127  case LIN8OFFSET:
128    A->ainfo.play.encoding    = AUDIO_ENCODING_LINEAR8;
129    A->ainfo.record.encoding  = AUDIO_ENCODING_LINEAR8;
130    A->ainfo.play.precision   = 8;
131    A->ainfo.record.precision = 8;
132    A->bytesPerSample         = sizeof(char);
133    break;
134  }
135
136  if (ioctl(A->afd, AUDIO_SETINFO, &A->ainfo) < 0) {
137    Tcl_AppendResult(interp, "Unssupported audio format.", NULL);
138    return TCL_ERROR;
139  }
140  A->time = SnackCurrentTime();
141  A->timep = 0.0;
142  A->freq = freq;
143
144  return TCL_OK;
145}
146
147int
148SnackAudioClose(ADesc *A)
149{
150  close(A->afd);
151  ckfree(A->convBuf);
152
153  return(0);
154}
155
156long
157SnackAudioPause(ADesc *A)
158{
159  /*  int count;*/
160  long res = SnackAudioPlayed(A);
161
162  AUDIO_INITINFO(&A->ainfo);
163
164  switch (A->mode) {
165  case RECORD:
166    A->ainfo.record.pause = 1;
167    ioctl(A->afd, AUDIO_SETINFO, &A->ainfo);
168    return(-1);
169    break;
170
171  case PLAY:
172    /*    count = SnackAudioPlayed(A);*/
173    A->ainfo.play.pause = 1;
174    ioctl(A->afd, AUDIO_SETINFO, &A->ainfo);
175    SnackAudioFlush(A);
176    /*    return(count);*/
177    A->timep = SnackCurrentTime();
178
179    return(res);
180    break;
181  }
182}
183
184void
185SnackAudioResume(ADesc *A)
186{
187  AUDIO_INITINFO(&A->ainfo);
188
189  switch (A->mode) {
190  case RECORD:
191    A->ainfo.record.pause = 0;
192    break;
193
194  case PLAY:
195    A->ainfo.play.pause = 0;
196    A->time = A->time + SnackCurrentTime() - A->timep;
197    break;
198  }
199  ioctl(A->afd, AUDIO_SETINFO, &A->ainfo);
200}
201
202void
203SnackAudioFlush(ADesc *A)
204{
205  if (A->mode == RECORD) {
206    ioctl(A->afd, I_FLUSH, FLUSHR);
207  } else {
208    ioctl(A->afd, I_FLUSH, FLUSHW);
209  }
210}
211
212void
213SnackAudioPost(ADesc *A)
214{
215}
216
217int
218SnackAudioRead(ADesc *A, void *buf, int nFrames)
219{
220  if (nFrames == 0) return(0);
221
222  if (A->convert) {
223    int n = 0, i, res;
224    short s[2];
225
226    for (i = 0; i < nFrames * A->nChannels; i += 2) {
227      res = read(A->afd, &s, 2*sizeof(short));
228      if (res <= 0) return(n / (A->bytesPerSample * A->nChannels));
229      if (A->convert == ALAW) {
230	((unsigned char *)buf)[i] = Snack_Lin2Alaw(s[0]);
231	((unsigned char *)buf)[i+1] = Snack_Lin2Alaw(s[1]);
232      } else {
233	((unsigned char *)buf)[i] = Snack_Lin2Mulaw(s[0]);
234	((unsigned char *)buf)[i+1] = Snack_Lin2Mulaw(s[1]);
235      }
236      n += res;
237    }
238
239    return(n / (A->bytesPerSample * A->nChannels));
240  } else {
241    int n = read(A->afd, buf, nFrames * A->bytesPerSample * A->nChannels);
242    if (n > 0) n /= (A->bytesPerSample * A->nChannels);
243    return(n);
244  }
245}
246
247int
248SnackAudioWrite(ADesc *A, void *buf, int nFrames)
249{
250  if (A->convert) {
251    int n = 0, i, res;
252
253    if (nFrames * A->bytesPerSample * A->nChannels > A->convSize) {
254      A->convSize = nFrames * A->bytesPerSample * A->nChannels;
255      if ((A->convBuf = (short *) ckrealloc(A->convBuf, A->convSize)) == NULL) {
256	return(-1);
257      }
258    }
259    A->convSize = nFrames * A->bytesPerSample * A->nChannels;
260    for (i = 0; i < nFrames * A->nChannels; i += 2) {
261      if (A->convert == ALAW) {
262	A->convBuf[i] = Snack_Alaw2Lin(((unsigned char *)buf)[i]);
263	A->convBuf[i+1] = Snack_Alaw2Lin(((unsigned char *)buf)[i+1]);
264      } else {
265	A->convBuf[i] = Snack_Mulaw2Lin(((unsigned char *)buf)[i]);
266	A->convBuf[i+1] = Snack_Mulaw2Lin(((unsigned char *)buf)[i+1]);
267      }
268    }
269    n = write(A->afd, A->convBuf, nFrames * A->bytesPerSample * A->nChannels);
270    if (n > 0) n /= (A->bytesPerSample * A->nChannels);
271    return(n);
272  } else {
273    int n = write(A->afd, buf, nFrames * A->bytesPerSample * A->nChannels);
274    if (n > 0) n /= (A->bytesPerSample * A->nChannels);
275    return(n);
276  }
277}
278
279int
280SnackAudioReadable(ADesc *A)
281{
282  int NBytes = 0;
283
284  ioctl(A->afd, FIONREAD, &NBytes);
285  return(NBytes / (A->bytesPerSample * A->nChannels));
286}
287
288int
289SnackAudioWriteable(ADesc *A)
290{
291  return -1;
292}
293
294long
295SnackAudioPlayed(ADesc *A)
296{
297  /*  ioctl(A->afd, AUDIO_GETINFO, &A->ainfo);
298
299      return(A->ainfo.play.samples);*/
300  long res;
301
302  res = (A->freq * (SnackCurrentTime() - A->time) +.5);
303  return(res);
304}
305
306void
307SnackAudioInit()
308{
309  char *audioCtl;
310
311  audioDev = getenv(ENV_AUDIO);  /* try environment variable first */
312  if (!audioDev)
313    audioDev = DEF_AUDIO;        /* take default */
314
315  audioCtl = ckalloc(strlen(audioDev) + 4);
316  if (audioCtl) {
317    strcpy(audioCtl, audioDev);
318    strcat(audioCtl, "ctl");
319    if ((ctlfd = open(audioCtl, O_RDWR)) < 0) {
320      fprintf(stderr, "Unable to open %s\n", audioCtl);
321    }
322    ckfree(audioCtl);
323  }
324}
325
326void
327SnackAudioFree()
328{
329  int i, j;
330
331  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
332    for (j = 0; j < 2; j++) {
333      if (mixerLinks[i][j].mixer != NULL) {
334	ckfree(mixerLinks[i][j].mixer);
335      }
336      if (mixerLinks[i][j].mixerVar != NULL) {
337	ckfree(mixerLinks[i][j].mixerVar);
338      }
339    }
340    if (mixerLinks[i][0].jack != NULL) {
341      ckfree(mixerLinks[i][0].jack);
342    }
343    if (mixerLinks[i][0].jackVar != NULL) {
344      ckfree((char *)mixerLinks[i][0].jackVar);
345    }
346  }
347
348  if (ctlfd > 0) close(ctlfd);
349}
350
351void
352ASetRecGain(int gain)
353{
354  int g = min(max(gain, 0), 100);
355
356  audio_info_t info;
357
358  AUDIO_INITINFO(&info);
359  info.record.gain = (int) (g * AUDIO_MAX_GAIN / 100.0 + 0.5);
360  ioctl(ctlfd, AUDIO_SETINFO, &info);
361}
362
363void
364ASetPlayGain(int gain)
365{
366  int g = min(max(gain, 0), 100);
367
368  audio_info_t info;
369
370  AUDIO_INITINFO(&info);
371  info.play.gain = (int) (g * AUDIO_MAX_GAIN / 100.0 + 0.5);
372  ioctl(ctlfd, AUDIO_SETINFO, &info);
373}
374
375int
376AGetRecGain()
377{
378  int g = 0;
379  audio_info_t info;
380
381  ioctl(ctlfd, AUDIO_GETINFO, &info);
382  g = (int) (info.record.gain * 100.0 / AUDIO_MAX_GAIN + 0.5);
383
384  return(g);
385}
386
387int
388AGetPlayGain()
389{
390  int g = 0;
391  audio_info_t info;
392
393  ioctl(ctlfd, AUDIO_GETINFO, &info);
394  g = (int) (info.play.gain * 100.0 / AUDIO_MAX_GAIN + 0.5);
395
396  return(g);
397}
398
399int
400SnackAudioGetEncodings(char *device)
401{
402  return(LIN16);
403}
404
405void
406SnackAudioGetRates(char *device, char *buf, int n)
407{
408  strncpy(buf, "8000 11025 16000 22050 32000 44100 48000", n);
409  buf[n-1] = '\0';
410}
411
412int
413SnackAudioMaxNumberChannels(char *device)
414{
415  return(2);
416}
417
418int
419SnackAudioMinNumberChannels(char *device)
420{
421  return(1);
422}
423
424void
425SnackMixerGetInputJackLabels(char *buf, int n)
426{
427  strncpy(buf, "Microphone \"Line In\"", n);
428  buf[n-1] = '\0';
429}
430
431void
432SnackMixerGetOutputJackLabels(char *buf, int n)
433{
434  strncpy(buf, "Speaker \"Line Out\" Headphones", n);
435  buf[n-1] = '\0';
436}
437
438void
439SnackMixerGetInputJack(char *buf, int n)
440{
441  audio_info_t info;
442  int pos = 0;
443
444  ioctl(ctlfd, AUDIO_GETINFO, &info);
445
446  if (info.record.port & AUDIO_LINE_IN) {
447    pos += (int) sprintf(&buf[pos],  "\"Line In\" ");
448  }
449  if (info.record.port & AUDIO_MICROPHONE) {
450    pos += (int) sprintf(&buf[pos],  "Microphone");
451  }
452}
453
454int
455SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status)
456{
457  audio_info_t info;
458  int jackLen = strlen(jack), start = 0, end = 0, mask;
459
460  ioctl(ctlfd, AUDIO_GETINFO, &info);
461
462  while(end < jackLen) {
463    while ((end < jackLen) && (!isspace(jack[end]))) end++;
464    if (strncasecmp(&jack[start], "Microphone", end - start) == 0) {
465      mask = AUDIO_MICROPHONE;
466    } else if (strncasecmp(&jack[start], "Line In", end - start) == 0) {
467      mask = AUDIO_LINE_IN;
468    }
469    if (strcmp(status, "0") == 0) {
470      info.record.port = (~mask) & 0x03;
471    } else {
472      info.record.port = mask;
473    }
474    while ((end < jackLen) && (isspace(jack[end]))) end++;
475    start = end;
476  }
477
478  ioctl(ctlfd, AUDIO_SETINFO, &info);
479
480  return 0;
481}
482
483void
484SnackMixerGetOutputJack(char *buf, int n)
485{
486  audio_info_t info;
487  int pos = 0;
488
489  ioctl(ctlfd, AUDIO_GETINFO, &info);
490
491  if (info.play.port & AUDIO_SPEAKER) {
492    pos += (int) sprintf(&buf[pos],  "Speaker ");
493  }
494  if (info.play.port & AUDIO_HEADPHONE) {
495    pos += (int) sprintf(&buf[pos],  "Headphones ");
496  }
497  if (info.play.port & AUDIO_LINE_OUT) {
498    pos += (int) sprintf(&buf[pos],  "\"Line Out\"");
499  }
500}
501
502void
503SnackMixerSetOutputJack(char *jack, char *status)
504{
505  audio_info_t info;
506  int jackLen = strlen(jack), start = 0, end = 0, mask;
507
508  ioctl(ctlfd, AUDIO_GETINFO, &info);
509
510  while(end < jackLen) {
511    while ((end < jackLen) && (!isspace(jack[end]))) end++;
512    if (strncasecmp(&jack[start], "Speaker", end - start) == 0) {
513      mask = AUDIO_SPEAKER;
514    } else if (strncasecmp(&jack[start], "Line Out", end - start) == 0) {
515      mask = AUDIO_LINE_OUT;
516    } else if (strncasecmp(&jack[start], "Headphones", end - start) == 0) {
517      mask = AUDIO_HEADPHONE;
518    }
519    if (strcmp(status, "0") == 0) {
520      info.play.port &= ~mask;
521    } else {
522      info.play.port |= mask;
523    }
524    while ((end < jackLen) && (isspace(jack[end]))) end++;
525    start = end;
526  }
527
528  ioctl(ctlfd, AUDIO_SETINFO, &info);
529}
530
531static char *
532JackVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
533	    CONST84 char *name2, int flags)
534{
535  MixerLink *mixLink = (MixerLink *) clientData;
536  int i, j, status = 0;
537  CONST84 char *stringValue;
538  char *jackLabels[] = { "Speaker", "Line Out", "Headphones",
539			 "Microphone", "Line In" };
540  Tcl_Obj *obj, *var;
541  audio_info_t info;
542
543  ioctl(ctlfd, AUDIO_GETINFO, &info);
544
545  for (i = 0; i < SNACK_NUMBER_JACKS; i++) {
546    if (strncasecmp(mixLink->jack, jackLabels[i], strlen(mixLink->jack))
547	== 0) {
548      break;
549    }
550  }
551  if ((i == 0 && info.play.port & AUDIO_SPEAKER) ||
552      (i == 1 && info.play.port & AUDIO_LINE_OUT) ||
553      (i == 2 && info.play.port & AUDIO_HEADPHONE) ||
554      (i == 3 && info.record.port & AUDIO_MICROPHONE) ||
555      (i == 4 && info.record.port & AUDIO_LINE_IN)) {
556    status = 1;
557  }
558
559  if (flags & TCL_TRACE_UNSETS) {
560    if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
561      obj = Tcl_NewIntObj(status);
562      var = Tcl_NewStringObj(mixLink->jackVar, -1);
563      Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
564      Tcl_TraceVar(interp, mixLink->jackVar,
565		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
566		   JackVarProc, mixLink);
567    }
568    return (char *) NULL;
569  }
570
571  stringValue = Tcl_GetVar(interp, mixLink->jackVar, TCL_GLOBAL_ONLY);
572  if (stringValue != NULL) {
573    if (i < SNACK_NUMBER_OUTJACKS) {
574      SnackMixerSetOutputJack(mixLink->jack, stringValue);
575    } else {
576      SnackMixerSetInputJack(interp, mixLink->jack, stringValue);
577    }
578  }
579
580  ioctl(ctlfd, AUDIO_GETINFO, &info);
581
582  for (j = SNACK_NUMBER_OUTJACKS; j < SNACK_NUMBER_JACKS; j++) {
583    if ((j == 3 && info.record.port & AUDIO_MICROPHONE) ||
584	(j == 4 && info.record.port & AUDIO_LINE_IN)) {
585      status = 1;
586    } else {
587      status = 0;
588    }
589    if (i == j) continue;
590    obj = Tcl_NewIntObj(status);
591    var = Tcl_NewStringObj(mixerLinks[j][0].jackVar, -1);
592    Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY |TCL_PARSE_PART1);
593  }
594
595  return (char *) NULL;
596}
597
598void
599SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var)
600{
601  char *jackLabels[] = { "Speaker", "Line Out", "Headphones",
602			 "Microphone", "Line In" };
603  int i, status = 0;
604  CONST84 char *value;
605  audio_info_t info;
606
607  ioctl(ctlfd, AUDIO_GETINFO, &info);
608
609  for (i = 0; i < SNACK_NUMBER_JACKS; i++) {
610    if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) {
611      if ((i == 0 && info.play.port & AUDIO_SPEAKER) ||
612	  (i == 1 && info.play.port & AUDIO_LINE_OUT) ||
613	  (i == 2 && info.play.port & AUDIO_HEADPHONE) ||
614	  (i == 3 && info.record.port & AUDIO_MICROPHONE) ||
615	  (i == 4 && info.record.port & AUDIO_LINE_IN)) {
616	status = 1;
617      }
618      mixerLinks[i][0].jack = (char *)SnackStrDup(jack);
619      mixerLinks[i][0].jackVar = (char *)SnackStrDup(Tcl_GetStringFromObj(var, NULL));
620      value = Tcl_GetVar(interp, mixerLinks[i][0].jackVar, TCL_GLOBAL_ONLY);
621      if (value != NULL) {
622	if (i < SNACK_NUMBER_OUTJACKS) {
623	  SnackMixerSetOutputJack(mixerLinks[i][0].jack, value);
624	} else {
625	  SnackMixerSetInputJack(interp, mixerLinks[i][0].jack, value);
626	}
627      } else {
628	Tcl_Obj *obj = Tcl_NewIntObj(status);
629	Tcl_ObjSetVar2(interp, var, NULL, obj,
630		       TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
631      }
632      Tcl_TraceVar(interp, mixerLinks[i][0].jackVar,
633		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
634		   JackVarProc, (ClientData) &mixerLinks[i][0]);
635      break;
636    }
637  }
638}
639
640void
641SnackMixerGetChannelLabels(char *line, char *buf, int n)
642{
643  strncpy(buf, "Mono", n);
644  buf[n-1] = '\0';
645}
646
647void
648SnackMixerGetVolume(char *line, int channel, char *buf, int n)
649{
650  if (strncasecmp(line, "Record", strlen(line)) == 0) {
651    sprintf(buf, "%d", AGetRecGain());
652  }
653  if (strncasecmp(line, "Play", strlen(line)) == 0) {
654    sprintf(buf, "%d", AGetPlayGain());
655  }
656}
657
658void
659SnackMixerSetVolume(char *line, int channel, int volume)
660{
661  if (strncasecmp(line, "Record", strlen(line)) == 0) {
662    ASetRecGain(volume);
663  }
664  if (strncasecmp(line, "Play", strlen(line)) == 0) {
665    ASetPlayGain(volume);
666  }
667}
668
669static char *
670VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
671	      CONST84 char *name2, int flags)
672{
673  MixerLink *mixLink = (MixerLink *) clientData;
674  CONST84 char *stringValue;
675
676  if (flags & TCL_TRACE_UNSETS) {
677    if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
678      Tcl_Obj *obj, *var;
679      char tmp[VOLBUFSIZE];
680
681      SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE);
682      obj = Tcl_NewIntObj(atoi(tmp));
683      var = Tcl_NewStringObj(mixLink->mixerVar, -1);
684      Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
685      Tcl_TraceVar(interp, mixLink->mixerVar,
686		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
687		   VolumeVarProc, mixLink);
688    }
689    return (char *) NULL;
690  }
691  stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY);
692  if (stringValue != NULL) {
693    SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue));
694  }
695
696  return (char *) NULL;
697}
698
699void
700SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n,
701		     Tcl_Obj *CONST objv[])
702{
703  char *mixLabels[] = { "Play", "Record" };
704  int i, j, channel;
705  CONST84 char *value;
706  char tmp[VOLBUFSIZE];
707
708  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
709    if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
710      for (j = 0; j < n; j++) {
711	if (n == 1) {
712	  channel = -1;
713	} else {
714	  channel = j;
715	}
716	mixerLinks[i][j].mixer = (char *)SnackStrDup(line);
717	mixerLinks[i][j].mixerVar = (char *)SnackStrDup(Tcl_GetStringFromObj(objv[j+3], NULL));
718	mixerLinks[i][j].channel = j;
719	value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY);
720	if (value != NULL) {
721	  SnackMixerSetVolume(line, channel, atoi(value));
722	} else {
723	  Tcl_Obj *obj;
724	  SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE);
725	  obj = Tcl_NewIntObj(atoi(tmp));
726	  Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj,
727			 TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
728	}
729	Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar,
730		     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
731		     VolumeVarProc, (ClientData) &mixerLinks[i][j]);
732      }
733    }
734  }
735}
736
737void
738SnackMixerUpdateVars(Tcl_Interp *interp)
739{
740  int i, j, status = 0;
741  char tmp[VOLBUFSIZE];
742  Tcl_Obj *obj, *var;
743  audio_info_t info;
744
745  ioctl(ctlfd, AUDIO_GETINFO, &info);
746
747  for (i = 0; i < SNACK_NUMBER_JACKS; i++) {
748    for (j = 0; j < 2; j++) {
749      if (mixerLinks[i][j].mixerVar != NULL) {
750	SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel,
751			    tmp, VOLBUFSIZE);
752	obj = Tcl_NewIntObj(atoi(tmp));
753	var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1);
754	Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1);
755      }
756    }
757    if ((i == 0 && info.play.port & AUDIO_SPEAKER) ||
758	(i == 1 && info.play.port & AUDIO_LINE_OUT) ||
759	(i == 2 && info.play.port & AUDIO_HEADPHONE) ||
760	(i == 3 && info.record.port & AUDIO_MICROPHONE) ||
761	(i == 4 && info.record.port & AUDIO_LINE_IN)) {
762      status = 1;
763    } else {
764      status = 0;
765    }
766
767    if (mixerLinks[i][j].jackVar != NULL) {
768      obj = Tcl_NewIntObj(status);
769      var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1);
770      Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY |TCL_PARSE_PART1);
771    }
772  }
773}
774
775void
776SnackMixerGetLineLabels(char *buf, int n)
777{
778  strncpy(buf, "Play Record", n);
779  buf[n-1] = '\0';
780}
781
782int
783SnackGetOutputDevices(char **arr, int n)
784{
785  arr[0] = (char *) SnackStrDup("default");
786
787  return 1;
788}
789
790int
791SnackGetInputDevices(char **arr, int n)
792{
793  arr[0] = (char *) SnackStrDup("default");
794
795  return 1;
796}
797
798int
799SnackGetMixerDevices(char **arr, int n)
800{
801  arr[0] = (char *) SnackStrDup("default");
802
803  return 1;
804}
805