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 "tcl.h" 23 #include "jkAudIO.h" 24 #include "jkSound.h" 25 #include <stdio.h> 26 #include <fcntl.h> 27 #include <unistd.h> 28 #include <sys/ioctl.h> 29 #ifdef __NetBSD__ 30 #include <soundcard.h> 31 #else /* OSS default */ 32 #include <sys/soundcard.h> 33 #endif 34 #include <string.h> 35 #include <ctype.h> 36 #include <stdlib.h> 37 #include <glob.h> 38 #define DEVICE_NAME "/dev/dsp" 39 #define MIXER_NAME "/dev/mixer" 40 static char *defaultDeviceName = DEVICE_NAME; 41 extern void Snack_WriteLog(char *s); 42 extern void Snack_WriteLogInt(char *s, int n); 43 44 #ifndef min 45 #define min(a,b) ((a)<(b)?(a):(b)) 46 #define max(a,b) ((a)>(b)?(a):(b)) 47 #endif 48 49 static int mfd = 0; 50 51 static struct MixerLink mixerLinks[SOUND_MIXER_NRDEVICES][2]; 52 53 static int littleEndian = 0; 54 55 static int minNumChan = 1; 56 57 int 58 SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq, 59 int nchannels, int encoding) 60 { 61 int format; 62 int nformat; 63 int channels; 64 int speed; 65 int mask; 66 /* int frag = 0x7fff0010;*/ 67 68 if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioOpen\n"); 69 70 if (device == NULL) { 71 device = defaultDeviceName; 72 } 73 if (strlen(device) == 0) { 74 device = defaultDeviceName; 75 } 76 77 /* Test if the device is not locked by another process. This is 78 * just a crude workaround to avoid complete lockup of snack. It is 79 * not perfect since it is theoretically possible that another program 80 * locks the device between the close() and open() calls below. */ 81 A->afd = open(device, O_WRONLY|O_NONBLOCK); 82 if(A->afd == -1) { 83 Tcl_AppendResult(interp, "Could not gain access to ", device, " for writing.",NULL); 84 return TCL_ERROR; 85 } 86 close(A->afd); 87 88 A->mode = mode; 89 switch (mode) { 90 case RECORD: 91 if ((A->afd = open(device, O_RDONLY, 0)) == -1) { 92 Tcl_AppendResult(interp, "Could not open ", device, " for read.", 93 NULL); 94 return TCL_ERROR; 95 } 96 break; 97 98 case PLAY: 99 if ((A->afd = open(device, O_WRONLY, 0)) == -1) { 100 Tcl_AppendResult(interp, "Could not open ", device, " for write.", 101 NULL); 102 return TCL_ERROR; 103 } 104 break; 105 } 106 107 fcntl(A->afd, F_SETFD, FD_CLOEXEC); 108 109 /* if (ioctl(A->afd, SNDCTL_DSP_SETFRAGMENT, &frag)) return(-1);*/ 110 111 if (ioctl(A->afd, SNDCTL_DSP_GETFMTS, &mask) == -1) { 112 close(A->afd); 113 Tcl_AppendResult(interp, "Failed getting formats.", NULL); 114 return TCL_ERROR; 115 } 116 117 A->convert = 0; 118 119 switch (encoding) { 120 case LIN16: 121 if (littleEndian) { 122 format = AFMT_S16_LE; 123 } else { 124 format = AFMT_S16_BE; 125 } 126 A->bytesPerSample = sizeof(short); 127 break; 128 #ifdef AFMT_S32_LE 129 case LIN24: 130 if (littleEndian) { 131 format = AFMT_S32_LE; 132 } else { 133 format = AFMT_S32_BE; 134 } 135 A->bytesPerSample = sizeof(int); 136 break; 137 #endif 138 case ALAW: 139 if (mask & AFMT_A_LAW) { 140 format = AFMT_A_LAW; 141 A->bytesPerSample = sizeof(char); 142 } else { 143 if (littleEndian) { 144 format = AFMT_S16_LE; 145 } else { 146 format = AFMT_S16_BE; 147 } 148 A->bytesPerSample = sizeof(short); 149 A->convert = ALAW; 150 } 151 break; 152 case MULAW: 153 if (mask & AFMT_MU_LAW) { 154 format = AFMT_MU_LAW; 155 A->bytesPerSample = sizeof(char); 156 } else { 157 if (littleEndian) { 158 format = AFMT_S16_LE; 159 } else { 160 format = AFMT_S16_BE; 161 } 162 A->bytesPerSample = sizeof(short); 163 A->convert = MULAW; 164 } 165 break; 166 case LIN8OFFSET: 167 format = AFMT_U8; 168 A->bytesPerSample = sizeof(char); 169 break; 170 case LIN8: 171 format = AFMT_S8; 172 A->bytesPerSample = sizeof(char); 173 break; 174 } 175 176 nformat = format; 177 if (ioctl(A->afd, SNDCTL_DSP_SETFMT, &format) == -1 178 || format != nformat) { 179 close(A->afd); 180 Tcl_AppendResult(interp, "Failed setting format.", NULL); 181 return TCL_ERROR; 182 } 183 184 A->nChannels = nchannels; 185 channels = nchannels; 186 if (ioctl(A->afd, SNDCTL_DSP_CHANNELS, &channels) == -1 187 || channels != nchannels) { 188 close(A->afd); 189 Tcl_AppendResult(interp, "Failed setting number of channels.", NULL); 190 return TCL_ERROR; 191 } 192 193 speed = freq; 194 if (ioctl(A->afd, SNDCTL_DSP_SPEED, &speed) == -1 195 || abs(speed - freq) > freq / 100) { 196 close(A->afd); 197 Tcl_AppendResult(interp, "Failed setting sample frequency.", NULL); 198 return TCL_ERROR; 199 } 200 201 /* A->count = 0;*/ 202 A->frag_size = 0; 203 if (ioctl(A->afd, SNDCTL_DSP_GETBLKSIZE, &A->frag_size) == -1) { 204 close(A->afd); 205 Tcl_AppendResult(interp, "Failed getting fragment size.", NULL); 206 return TCL_ERROR; 207 } 208 /* printf("Frag size: %d\n", A->frag_size);*/ 209 A->time = SnackCurrentTime(); 210 A->timep = 0.0; 211 A->freq = freq; 212 A->warm = 0; 213 214 if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioOpen", A->frag_size); 215 216 return TCL_OK; 217 } 218 219 int 220 SnackAudioClose(ADesc *A) 221 { 222 if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioClose\n"); 223 224 /* A->count = 0;*/ 225 226 close(A->afd); 227 228 if (A->debug > 1) Snack_WriteLog(" Exit SnackAudioClose\n"); 229 230 return(0); 231 } 232 233 long 234 SnackAudioPause(ADesc *A) 235 { 236 long res = SnackAudioPlayed(A); 237 238 A->timep = SnackCurrentTime(); 239 ioctl(A->afd, SNDCTL_DSP_RESET, 0); 240 241 return(res); 242 } 243 244 void 245 SnackAudioResume(ADesc *A) 246 { 247 A->time = A->time + SnackCurrentTime() - A->timep; 248 } 249 250 void 251 SnackAudioFlush(ADesc *A) 252 { 253 if (A->mode == RECORD) { 254 } else { 255 ioctl(A->afd, SNDCTL_DSP_RESET, 0); 256 } 257 } 258 259 static char zeroBlock[16]; 260 261 void 262 SnackAudioPost(ADesc *A) 263 { 264 if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioPost\n"); 265 266 if (A->warm == 1) { 267 int i; 268 for (i = 0; i < A->frag_size / (A->bytesPerSample * A->nChannels); i++) { 269 write(A->afd, zeroBlock, A->bytesPerSample * A->nChannels); 270 } 271 A->warm = 2; 272 ioctl(A->afd, SNDCTL_DSP_POST, 0); 273 } 274 275 if (A->debug > 1) Snack_WriteLog(" Exit SnackAudioPost\n"); 276 } 277 278 int 279 SnackAudioRead(ADesc *A, void *buf, int nFrames) 280 { 281 int n = 2; 282 283 if (A->debug > 1) Snack_WriteLogInt(" Enter SnackAudioRead", nFrames); 284 285 286 while (nFrames > n * 2) n *= 2; 287 nFrames = n; 288 289 if (A->convert) { 290 int n = 0, i, res; 291 short s[2]; 292 293 for (i = 0; i < nFrames * A->nChannels; i += A->nChannels) { 294 res = read(A->afd, &s, A->nChannels * sizeof(short)); 295 if (res <= 0) return(n / (A->bytesPerSample * A->nChannels)); 296 if (A->convert == ALAW) { 297 ((unsigned char *)buf)[i] = Snack_Lin2Alaw(s[0]); 298 if (A->nChannels == 2) { 299 ((unsigned char *)buf)[i+1] = Snack_Lin2Alaw(s[1]); 300 } 301 } else { 302 ((unsigned char *)buf)[i] = Snack_Lin2Mulaw(s[0]); 303 if (A->nChannels == 2) { 304 ((unsigned char *)buf)[i+1] = Snack_Lin2Mulaw(s[1]); 305 } 306 } 307 n += res; 308 } 309 310 return(n / (A->bytesPerSample * A->nChannels)); 311 } else { 312 int n = read(A->afd, (unsigned char *)buf, nFrames * A->bytesPerSample * A->nChannels); 313 314 if (n > 0) n /= (A->bytesPerSample * A->nChannels); 315 316 if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioRead", n); 317 318 return(n); 319 } 320 } 321 322 int 323 SnackAudioWrite(ADesc *A, void *buf, int nFrames) 324 { 325 if (A->warm == 0) A->warm = 1; 326 327 if (A->convert) { 328 int n = 0, i, res; 329 short s; 330 331 for (i = 0; i < nFrames * A->nChannels; i++) { 332 if (A->convert == ALAW) { 333 s = Snack_Alaw2Lin(((unsigned char *)buf)[i]); 334 } else { 335 s = Snack_Mulaw2Lin(((unsigned char *)buf)[i]); 336 } 337 res = write(A->afd, &s, sizeof(short)); 338 if (res <= 0) return(n / (A->bytesPerSample * A->nChannels)); 339 n += res; 340 } 341 342 return(n / (A->bytesPerSample * A->nChannels)); 343 } else { 344 int n = write(A->afd, buf, nFrames * A->bytesPerSample * A->nChannels); 345 if (n > 0) n /= (A->bytesPerSample * A->nChannels); 346 347 return(n); 348 } 349 } 350 351 int 352 SnackAudioReadable(ADesc *A) 353 { 354 audio_buf_info info; 355 356 if (A->debug > 1) Snack_WriteLog(" Enter SnackAudioReadable\n"); 357 358 ioctl(A->afd, SNDCTL_DSP_GETISPACE, &info); 359 if (info.bytes > 60*44100*4) info.bytes = 0; 360 if (A->debug > 1) Snack_WriteLogInt(" Exit SnackAudioReadable", info.bytes); 361 362 return (info.bytes / (A->bytesPerSample * A->nChannels)); 363 } 364 365 int 366 SnackAudioWriteable(ADesc *A) 367 { 368 audio_buf_info info; 369 370 ioctl(A->afd, SNDCTL_DSP_GETOSPACE, &info); 371 372 return (info.bytes / (A->bytesPerSample * A->nChannels)); 373 } 374 375 long 376 SnackAudioPlayed(ADesc *A) 377 { 378 /* 379 count_info info; 380 int res = 0; 381 382 if (A->warm) { 383 ioctl(A->afd, SNDCTL_DSP_GETOPTR, &info); 384 if (info.bytes > 0 || A->warm == 2) { 385 res = (A->freq * (SnackCurrentTime() - A->time) +.5); 386 A->warm = 2; 387 } 388 } 389 */ 390 long res; 391 392 res = (A->freq * (SnackCurrentTime() - A->time) +.5); 393 394 return(res); 395 } 396 397 void 398 SnackAudioInit() 399 { 400 union { 401 char c[sizeof(short)]; 402 short s; 403 } order; 404 int afd, format, channels, nchannels; 405 /* 406 int i, n; 407 char *arr[MAX_NUM_DEVICES]; 408 */ 409 410 /* Compute the byte order of this machine. */ 411 412 order.s = 1; 413 if (order.c[0] == 1) { 414 littleEndian = 1; 415 } 416 417 if ((mfd = open(MIXER_NAME, O_RDWR, 0)) == -1) { 418 fprintf(stderr, "Unable to open mixer %s\n", MIXER_NAME); 419 } 420 /* 421 n = SnackGetOutputDevices(arr, MAX_NUM_DEVICES); 422 for (i = 0; i < n; i++) { 423 printf("Trying %s %d\n",arr[i], open(arr[i], O_WRONLY, 0)); 424 if ((afd = open(arr[i], O_WRONLY, 0)) != -1) { 425 defaultDeviceName = arr[i]; 426 printf("accepting %s %d\n",defaultDeviceName,afd); 427 break; 428 } 429 } 430 */ 431 432 if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) { 433 defaultDeviceName = "/dev/sound/dsp"; 434 if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) { 435 return; 436 } 437 } 438 close(afd); 439 440 /* Determine minimum number of channels supported. */ 441 442 if ((afd = open(defaultDeviceName, O_WRONLY, 0)) == -1) { 443 return; 444 } 445 446 if (littleEndian) { 447 format = AFMT_S16_LE; 448 } else { 449 format = AFMT_S16_BE; 450 } 451 if (ioctl(afd, SNDCTL_DSP_SETFMT, &format) == -1) { 452 close(afd); 453 return; 454 } 455 channels = nchannels = 1; 456 if (ioctl(afd, SNDCTL_DSP_CHANNELS, &channels) == -1 457 || channels != nchannels) { 458 minNumChan = channels; 459 } 460 close(afd); 461 } 462 463 void 464 SnackAudioFree() 465 { 466 int i, j; 467 468 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 469 for (j = 0; j < 2; j++) { 470 if (mixerLinks[i][j].mixer != NULL) { 471 ckfree(mixerLinks[i][j].mixer); 472 } 473 if (mixerLinks[i][j].mixerVar != NULL) { 474 ckfree(mixerLinks[i][j].mixerVar); 475 } 476 } 477 if (mixerLinks[i][0].jack != NULL) { 478 ckfree(mixerLinks[i][0].jack); 479 } 480 if (mixerLinks[i][0].jackVar != NULL) { 481 ckfree((char *)mixerLinks[i][0].jackVar); 482 } 483 } 484 485 close(mfd); 486 } 487 488 void 489 ASetRecGain(int gain) 490 { 491 int g = min(max(gain, 0), 100); 492 int recsrc = 0; 493 494 g = g * 256 + g; 495 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc); 496 if (recsrc & SOUND_MASK_LINE) { 497 ioctl(mfd, SOUND_MIXER_WRITE_LINE, &g); 498 } else { 499 ioctl(mfd, SOUND_MIXER_WRITE_MIC, &g); 500 } 501 } 502 503 void 504 ASetPlayGain(int gain) 505 { 506 int g = min(max(gain, 0), 100); 507 int pcm_gain = 25700; 508 509 g = g * 256 + g; 510 ioctl(mfd, SOUND_MIXER_WRITE_VOLUME, &g); 511 ioctl(mfd, SOUND_MIXER_WRITE_PCM, &pcm_gain); 512 } 513 514 int 515 AGetRecGain() 516 { 517 int g = 0, left, right, recsrc = 0; 518 519 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recsrc); 520 if (recsrc & SOUND_MASK_LINE) { 521 ioctl(mfd, SOUND_MIXER_READ_LINE, &g); 522 } else { 523 ioctl(mfd, SOUND_MIXER_READ_MIC, &g); 524 } 525 left = g & 0xff; 526 right = (g & 0xff00) / 256; 527 g = (left + right) / 2; 528 529 return(g); 530 } 531 532 int 533 AGetPlayGain() 534 { 535 int g = 0, left, right; 536 537 ioctl(mfd, SOUND_MIXER_READ_VOLUME, &g); 538 left = g & 0xff; 539 right = (g & 0xff00) / 256; 540 g = (left + right) / 2; 541 542 return(g); 543 } 544 545 int 546 SnackAudioGetEncodings(char *device) 547 { 548 int afd, mask; 549 550 if ((afd = open(DEVICE_NAME, O_WRONLY, 0)) == -1) { 551 return(0); 552 } 553 if (ioctl(afd, SNDCTL_DSP_GETFMTS, &mask) == -1) { 554 return(0); 555 } 556 close(afd); 557 558 if (mask & AFMT_S16_LE || mask & AFMT_S16_BE) { 559 return(LIN16); 560 } else { 561 return(0); 562 } 563 } 564 565 void 566 SnackAudioGetRates(char *device, char *buf, int n) 567 { 568 int afd, freq, pos= 0, i; 569 int f[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000 }; 570 571 if ((afd = open(DEVICE_NAME, O_WRONLY, 0)) == -1) { 572 buf[0] = '\0'; 573 return; 574 } 575 for (i = 0; i < 8; i++) { 576 freq = f[i]; 577 if (ioctl(afd, SNDCTL_DSP_SPEED, &freq) == -1) break; 578 if (abs(f[i] - freq) > freq / 100) continue; 579 pos += sprintf(&buf[pos], "%d ", freq); 580 } 581 close(afd); 582 } 583 584 int 585 SnackAudioMaxNumberChannels(char *device) 586 { 587 return(2); 588 } 589 590 int 591 SnackAudioMinNumberChannels(char *device) 592 { 593 return(minNumChan); 594 } 595 596 void 597 SnackMixerGetInputJackLabels(char *buf, int n) 598 { 599 char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 600 int i, recMask, pos = 0; 601 602 if (mfd != -1) { 603 ioctl(mfd, SOUND_MIXER_READ_RECMASK, &recMask); 604 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 605 if ((1 << i) & recMask) { 606 pos += sprintf(&buf[pos], "%s", jackLabels[i]); 607 pos += sprintf(&buf[pos], " "); 608 } 609 } 610 } else { 611 buf[0] = '\0'; 612 } 613 buf[n-1] = '\0'; 614 } 615 616 void 617 SnackMixerGetOutputJackLabels(char *buf, int n) 618 { 619 buf[0] = '\0'; 620 } 621 622 void 623 SnackMixerGetInputJack(char *buf, int n) 624 { 625 char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 626 int i, recSrc = 0, pos = 0; 627 628 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 629 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 630 if ((1 << i) & recSrc) { 631 pos += sprintf(&buf[pos], "%s", jackLabels[i]); 632 while (isspace(buf[pos-1])) pos--; 633 pos += sprintf(&buf[pos], " "); 634 } 635 } 636 if(isspace(buf[pos-1])) pos--; 637 buf[pos] = '\0'; 638 /*printf("SnackMixerGetInputJack %x, %s\n", recSrc, buf);*/ 639 } 640 641 int 642 SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status) 643 { 644 char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 645 int i, recSrc = 0, currSrc; 646 647 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 648 if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) { 649 recSrc = 1 << i; 650 break; 651 } 652 } 653 654 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &currSrc); 655 656 /* printf("SnackMixerSetInputJack1 %x %s %s\n", currSrc, jack, status);*/ 657 658 if (strcmp(status, "1") == 0) { 659 recSrc |= currSrc; 660 } else { 661 recSrc = (currSrc & ~recSrc); 662 } 663 /* printf("SnackMixerSetInputJack2 %x\n", recSrc);*/ 664 665 if (ioctl(mfd, SOUND_MIXER_WRITE_RECSRC, &recSrc) == -1) { 666 return 1; 667 } else { 668 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 669 /* printf("SnackMixerSetInputJack3 %x\n", recSrc);*/ 670 return 0; 671 } 672 return 1; 673 } 674 675 void 676 SnackMixerGetOutputJack(char *buf, int n) 677 { 678 buf[0] = '\0'; 679 } 680 681 void 682 SnackMixerSetOutputJack(char *jack, char *status) 683 { 684 } 685 686 static int dontTrace = 0; 687 688 static char * 689 JackVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1, 690 CONST84 char *name2, int flags) 691 { 692 MixerLink *mixLink = (MixerLink *) clientData; 693 char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 694 int i, recSrc = 0, status = 0; 695 CONST84 char *stringValue; 696 Tcl_Obj *obj, *var; 697 698 if (dontTrace) return (char *) NULL; 699 700 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 701 /*printf("JackVarProc %x %s %s\n", recSrc, name1, name2);*/ 702 if (flags & TCL_TRACE_UNSETS) { 703 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { 704 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 705 if (strncasecmp(mixLink->jack, jackLabels[i], strlen(mixLink->jack)) 706 == 0) { 707 if ((1 << i) & recSrc) { 708 status = 1; 709 } else { 710 status = 0; 711 } 712 break; 713 } 714 } 715 obj = Tcl_NewIntObj(status); 716 var = Tcl_NewStringObj(mixLink->jackVar, -1); 717 Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1); 718 Tcl_TraceVar(interp, mixLink->jackVar, 719 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, 720 JackVarProc, mixLink); 721 } 722 return (char *) NULL; 723 } 724 725 stringValue = Tcl_GetVar(interp, mixLink->jackVar, TCL_GLOBAL_ONLY); 726 if (stringValue != NULL) { 727 SnackMixerSetInputJack(interp, mixLink->jack, stringValue); 728 } 729 730 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 731 /*printf("JackVarProc2 %x\n", recSrc);*/ 732 dontTrace = 1; 733 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 734 if (mixerLinks[i][0].jackVar != NULL) { 735 if ((1 << i) & recSrc) { 736 status = 1; 737 } else { 738 status = 0; 739 } 740 obj = Tcl_NewIntObj(status); 741 var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1); 742 Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY |TCL_PARSE_PART1); 743 } 744 } 745 dontTrace = 0; 746 747 return (char *) NULL; 748 } 749 750 void 751 SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var) 752 { 753 char *jackLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 754 int i, recSrc = 0, status; 755 CONST84 char *value; 756 757 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 758 759 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 760 if (strncasecmp(jack, jackLabels[i], strlen(jack)) == 0) { 761 if ((1 << i) & recSrc) { 762 status = 1; 763 } else { 764 status = 0; 765 } 766 mixerLinks[i][0].jack = SnackStrDup(jack); 767 mixerLinks[i][0].jackVar = SnackStrDup(Tcl_GetStringFromObj(var, NULL)); 768 value = Tcl_GetVar(interp, mixerLinks[i][0].jackVar, TCL_GLOBAL_ONLY); 769 if (value != NULL) { 770 SnackMixerSetInputJack(interp, mixerLinks[i][0].jack, value); 771 } else { 772 Tcl_Obj *obj = Tcl_NewIntObj(status); 773 Tcl_ObjSetVar2(interp, var, NULL, obj, 774 TCL_GLOBAL_ONLY | TCL_PARSE_PART1); 775 776 } 777 Tcl_TraceVar(interp, mixerLinks[i][0].jackVar, 778 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, 779 JackVarProc, (ClientData) &mixerLinks[i][0]); 780 break; 781 } 782 } 783 } 784 785 void 786 SnackMixerGetChannelLabels(char *line, char *buf, int n) 787 { 788 char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 789 int i, devMask; 790 791 ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask); 792 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 793 if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) { 794 if (devMask & (1 << i)) { 795 sprintf(buf, "Left Right"); 796 } else { 797 sprintf(buf, "Mono"); 798 } 799 break; 800 } 801 } 802 } 803 804 void 805 SnackMixerGetVolume(char *line, int channel, char *buf, int n) 806 { 807 char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 808 int i, vol = 0, devMask, isStereo = 0, left, right; 809 810 buf[0] = '\0'; 811 812 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 813 if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) { 814 ioctl(mfd, MIXER_READ(i), &vol); 815 ioctl(mfd, SOUND_MIXER_READ_STEREODEVS, &devMask); 816 if (devMask & (1 << i)) { 817 isStereo = 1; 818 } 819 break; 820 } 821 } 822 left = vol & 0xff; 823 right = (vol & 0xff00) >> 8; 824 if (isStereo) { 825 if (channel == 0) { 826 sprintf(buf, "%d", left); 827 } else if (channel == 1) { 828 sprintf(buf, "%d", right); 829 } else if (channel == -1) { 830 sprintf(buf, "%d", (left + right)/2); 831 } 832 } else { 833 sprintf(buf, "%d", left); 834 } 835 } 836 837 void 838 SnackMixerSetVolume(char *line, int channel, int volume) 839 { 840 char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 841 int tmp = min(max(volume, 0), 100), i, oldVol = 0; 842 int vol = (tmp << 8) + tmp; 843 844 if (channel == 0) { 845 vol = tmp; 846 } 847 if (channel == 1) { 848 vol = tmp << 8; 849 } 850 851 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 852 if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) { 853 ioctl(mfd, MIXER_READ(i), &oldVol); 854 if (channel == 0) { 855 vol = (oldVol & 0xff00) | (vol & 0x00ff); 856 } 857 if (channel == 1) { 858 vol = (vol & 0xff00) | (oldVol & 0x00ff); 859 } 860 ioctl(mfd, MIXER_WRITE(i), &vol); 861 break; 862 } 863 } 864 } 865 866 static char * 867 VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1, 868 CONST84 char *name2, int flags) 869 { 870 MixerLink *mixLink = (MixerLink *) clientData; 871 CONST84 char *stringValue; 872 873 if (flags & TCL_TRACE_UNSETS) { 874 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { 875 Tcl_Obj *obj, *var; 876 char tmp[VOLBUFSIZE]; 877 878 SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE); 879 obj = Tcl_NewIntObj(atoi(tmp)); 880 var = Tcl_NewStringObj(mixLink->mixerVar, -1); 881 Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1); 882 Tcl_TraceVar(interp, mixLink->mixerVar, 883 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, 884 VolumeVarProc, mixLink); 885 } 886 return (char *) NULL; 887 } 888 889 stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY); 890 if (stringValue != NULL) { 891 SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue)); 892 } 893 894 return (char *) NULL; 895 } 896 897 void 898 SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n, 899 Tcl_Obj *CONST objv[]) 900 { 901 char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 902 int i, j, channel; 903 CONST84 char *value; 904 char tmp[VOLBUFSIZE]; 905 906 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 907 if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) { 908 for (j = 0; j < n; j++) { 909 if (n == 1) { 910 channel = -1; 911 } else { 912 channel = j; 913 } 914 mixerLinks[i][j].mixer = SnackStrDup(line); 915 mixerLinks[i][j].mixerVar = SnackStrDup(Tcl_GetStringFromObj(objv[j+3],NULL)); 916 mixerLinks[i][j].channel = j; 917 value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY); 918 if (value != NULL) { 919 SnackMixerSetVolume(line, channel, atoi(value)); 920 } else { 921 Tcl_Obj *obj; 922 SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE); 923 obj = Tcl_NewIntObj(atoi(tmp)); 924 Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj, 925 TCL_GLOBAL_ONLY | TCL_PARSE_PART1); 926 } 927 Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar, 928 TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, 929 VolumeVarProc, (ClientData) &mixerLinks[i][j]); 930 } 931 } 932 } 933 } 934 935 void 936 SnackMixerUpdateVars(Tcl_Interp *interp) 937 { 938 int i, j, recSrc, status; 939 char tmp[VOLBUFSIZE]; 940 Tcl_Obj *obj, *var; 941 942 ioctl(mfd, SOUND_MIXER_READ_RECSRC, &recSrc); 943 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 944 for (j = 0; j < 2; j++) { 945 if (mixerLinks[i][j].mixerVar != NULL) { 946 SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel, 947 tmp, VOLBUFSIZE); 948 obj = Tcl_NewIntObj(atoi(tmp)); 949 var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1); 950 Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1); 951 } 952 } 953 if (mixerLinks[i][0].jackVar != NULL) { 954 if ((1 << i) & recSrc) { 955 status = 1; 956 } else { 957 status = 0; 958 } 959 obj = Tcl_NewIntObj(status); 960 var = Tcl_NewStringObj(mixerLinks[i][0].jackVar, -1); 961 Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1); 962 } 963 } 964 } 965 966 void 967 SnackMixerGetLineLabels(char *buf, int n) 968 { 969 char *mixLabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; 970 int i, devMask, pos = 0; 971 972 if (mfd != -1) { 973 ioctl(mfd, SOUND_MIXER_READ_DEVMASK, &devMask); 974 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { 975 if ((1 << i) & devMask && pos < n-8) { 976 pos += sprintf(&buf[pos], "%s", mixLabels[i]); 977 pos += sprintf(&buf[pos], " "); 978 } 979 } 980 } else { 981 buf[0] = '\0'; 982 } 983 buf[n-1] = '\0'; 984 } 985 986 int 987 SnackGetOutputDevices(char **arr, int n) 988 { 989 return SnackGetInputDevices(arr, n); 990 } 991 992 int 993 SnackGetInputDevices(char **arr, int n) 994 { 995 int i, j = 0; 996 glob_t globt; 997 998 glob("/dev/dsp*", 0, NULL, &globt); 999 glob("/dev/audio*", GLOB_APPEND, NULL, &globt); 1000 glob("/dev/sound/dsp*", GLOB_APPEND, NULL, &globt); 1001 glob("/dev/sound/audio*", GLOB_APPEND, NULL, &globt); 1002 1003 for (i = 0; i < globt.gl_pathc; i++) { 1004 if (j < n) { 1005 arr[j++] = (char *) SnackStrDup(globt.gl_pathv[i]); 1006 } 1007 } 1008 globfree(&globt); 1009 1010 return(j); 1011 } 1012 1013 int 1014 SnackGetMixerDevices(char **arr, int n) 1015 { 1016 int i, j = 0; 1017 glob_t globt; 1018 1019 glob("/dev/mixer*", 0, NULL, &globt); 1020 glob("/dev/sound/mixer*", GLOB_APPEND, NULL, &globt); 1021 1022 for (i = 0; i < globt.gl_pathc; i++) { 1023 if (j < n) { 1024 arr[j++] = (char *) SnackStrDup(globt.gl_pathv[i]); 1025 } 1026 } 1027 globfree(&globt); 1028 1029 return(j); 1030 } 1031