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 
30 extern int rop, wop;
31 extern double startDevTime;
32 extern struct jkQueuedSound *soundQueue;
33 extern 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 
42 double globalLatency = BUFSECS;
43 float globalScaling = 1.0f;
44 
45 char defaultOutDevice[MAX_DEVICE_NAME_LENGTH];
46 char defaultInDevice[MAX_DEVICE_NAME_LENGTH];
47 
48 char *
SnackStrDup(const char * str)49 SnackStrDup(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 
60 static int
outDevicesCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])61 outDevicesCmd(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 
79 static int
inDevicesCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])80 inDevicesCmd(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 
98 static int
selectOutCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])99 selectOutCmd(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 
128 static int
selectInCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])129 selectInCmd(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 
158 static int
encodingsCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])159 encodingsCmd(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 
168 static int
ratesCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])169 ratesCmd(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 
179 static int
activeCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])180 activeCmd(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 
191 static int
play_gainCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])192 play_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 
209 static int
record_gainCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])210 record_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 
227 static int
elapsedTimeCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])228 elapsedTimeCmd(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 
243 static int
currentSoundCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])244 currentSoundCmd(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 
269 static int
playLatencyCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])270 playLatencyCmd(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 
288 static int
scalingCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])289 scalingCmd(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 
307 static int
audioPlayCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])308 audioPlayCmd(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 
317 static int
audioStopCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])318 audioStopCmd(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 
342 static int
audioPauseCmd(Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])343 audioPauseCmd(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 
353 int nAudioCommands   = NAUDIOCOMMANDS;
354 int maxAudioCommands = MAXAUDIOCOMMANDS;
355 
356 CONST84 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 
380 audioCmd *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 
401 audioDelCmd *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 
421 int
Snack_AudioCmd(ClientData cdata,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])422 Snack_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 
440 void
Snack_AudioDeleteCmd(ClientData clientData)441 Snack_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