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 <stdio.h>
25#include <fcntl.h>
26#include <unistd.h>
27
28extern void Snack_WriteLog(char *s);
29extern void Snack_WriteLogInt(char *s, int n);
30
31#ifndef min
32#define min(a,b) ((a)<(b)?(a):(b))
33#define max(a,b) ((a)>(b)?(a):(b))
34#endif
35
36#define SNACK_NUMBER_MIXERS 2
37
38struct MixerLink mixerLinks[SNACK_NUMBER_MIXERS][2];
39
40#define OLD_AL
41
42int
43SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq,
44	       int nchannels, int encoding)
45{
46#ifdef OLD_AL
47#  define alSetWidth(A,B)       ALsetwidth(A,B)
48#  define alSetChannels(A,B)    ALsetchannels(A,B)
49#  define alGetFrameNumber(A,B) ALgetframenumber(A, (unsigned long long *)B)
50#  define alGetFilled(A)        ALgetfilled(A)
51#  define alClosePort(A)        ALcloseport(A)
52#  define alFreeConfig(A)       ALfreeconfig(A)
53#  define alReadFrames(A,B,C)   ALreadsamps(A,B,C)
54#  define alWriteFrames(A,B,C)  ALwritesamps(A,B,C)
55#  define alSetFillPoint(A,B)   ALsetfillpoint(A,B)
56#  define alGetFillable(A)      ALgetfillable(A)
57
58  typedef long long stamp_t;
59
60  long buf[2];
61  A->config = ALnewconfig();
62#else
63  ALpv pv[2];
64  A->config = alNewConfig();
65#endif /* OLD_AL */
66
67  switch (encoding) {
68  case LIN16:
69    if (alSetWidth(A->config, AL_SAMPLE_16) == -1) {
70      Tcl_AppendResult(interp, "Failed alSetWidth: AL_SAMPLE_16.", NULL);
71      return TCL_ERROR;
72    }
73    A->bytesPerSample = sizeof(short);
74    break;
75  case ALAW:
76    A->bytesPerSample = sizeof(char);
77    Tcl_AppendResult(interp, "Unsupported format ALAW.", NULL);
78    return TCL_ERROR;
79  case MULAW:
80    A->bytesPerSample = sizeof(char);
81    Tcl_AppendResult(interp, "Unsupported format MULAW.", NULL);
82    return TCL_ERROR;
83  case LIN8OFFSET:
84    A->bytesPerSample = sizeof(char);
85    Tcl_AppendResult(interp, "Unsupported format LIN8OFFSET.", NULL);
86    return TCL_ERROR;
87  case LIN8:
88    if (alSetWidth(A->config, AL_SAMPLE_8) == -1) {
89      Tcl_AppendResult(interp, "Failed alSetWidth: AL_SAMPLE_8.", NULL);
90      return TCL_ERROR;
91    }
92    A->bytesPerSample = sizeof(char);
93    break;
94  }
95
96  if (alSetChannels(A->config, nchannels) == -1) {
97    Tcl_AppendResult(interp, "Failed alSetChannels.", NULL);
98    return TCL_ERROR;
99  }
100  A->nChannels = nchannels;
101
102  A->mode = mode;
103  switch (mode) {
104  case RECORD:
105
106#ifdef OLD_AL
107    buf[0] = AL_INPUT_RATE;
108    buf[1] = freq;
109    if (ALsetparams(AL_DEFAULT_DEVICE, buf, 2) == -1) {
110      Tcl_AppendResult(interp, "Failed ALsetparams.", NULL);
111      return TCL_ERROR;
112    }
113    if ((A->port = ALopenport("snack-in", "r", A->config)) == NULL) {
114      Tcl_AppendResult(interp, "Failed ALopenport.", NULL);
115      return TCL_ERROR;
116    }
117#else
118    pv[0].param = AL_MASTER_CLOCK;
119    pv[0].value.i = AL_CRYSTAL_MCLK_TYPE;
120    pv[1].param = AL_RATE;
121    pv[1].value.ll = alDoubleToFixed((double)(freq * 1.0));
122    if (alSetParams(AL_DEFAULT_INPUT, pv, 2) == -1) {
123      printf("Error: %s\n", alGetErrorString(oserror()));
124      if (pv[0].sizeOut < 0) printf("AL_MASTER_CLOCK\n");
125      if (pv[1].sizeOut < 0) printf("Rate invalid\n");
126      Tcl_AppendResult(interp, "Failed alSetParams.", NULL);
127      return TCL_ERROR;
128    }
129    if ((A->port = alOpenPort("snack-in", "r", A->config)) == NULL) {
130      Tcl_AppendResult(interp, "Failed ALopenport.", NULL);
131      return TCL_ERROR;
132    }
133#endif /* OLD_AL */
134    break;
135
136  case PLAY:
137#ifdef OLD_AL
138    buf[0] = AL_OUTPUT_RATE;
139    buf[1] = freq;
140    if (ALsetparams(AL_DEFAULT_DEVICE, buf, 2) == -1) {
141      Tcl_AppendResult(interp, "Failed ALsetparams.", NULL);
142      return TCL_ERROR;
143    }
144    if ((A->port = ALopenport("snack-out", "w", A->config)) ==NULL) {
145      Tcl_AppendResult(interp, "Failed ALopenport.", NULL);
146      return TCL_ERROR;
147    }
148#else
149    pv[0].param = AL_MASTER_CLOCK;
150    pv[0].value.i = AL_CRYSTAL_MCLK_TYPE;
151    pv[1].param = AL_RATE;
152    pv[1].value.ll = alDoubleToFixed((double)(freq * 1.0));
153    if (alSetParams(AL_DEFAULT_OUTPUT, pv, 1) == -1) {
154      Tcl_AppendResult(interp, "Failed ALsetparams.", NULL);
155      return TCL_ERROR;
156    }
157    if ((A->port = alOpenPort("snack-out", "w", A->config)) == NULL) {
158      Tcl_AppendResult(interp, "Failed ALopenport.", NULL);
159      return TCL_ERROR;
160    }
161#endif /* OLD_AL */
162    break;
163  }
164
165  alGetFrameNumber(A->port, (stamp_t*) &A->startfn);
166  A->count = 0;
167
168  return TCL_OK;
169}
170
171int
172SnackAudioClose(ADesc *A)
173{
174  if (A->port != NULL) {
175    if (alGetFilled(A->port) > 0) {
176      return(-1);
177    }
178    alClosePort(A->port);
179  }
180  alFreeConfig(A->config);
181  A->count = 0;
182
183  return(0);
184}
185
186long
187SnackAudioPause(ADesc *A)
188{
189  switch (A->mode) {
190  case RECORD:
191#ifdef OLD_AL
192    alClosePort(A->port);
193    A->port = NULL;
194#else
195#endif
196    break;
197
198  case PLAY:
199#ifdef OLD_AL
200    A->count = SnackAudioPlayed(A);
201    alClosePort(A->port);
202    A->port = NULL;
203    return(A->count);
204#else
205#endif
206    /*    break;*/
207  }
208  return(-1);
209}
210
211void
212SnackAudioResume(ADesc *A)
213{
214  switch (A->mode) {
215  case RECORD:
216    break;
217
218  case PLAY:
219#ifdef OLD_AL
220    A->port = ALopenport("snack-out", "w", A->config);
221#else
222#endif
223    break;
224  }
225}
226
227void
228SnackAudioFlush(ADesc *A)
229{
230  switch (A->mode) {
231  case RECORD:
232    break;
233
234  case PLAY:
235#ifdef OLD_AL
236    alClosePort(A->port);
237    A->port = NULL;
238#else
239    alDiscardFrames(A->port, 10000000);
240#endif
241    break;
242  }
243}
244
245void
246SnackAudioPost(ADesc *A)
247{
248}
249
250int
251SnackAudioRead(ADesc *A, void *buf, int nFrames)
252{
253  alReadFrames(A->port, buf, nFrames * A->nChannels); /* always returns 0 */
254  return(nFrames);
255}
256
257int
258SnackAudioWrite(ADesc *A, void *buf, int nFrames)
259{
260  alWriteFrames(A->port, buf, nFrames * A->nChannels);
261  return(nFrames);
262}
263
264int
265SnackAudioReadable(ADesc *A)
266{
267  if (A->port == NULL) {
268    return(0);
269  }
270  return alGetFilled(A->port);
271}
272
273int
274SnackAudioWriteable(ADesc *A)
275{
276  return alGetFillable(A->port);
277}
278
279long
280SnackAudioPlayed(ADesc *A)
281{
282  unsigned long long fnum;
283
284  /* Needed on IRIX 6.2 */
285  typedef long long stamp_t;
286
287  alGetFrameNumber(A->port, (stamp_t*) &fnum);
288
289  return(A->count + (int)(fnum - A->startfn));
290}
291
292void
293SnackAudioInit()
294{
295}
296
297void
298SnackAudioFree()
299{
300  int i, j;
301
302  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
303    for (j = 0; j < 2; j++) {
304      if (mixerLinks[i][j].mixer != NULL) {
305	ckfree(mixerLinks[i][j].mixer);
306      }
307      if (mixerLinks[i][j].mixerVar != NULL) {
308	ckfree(mixerLinks[i][j].mixerVar);
309      }
310    }
311    if (mixerLinks[i][0].jack != NULL) {
312      ckfree(mixerLinks[i][0].jack);
313    }
314    if (mixerLinks[i][0].jackVar != NULL) {
315      ckfree((char *)mixerLinks[i][0].jackVar);
316    }
317  }
318}
319
320void
321ASetRecGain(int gain)
322{
323  int g = min(max(gain, 0), 100);
324
325#ifdef OLD_AL
326  long buf[4];
327
328  g = 255 - g * 255 / 100;
329  buf[0] = AL_LEFT_INPUT_ATTEN;
330  buf[1] = g;
331  buf[2] = AL_RIGHT_INPUT_ATTEN;
332  buf[3] = g;
333  ALsetparams(AL_DEFAULT_DEVICE, buf, 4);
334
335#else
336  ALpv x[2];
337  ALfixed gains[2];
338  ALparamInfo pi;
339  double gmin, gmax, fg;
340
341  alGetParamInfo(AL_DEFAULT_INPUT, AL_GAIN, &pi);
342  gmax = alFixedToDouble(pi.max.ll);
343  gmin = alFixedToDouble(pi.min.ll);
344
345  x[0].param = AL_GAIN;
346  x[0].value.ptr = gains;
347  x[0].sizeIn = 2;
348  x[1].param = AL_CHANNELS;
349
350  fg = alDoubleToFixed(((double) g / 100.0) * (gmax - gmin) + gmin);
351  gains[0] = gains[1] = fg;
352#endif /* OLD_AL */
353}
354
355void
356ASetPlayGain(int gain)
357{
358  int g = min(max(gain, 0), 100);
359
360#ifdef OLD_AL
361  long buf[4];
362
363  g = 255 - g * 255 / 100;
364  buf[0] = AL_LEFT_INPUT_ATTEN;
365  buf[1] = g;
366  buf[2] = AL_RIGHT_INPUT_ATTEN;
367  buf[3] = g;
368  ALsetparams(AL_DEFAULT_DEVICE, buf, 4);
369#else
370  ALpv x[2];
371  ALfixed gains[2];
372  ALparamInfo pi;
373  double gmin, gmax, fg;
374
375  alGetParamInfo(AL_DEFAULT_OUTPUT, AL_GAIN, &pi);
376  gmax = alFixedToDouble(pi.max.ll);
377  gmin = alFixedToDouble(pi.min.ll);
378
379  x[0].param = AL_GAIN;
380  x[0].value.ptr = gains;
381  x[0].sizeIn = 2;
382  x[1].param = AL_CHANNELS;
383
384  fg = alDoubleToFixed(((double) g / 100.0) * (gmax - gmin) + gmin);
385  gains[0] = gains[1] = fg;
386#endif /* OLD_AL */
387}
388
389int
390AGetRecGain()
391{
392#ifdef OLD_AL
393  int g = 0;
394  long buf[2];
395
396  buf[0] = AL_LEFT_INPUT_ATTEN;
397  ALgetparams(AL_DEFAULT_DEVICE, buf, 2);
398  g = 100 - buf[1] * 100 / 255;
399
400#else
401  int g = 0;
402  ALpv x[2];
403  ALfixed gain[2];
404  ALparamInfo pi;
405  double gmin, gmax;
406
407  alGetParamInfo(AL_DEFAULT_INPUT, AL_GAIN, &pi);
408  gmax = alFixedToDouble(pi.max.ll);
409  gmin = alFixedToDouble(pi.min.ll);
410
411  x[0].param = AL_GAIN;
412  x[0].value.ptr = gain;
413  x[0].sizeIn = 2;
414  x[1].param = AL_CHANNELS;
415
416  alGetParams(AL_DEFAULT_INPUT, x, 2);
417
418  g = (int) (100.0 * (alFixedToDouble(gain[0]) - gmin) / (gmax - gmin));
419#endif /* OLD_AL */
420
421  return(g);
422}
423
424int
425AGetPlayGain()
426{
427#ifdef OLD_AL
428  int g = 0;
429  long buf[2];
430
431  buf[0] = AL_LEFT_SPEAKER_GAIN;
432  ALgetparams(AL_DEFAULT_DEVICE, buf, 2);
433  g = buf[1] * 100 / 255;
434
435#else
436  int g = 0;
437  ALpv x[2];
438  ALfixed gain[2];
439  ALparamInfo pi;
440  double gmin, gmax;
441
442  alGetParamInfo(AL_DEFAULT_OUTPUT, AL_GAIN, &pi);
443  gmax = alFixedToDouble(pi.max.ll);
444  gmin = alFixedToDouble(pi.min.ll);
445
446  x[0].param = AL_GAIN;
447  x[0].value.ptr = gain;
448  x[0].sizeIn = 2;
449  x[1].param = AL_CHANNELS;
450
451  alGetParams(AL_DEFAULT_INPUT, x, 2);
452
453  g = (int) (100.0 * (alFixedToDouble(gain[0]) - gmin) / (gmax - gmin));
454#endif /* OLD_AL */
455
456  return(g);
457}
458
459int
460SnackAudioGetEncodings(char *device)
461{
462  return(LIN24 | LIN16);
463}
464
465void
466SnackAudioGetRates(char *device, char *buf, int n)
467{
468  strncpy(buf, "8000 11025 16000 22050 32000 44100 48000", n);
469  buf[n-1] = '\0';
470}
471
472int
473SnackAudioMaxNumberChannels(char *device)
474{
475  return(16);
476}
477
478int
479SnackAudioMinNumberChannels(char *device)
480{
481  return(1);
482}
483
484void
485SnackMixerGetInputJackLabels(char *buf, int n)
486{
487  strncpy(buf, "Microphone \"Line In\"", n);
488  buf[n-1] = '\0';
489}
490
491void
492SnackMixerGetOutputJackLabels(char *buf, int n)
493{
494  strncpy(buf, "Line Headphones", n);
495  buf[n-1] = '\0';
496}
497
498void
499SnackMixerGetInputJack(char *buf, int n)
500{
501  strncpy(buf, "Microphone", n);
502  buf[n-1] = '\0';
503}
504
505int
506SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status)
507{
508  return 1;
509}
510
511void
512SnackMixerGetOutputJack(char *buf, int n)
513{
514  buf[0] = '\0';
515}
516
517void
518SnackMixerSetOutputJack(char *jack, char *status)
519{
520}
521
522void
523SnackMixerGetChannelLabels(char *line, char *buf, int n)
524{
525  strncpy(buf, "Mono", n);
526  buf[n-1] = '\0';
527}
528
529void
530SnackMixerGetVolume(char *line, int channel, char *buf, int n)
531{
532  if (strncasecmp(line, "Record", strlen(line)) == 0) {
533    sprintf(buf, "%d", AGetRecGain());
534  }
535  if (strncasecmp(line, "Play", strlen(line)) == 0) {
536    sprintf(buf, "%d", AGetPlayGain());
537  }
538}
539
540void
541SnackMixerSetVolume(char *line, int channel, int volume)
542{
543  if (strncasecmp(line, "Record", strlen(line)) == 0) {
544    ASetRecGain(volume);
545  }
546  if (strncasecmp(line, "Play", strlen(line)) == 0) {
547    ASetPlayGain(volume);
548  }
549}
550
551void
552SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var)
553{
554}
555
556static char *
557VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
558	      CONST84 char *name2, int flags)
559{
560  MixerLink *mixLink = (MixerLink *) clientData;
561  CONST84 char *stringValue;
562
563  if (flags & TCL_TRACE_UNSETS) {
564    if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
565      Tcl_Obj *obj, *var;
566      char tmp[VOLBUFSIZE];
567
568      SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE);
569      obj = Tcl_NewIntObj(atoi(tmp));
570      var = Tcl_NewStringObj(mixLink->mixerVar, -1);
571      Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
572      Tcl_TraceVar(interp, mixLink->mixerVar,
573		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
574		   VolumeVarProc, (int *)mixLink);
575    }
576    return (char *) NULL;
577  }
578  stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY);
579  if (stringValue != NULL) {
580    SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue));
581  }
582
583  return (char *) NULL;
584}
585
586void
587SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n,
588		     Tcl_Obj *CONST objv[])
589{
590  char *mixLabels[] = { "Play", "Record" };
591  int i, j, channel;
592  CONST84 char *value;
593  char tmp[VOLBUFSIZE];
594
595  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
596    if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
597      for (j = 0; j < n; j++) {
598	if (n == 1) {
599	  channel = -1;
600	} else {
601	  channel = j;
602	}
603	mixerLinks[i][j].mixer = (char *)SnackStrDup(line);
604	mixerLinks[i][j].mixerVar = (char *)SnackStrDup(Tcl_GetStringFromObj(objv[j+3], NULL));
605	mixerLinks[i][j].channel = j;
606	value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY);
607	if (value != NULL) {
608	  SnackMixerSetVolume(line, channel, atoi(value));
609	} else {
610	  Tcl_Obj *obj;
611	  SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE);
612	  obj = Tcl_NewIntObj(atoi(tmp));
613	  Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj,
614			 TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
615	}
616	Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar,
617		     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
618		     VolumeVarProc, (ClientData) &mixerLinks[i][j]);
619      }
620    }
621  }
622}
623
624void
625SnackMixerUpdateVars(Tcl_Interp *interp)
626{
627  int i, j;
628  char tmp[VOLBUFSIZE];
629  Tcl_Obj *obj, *var;
630
631  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
632    for (j = 0; j < 2; j++) {
633      if (mixerLinks[i][j].mixerVar != NULL) {
634	SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel,
635			    tmp, VOLBUFSIZE);
636	obj = Tcl_NewIntObj(atoi(tmp));
637	var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1);
638	Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1);
639      }
640    }
641  }
642}
643
644void
645SnackMixerGetLineLabels(char *buf, int n)
646{
647  strncpy(buf, "Play Record", n);
648  buf[n-1] = '\0';
649}
650
651int
652SnackGetOutputDevices(char **arr, int n)
653{
654  arr[0] = (char *) SnackStrDup("default");
655
656  return 1;
657}
658
659int
660SnackGetInputDevices(char **arr, int n)
661{
662  arr[0] = (char *) SnackStrDup("default");
663
664  return 1;
665}
666
667int
668SnackGetMixerDevices(char **arr, int n)
669{
670  arr[0] = (char *) SnackStrDup("default");
671
672  return 1;
673}
674