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