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