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
34int rop = IDLE;
35int numRec = 0;
36int wop = IDLE;
37static ADesc adi;
38static ADesc ado;
39static int globalRate = 16000;
40static int globalOutWidth = 0;
41static int globalStreamWidth = 0;
42static long globalNWritten = 0;
43static int globalNFlowThrough = 0;
44
45short shortBuffer[PBSIZE];
46float floatBuffer[PBSIZE];
47float fff[PBSIZE];
48static Tcl_TimerToken ptoken;
49static Tcl_TimerToken rtoken;
50
51#define FPS 32
52#define RECGRAIN 10
53#define BUFSCROLLSIZE 25000
54struct jkQueuedSound *rsoundQueue = NULL;
55
56extern int debugLevel;
57extern char *snackDumpFile;
58static Tcl_Channel snackDumpCh = NULL;
59
60extern struct Snack_FileFormat *snackFileFormats;
61
62static void
63RecCallback(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
156static void
157ExecSoundCmd(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
171struct jkQueuedSound *soundQueue = NULL;
172static int corr = 0;
173static Sound *sCurr = NULL;
174
175static void
176CleanPlayQueue()
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
203static void
204CleanRecordQueue()
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/*
220static void
221DumpQueue(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
244extern Tcl_HashTable *filterHashTable;
245extern float globalScaling;
246
247static int
248AssembleSoundChunk(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
535extern double globalLatency;
536double startDevTime;
537static int playid = 0;
538static int inPlayCB = 0;
539
540static void
541PlayCallback(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
695void
696Snack_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
927extern char defaultOutDevice[];
928
929int
930playCmd(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
1307extern char defaultInDevice[];
1308
1309int
1310recordCmd(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
1525int
1526stopCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1527{
1528  Snack_StopSound(s, interp);
1529
1530  return TCL_OK;
1531}
1532
1533int
1534pauseCmd(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
1688int
1689current_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
1725void
1726Snack_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
1767double
1768SnackCurrentTime()
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
1797void 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