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 <stdlib.h>
23#include <stdio.h>
24#include <signal.h>
25#include <math.h>
26#include <string.h>
27#include "tcl.h"
28#include "snack.h"
29
30extern int rop, wop;
31extern double startDevTime;
32extern struct jkQueuedSound *soundQueue;
33extern struct jkQueuedSound *rsoundQueue;
34
35#if defined(HPUX) || defined(MAC)
36/* Choosing a good generic value for HP-UX is not easy */
37#  define BUFSECS 2.0
38#else
39#  define BUFSECS 0.25
40#endif
41
42double globalLatency = BUFSECS;
43float globalScaling = 1.0f;
44
45char defaultOutDevice[MAX_DEVICE_NAME_LENGTH];
46char defaultInDevice[MAX_DEVICE_NAME_LENGTH];
47
48char *
49SnackStrDup(const char *str)
50{
51  char *new = ckalloc(strlen(str)+1);
52
53  if (new) {
54    strcpy(new, str);
55  }
56
57  return new;
58}
59
60static int
61outDevicesCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
62{
63  int i, n;
64  char *arr[MAX_NUM_DEVICES];
65  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
66
67  n = SnackGetOutputDevices(arr, MAX_NUM_DEVICES);
68
69  for (i = 0; i < n; i++) {
70    Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(arr[i], -1));
71    ckfree(arr[i]);
72  }
73
74  Tcl_SetObjResult(interp, list);
75
76  return TCL_OK;
77}
78
79static int
80inDevicesCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
81{
82  int i, n;
83  char *arr[MAX_NUM_DEVICES];
84  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
85
86  n = SnackGetInputDevices(arr, MAX_NUM_DEVICES);
87
88  for (i = 0; i < n; i++) {
89    Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(arr[i], -1));
90    ckfree(arr[i]);
91  }
92
93  Tcl_SetObjResult(interp, list);
94
95  return TCL_OK;
96}
97
98static int
99selectOutCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
100{
101  int i, n, found = 0;
102  char *arr[MAX_NUM_DEVICES];
103  char *devstr;
104
105  n = SnackGetOutputDevices(arr, MAX_NUM_DEVICES);
106
107  if (objc == 3) {
108    devstr = Tcl_GetStringFromObj(objv[2], NULL);
109    for (i = 0; i < n; i++) {
110      if (strncmp(devstr, arr[i], strlen(devstr)) == 0 && found == 0) {
111	strcpy(defaultOutDevice, arr[i]);
112	found = 1;
113      }
114      ckfree(arr[i]);
115    }
116    if (found == 0) {
117      Tcl_AppendResult(interp, "No such device: ", devstr, (char *) NULL);
118      return TCL_ERROR;
119    }
120  } else {
121    Tcl_WrongNumArgs(interp, 1, objv, "selectOutput device");
122    return TCL_ERROR;
123  }
124
125  return TCL_OK;
126}
127
128static int
129selectInCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
130{
131  int i, n, found = 0;
132  char *arr[MAX_NUM_DEVICES];
133  char *devstr;
134
135  n = SnackGetInputDevices(arr, MAX_NUM_DEVICES);
136
137  if (objc == 3) {
138    devstr = Tcl_GetStringFromObj(objv[2], NULL);
139    for (i = 0; i < n; i++) {
140      if (strncmp(devstr, arr[i], strlen(devstr)) == 0 && found == 0) {
141	strcpy(defaultInDevice, arr[i]);
142	found = 1;
143      }
144      ckfree(arr[i]);
145    }
146    if (found == 0) {
147      Tcl_AppendResult(interp, "No such device: ", devstr, (char *) NULL);
148      return TCL_ERROR;
149    }
150  } else {
151    Tcl_WrongNumArgs(interp, 1, objv, "selectInput device");
152    return TCL_ERROR;
153  }
154
155  return TCL_OK;
156}
157
158static int
159encodingsCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
160{
161  char *str = "Lin16 Mulaw Alaw Lin8offset Lin8 Lin24 Lin24packed Lin32 Float";
162
163  Tcl_SetObjResult(interp, Tcl_NewStringObj(str, -1));
164
165  return TCL_OK;
166}
167
168static int
169ratesCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
170{
171  char tmpstr[QUERYBUFSIZE];
172
173  SnackAudioGetRates(defaultOutDevice, tmpstr, QUERYBUFSIZE);
174  Tcl_SetObjResult(interp, Tcl_NewStringObj(tmpstr, -1));
175
176  return TCL_OK;
177}
178
179static int
180activeCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
181{
182  if (wop == IDLE && rop == IDLE) {
183    Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
184  } else {
185    Tcl_SetObjResult(interp, Tcl_NewIntObj(1));
186  }
187
188  return TCL_OK;
189}
190
191static int
192play_gainCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
193{
194  int g;
195
196  if (objc == 3) {
197    if (Tcl_GetIntFromObj(interp, objv[2], &g) != TCL_OK) return TCL_ERROR;
198    ASetPlayGain(g);
199  } else {
200#ifdef HPUX
201    if (wop == IDLE)
202#endif
203      Tcl_SetObjResult(interp, Tcl_NewIntObj(AGetPlayGain()));
204  }
205
206  return TCL_OK;
207}
208
209static int
210record_gainCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
211{
212  int g;
213
214  if (objc == 3) {
215    if (Tcl_GetIntFromObj(interp, objv[2], &g) != TCL_OK) return TCL_ERROR;
216	ASetRecGain(g);
217  } else {
218#ifdef HPUX
219    if (rop == IDLE)
220#endif
221      Tcl_SetObjResult(interp, Tcl_NewIntObj(AGetRecGain()));
222  }
223
224  return TCL_OK;
225}
226
227static int
228elapsedTimeCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
229{
230  double elapsedTime = SnackCurrentTime() - startDevTime;
231
232  if (wop == IDLE && rop == IDLE) {
233    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(0.0));
234  }  else if (wop == PAUSED || rop == PAUSED) {
235    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(startDevTime));
236  } else {
237    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(elapsedTime));
238  }
239
240  return TCL_OK;
241}
242
243static int
244currentSoundCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
245{
246  jkQueuedSound *p;
247  char *res;
248  Tcl_HashSearch hashSearch;
249  Tcl_HashEntry *entryPtr;
250
251  if (soundQueue == NULL) {
252    Tcl_SetObjResult(interp, Tcl_NewStringObj("", -1));
253    return TCL_OK;
254  }
255  for (p = soundQueue; p->next != NULL && p->next->status == SNACK_QS_DONE;
256       p = p->next);
257
258  entryPtr = Tcl_FirstHashEntry(p->sound->soundTable, &hashSearch);
259
260  if (p->sound != (Sound *) Tcl_GetHashValue(entryPtr)) {
261    entryPtr = Tcl_NextHashEntry(&hashSearch);
262  }
263  res = Tcl_GetHashKey(p->sound->soundTable, entryPtr);
264  Tcl_SetObjResult(interp, Tcl_NewStringObj(res, -1));
265
266  return TCL_OK;
267}
268
269static int
270playLatencyCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
271{
272  double d;
273
274  if (objc == 2) {
275    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(1000.0*globalLatency));
276  } else if (objc == 3) {
277    if (Tcl_GetDoubleFromObj(interp, objv[2], &d) != TCL_OK) {
278      return TCL_ERROR;
279    }
280    globalLatency = d / 1000.0;
281  } else {
282    Tcl_WrongNumArgs(interp, 1, objv, "playLatency ?milliseconds?");
283    return TCL_ERROR;
284  }
285  return TCL_OK;
286}
287
288static int
289scalingCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
290{
291  double d = 0.0;
292
293  if (objc == 2) {
294    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(globalScaling));
295  } else if (objc == 3) {
296    if (Tcl_GetDoubleFromObj(interp, objv[2], &d) != TCL_OK) {
297      return TCL_ERROR;
298    }
299    globalScaling = (float) d;
300  } else {
301    Tcl_WrongNumArgs(interp, 1, objv, "scaling ?factor?");
302    return TCL_ERROR;
303  }
304  return TCL_OK;
305}
306
307static int
308audioPlayCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
309{
310  if (rop == PAUSED || wop == PAUSED) {
311    SnackPauseAudio();
312  }
313
314  return TCL_OK;
315}
316
317static int
318audioStopCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
319{
320  jkQueuedSound *p;
321
322  if (rop == READ || rop == PAUSED) {
323    for (p = rsoundQueue; p != NULL; p = p->next) {
324      Snack_StopSound(p->sound, interp);
325    }
326  }
327  if (wop == WRITE || wop == PAUSED) {
328    for (p = soundQueue; p != NULL; p = p->next) {
329      Snack_StopSound(p->sound, interp);
330      /*
331       * The soundQueue can be remooved during a stop, so check it
332       * otherwise p is garbage
333       */
334      if (soundQueue == NULL)
335	break;
336    }
337  }
338
339  return TCL_OK;
340}
341
342static int
343audioPauseCmd(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
344{
345  SnackPauseAudio();
346
347  return TCL_OK;
348}
349
350#define NAUDIOCOMMANDS   17
351#define MAXAUDIOCOMMANDS 25
352
353int nAudioCommands   = NAUDIOCOMMANDS;
354int maxAudioCommands = MAXAUDIOCOMMANDS;
355
356CONST84 char *audioCmdNames[MAXAUDIOCOMMANDS] = {
357  "outputDevices",
358  "inputDevices",
359  "selectOutput",
360  "selectInput",
361  "formats",
362  "frequencies",
363  "active",
364  "play_gain",
365  "record_gain",
366  "elapsedTime",
367  "currentSound",
368  "playLatency",
369  "scaling",
370  "encodings",
371  "rates",
372  "play",
373  "stop",
374  "pause",
375  NULL
376};
377
378/* NOTE: NAUDIOCOMMANDS needs updating when new commands are added. */
379
380audioCmd *audioCmdProcs[MAXAUDIOCOMMANDS] = {
381  outDevicesCmd,
382  inDevicesCmd,
383  selectOutCmd,
384  selectInCmd,
385  encodingsCmd,
386  ratesCmd,
387  activeCmd,
388  play_gainCmd,
389  record_gainCmd,
390  elapsedTimeCmd,
391  currentSoundCmd,
392  playLatencyCmd,
393  scalingCmd,
394  encodingsCmd,
395  ratesCmd,
396  audioPlayCmd,
397  audioStopCmd,
398  audioPauseCmd
399};
400
401audioDelCmd *audioDelCmdProcs[MAXAUDIOCOMMANDS] = {
402  NULL,
403  NULL,
404  NULL,
405  NULL,
406  NULL,
407  NULL,
408  NULL,
409  NULL,
410  NULL,
411  NULL,
412  NULL,
413  NULL,
414  NULL,
415  NULL,
416  NULL,
417  NULL,
418  NULL
419};
420
421int
422Snack_AudioCmd(ClientData cdata, Tcl_Interp *interp, int objc,
423	       Tcl_Obj *CONST objv[])
424{
425  int index;
426
427  if (objc < 2) {
428    Tcl_WrongNumArgs(interp, 1, objv, "option ?arg?");
429    return TCL_ERROR;
430  }
431
432  if (Tcl_GetIndexFromObj(interp, objv[1], audioCmdNames, "option", 0,
433			  &index) != TCL_OK) {
434    return TCL_ERROR;
435  }
436
437  return((audioCmdProcs[index])(interp, objc, objv));
438}
439
440void
441Snack_AudioDeleteCmd(ClientData clientData)
442{
443  int i;
444
445  for (i = 0; i < nAudioCommands; i++) {
446    if (audioDelCmdProcs[i] != NULL) {
447      (audioDelCmdProcs[i])();
448    }
449  }
450}
451