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 #if defined(MAC)
31 #  define FIXED_READ_CHUNK 1
32 #endif /* MAC */
33 
34 int rop = IDLE;
35 int numRec = 0;
36 int wop = IDLE;
37 static ADesc adi;
38 static ADesc ado;
39 static int globalRate = 16000;
40 static int globalOutWidth = 0;
41 static int globalStreamWidth = 0;
42 static long globalNWritten = 0;
43 static int globalNFlowThrough = 0;
44 
45 short shortBuffer[PBSIZE];
46 float floatBuffer[PBSIZE];
47 float fff[PBSIZE];
48 static Tcl_TimerToken ptoken;
49 static Tcl_TimerToken rtoken;
50 
51 #define FPS 32
52 #define RECGRAIN 10
53 #define BUFSCROLLSIZE 25000
54 struct jkQueuedSound *rsoundQueue = NULL;
55 
56 extern int debugLevel;
57 extern char *snackDumpFile;
58 static Tcl_Channel snackDumpCh = NULL;
59 
60 extern struct Snack_FileFormat *snackFileFormats;
61 
62 static void
RecCallback(ClientData clientData)63 RecCallback(ClientData clientData)
64 {
65   jkQueuedSound *p;
66   int nRead = 0, i, sampsleft = SnackAudioReadable(&adi);
67   int size = globalRate / FPS;
68   Snack_FileFormat *ff;
69 
70   if (debugLevel > 1) Snack_WriteLogInt("  Enter RecCallback", sampsleft);
71 
72   if (sampsleft > size * 2) size *= 2;
73   if (sampsleft > size * 2) size = sampsleft;
74   if (sampsleft < size) size = sampsleft;
75   if (size > PBSIZE / globalStreamWidth) {
76     size = PBSIZE / globalStreamWidth;
77   }
78 
79 #ifdef FIXED_READ_CHUNK
80   size = globalRate / 16;
81 #endif
82 
83   if (adi.bytesPerSample == 4) {
84     nRead = SnackAudioRead(&adi, floatBuffer, size);
85   } else {
86     nRead = SnackAudioRead(&adi, shortBuffer, size);
87   }
88 
89   for (p = rsoundQueue; p != NULL; p = p->next) {
90     Sound *s = p->sound;
91 
92     if (s->debug > 2) Snack_WriteLogInt("    readstatus? ", s->readStatus);
93     if (s->readStatus == IDLE) continue;
94     if (p->status) continue;
95     if (s->rwchan) { /* sound from file or channel */
96 
97       if ((s->length + nRead - s->validStart) * s->nchannels > FBLKSIZE) {
98 	s->validStart += (BUFSCROLLSIZE / s->nchannels);
99 	memmove(&s->blocks[0][0], &s->blocks[0][BUFSCROLLSIZE],
100 		(FBLKSIZE-BUFSCROLLSIZE) * sizeof(float));
101       }
102 
103       if (adi.bytesPerSample == 4) {
104 	for (i = 0; i < nRead * s->nchannels; i++) {
105 	  FSAMPLE(s, (s->length - s->validStart) * s->nchannels + i) =
106 	    (float) (((int*)floatBuffer)[i]/256);
107 	}
108       } else {
109 	for (i = 0; i < nRead * s->nchannels; i++) {
110 	  FSAMPLE(s, (s->length - s->validStart) * s->nchannels + i) =
111 	    (float) shortBuffer[i];
112 	}
113       }
114 
115       for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
116 	if (strcmp(s->fileType, ff->name) == 0) {
117 	  WriteSound(ff->writeProc, s, s->interp, s->rwchan, NULL,
118 		     s->length - s->validStart, nRead);
119 	}
120       }
121 
122       Tcl_Flush(s->rwchan);
123 
124     } else { /* sound in memory */
125       if (s->length > s->maxlength - max(sampsleft, adi.bytesPerSample * nRead)) {
126 	if (Snack_ResizeSoundStorage(s, s->length + max(sampsleft, adi.bytesPerSample * nRead)) != TCL_OK) {
127 	  return;
128 	}
129       }
130 
131       if (s->debug > 2) Snack_WriteLogInt("    adding frames", nRead);
132       if (adi.bytesPerSample == 4) {
133 	for (i = 0; i < nRead * s->nchannels; i++) {
134 	  FSAMPLE(s, s->length * s->nchannels + i) = (float) (((int*)floatBuffer)[i]/256);
135 	}
136       } else {
137 	for (i = 0; i < nRead * s->nchannels; i++) {
138 	  FSAMPLE(s, s->length * s->nchannels + i) = (float) shortBuffer[i];
139 	}
140       }
141     }
142     if (nRead > 0) {
143       if (s->storeType == SOUND_IN_MEMORY) {
144 	Snack_UpdateExtremes(s, s->length, s->length + nRead, SNACK_MORE_SOUND);
145       }
146       s->length += nRead;
147       Snack_ExecCallbacks(s, SNACK_MORE_SOUND);
148     }
149   }
150   rtoken = Tcl_CreateTimerHandler(RECGRAIN, (Tcl_TimerProc *) RecCallback,
151 				  (int *) NULL);
152 
153   if (debugLevel > 1) Snack_WriteLogInt("  Exit RecCallback", nRead);
154 }
155 
156 static void
ExecSoundCmd(Sound * s,Tcl_Obj * cmdPtr)157 ExecSoundCmd(Sound *s, Tcl_Obj *cmdPtr)
158 {
159   Tcl_Interp *interp = s->interp;
160 
161   if (cmdPtr != NULL) {
162     Tcl_Preserve((ClientData) interp);
163     if (Tcl_GlobalEvalObj(interp, cmdPtr) != TCL_OK) {
164       Tcl_AddErrorInfo(interp, "\n    (\"command\" script)");
165       Tcl_BackgroundError(interp);
166     }
167     Tcl_Release((ClientData) interp);
168   }
169 }
170 
171 struct jkQueuedSound *soundQueue = NULL;
172 static int corr = 0;
173 static Sound *sCurr = NULL;
174 
175 static void
CleanPlayQueue()176 CleanPlayQueue()
177 {
178   jkQueuedSound *p, *q;
179 
180   if (soundQueue == NULL) return;
181 
182   p = soundQueue;
183   do {
184     q = p->next;
185     p->sound->writeStatus = IDLE;
186     if (p->cmdPtr != NULL) {
187       Tcl_DecrRefCount(p->cmdPtr);
188       p->cmdPtr = NULL;
189     }
190     if (p->sound->destroy) {
191       Snack_DeleteSound(p->sound);
192     }
193     if (p->filterName != NULL) {
194       ckfree((char *)p->filterName);
195     }
196     ckfree((char *)p);
197     p = q;
198   } while (p != NULL);
199 
200   soundQueue = NULL;
201 }
202 
203 static void
CleanRecordQueue()204 CleanRecordQueue()
205 {
206   jkQueuedSound *p, *q;
207 
208   if (rsoundQueue == NULL) return;
209 
210   p = rsoundQueue;
211   do {
212     q = p->next;
213     ckfree((char *)p);
214     p = q;
215   } while (p != NULL);
216 
217   rsoundQueue = NULL;
218 }
219 /*
220 static void
221 DumpQueue(char *msg, struct jkQueuedSound *q)
222 {
223   jkQueuedSound *p;
224 
225   printf("%s\t", msg);
226 
227   for (p = q; p != NULL; p = p->next) {
228     printf("%s\t", p->name);
229   }
230   printf("\n\t");
231 
232   for (p = q; p != NULL; p = p->next) {
233     if (p->status == SNACK_QS_QUEUED)
234       printf("Q\t");
235     else if (p->status == SNACK_QS_PAUSED)
236       printf("P\t");
237     else
238       printf("D\t");
239   }
240   printf("\n");
241 }
242 */
243 
244 extern Tcl_HashTable *filterHashTable;
245 extern float globalScaling;
246 
247 static int
AssembleSoundChunk(int inSize)248 AssembleSoundChunk(int inSize)
249 {
250   int chunkWritten = 1, writeSize = 0, size = inSize, i, j;
251   int longestChunk = 0, startPos, endPos, totLen;
252   long nWritten;
253   int emptyQueue = 1;
254   jkQueuedSound *p;
255   Sound *s;
256   Tcl_HashEntry *hPtr;
257   Snack_Filter f;
258 
259   if (debugLevel > 2) Snack_WriteLogInt("    Enter AssembleSoundChunk", size);
260 
261   for (i = 0; i < inSize * globalOutWidth; i++) {
262     floatBuffer[i] = 0.0f;
263     fff[i] = 0.0f;
264   }
265 
266   for (p = soundQueue; p != NULL; p = p->next) {
267     int first = 0, inFrames, outFrames, nPrepared = 0, inputExhausted = 0;
268     float frac = 1.0f;
269 
270     if (p->status == SNACK_QS_PAUSED || p->status == SNACK_QS_DONE) continue;
271     emptyQueue = 0;
272     if (p->startTime > globalNWritten + size) continue;
273 
274     s = p->sound;
275     startPos = p->startPos;
276     endPos = p->endPos;
277     totLen = endPos - startPos + 1;
278     nWritten = p->nWritten;
279     frac = (float) s->samprate / (float) globalRate;
280     if (s->debug > 1) {
281       Snack_WriteLogInt("    asc len", s->length);
282       Snack_WriteLogInt("        end", p->endPos);
283       Snack_WriteLogInt("        wrt", nWritten);
284     }
285 
286     if (s->storeType == SOUND_IN_MEMORY) { /* sound in memory */
287 
288       if (nWritten < totLen && (startPos + nWritten < s->length)) {
289 	writeSize = size;
290 	if (writeSize > (totLen - nWritten) / frac) {
291 	  writeSize = (int) ((totLen - nWritten) / frac);
292 	}
293 	if (p->nWritten == 0 && p->startTime > 0) {
294 	  first = max(p->startTime - globalNWritten, 0);
295 	  if (writeSize > first) {
296 	    writeSize -= first;
297 	  }
298 	}
299 	if (s->debug > 1) Snack_WriteLogInt("      first ", first);
300 	longestChunk = max(longestChunk, writeSize);
301 	for (i = 0; i < first * s->nchannels; i++) fff[i] = 0.0f;
302 	if (s->samprate == globalRate) {
303 	  for (i = first * s->nchannels, j = (startPos + nWritten) *
304 		 s->nchannels; i < writeSize * s->nchannels;
305 	       i++, j++) {
306 	    fff[i] = FSAMPLE(s, j);
307 	  }
308 	  nPrepared = writeSize;
309 	} else {
310 	  int c, ij, pos;
311 	  float smp1 = 0.0, smp2, f, dj;
312 
313 	  for (c = 0; c < s->nchannels; c++) {
314 	    for (i = first * s->nchannels, j = 0;
315 		 i < writeSize * s->nchannels; i++, j++) {
316 	      dj = frac * i;
317 	      ij = (int) dj;
318 	      f = dj - ij;
319 	      pos = (startPos + nWritten + ij) * s->nchannels + c;
320 	      if (pos >= (s->length - 1) * s->nchannels) break;
321 	      smp1 = FSAMPLE(s, pos);
322 	      smp2 = FSAMPLE(s, pos + s->nchannels);
323 	      fff[i * s->nchannels + c] = smp1 * (1.0f - f) + smp2 * f;
324 	    }
325 	  }
326 	  nPrepared = (int) (frac * writeSize + 0.5);
327 	} /* s->samprate != globalRate */
328 	if (totLen <= nWritten + nPrepared + 1) inputExhausted = 1;
329       } else { /* nWritten < totLen ... */
330 	if (s->readStatus != READ) {
331 	  inputExhausted = 1;
332 	}
333 	writeSize = 0;
334       } /* nWritten < totLen ... */
335     } else { /* sound in file or channel */
336       if ((nWritten < totLen || endPos == -1 || totLen == 0) &&
337 	  s->linkInfo.eof == 0) {
338 	writeSize = size;
339 	if (s->length > 0) {
340 	  if (writeSize > (s->length - startPos - nWritten) / frac) {
341 	    writeSize = (int) ((s->length - startPos - nWritten) / frac);
342 	  }
343 	}
344 	if (endPos != -1) {
345 	  if (totLen != 0 && writeSize > (totLen - nWritten) / frac) {
346 	    writeSize = (int) ((totLen - nWritten) / frac);
347 	  }
348 	}
349 	if (nWritten == 0 && p->startTime > 0) {
350 	  first = max(p->startTime - globalNWritten, 0);
351 	  writeSize -= first;
352 	}
353 	for (i = 0; i < first * s->nchannels; i++) fff[i] = 0.0f;
354 	if (s->samprate == globalRate) {
355 	  for (i = first * s->nchannels, j = (startPos + nWritten) *
356 		 s->nchannels; i < writeSize * s->nchannels; i++, j++) {
357 	    fff[i] = GetSample(&s->linkInfo, j);
358 	    if (s->linkInfo.eof) {
359 	      inputExhausted = 1;
360 	      writeSize = i / s->nchannels;
361 	      break;
362 	    }
363 	  }
364 	  nPrepared = writeSize;
365 	} else {
366 	  int c, ij, pos;
367 	  float smp1 = 0.0, smp2, f, dj;
368 
369 	  for (c = 0; c < s->nchannels; c++) {
370 	    for (i = first * s->nchannels, j = 0;
371 		 i < writeSize * s->nchannels; i++, j++) {
372 	      dj = frac * i;
373 	      ij = (int) dj;
374 	      f = dj - ij;
375 	      pos = (startPos + nWritten + ij) * s->nchannels + c;
376 	      if (pos >= (s->length - 1) * s->nchannels) break;
377 	      smp1 = GetSample(&s->linkInfo, pos);
378 	      smp2 = GetSample(&s->linkInfo, pos + s->nchannels);
379 	      fff[i * s->nchannels + c] = smp1 * (1.0f - f) + smp2 * f;
380 	      if (s->linkInfo.eof) {
381 		inputExhausted = 1;
382 		writeSize = i / s->nchannels;
383 		break;
384 	      }
385 	    }
386 	  }
387 	  nPrepared = (int) (frac * writeSize + 0.5);
388 	} /* s->samprate != globalRate */
389 	longestChunk = max(longestChunk, writeSize);
390       } else { /* p->nWritten == totLen or EOF */
391 	if (s->readStatus != READ) {
392 	  if (s->storeType == SOUND_IN_FILE) {
393 	    if (s->linkInfo.linkCh != NULL) {
394 	      CloseLinkedFile(&s->linkInfo);
395               if (s->debug > 1)
396 		Snack_WriteLogInt("    Closing File, len= ", s->length);
397 	      s->linkInfo.linkCh = NULL;
398 	    }
399 	  } else {
400 	    s->linkInfo.linkCh = NULL;
401 	    if (s->linkInfo.buffer != NULL) {
402 	      ckfree((char *) s->linkInfo.buffer);
403 	      s->linkInfo.buffer = NULL;
404 	    }
405 	  }
406 	  inputExhausted = 1;
407 	}
408       } /* nWritten < totLen ... */
409       /*if (totLen == nWritten + nPrepared) inputExhausted = 1;*/
410       if (totLen > 0)
411 	if (totLen <= nWritten + nPrepared + 1) inputExhausted = 1;
412     } /* s->storeType */
413 
414     if (s->nchannels != globalStreamWidth) {
415       if (s->nchannels < globalStreamWidth) {
416 	for (i = writeSize - 1; i >= first; i--) {
417 	  int c;
418 
419 	  for (c = 0; c < s->nchannels; c++) {
420 	    fff[i * globalStreamWidth + c] = fff[i * s->nchannels + c];
421 	  }
422 	  for (;c < globalStreamWidth; c++) {
423 	    fff[i * globalStreamWidth + c] = fff[i * s->nchannels];
424 	  }
425 	}
426       } else {
427 	for (i = 0; i < writeSize; i++) {
428 	  int c;
429 
430 	  for (c = 0; c < s->nchannels; c++) {
431 	    fff[i * globalStreamWidth + c] = fff[i * s->nchannels + c];
432 	  }
433 	}
434       }
435     }
436 
437     inFrames = writeSize;
438     if (s->readStatus != READ) {
439       outFrames = size;
440     } else {
441       outFrames = writeSize;
442     }
443     if (p->filterName != NULL) { /* Apply filter */
444       hPtr = Tcl_FindHashEntry(filterHashTable, p->filterName);
445       if (hPtr != NULL) {
446 	f = (Snack_Filter) Tcl_GetHashValue(hPtr);
447 	f->si->streamWidth = globalStreamWidth;
448 	(f->flowProc)(f, f->si, fff, fff, &inFrames, &outFrames);
449       }
450       p->nWritten += nPrepared;
451       if (s->readStatus != READ) {
452 	if (inFrames < outFrames || outFrames == 0 || inputExhausted) {
453 	  p->status = SNACK_QS_DRAIN;
454 	}
455 	if (outFrames < size && p->status == SNACK_QS_DRAIN) {
456 	  p->status = SNACK_QS_DONE;
457 	}
458       }
459       longestChunk = max(longestChunk, outFrames);
460     } else { /* No filter to apply */
461       if (inputExhausted) {
462 	p->status = SNACK_QS_DONE;
463       }
464       p->nWritten += nPrepared;
465       outFrames = writeSize;
466     }
467 
468     for (i = first * globalOutWidth, j = first * globalStreamWidth;
469 	 i < outFrames * globalOutWidth;) {
470       int c;
471 
472       for (c = 0; c < globalOutWidth; c++, i++, j++) {
473 
474 	switch (s->encoding) {
475 	case LIN16:
476 	case ALAW:
477 	case MULAW:
478 	  floatBuffer[i] += fff[j];
479 	  break;
480 	case LIN32:
481 	  floatBuffer[i] += fff[j] / 65536.0f;
482 	  break;
483 	case LIN8:
484 	  floatBuffer[i] += fff[j] * 256.0f;
485 	  break;
486 	case LIN8OFFSET:
487 	  floatBuffer[i] += (fff[j] - 128.0f) * 256.0f;
488 	  break;
489 	case LIN24:
490 	case LIN24PACKED:
491 	  floatBuffer[i] += fff[j] / 256.0f;
492 	  break;
493 	case SNACK_FLOAT:
494 	case SNACK_DOUBLE:
495 	  if (s->maxsamp > 1.0) {
496 	    floatBuffer[i] += fff[j];
497 	  } else {
498 	    floatBuffer[i] += fff[j] * 65536.0f;
499 	  }
500 	  break;
501 	}
502       }
503       if (globalStreamWidth > globalOutWidth) {
504 	j += (globalStreamWidth - globalOutWidth);
505       }
506     }
507   } /* p = soundQueue */
508 
509   if (emptyQueue == 0 && longestChunk == 0) longestChunk = inSize;
510 
511   for (i = 0; i < longestChunk * globalOutWidth; i++) {
512     float tmp = floatBuffer[i] * globalScaling;
513 
514     if (tmp > 32767.0f) tmp = 32767.0f;
515     if (tmp < -32768.0f) tmp = -32768.0f;
516     shortBuffer[i] = (short) tmp;
517   }
518 
519   if (snackDumpCh) {
520     Tcl_Write(snackDumpCh, (char *)shortBuffer,2*longestChunk*globalOutWidth);
521   }
522   chunkWritten = SnackAudioWrite(&ado, shortBuffer, longestChunk);
523   globalNWritten += chunkWritten;
524 
525   if (debugLevel > 2) {
526     Snack_WriteLogInt("    Exit AssembleSoundChunk", chunkWritten);
527   }
528 
529   return chunkWritten;
530 }
531 
532 #define IPLAYGRAIN 0
533 #define PLAYGRAIN 100
534 
535 extern double globalLatency;
536 double startDevTime;
537 static int playid = 0;
538 static int inPlayCB = 0;
539 
540 static void
PlayCallback(ClientData clientData)541 PlayCallback(ClientData clientData)
542 {
543   long currPlayed, writeable, totPlayed = 0;
544   int closedDown = 0, size;
545   int playgrain, blockingPlay = sCurr->blockingPlay, lastid;
546   jkQueuedSound *p, *last, *q;
547   Tcl_Interp *interp = sCurr->interp;
548 
549   if (debugLevel > 1) Snack_WriteLog("  Enter PlayCallback\n");
550 
551   do {
552     totPlayed = SnackAudioPlayed(&ado);
553     currPlayed = totPlayed - corr;
554     writeable = SnackAudioWriteable(&ado);
555 
556     if (debugLevel > 2) Snack_WriteLogInt("    totPlayed", totPlayed);
557 
558     if (totPlayed == -1) { /* error in SnackAudioPlayed */
559       closedDown = 1;
560       break;
561     }
562 
563     if (globalNWritten - currPlayed < globalLatency * globalRate ||
564 	blockingPlay) {
565       size = (int)(globalLatency * globalRate) - (globalNWritten - currPlayed);
566 
567       if (writeable >= 0 && writeable < size) {
568 	size = writeable;
569       }
570 
571       if (size > PBSIZE / globalStreamWidth/* || blockingPlay*/) {
572 	size = PBSIZE / globalStreamWidth;
573       }
574 
575       if (AssembleSoundChunk(size) < size && globalNFlowThrough == 0) {
576 	static int oplayed = -1;
577 	double stCheck =(SnackCurrentTime() - startDevTime )*(double)globalRate;
578 	jkQueuedSound *p;
579 	int hw = 0, canCloseDown = 1;
580 
581 	for (p = soundQueue; p != NULL; p = p->next) {
582 	  if (p->status == SNACK_QS_PAUSED) {
583 	    hw = 1;
584 	  }
585 	}
586 	if (hw) {
587 	  SnackAudioPause(&ado);
588 	  startDevTime = SnackCurrentTime() - startDevTime;
589 	  wop = PAUSED;
590 	  Tcl_DeleteTimerHandler(ptoken);
591 	  return;
592 	}
593 
594 	lastid = playid;
595 	for (p = soundQueue; p!=NULL; p=p->next) {
596 	  if (p->status == SNACK_QS_DONE) {
597 	    if ((p->sound->linkInfo.eof == 0 && p->startPos + p->nWritten >=
598 		 p->endPos) ||
599 		(p->sound->linkInfo.eof && p->nWritten < (int)stCheck) ||
600 		(p->nWritten - currPlayed <= 0 || currPlayed == oplayed)) {
601 	      /*
602 		(SnackCurrentTime() - startDevTime)*globalRate)
603 		often never makes it to p->nWritten before object is ready to
604 		be closed down, so we have the last check above to make sure
605 	      */
606 	      if (p->cmdPtr != NULL) {
607 		ExecSoundCmd(p->sound, p->cmdPtr);
608 		if (debugLevel > 0)
609 		  Snack_WriteLogInt("   a ExecSoundCmd", (int)stCheck);
610                 /*
611                  * The soundQueue can be removed by the -command, so check it
612                  * otherwise p is garbage
613                  */
614                 if (soundQueue == NULL) {
615 		  oplayed = currPlayed; /* close it down */
616 		  break;
617                 }
618 		if (p->cmdPtr != NULL) {
619 		  Tcl_DecrRefCount(p->cmdPtr);
620 		  p->cmdPtr = NULL;
621 		}
622 	      }
623 	    }
624 	  } else {
625 	    canCloseDown = 0;
626 	  }
627 	}
628 	if (canCloseDown) {
629 	  SnackAudioPost(&ado);
630 	  if (globalNWritten - currPlayed <= 0 || currPlayed == oplayed) {
631 	    if (debugLevel > 0)
632 	      Snack_WriteLogInt("    Closing Down",(int)SnackCurrentTime());
633 	    if (SnackAudioClose(&ado) != -1) {
634 	      if (snackDumpCh) {
635 		Tcl_Close(interp, snackDumpCh);
636 	      }
637 	      closedDown = 1;
638 	      oplayed = -1;
639 	      break;
640 	    }
641 	  } else {
642 	    oplayed = currPlayed;
643 	  }
644 	}
645       }
646     } /* if (globalNWritten - currPlayed < globalLatency * globalRate) */
647   } while (blockingPlay);
648 
649   last = soundQueue;
650   for (p = soundQueue; p != NULL; p = p->next) {
651     /*    printf("%d %d %d %d %d %f\n", p->id, p->status,
652 	   p->startPos + p->nWritten,
653 		 p->endPos,
654 		 p->sound->linkInfo.eof,
655 		 (SnackCurrentTime() - startDevTime)*globalRate);*/
656     if (p->status == SNACK_QS_DONE && p->sound->destroy == 0 &&
657 	p->cmdPtr == NULL) {
658       int count = 0;
659 
660       for (q = soundQueue; q != NULL; q = q->next) {
661 	if (p->sound == q->sound) count++;
662       }
663 
664       /*      printf("deleted %d\n", p->id);*/
665       last->next = p->next;
666       if (p == soundQueue) soundQueue = p->next;
667 
668       if (count == 1) p->sound->writeStatus = IDLE;
669       if (p->filterName != NULL) {
670 	ckfree((char *)p->filterName);
671       }
672       ckfree((char *)p);
673       break;
674     }
675     last = p;
676   }
677 
678 
679   if (closedDown) {
680     CleanPlayQueue();
681     wop = IDLE;
682     return;
683   }
684 
685   if (!blockingPlay) {
686     playgrain = 30;/*max(min(PLAYGRAIN, (int) (globalLatency * 500.0)), 1);*/
687 
688     ptoken = Tcl_CreateTimerHandler(playgrain, (Tcl_TimerProc *) PlayCallback,
689 				    (int *) NULL);
690   }
691 
692   if (debugLevel > 1) Snack_WriteLogInt("  Exit PlayCallback", globalNWritten);
693 }
694 
695 void
Snack_StopSound(Sound * s,Tcl_Interp * interp)696 Snack_StopSound(Sound *s, Tcl_Interp *interp)
697 {
698   jkQueuedSound *p;
699   int i;
700 
701   if (s->debug > 1) Snack_WriteLog("  Enter Snack_StopSound\n");
702 
703   if (s->writeStatus == WRITE && s->readStatus == READ) {
704     globalNFlowThrough--;
705   }
706 
707   if (s->storeType == SOUND_IN_MEMORY) {
708 
709     /* In-memory sound record */
710 
711     if ((rop == READ || rop == PAUSED) && (s->readStatus == READ)) {
712       for (p = rsoundQueue; p->sound != s; p = p->next);
713       if (p->sound == s) {
714 	if (p->next != NULL) {
715 	  p->next->prev = p->prev;
716 	}
717 	if (p->prev != NULL) {
718 	  p->prev->next = p->next;
719 	} else {
720 	  rsoundQueue = p->next;
721 	}
722 	ckfree((char *)p);
723       }
724 
725       if (rsoundQueue == NULL && rop == READ) {
726 	int remaining;
727 
728 	SnackAudioPause(&adi);
729 	remaining = SnackAudioReadable(&adi);
730 
731 	while (remaining > 0) {
732 	  if (s->length < s->maxlength - s->samprate / 16) {
733 	    int nRead = 0;
734 	    int size = s->samprate / 16;
735 
736 	    nRead = SnackAudioRead(&adi, shortBuffer, size);
737 	    for (i = 0; i < nRead * s->nchannels; i++) {
738 	      FSAMPLE(s, s->length * s->nchannels + i) =
739 		(float) shortBuffer[i];
740 	    }
741 
742 	    if (nRead > 0) {
743 	      if (s->debug > 1) Snack_WriteLogInt("  Recording", nRead);
744 	      Snack_UpdateExtremes(s, s->length, s->length + nRead,
745 				   SNACK_MORE_SOUND);
746 	      s->length += nRead;
747 	    }
748 	    remaining -= nRead;
749 	  } else {
750 	    break;
751 	  }
752 	}
753 	SnackAudioFlush(&adi);
754 	SnackAudioClose(&adi);
755 	Tcl_DeleteTimerHandler(rtoken);
756 	rop = IDLE;
757       }
758       s->readStatus = IDLE;
759       Snack_ExecCallbacks(s, SNACK_MORE_SOUND);
760     }
761 
762     /* In-memory sound play */
763 
764     if ((wop == WRITE || wop == PAUSED) && (s->writeStatus == WRITE)) {
765       int hw = 1;
766 
767       if (s->debug > 1) Snack_WriteLogInt("  Stopping",SnackAudioPlayed(&ado));
768 
769       for (p = soundQueue; p != NULL; p = p->next) {
770 	if (p->sound == s) {
771 	  p->status = SNACK_QS_DONE;
772 	}
773       }
774 
775       for (p = soundQueue; p != NULL; p = p->next) {
776 	if (p->status != SNACK_QS_DONE) {
777 	  hw = 0;
778 	}
779       }
780 
781       if (hw == 1) {
782 	if (wop == PAUSED) {
783 	  SnackAudioResume(&ado);
784 	}
785 	SnackAudioFlush(&ado);
786 	SnackAudioClose(&ado);
787 	wop = IDLE;
788 	Tcl_DeleteTimerHandler(ptoken);
789 	CleanPlayQueue();
790       }
791 
792     }
793   } else { /* sound in file or channel */
794 
795     /* file or channel sound record */
796 
797     if ((rop == READ || rop == PAUSED) && (s->readStatus == READ)) {
798       Snack_FileFormat *ff;
799       for (p = rsoundQueue; p->sound != s; p = p->next);
800       if (p->sound == s) {
801 	if (p->next != NULL) {
802 	  p->next->prev = p->prev;
803 	}
804 	if (p->prev != NULL) {
805 	  p->prev->next = p->next;
806 	} else {
807 	  rsoundQueue = p->next;
808 	}
809 	ckfree((char *)p);
810       }
811 
812       if (rsoundQueue == NULL && rop == READ) {
813 	int remaining;
814 
815 	SnackAudioPause(&adi);
816 	remaining = SnackAudioReadable(&adi);
817 
818 	while (remaining > 0) {
819 	  int nRead = 0, i;
820 	  int size = s->samprate / 16;
821 	  nRead = SnackAudioRead(&adi, shortBuffer, size);
822 
823        	  if ((s->length + nRead - s->validStart) * s->nchannels > FBLKSIZE) {
824 	    s->validStart += (BUFSCROLLSIZE / s->nchannels);
825 	    memmove(&s->blocks[0][0], &s->blocks[0][BUFSCROLLSIZE],
826 		    (FBLKSIZE-BUFSCROLLSIZE) * sizeof(float));
827 	  }
828 
829 	  for (i = 0; i < nRead * s->nchannels; i++) {
830 	    FSAMPLE(s, (s->length - s->validStart) * s->nchannels + i) =
831 	      (float) shortBuffer[i];
832 	  }
833 
834 	  for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
835 	    if (strcmp(s->fileType, ff->name) == 0) {
836 	      WriteSound(ff->writeProc, s, s->interp, s->rwchan, NULL,
837 			 s->length - s->validStart, nRead);
838 	    }
839 	  }
840 	  /*
841 	  WriteSound(NULL, s, s->interp, s->rwchan, NULL,
842 		     (s->length - s->validStart) * s->nchannels,
843 		     nRead * s->nchannels);
844 	  */
845 	  Tcl_Flush(s->rwchan);
846 
847 	  if (s->debug > 2) Snack_WriteLogInt("    Tcl_Read", nRead);
848 
849 	  s->length += nRead;
850 	  remaining -= nRead;
851 	}
852 	SnackAudioFlush(&adi);
853 	SnackAudioClose(&adi);
854 	Tcl_DeleteTimerHandler(rtoken);
855 	rop = IDLE;
856 	CleanRecordQueue();
857       }
858       if (TCL_SEEK(s->rwchan, 0, SEEK_SET) != -1) {
859 	PutHeader(s, interp, 0, NULL, s->length);
860 	TCL_SEEK(s->rwchan, 0, SEEK_END);
861       }
862       if (s->storeType == SOUND_IN_FILE) {
863 	for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
864 	  if (strcmp(s->fileType, ff->name) == 0) {
865 	    SnackCloseFile(ff->closeProc, s, interp, &s->rwchan);
866 	  }
867 	}
868 	/*Tcl_Close(interp, s->rwchan);*/
869       }
870       /*ckfree((char *)s->tmpbuf);
871 	s->tmpbuf = NULL;*/
872       s->rwchan = NULL;
873       s->validStart = 0;
874       s->readStatus = IDLE;
875       Snack_ExecCallbacks(s, SNACK_MORE_SOUND);
876     }
877 
878     /* file or channel sound play */
879 
880     if ((wop == WRITE || wop == PAUSED) && (s->writeStatus == WRITE)) {
881       int hw = 1;
882 
883       if (s->debug > 1) Snack_WriteLogInt("  Stopping",SnackAudioPlayed(&ado));
884 
885       for (p = soundQueue; p != NULL; p = p->next) {
886 	if (p->sound == s) {
887 	  p->status = SNACK_QS_DONE;
888 	}
889       }
890 
891       for (p = soundQueue; p != NULL; p = p->next) {
892 	if (p->status != SNACK_QS_DONE) {
893 	  hw = 0;
894 	}
895       }
896 
897       if (hw == 1) {
898 	if (wop == PAUSED) {
899 	  SnackAudioResume(&ado);
900 	}
901 	SnackAudioFlush(&ado);
902 	SnackAudioClose(&ado);
903 	wop = IDLE;
904 	Tcl_DeleteTimerHandler(ptoken);
905 	CleanPlayQueue();
906       }
907       /*      ckfree((char *)s->tmpbuf);
908 	      s->tmpbuf = NULL;*/
909       if (s->rwchan != NULL) {
910 	if (s->storeType == SOUND_IN_FILE) {
911 	  Snack_FileFormat *ff;
912 	  for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
913 	    if (strcmp(s->fileType, ff->name) == 0) {
914 	      SnackCloseFile(ff->closeProc, s, s->interp, &s->rwchan);
915 	      s->rwchan = NULL;
916 	      break;
917 	    }
918 	  }
919 	}
920       }
921     }
922   }
923 
924   if (s->debug > 1) Snack_WriteLog("  Exit Snack_StopSound\n");
925 }
926 
927 extern char defaultOutDevice[];
928 
929 int
playCmd(Sound * s,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])930 playCmd(Sound *s, Tcl_Interp *interp, int objc,	Tcl_Obj *CONST objv[])
931 {
932   int startPos = 0, endPos = -1, block = 0, arg, startTime = 0, duration = 0;
933   int devChannels = -1, rate = -1, noPeeping = 0;
934   double dStart = 0.0, dDuration = 0.0;
935   static CONST84 char *subOptionStrings[] = {
936     "-output", "-start", "-end", "-command", "-blocking", "-device", "-filter",
937     "-starttime", "-duration", "-devicechannels", "-devicerate", "-nopeeping",
938     NULL
939   };
940   enum subOptions {
941     OUTPUT, STARTPOS, END, COMMAND, BLOCKING, DEVICE, FILTER, STARTTIME,
942     DURATION, DEVCHANNELS, DEVRATE, NOPEEPING
943   };
944   jkQueuedSound *qs, *p;
945   Snack_FileFormat *ff;
946   Snack_Filter f = NULL;
947   char *filterName = NULL;
948   Tcl_Obj *cmdPtr = NULL;
949 
950   if (s->writeStatus == WRITE && wop == PAUSED) {
951     for (p = soundQueue; p != NULL; p = p->next) {
952       if (p->sound == s) {
953 	if (p->status == SNACK_QS_PAUSED) {
954 	  p->status = SNACK_QS_QUEUED;
955 	}
956       }
957     }
958     startDevTime = SnackCurrentTime() - startDevTime;
959     wop = WRITE;
960     SnackAudioResume(&ado);
961     ptoken = Tcl_CreateTimerHandler(IPLAYGRAIN,
962 			     (Tcl_TimerProc *) PlayCallback, (int *) NULL);
963     return TCL_OK;
964   }
965 
966   s->firstNRead = 0;
967   s->devStr = defaultOutDevice;
968 
969   for (arg = 2; arg < objc; arg+=2) {
970     int index, length;
971     char *str;
972 
973     if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings,
974 			    "option", 0, &index) != TCL_OK) {
975       return TCL_ERROR;
976     }
977 
978     if (arg + 1 == objc) {
979       Tcl_AppendResult(interp, "No argument given for ",
980 		       subOptionStrings[index], " option", (char *) NULL);
981       return TCL_ERROR;
982     }
983 
984     switch ((enum subOptions) index) {
985     case OUTPUT:
986       {
987 	str = Tcl_GetStringFromObj(objv[arg+1], &length);
988 	SnackMixerSetOutputJack(str, "1");
989 	break;
990       }
991     case STARTPOS:
992       {
993 	if (Tcl_GetIntFromObj(interp, objv[arg+1], &startPos) != TCL_OK)
994 	  return TCL_ERROR;
995 	break;
996       }
997     case END:
998       {
999 	if (Tcl_GetIntFromObj(interp, objv[arg+1], &endPos) != TCL_OK)
1000 	  return TCL_ERROR;
1001 	break;
1002       }
1003     case COMMAND:
1004       {
1005 	Tcl_IncrRefCount(objv[arg+1]);
1006 	cmdPtr = objv[arg+1];
1007 	break;
1008       }
1009     case BLOCKING:
1010       {
1011 	if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &block) != TCL_OK)
1012 	  return TCL_ERROR;
1013 	break;
1014       }
1015     case DEVICE:
1016       {
1017 	int i, n, found = 0;
1018 	char *arr[MAX_NUM_DEVICES];
1019 
1020 	s->devStr = Tcl_GetStringFromObj(objv[arg+1], NULL);
1021 
1022 	if (strlen(s->devStr) > 0) {
1023 	  n = SnackGetOutputDevices(arr, MAX_NUM_DEVICES);
1024 
1025 	  for (i = 0; i < n; i++) {
1026 	    if (strncmp(s->devStr, arr[i], strlen(s->devStr)) == 0) {
1027 	      found = 1;
1028 	    }
1029 	    ckfree(arr[i]);
1030 	  }
1031 	  if (found == 0) {
1032 	    Tcl_AppendResult(interp, "No such device: ", s->devStr,
1033 			     (char *) NULL);
1034 	    return TCL_ERROR;
1035 	  }
1036 	}
1037 	break;
1038       }
1039     case FILTER:
1040       {
1041 	char *str = Tcl_GetStringFromObj(objv[arg+1], NULL);
1042 
1043 	if (strlen(str) > 0) {
1044 	  Tcl_HashEntry *hPtr;
1045 
1046 	  hPtr = Tcl_FindHashEntry(filterHashTable, str);
1047 	  if (hPtr == NULL) {
1048 	    Tcl_AppendResult(interp, "No such filter: ", str,
1049 			     (char *) NULL);
1050 	    return TCL_ERROR;
1051 	  }
1052 	  filterName = ckalloc(strlen(str)+1);
1053 	  if (filterName) {
1054 	    strncpy(filterName, str, strlen(str)+1);
1055 	  }
1056 	  f = (Snack_Filter) Tcl_GetHashValue(hPtr);
1057 	  if (f->si != NULL) ckfree((char *) f->si);
1058 	  f->si = (Snack_StreamInfo) ckalloc(sizeof(SnackStreamInfo));
1059 	}
1060 	break;
1061       }
1062     case STARTTIME:
1063       {
1064 	if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &dStart) != TCL_OK) {
1065 	  return TCL_ERROR;
1066 	}
1067 	break;
1068       }
1069     case DURATION:
1070       {
1071 	if (Tcl_GetDoubleFromObj(interp, objv[arg+1], &dDuration) != TCL_OK) {
1072 	  return TCL_ERROR;
1073 	}
1074 	break;
1075       }
1076     case DEVCHANNELS:
1077       {
1078 	if (Tcl_GetIntFromObj(interp, objv[arg+1], &devChannels) != TCL_OK) {
1079 	  return TCL_ERROR;
1080 	}
1081 	break;
1082       }
1083     case DEVRATE:
1084       {
1085 	if (Tcl_GetIntFromObj(interp, objv[arg+1], &rate) != TCL_OK) {
1086 	  return TCL_ERROR;
1087 	}
1088 	break;
1089       }
1090     case NOPEEPING:
1091       {
1092  	if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &noPeeping) != TCL_OK)
1093  	  return TCL_ERROR;
1094  	break;
1095       }
1096     }
1097   }
1098   if (s->storeType == SOUND_IN_CHANNEL && !noPeeping) {
1099     int tlen = 0, rlen = 0;
1100 
1101     s->buffersize = CHANNEL_HEADER_BUFFER;
1102     if ((s->tmpbuf = (short *) ckalloc(CHANNEL_HEADER_BUFFER)) == NULL) {
1103       Tcl_AppendResult(interp, "Could not allocate buffer!", NULL);
1104       return TCL_ERROR;
1105     }
1106     while (tlen < s->buffersize) {
1107       rlen = Tcl_Read(s->rwchan, &((char *)s->tmpbuf)[tlen], 1);
1108       if (rlen <= 0) break;
1109       s->firstNRead += rlen;
1110       tlen += rlen;
1111       if (s->forceFormat == 0) {
1112 	s->fileType = GuessFileType((char *)s->tmpbuf, tlen, 0);
1113 	if (strcmp(s->fileType, QUE_STRING) != 0) break;
1114       }
1115     }
1116     for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
1117       if (strcmp(s->fileType, ff->name) == 0) {
1118 	if ((ff->getHeaderProc)(s, interp, s->rwchan, NULL,
1119 				(char *)s->tmpbuf)
1120 	    != TCL_OK) return TCL_ERROR;
1121 	break;
1122       }
1123     }
1124     if (strcmp(s->fileType, RAW_STRING) == 0 && s->guessEncoding) {
1125       GuessEncoding(s, (unsigned char *)s->tmpbuf, s->firstNRead / 2);
1126     }
1127     ckfree((char *)s->tmpbuf);
1128     s->tmpbuf = NULL;
1129     s->firstNRead -= s->headSize;
1130   }
1131   if (s->storeType != SOUND_IN_MEMORY) {
1132     /*if (s->buffersize < s->samprate / 2) {
1133       s->buffersize = s->samprate / 2;
1134     }
1135     if (s->tmpbuf) {
1136       ckfree((char *)s->tmpbuf);
1137     }
1138     if ((s->tmpbuf = (short *) ckalloc(s->buffersize * s->sampsize *
1139 				       s->nchannels)) == NULL) {
1140       Tcl_AppendResult(interp, "Could not allocate buffer!", NULL);
1141       return TCL_ERROR;
1142     }
1143      */
1144     if (s->linkInfo.linkCh == NULL && s->storeType == SOUND_IN_FILE) {
1145       if (OpenLinkedFile(s, &s->linkInfo) != TCL_OK) {
1146 	return TCL_ERROR;
1147       }
1148     }
1149   }
1150   if (s->storeType == SOUND_IN_MEMORY) {
1151     if (endPos < 0 || endPos > s->length - 1) endPos = s->length - 1;
1152   } else if (s->length != -1 && s->storeType == SOUND_IN_FILE) {
1153     if (endPos < 0 || endPos > s->length - 1) endPos = s->length - 1;
1154   } else {
1155     s->length = 0;
1156   }
1157   if (startPos >= endPos && endPos != -1) {
1158     ExecSoundCmd(s, cmdPtr);
1159     if (cmdPtr != NULL) Tcl_DecrRefCount(cmdPtr);
1160     return TCL_OK;
1161   }
1162   if (startPos < 0) startPos = 0;
1163   if (s->storeType == SOUND_IN_CHANNEL) {
1164     s->linkInfo.sound = s;
1165     s->linkInfo.buffer = (float *) ckalloc(ITEMBUFFERSIZE);
1166     s->linkInfo.filePos = -1;
1167     s->linkInfo.linkCh = s->rwchan;
1168     s->linkInfo.validSamples = 0;
1169     s->linkInfo.eof = 0;
1170   }
1171   if (rate == -1) {
1172     rate = s->samprate;
1173   }
1174 
1175 #ifdef MAC_OSX_TCL
1176   rate = 44100;
1177 #endif
1178 
1179   if (dStart > 0) {
1180     if (wop == IDLE) {
1181       startTime = (int) (dStart / 1000.0 * rate + .5);
1182     } else {
1183       startTime = (int) (dStart / 1000.0 * globalRate + .5);
1184     }
1185   }
1186   if (inPlayCB) {
1187     startTime += inPlayCB;
1188   }
1189   if (dDuration > 0) {
1190     if (wop == IDLE) {
1191       duration = (int) (dDuration / 1000.0 * rate + .5);
1192     } else {
1193       duration = (int) (dDuration / 1000.0 * globalRate + .5);
1194     }
1195   }
1196   qs = (jkQueuedSound *) ckalloc(sizeof(jkQueuedSound));
1197 
1198   if (qs == NULL) {
1199     Tcl_AppendResult(interp, "Unable to alloc queue struct", NULL);
1200     return TCL_ERROR;
1201   }
1202   qs->sound = s;
1203   qs->name = "junk";
1204   qs->startPos = startPos;
1205   qs->endPos = endPos;
1206   qs->nWritten = 0;
1207   qs->startTime = startTime;
1208   qs->duration = duration;
1209   qs->cmdPtr = cmdPtr;
1210   qs->status = SNACK_QS_QUEUED;
1211   qs->filterName = filterName;
1212   qs->next = NULL;
1213   qs->id = playid++;
1214   if (soundQueue == NULL) {
1215     soundQueue = qs;
1216   } else {
1217     for (p = soundQueue; p->next != NULL; p = p->next);
1218     p->next = qs;
1219   }
1220 
1221   if (wop == IDLE) {
1222     if (devChannels == -1) {
1223       globalStreamWidth = s->nchannels;
1224       if (s->nchannels > SnackAudioMaxNumberChannels(s->devStr)) {
1225 	devChannels = SnackAudioMaxNumberChannels(s->devStr);
1226       } else {
1227 	devChannels = s->nchannels;
1228       }
1229       if (devChannels < SnackAudioMinNumberChannels(s->devStr)) {
1230 	devChannels = SnackAudioMinNumberChannels(s->devStr);
1231 	globalStreamWidth = devChannels;
1232       }
1233     } else {
1234       globalStreamWidth = devChannels; /* option -devicechannels used */
1235     }
1236   } else {
1237     if (s->nchannels > globalStreamWidth) {
1238       globalStreamWidth = s->nchannels;
1239     }
1240     devChannels = globalStreamWidth;
1241   }
1242 
1243   if (filterName != NULL) {
1244     f->si->streamWidth = globalStreamWidth;
1245     f->si->outWidth    = devChannels;
1246     f->si->rate        = rate;
1247     (f->startProc)(f, f->si);
1248   }
1249 
1250   if (!((wop == IDLE) && (s->writeStatus == IDLE))) {
1251     s->writeStatus = WRITE;
1252 
1253     if (wop == PAUSED) {
1254       startDevTime = SnackCurrentTime() - startDevTime;
1255       wop = WRITE;
1256       SnackAudioResume(&ado);
1257       ptoken = Tcl_CreateTimerHandler(IPLAYGRAIN,
1258 				      (Tcl_TimerProc *) PlayCallback,
1259 				      (int *) NULL);
1260     }
1261     return TCL_OK;
1262   } else {
1263     qs->status = SNACK_QS_QUEUED;
1264   }
1265   ado.debug = s->debug;
1266   if (s->storeType == SOUND_IN_FILE) {
1267     s->rwchan = NULL;
1268   }
1269   wop = WRITE;
1270   s->writeStatus = WRITE;
1271 
1272   if (SnackAudioOpen(&ado, interp, s->devStr, PLAY, rate, devChannels,
1273 		     LIN16) != TCL_OK) {
1274     wop = IDLE;
1275     s->writeStatus = IDLE;
1276     return TCL_ERROR;
1277   }
1278   if (snackDumpFile) {
1279     snackDumpCh = Tcl_OpenFileChannel(interp, snackDumpFile, "w", 438);
1280     Tcl_SetChannelOption(interp, snackDumpCh, "-translation", "binary");
1281 #ifdef TCL_81_API
1282     Tcl_SetChannelOption(interp, snackDumpCh, "-encoding", "binary");
1283 #endif
1284   }
1285   globalRate = rate;
1286   globalOutWidth = devChannels;
1287   globalNWritten = 0;
1288   if (s->writeStatus == WRITE && s->readStatus == READ) {
1289     globalNFlowThrough++;
1290   }
1291   sCurr = s;
1292   s->blockingPlay = block;
1293   corr = 0;
1294   if (s->blockingPlay) {
1295     PlayCallback((ClientData) s);
1296   } else {
1297     ptoken = Tcl_CreateTimerHandler(IPLAYGRAIN, (Tcl_TimerProc *) PlayCallback,
1298 				    (int *) NULL);
1299   }
1300   if (rop == IDLE) {
1301    startDevTime = SnackCurrentTime();
1302   }
1303 
1304   return TCL_OK;
1305 }
1306 
1307 extern char defaultInDevice[];
1308 
1309 int
recordCmd(Sound * s,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1310 recordCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1311 {
1312   jkQueuedSound *qs, *p;
1313   int arg, append = 0, mode, encoding = LIN16;
1314   static CONST84 char *subOptionStrings[] = {
1315     "-input", "-append", "-device", "-fileformat", NULL
1316   };
1317   enum subOptions {
1318     INPUT, APPEND, DEVICE, FILEFORMAT
1319   };
1320 
1321   if (s->debug > 0) { Snack_WriteLog("Enter recordCmd\n"); }
1322 
1323   if (s->encoding == LIN24 || s->encoding == LIN24PACKED || s->encoding == SNACK_FLOAT
1324       || s->encoding == LIN32) encoding = LIN24;
1325 
1326   if (s->readStatus == READ && rop == PAUSED) {
1327     startDevTime = SnackCurrentTime() - startDevTime;
1328     rop = READ;
1329     if (SnackAudioOpen(&adi, interp, s->devStr, RECORD, s->samprate,
1330 		       s->nchannels, encoding) != TCL_OK) {
1331       rop = IDLE;
1332       s->readStatus = IDLE;
1333       return TCL_ERROR;
1334     }
1335     SnackAudioFlush(&adi);
1336     SnackAudioResume(&adi);
1337     Snack_ExecCallbacks(s, SNACK_MORE_SOUND);
1338     rtoken = Tcl_CreateTimerHandler(RECGRAIN, (Tcl_TimerProc *) RecCallback,
1339 				    (int *) NULL);
1340 
1341     return TCL_OK;
1342   }
1343 
1344   if (s->readStatus == IDLE) {
1345     s->readStatus = READ;
1346   } else {
1347     return TCL_OK;
1348   }
1349 
1350   s->devStr = defaultInDevice;
1351   s->tmpbuf = NULL;
1352 
1353   for (arg = 2; arg < objc; arg+=2) {
1354     int index, length;
1355     char *str;
1356 
1357     if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, "option",
1358 			    0, &index) != TCL_OK) {
1359       return TCL_ERROR;
1360     }
1361 
1362     if (arg + 1 == objc) {
1363       Tcl_AppendResult(interp, "No argument given for ",
1364 		       subOptionStrings[index], " option", (char *) NULL);
1365       return TCL_ERROR;
1366     }
1367 
1368     switch ((enum subOptions) index) {
1369     case INPUT:
1370       {
1371 	str = Tcl_GetStringFromObj(objv[arg+1], &length);
1372 	SnackMixerSetInputJack(interp, str, "1");
1373 	break;
1374       }
1375     case APPEND:
1376       {
1377 	if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &append) != TCL_OK) {
1378 	  return TCL_ERROR;
1379 	}
1380 	break;
1381       }
1382     case DEVICE:
1383       {
1384 	int i, n, found = 0;
1385 	char *arr[MAX_NUM_DEVICES];
1386 
1387 	s->devStr = Tcl_GetStringFromObj(objv[arg+1], NULL);
1388 
1389 	if (strlen(s->devStr) > 0) {
1390 	  n = SnackGetInputDevices(arr, MAX_NUM_DEVICES);
1391 
1392 	  for (i = 0; i < n; i++) {
1393 	    if (strncmp(s->devStr, arr[i], strlen(s->devStr)) == 0) {
1394 	      found = 1;
1395 	    }
1396 	    ckfree(arr[i]);
1397 	  }
1398 	  if (found == 0) {
1399 	    Tcl_AppendResult(interp, "No such device: ", s->devStr,
1400 			     (char *) NULL);
1401 	    return TCL_ERROR;
1402 	  }
1403 	}
1404 	break;
1405       }
1406     case FILEFORMAT:
1407       {
1408 	if (GetFileFormat(interp, objv[arg+1], &s->fileType) != TCL_OK)
1409 	  return TCL_ERROR;
1410 	break;
1411       }
1412     }
1413   }
1414 
1415   qs = (jkQueuedSound *) ckalloc(sizeof(jkQueuedSound));
1416 
1417   if (qs == NULL) {
1418     Tcl_AppendResult(interp, "Unable to alloc queue struct", NULL);
1419     return TCL_ERROR;
1420   }
1421   qs->sound = s;
1422   qs->name = Tcl_GetStringFromObj(objv[0], NULL);
1423   qs->status = SNACK_QS_QUEUED;
1424   qs->next = NULL;
1425   qs->prev = NULL;
1426   if (rsoundQueue == NULL) {
1427     rsoundQueue = qs;
1428   } else {
1429     for (p = rsoundQueue; p->next != NULL; p = p->next);
1430     p->next = qs;
1431     qs->prev = p;
1432   }
1433 
1434   if (!append) {
1435     s->length = 0;
1436     s->maxsamp = 0.0f;
1437     s->minsamp = 0.0f;
1438   }
1439 
1440   if (s->storeType == SOUND_IN_MEMORY) {
1441   } else { /* SOUND_IN_FILE or SOUND_IN_CHANNEL */
1442     if (s->buffersize < s->samprate / 2) {
1443       s->buffersize = s->samprate / 2;
1444     }
1445 
1446     if ((s->tmpbuf = (short *) ckalloc(s->buffersize * s->sampsize *
1447 				       s->nchannels)) == NULL) {
1448       Tcl_AppendResult(interp, "Could not allocate buffer!", NULL);
1449       return TCL_ERROR;
1450     }
1451 
1452     if (s->storeType == SOUND_IN_FILE) {
1453       Snack_FileFormat *ff;
1454 
1455       for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) {
1456 	if (strcmp(s->fileType, ff->name) == 0) {
1457 	  if (SnackOpenFile(ff->openProc, s, interp, &s->rwchan, "w") !=
1458 	      TCL_OK) {
1459 	    return TCL_ERROR;
1460 	  }
1461 	}
1462       }
1463 
1464       /*
1465 	s->rwchan = Tcl_OpenFileChannel(interp, s->fcname, "w", 420);
1466       */
1467       if (s->rwchan != NULL) {
1468 	mode = TCL_WRITABLE;
1469       }
1470     } else {
1471       s->rwchan = Tcl_GetChannel(interp, s->fcname, &mode);
1472     }
1473 
1474     if (s->rwchan == NULL) {
1475       return TCL_ERROR;
1476     }
1477     Tcl_SetChannelOption(interp, s->rwchan, "-translation", "binary");
1478 #ifdef TCL_81_API
1479     Tcl_SetChannelOption(interp, s->rwchan, "-encoding", "binary");
1480 #endif
1481     if (!(mode & TCL_WRITABLE)) {
1482       Tcl_AppendResult(interp, "channel \"", s->fcname,
1483 		       "\" wasn't opened for writing", NULL);
1484       s->rwchan = NULL;
1485       return TCL_ERROR;
1486     }
1487 
1488     if (PutHeader(s, interp, 0, NULL, -1) < 0) {
1489       return TCL_ERROR;
1490     }
1491     s->validStart = 0;
1492   }
1493   Snack_ResizeSoundStorage(s, FBLKSIZE);
1494 
1495   if (rop == IDLE || rop == PAUSED) {
1496     adi.debug = s->debug;
1497     if (SnackAudioOpen(&adi, interp, s->devStr, RECORD, s->samprate,
1498 		       s->nchannels, encoding) != TCL_OK) {
1499       rop = IDLE;
1500       s->readStatus = IDLE;
1501       return TCL_ERROR;
1502     }
1503     SnackAudioFlush(&adi);
1504     SnackAudioResume(&adi);
1505     rtoken = Tcl_CreateTimerHandler(RECGRAIN,(Tcl_TimerProc *) RecCallback,
1506 				    (int *) NULL);
1507   }
1508   globalRate = s->samprate;
1509   if (s->writeStatus == WRITE && s->readStatus == READ) {
1510     globalNFlowThrough++;
1511   }
1512   globalStreamWidth = s->nchannels;
1513   numRec++;
1514   rop = READ;
1515   if (wop == IDLE) {
1516     startDevTime = SnackCurrentTime();
1517   }
1518   Snack_ExecCallbacks(s, SNACK_NEW_SOUND);
1519 
1520   if (s->debug > 0) { Snack_WriteLog("Exit recordCmd\n"); }
1521 
1522   return TCL_OK;
1523 }
1524 
1525 int
stopCmd(Sound * s,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1526 stopCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1527 {
1528   Snack_StopSound(s, interp);
1529 
1530   return TCL_OK;
1531 }
1532 
1533 int
pauseCmd(Sound * s,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1534 pauseCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1535 {
1536   jkQueuedSound *p;
1537 
1538   if (s->debug > 1) Snack_WriteLog("  Enter pauseCmd\n");
1539 
1540   if (s->writeStatus == WRITE) {
1541     int hw = 1;
1542 
1543     for (p = soundQueue; p != NULL; p = p->next) {
1544       if (p->sound == s) {
1545 	if (p->status == SNACK_QS_QUEUED) {
1546 	  p->status = SNACK_QS_PAUSED;
1547 	} else if (p->status == SNACK_QS_PAUSED) {
1548 	  p->status = SNACK_QS_QUEUED;
1549 	}
1550       }
1551     }
1552 
1553     for (p = soundQueue; p != NULL; p = p->next) {
1554       if (p->status == SNACK_QS_QUEUED) {
1555 	hw = 0;
1556       }
1557     }
1558 
1559     if (hw == 1 || wop == PAUSED) {
1560       if (wop == WRITE) {
1561 	long tmp = SnackAudioPause(&ado);
1562 
1563 	startDevTime = SnackCurrentTime() - startDevTime;
1564 	wop = PAUSED;
1565 
1566         Tcl_DeleteTimerHandler(ptoken);
1567 	if (tmp != -1) {
1568 	  jkQueuedSound *p;
1569 	  long count = 0;
1570 
1571 	  for (p = soundQueue; p != NULL && p->status == SNACK_QS_PAUSED;
1572 	       p = p->next) {
1573 	    long totLen;
1574 
1575             if (p->endPos == -1) {
1576 	      totLen = (p->sound->length - p->startPos);
1577 	    } else {
1578 	      totLen = (p->endPos - p->startPos + 1);
1579 	    }
1580 
1581 	    count += totLen;
1582 
1583 	    if (count > tmp) {
1584 	      sCurr = p->sound;
1585 	      globalNWritten = tmp - (count - totLen);
1586 	      corr = count - totLen;
1587 	      break;
1588 	    }
1589 	  }
1590 	  /*
1591 	  for (p = p->next; p != NULL && p->status == SNACK_QS_PAUSED;
1592 	       p = p->next) {
1593 	    p->status = SNACK_QS_QUEUED;
1594 	    }*/
1595 	}
1596       } else if (wop == PAUSED) {
1597 	startDevTime = SnackCurrentTime() - startDevTime;
1598 	wop = WRITE;
1599 	SnackAudioResume(&ado);
1600 	ptoken = Tcl_CreateTimerHandler(IPLAYGRAIN, (Tcl_TimerProc *) PlayCallback,
1601 					(int *) NULL);
1602       }
1603     }
1604   }
1605   if (s->readStatus == READ) {
1606     int hw = 1;
1607 
1608     for (p = rsoundQueue; p != NULL && p->sound != s; p = p->next);
1609     if (p->sound == s) {
1610       if (p->status == SNACK_QS_QUEUED) {
1611 	p->status = SNACK_QS_PAUSED;
1612       } else if (p->status == SNACK_QS_PAUSED) {
1613 	p->status = SNACK_QS_QUEUED;
1614       }
1615     }
1616 
1617     for (p = rsoundQueue; p != NULL; p = p->next) {
1618       if (p->status == SNACK_QS_QUEUED) {
1619 	hw = 0;
1620       }
1621     }
1622 
1623     if (hw == 1 || rop == PAUSED) {
1624       if (rop == READ) {
1625 	int remaining;
1626 
1627 	SnackAudioPause(&adi);
1628 	startDevTime = SnackCurrentTime() - startDevTime;
1629 
1630 	remaining = SnackAudioReadable(&adi);
1631 
1632 	while (remaining > 0) {
1633 	  if (s->length < s->maxlength - s->samprate / 16) {
1634 	    int nRead = 0;
1635 	    int size = s->samprate / 16, i;
1636 
1637 	    nRead = SnackAudioRead(&adi, shortBuffer, size);
1638 	    for (i = 0; i < nRead * s->nchannels; i++) {
1639 	      FSAMPLE(s, s->length * s->nchannels + i) =
1640 		(float) shortBuffer[i];
1641 	    }
1642 
1643 	    if (nRead > 0) {
1644 	      if (s->debug > 1) Snack_WriteLogInt("  Recording", nRead);
1645 	      Snack_UpdateExtremes(s, s->length, s->length + nRead,
1646 				   SNACK_MORE_SOUND);
1647 	      s->length += nRead;
1648 	    }
1649 	    remaining -= nRead;
1650 	  } else {
1651 	    break;
1652 	  }
1653 	}
1654 	SnackAudioFlush(&adi);
1655 	SnackAudioClose(&adi);
1656 	rop = PAUSED;
1657 	s->readStatus = READ;
1658 	Tcl_DeleteTimerHandler(rtoken);
1659       } else if (rop == PAUSED) {
1660 	for (p = rsoundQueue; p->sound != s; p = p->next);
1661 	if (p->sound == s) {
1662 	  p->status = SNACK_QS_QUEUED;
1663 	}
1664 
1665 	rop = READ;
1666 	if (SnackAudioOpen(&adi, interp, s->devStr, RECORD, s->samprate,
1667 			   s->nchannels, LIN16) != TCL_OK) {
1668 	  rop = IDLE;
1669 	  s->readStatus = IDLE;
1670 	  return TCL_ERROR;
1671 	}
1672 	SnackAudioFlush(&adi);
1673 	SnackAudioResume(&adi);
1674 	startDevTime = SnackCurrentTime() - startDevTime;
1675 	Snack_ExecCallbacks(s, SNACK_MORE_SOUND);
1676 	rtoken = Tcl_CreateTimerHandler(RECGRAIN,
1677 					(Tcl_TimerProc *) RecCallback,
1678 					(int *) NULL);
1679       }
1680     }
1681   }
1682 
1683   if (s->debug > 1) Snack_WriteLog("  Exit pauseCmd\n");
1684 
1685   return TCL_OK;
1686 }
1687 
1688 int
current_positionCmd(Sound * s,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1689 current_positionCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1690 {
1691   int n = -1;
1692   int arg, len, type = 0;
1693   jkQueuedSound *p;
1694 
1695   if (soundQueue != NULL) {
1696     for (p = soundQueue; p != NULL && p->sound != s; p = p->next);
1697     if (p->sound == s) {
1698       n = p->startPos + p->nWritten;
1699     }
1700   }
1701   if (wop == IDLE) {
1702     Tcl_SetObjResult(interp, Tcl_NewIntObj(-1));
1703     return TCL_OK;
1704   }
1705   for (arg = 2; arg < objc; arg++) {
1706     char *string = Tcl_GetStringFromObj(objv[arg], &len);
1707 
1708     if (strncmp(string, "-units", len) == 0) {
1709       string = Tcl_GetStringFromObj(objv[++arg], &len);
1710       if (strncasecmp(string, "seconds", len) == 0) type = 1;
1711       if (strncasecmp(string, "samples", len) == 0) type = 0;
1712       arg++;
1713     }
1714   }
1715 
1716   if (type == 0) {
1717     Tcl_SetObjResult(interp, Tcl_NewIntObj(max(n, 0)));
1718   } else {
1719     Tcl_SetObjResult(interp, Tcl_NewDoubleObj((float) max(n,0) / s->samprate));
1720   }
1721 
1722   return TCL_OK;
1723 }
1724 
1725 void
Snack_ExitProc(ClientData clientData)1726 Snack_ExitProc(ClientData clientData)
1727 {
1728   if (debugLevel > 1) Snack_WriteLog("  Enter Snack_ExitProc\n");
1729 
1730   if (rop != IDLE) {
1731     SnackAudioFlush(&adi);
1732     SnackAudioClose(&adi);
1733   }
1734   if (wop != IDLE) {
1735     SnackAudioFlush(&ado);
1736     SnackAudioClose(&ado);
1737   }
1738   SnackAudioFree();
1739   rop = IDLE;
1740   wop = IDLE;
1741   if (debugLevel > 1) Snack_WriteLog("  Exit Snack\n");
1742 }
1743 
1744 /*
1745  *----------------------------------------------------------------------
1746  *
1747  * SnackCurrentTime --
1748  *
1749  *	Returns the current system time in seconds (with decimals)
1750  *	since the beginning of the epoch: 00:00 UCT, January 1, 1970.
1751  *
1752  * Results:
1753  *	Returns the current time.
1754  *
1755  *----------------------------------------------------------------------
1756  */
1757 
1758 #ifdef MAC
1759 #  include <time.h>
1760 #elif  defined(WIN)
1761 #  include <sys/types.h>
1762 #  include <sys/timeb.h>
1763 #else
1764 #  include <sys/time.h>
1765 #endif
1766 
1767 double
SnackCurrentTime()1768 SnackCurrentTime()
1769 {
1770 #if defined(MAC)
1771 	double nTime;
1772 	clock_t tclock;
1773 	double t;
1774 
1775 	tclock = clock();
1776 	t = (double) CLOCKS_PER_SEC;
1777 	nTime = (double) tclock;
1778 	nTime = nTime / t;
1779 	return(nTime);
1780 #elif defined(WIN)
1781   struct timeb t;
1782 
1783   ftime(&t);
1784 
1785   return(t.time + t.millitm * 0.001);
1786 #else
1787   struct timeval tv;
1788   struct timezone tz;
1789 
1790   (void) gettimeofday(&tv, &tz);
1791 
1792   return(tv.tv_sec + tv.tv_usec * 0.000001);
1793 
1794 #endif
1795 }
1796 
SnackPauseAudio()1797 void SnackPauseAudio()
1798 {
1799   if (wop == WRITE) {
1800     SnackAudioPause(&ado);
1801     startDevTime = SnackCurrentTime() - startDevTime;
1802     wop = PAUSED;
1803     Tcl_DeleteTimerHandler(ptoken);
1804   } else if (wop == PAUSED) {
1805     startDevTime = SnackCurrentTime() - startDevTime;
1806     wop = WRITE;
1807     SnackAudioResume(&ado);
1808     ptoken = Tcl_CreateTimerHandler(IPLAYGRAIN, (Tcl_TimerProc *) PlayCallback,
1809 				    (int *) NULL);
1810   }
1811 }
1812