/* * Copyright (C) 1997-2005 Kare Sjolander * * This file is part of the Snack Sound Toolkit. * The latest version can be found at http://www.speech.kth.se/snack/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "snack.h" extern int littleEndian; struct Snack_FileFormat *snackFileFormats = NULL; extern int useOldObjAPI; static char * GuessWavFile(char *buf, int len) { if (len < 21) return(QUE_STRING); if (strncasecmp("RIFF", buf, strlen("RIFF")) == 0) { if (buf[20] == 85) { return(MP3_STRING); } if (strncasecmp("WAVE", &buf[8], strlen("WAVE")) == 0) { return(WAV_STRING); } } return(NULL); } static char * GuessAuFile(char *buf, int len) { if (len < 4) return(QUE_STRING); if (strncmp(".snd", buf, strlen(".snd")) == 0) { return(AU_STRING); } return(NULL); } static char * GuessAiffFile(char *buf, int len) { if (len < 12) return(QUE_STRING); if (strncasecmp("FORM", buf, strlen("FORM")) == 0) { if (strncasecmp("AIFF", &buf[8], strlen("AIFF")) == 0) { return(AIFF_STRING); } } return(NULL); } static char * GuessSmpFile(char *buf, int len) { int i, end = len - strlen("file=samp"); for (i = 0; i < end; i++) { if (strncasecmp("file=samp", &buf[i], strlen("file=samp")) == 0) { return(SMP_STRING); } } if (len < 512) return(QUE_STRING); return(NULL); } static char * GuessSdFile(char *buf, int len) { if (len < 20) return(QUE_STRING); if (buf[16] == 0 && buf[17] == 0 && buf[18] == 106 && buf[19] == 26) { return(SD_STRING); } return(NULL); } static char * GuessCslFile(char *buf, int len) { if (len < 8) return(QUE_STRING); if (strncmp("FORMDS16", buf, strlen("FORMDS16")) == 0) { return(CSL_STRING); } return(NULL); } static char * GuessRawFile(char *buf, int len) { return(RAW_STRING); } char * GuessFileType(char *buf, int len, int eof) { Snack_FileFormat *ff; int flag = 0; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { char *type = (ff->guessProc)(buf, len); if (type == NULL) { /* guessProc can't recognize this header */ } else if (strcmp(type, QUE_STRING) == 0) { flag = 1; /* guessProc needs more bytes in order to decide */ } else if (strcmp(type, RAW_STRING) != 0) { return(type); } } /* Don't decide yet if there's more header bytes to be had */ if (flag && !eof) { return(QUE_STRING); } /* No guessProc recognized this header => guess RAW format */ return(RAW_STRING); } static int ExtCmp(char *s1, char *s2) { int l1 = strlen(s1); int l2 = strlen(s2); return(strncasecmp(s1, &s2[l2 - l1], l1)); } static char * ExtSmpFile(char *s) { if (ExtCmp(".smp", s) == 0) { return(SMP_STRING); } return(NULL); } static char * ExtWavFile(char *s) { if (ExtCmp(".wav", s) == 0) { return(WAV_STRING); } return(NULL); } static char * ExtAuFile(char *s) { if (ExtCmp(".au", s) == 0 || ExtCmp(".snd", s) == 0) { return(AU_STRING); } return(NULL); } static char * ExtAiffFile(char *s) { if (ExtCmp(".aif", s) == 0 || ExtCmp(".aiff", s) == 0) { return(AIFF_STRING); } return(NULL); } static char * ExtSdFile(char *s) { if (ExtCmp(".sd", s) == 0) { return(SD_STRING); } return(NULL); } static char * ExtCslFile(char *s) { if (ExtCmp(".nsp", s) == 0) { return(CSL_STRING); } return(NULL); } char * NameGuessFileType(char *s) { Snack_FileFormat *ff; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (ff->extProc != NULL) { char *type = (ff->extProc)(s); if (type != NULL) { return(type); } } } return(RAW_STRING); } /* static short ReadBEShort(Tcl_Channel ch) { short ts; Tcl_Read(ch, (char *) &ts, sizeof(short)); if (littleEndian) { ts = Snack_SwapShort(ts); } return(ts); } static short ReadLEShort(Tcl_Channel ch) { short ts; Tcl_Read(ch, (char *) &ts, sizeof(short)); if (!littleEndian) { ts = Snack_SwapShort(ts); } return(ts); } static int32_t ReadBELong(Tcl_Channel ch) { int32_t tl; Tcl_Read(ch, (char *) &tl, sizeof(int32_t)); if (littleEndian) { tl = Snack_SwapLong(tl); } return(tl); } static int32_t ReadLELong(Tcl_Channel ch) { int32_t tl; Tcl_Read(ch, (char *) &tl, sizeof(int32_t)); if (!littleEndian) { tl = Snack_SwapLong(tl); } return(tl); } */ static int WriteLEShort(Tcl_Channel ch, short s) { short ts = s; if (!littleEndian) { ts = Snack_SwapShort(ts); } return(Tcl_Write(ch, (char *) &ts, sizeof(short))); } int WriteLELong(Tcl_Channel ch, int32_t l) { int32_t tl = l; if (!littleEndian) { tl = Snack_SwapLong(tl); } return(Tcl_Write(ch, (char *) &tl, sizeof(int32_t))); } static int WriteBEShort(Tcl_Channel ch, short s) { short ts = s; if (littleEndian) { ts = Snack_SwapShort(ts); } return(Tcl_Write(ch, (char *) &ts, sizeof(short))); } int WriteBELong(Tcl_Channel ch, int32_t l) { int32_t tl = l; if (littleEndian) { tl = Snack_SwapLong(tl); } return(Tcl_Write(ch, (char *) &tl, sizeof(int32_t))); } static int32_t GetLELong(char *buf, int pos) { int32_t tl; memcpy(&tl, &buf[pos], sizeof(int32_t)); if (!littleEndian) { tl = Snack_SwapLong(tl); } return(tl); } static short GetLEShort(char *buf, int pos) { short ts; char *p; short *q; p = &buf[pos]; q = (short *) p; ts = *q; if (!littleEndian) { ts = Snack_SwapShort(ts); } return(ts); } static int32_t GetBELong(char *buf, int pos) { int32_t tl; memcpy(&tl, &buf[pos], sizeof(int32_t)); if (littleEndian) { tl = Snack_SwapLong(tl); } return(tl); } static short GetBEShort(char *buf, int pos) { short ts; char *p; short *q; p = &buf[pos]; q = (short *) p; ts = *q; if (littleEndian) { ts = Snack_SwapShort(ts); } return(ts); } static void PutBELong(char *buf, int pos, int32_t l) { int32_t tl = l; if (littleEndian) { tl = Snack_SwapLong(tl); } memcpy(&buf[pos], &tl, sizeof(int32_t)); } static void PutBEShort(char *buf, int pos, short s) { short ts = s; char *p; short *q; p = &buf[pos]; q = (short *) p; if (littleEndian) { ts = Snack_SwapShort(ts); } *q = ts; } /* Note: pos must be a multiple of 4 */ static void PutLELong(char *buf, int pos, int32_t l) { int32_t tl = l; char *p; int32_t *q; p = &buf[pos]; q = (int32_t *) p; if (!littleEndian) { tl = Snack_SwapLong(tl); } *q = tl; } static void PutLEShort(char *buf, int pos, short s) { short ts = s; char *p; short *q; p = &buf[pos]; q = (short *) p; if (!littleEndian) { ts = Snack_SwapShort(ts); } *q = ts; } extern short shortBuffer[]; extern float floatBuffer[]; static int ReadSound(readSamplesProc *readProc, Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int startpos, int endpos) { int tot, totrlen = 0, res, i, j = s->loadOffset, size; char *b = (char *) shortBuffer; if (s->debug > 1) Snack_WriteLogInt(" Enter ReadSound", s->length); if (s->length > 0) { if (endpos < 0 || endpos > (s->length - 1)) { endpos = s->length - 1; } s->length = endpos - startpos + 1; if (s->length < 0) s->length = 0; if (Snack_ResizeSoundStorage(s, s->length) != TCL_OK) { s->length = 0; Tcl_AppendResult(interp, "Memory allocation failed", NULL); return TCL_ERROR; } } if (s->encoding == SNACK_DOUBLE) { s->sampsize = 8; } if (s->length == -1) { tot = 1 << 30; } else { tot = (s->length - s->loadOffset) * s->sampsize * s->nchannels; } Snack_ProgressCallback(s->cmdPtr, interp, "Reading sound", 0.0); while (tot > 0) { int rlen; if (s->encoding != LIN24) { size = min(tot, sizeof(short) * PBSIZE); } else { size = min(tot, sizeof(short) * (PBSIZE - 1)); } /* Samples on disk are 8 bytes -> make sure they fit in buffer */ if (s->encoding == SNACK_DOUBLE && size > (PBSIZE / 2)) { size /= 2; } if (s->length == -1) { if (Snack_ResizeSoundStorage(s, s->maxlength+1) != TCL_OK) { s->length = 0; Tcl_AppendResult(interp, "Memory allocation failed", NULL); return TCL_ERROR; } } if (ch != NULL) { if (readProc == NULL) { rlen = Tcl_Read(ch, b, size); tot -= size; } else { size = min(s->length * s->nchannels, PBSIZE); rlen = (readProc)(s, interp, ch, NULL, (float*)&floatBuffer, size); Snack_PutSoundData(s, totrlen / s->sampsize, &floatBuffer, rlen); if (rlen > 0) { rlen *= s->sampsize; tot -= rlen; } } if (rlen < 0) { Tcl_AppendResult(interp, "Error reading data", NULL); return TCL_ERROR; } totrlen += rlen; if (rlen < size) { tot = 0; } } else { int length = 0; unsigned char *ptr = NULL; if (useOldObjAPI) { ptr = (unsigned char *) obj->bytes; } else { #ifdef TCL_81_API Tcl_GetByteArrayFromObj(obj, &length); ptr = Tcl_GetByteArrayFromObj(obj, NULL); #endif } if (readProc == NULL) { memcpy(b, &ptr[totrlen + s->headSize + startpos * s->sampsize * s->nchannels], size); totrlen += size; tot -= size; } else { size = min(tot / (s->sampsize * s->nchannels), PBSIZE); /*printf("%d cnk %d obj %d slen %d\n", tot, size, length, s->length);*/ rlen = (readProc)(s, interp, NULL, (char *) ptr, (float*)&floatBuffer, size); Snack_PutSoundData(s, totrlen / s->sampsize, &floatBuffer, rlen); rlen *= s->sampsize; totrlen += rlen; tot -= rlen; if (rlen < size) { tot = 0; } } } if (readProc == NULL) { /* unpack block */ unsigned char *q = (unsigned char *) b; char *sc = (char *) b; short *r = (short *) b; int *is = (int *) b; float *fs = (float *) b; double *fd = (double *) b; if (s->precision == SNACK_SINGLE_PREC) { for (i = 0; i < size / s->sampsize; i++, j++) { int writeblock = (j >> FEXP); if (writeblock >= s->nblks) { /* Reached end of allocated blocks for s */ break; } switch (s->encoding) { case LIN16: if (s->swap) *r = Snack_SwapShort(*r); FSAMPLE(s, j) = (float) *r++; break; case LIN32: if (s->swap) *is = Snack_SwapLong(*is); FSAMPLE(s, j) = (float) *is++; break; case SNACK_FLOAT: if (s->swap) *fs = (float) Snack_SwapFloat(*fs); FSAMPLE(s, j) = (float) *fs++; break; case SNACK_DOUBLE: if (s->swap) *fd = (float) Snack_SwapDouble(*fd); FSAMPLE(s, j) = (float) *fd++; break; case ALAW: FSAMPLE(s, j) = (float) Snack_Alaw2Lin(*q++); break; case MULAW: FSAMPLE(s, j) = (float) Snack_Mulaw2Lin(*q++); break; case LIN8: FSAMPLE(s, j) = (float) *sc++; break; case LIN8OFFSET: FSAMPLE(s, j) = (float) *q++; break; case LIN24: case LIN24PACKED: { int ee; if (s->swap) { if (littleEndian) { ee = 0; } else { ee = 1; } } else { if (littleEndian) { ee = 1; } else { ee = 0; } } if (ee) { int t = *q++; t |= *q++ << 8; t |= *q++ << 16; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } FSAMPLE(s, j) = (float) t; } else { int t = *q++ << 16; t |= *q++ << 8; t |= *q++; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } FSAMPLE(s, j) = (float) t; } break; } } } } else { /*s->precision == SNACK_DOUBLE_PREC */ for (i = 0; i < size / s->sampsize; i++, j++) { int writeblock = (j >> DEXP); if (writeblock >= s->nblks) { /* Reached end of allocated blocks for s */ break; } switch (s->encoding) { case LIN16: DSAMPLE(s, j) = (float) *r++; break; case LIN32: DSAMPLE(s, j) = (float) *is++; break; case SNACK_FLOAT: DSAMPLE(s, j) = (float) *fs++; break; case ALAW: DSAMPLE(s, j) = (float) Snack_Alaw2Lin(*q++); break; case MULAW: DSAMPLE(s, j) = (float) Snack_Mulaw2Lin(*q++); break; case LIN8: DSAMPLE(s, j) = (float) *sc++; break; case LIN8OFFSET: DSAMPLE(s, j) = (float) *q++; break; case LIN24: case LIN24PACKED: { if (littleEndian) { int t = *q++; t |= *q++ << 8; t |= *q++ << 16; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } DSAMPLE(s, j) = (float) t; } else { int t = *q++ << 16; t |= *q++ << 8; t |= *q++; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } DSAMPLE(s, j) = (float) t; } break; } } } } /*s->precision == SNACK_DOUBLE_PREC */ } /* unpack block */ res = Snack_ProgressCallback(s->cmdPtr, interp, "Reading sound", (double) totrlen / (s->length * s->sampsize * s->nchannels)); if (res != TCL_OK) { Snack_ResizeSoundStorage(s, 0); s->length = 0; return TCL_ERROR; } } if ((double) totrlen / (s->length * s->sampsize * s->nchannels) != 1.0) { Snack_ProgressCallback(s->cmdPtr, interp, "Reading sound", 1.0); } if (s->length * s->sampsize * s->nchannels != totrlen) { s->length = totrlen / (s->sampsize * s->nchannels); } if (s->length == -1) { s->length = totrlen / (s->sampsize * s->nchannels); } if (s->loadOffset > 0) { if (s->precision == SNACK_SINGLE_PREC) { for (i = 0; i < s->loadOffset; i++) { FSAMPLE(s, i) = 0.0f; } } else { for (i = 0; i < s->loadOffset; i++) { DSAMPLE(s, i) = 0.0; } } s->length += s->loadOffset; s->loadOffset = 0; } if (s->encoding == SNACK_DOUBLE) { s->sampsize = 4; } if (s->debug > 1) Snack_WriteLogInt(" Exit ReadSound", s->length); return TCL_OK; } int WriteSound(writeSamplesProc *writeProc, Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int startpos, int len) { int i = 0, j; short sh; int is; float fs; unsigned char uc; char c; if (s->debug > 1) Snack_WriteLog(" Enter WriteSound\n"); if (s->inByteOrder == SNACK_NATIVE && s->swap) { if (littleEndian) { s->inByteOrder = SNACK_BIGENDIAN; } else { s->inByteOrder = SNACK_LITTLEENDIAN; } } startpos *= s->nchannels; len *= s->nchannels; if (ch != NULL) { Snack_ProgressCallback(s->cmdPtr, interp, "Writing sound", 0.0); if (writeProc == NULL) { for (i = startpos; i < startpos + len; i++) { if (s->storeType == SOUND_IN_MEMORY || s->readStatus == READ) { fs = FSAMPLE(s, i); } else { fs = GetSample(&s->linkInfo, i); } /* pack sample */ switch (s->encoding) { case LIN16: if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; sh = (short) fs; switch (s->inByteOrder) { case SNACK_NATIVE: if (Tcl_Write(ch, (char *) &sh, 2) == -1) return TCL_ERROR; break; case SNACK_BIGENDIAN: if (WriteBEShort(ch, sh) == -1) return TCL_ERROR; break; case SNACK_LITTLEENDIAN: if (WriteLEShort(ch, sh) == -1) return TCL_ERROR; break; } break; case LIN32: if (fs > 2147483647.0f) fs = 2147483647.0f; if (fs < -2147483648.0f) fs = -2147483648.0f; is = (int) fs; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { is = Snack_SwapLong(is); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { is = Snack_SwapLong(is); } break; } if (Tcl_Write(ch, (char *) &is, 4) == -1) return TCL_ERROR; break; case SNACK_FLOAT: if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { fs = Snack_SwapFloat(fs); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { fs = Snack_SwapFloat(fs); } break; } if (Tcl_Write(ch, (char *) &fs, 4) == -1) return TCL_ERROR; break; case ALAW: { if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; uc = Snack_Lin2Alaw((short) fs); if (Tcl_Write(ch, (char *)&uc, 1) == -1) return TCL_ERROR; break; } case MULAW: { if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; uc = Snack_Lin2Mulaw((short) fs); if (Tcl_Write(ch, (char *)&uc, 1) == -1) return TCL_ERROR; break; } case LIN8: { if (fs > 127.0f) fs = 127.0f; if (fs < -128.0f) fs = -128.0f; c = (char) fs; if (Tcl_Write(ch, (char *)&c, 1) == -1) return TCL_ERROR; break; } case LIN8OFFSET: { if (fs > 255.0f) fs = 255.0f; if (fs < 0.0f) fs = 0.0f; uc = (unsigned char) fs; if (Tcl_Write(ch, (char *)&uc, 1) == -1) return TCL_ERROR; break; } case LIN24: case LIN24PACKED: { int offset = 0; union { char c[sizeof(int)]; int i; } pack; if (fs > 8388607.0f) fs = 8388607.0f; if (fs < -8388608.0f) fs = -8388608.0f; is = (int) fs; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { is = Snack_SwapLong(is); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { is = Snack_SwapLong(is); } break; } if (littleEndian) { offset = 1; } else { offset = 1; } pack.i = (int) is; if (Tcl_Write(ch, (char *) &pack.c[offset], 3) == -1) { return TCL_ERROR; } } } if ((i % 100000) == 99999) { int res = Snack_ProgressCallback(s->cmdPtr, interp, "Writing sound", (double)(i-startpos)/len); if (res != TCL_OK) { return TCL_ERROR; } } } } else { /* writeProc != NULL */ int tot = len; while (tot > 0) { int size = min(tot, FBLKSIZE/2), res; (writeProc)(s, ch, obj, startpos, size); tot -= size; startpos += size; res = Snack_ProgressCallback(s->cmdPtr, interp, "Writing sound", 1.0-(double)tot/len); if (res != TCL_OK) { return TCL_ERROR; } } } Snack_ProgressCallback(s->cmdPtr, interp, "Writing sound", 1.0); } else { /* ch == NULL */ unsigned char *p = NULL; if (useOldObjAPI) { Tcl_SetObjLength(obj, s->headSize + len * s->sampsize); p = (unsigned char *) &obj->bytes[s->headSize]; } else { #ifdef TCL_81_API p = Tcl_SetByteArrayLength(obj, s->headSize +len * s->sampsize); p = &p[s->headSize]; #endif } for (i = startpos, j = 0; i < startpos + len; i++, j++) { short *sp = (short *) p; int *ip = (int *) p; float *fp = (float *) p; char *cp = (char *) p; if (s->storeType == SOUND_IN_MEMORY) { fs = FSAMPLE(s, i); } else { fs = GetSample(&s->linkInfo, i); } /* pack sample */ switch (s->encoding) { case LIN16: if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; sh = (short) fs; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { sh = Snack_SwapShort(sh); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { sh = Snack_SwapShort(sh); } break; } sp[j] = sh; break; case LIN32: if (fs > 2147483647.0f) fs = 2147483647.0f; if (fs < -2147483648.0f) fs = -2147483648.0f; is = (int) fs; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { is = Snack_SwapLong(is); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { is = Snack_SwapLong(is); } break; } ip[j] = is; break; case SNACK_FLOAT: if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { fs = Snack_SwapFloat(fs); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { fs = Snack_SwapFloat(fs); } break; } fp[j] = fs; break; case ALAW: { if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; p[j] = Snack_Lin2Alaw((short) fs); break; } case MULAW: { if (fs > 32767.0f) fs = 32767.0f; if (fs < -32768.0f) fs = -32768.0f; p[j] = Snack_Lin2Mulaw((short) fs); break; } case LIN8: { if (fs > 127.0f) fs = 127.0f; if (fs < -128.0f) fs = -128.0f; cp[j] = (char) fs; break; } case LIN8OFFSET: { if (fs > 255.0f) fs = 255.0f; if (fs < 0.0f) fs = 0.0f; p[j] = (unsigned char) fs; break; } case LIN24: case LIN24PACKED: { int offset = 0; union { char c[sizeof(int)]; int i; } pack; if (fs > 8388607.0f) fs = 8388607.0f; if (fs < -8388608.0f) fs = -8388608.0f; is = (int) fs; switch (s->inByteOrder) { case SNACK_NATIVE: break; case SNACK_BIGENDIAN: if (littleEndian) { is = Snack_SwapLong(is); } break; case SNACK_LITTLEENDIAN: if (!littleEndian) { is = Snack_SwapLong(is); } break; } if (littleEndian) { offset = 0; } else { offset = 1; } pack.i = (int) is; memcpy(&p, &pack.c[offset],3); p += 3; } } } } if (s->debug > 1) Snack_WriteLog(" Exit WriteSound\n"); return TCL_OK; } #define NFIRSTSAMPLES 40000 #define DEFAULT_MULAW_RATE 8000 #define DEFAULT_ALAW_RATE 8000 #define DEFAULT_LIN8OFFSET_RATE 11025 #define DEFAULT_LIN8_RATE 11025 typedef enum { GUESS_LIN16, GUESS_LIN16S, GUESS_ALAW, GUESS_MULAW, GUESS_LIN8OFFSET, GUESS_LIN8, GUESS_LIN24, GUESS_LIN24S } sampleEncoding; #define GUESS_FFT_LENGTH 512 #define SNACK_DEFAULT_GFWINTYPE SNACK_WIN_HAMMING int GuessEncoding(Sound *s, unsigned char *buf, int len) { int i, j, format; float energyLIN16 = 0.0, energyLIN16S = 0.0; float energyMULAW = 0.0, energyALAW = 0.0; float energyLIN8 = 0.0, energyLIN8O = 0.0, minEnergy; float energyLIN24 = 0.0, energyLIN24S = 0.0; float fft[GUESS_FFT_LENGTH]; float totfft[GUESS_FFT_LENGTH]; float hamwin[GUESS_FFT_LENGTH]; double toterg = 0.0, cmperg = 0.0, minBin = 0.0; if (s->debug > 2) Snack_WriteLogInt(" Enter GuessEncoding", len); /* Byte order and sample encoding detection suggested by David van Leeuwen */ for (i = 0; i < len / 2; i++) { short sampleLIN16 = ((short *)buf)[i]; short sampleLIN16S = Snack_SwapShort(sampleLIN16); short sampleMULAW = Snack_Mulaw2Lin(buf[i]); short sampleALAW = Snack_Alaw2Lin(buf[i]); short sampleLIN8O = (char)(buf[i] ^ 128) << 8; short sampleLIN8 = (char)buf[i] << 8; energyLIN16 += (float) sampleLIN16 * (float) sampleLIN16; energyLIN16S += (float) sampleLIN16S * (float) sampleLIN16S; energyMULAW += (float) sampleMULAW * (float) sampleMULAW; energyALAW += (float) sampleALAW * (float) sampleALAW; energyLIN8O += (float) sampleLIN8O * (float) sampleLIN8O; energyLIN8 += (float) sampleLIN8 * (float) sampleLIN8; } for (i = 0; i < len / 2; i+=3) { union { char c[sizeof(int)]; int s; } sampleLIN24, sampleLIN24S; sampleLIN24.c[0] = (char)buf[i]; sampleLIN24.c[1] = (char)buf[i+1]; sampleLIN24.c[2] = (char)buf[i+2]; sampleLIN24S.c[2] = (char)buf[i]; sampleLIN24S.c[1] = (char)buf[i+1]; sampleLIN24S.c[0] = (char)buf[i+2]; sampleLIN24.s /= 65536; sampleLIN24S.s /= 65536; energyLIN24 += (float) sampleLIN24.s * (float) sampleLIN24.s; energyLIN24S += (float) sampleLIN24S.s * (float) sampleLIN24S.s; } format = GUESS_LIN16; minEnergy = energyLIN16; if (energyLIN16S < minEnergy) { format = GUESS_LIN16S; minEnergy = energyLIN16S; } if (energyALAW < minEnergy) { format = GUESS_ALAW; minEnergy = energyALAW; } if (energyMULAW < minEnergy) { format = GUESS_MULAW; minEnergy = energyMULAW; } if (energyLIN8O < minEnergy) { format = GUESS_LIN8OFFSET; minEnergy = energyLIN8O; } if (energyLIN8 < minEnergy) { format = GUESS_LIN8; minEnergy = energyLIN8; } /*if (energyLIN24 < minEnergy) { format = GUESS_LIN24; minEnergy = energyLIN24; } if (energyLIN24S < minEnergy) { format = GUESS_LIN24S; minEnergy = energyLIN24S; } printf("AA %f %f %f %f\n", energyLIN16, energyLIN16S, energyLIN24, energyLIN24S);*/ switch (format) { case GUESS_LIN16: s->swap = 0; if (s->sampsize == 1) { s->length /= 2; } s->encoding = LIN16; s->sampsize = 2; break; case GUESS_LIN16S: s->swap = 1; if (s->sampsize == 1) { s->length /= 2; } s->encoding = LIN16; s->sampsize = 2; break; case GUESS_ALAW: if (s->sampsize == 2) { s->length *= 2; } s->encoding = ALAW; s->sampsize = 1; if (s->guessRate) { s->samprate = DEFAULT_ALAW_RATE; } break; case GUESS_MULAW: if (s->sampsize == 2) { s->length *= 2; } s->encoding = MULAW; s->sampsize = 1; if (s->guessRate) { s->samprate = DEFAULT_MULAW_RATE; } break; case GUESS_LIN8OFFSET: if (s->sampsize == 2) { s->length *= 2; } s->encoding = LIN8OFFSET; s->sampsize = 1; if (s->guessRate) { s->samprate = DEFAULT_LIN8OFFSET_RATE; } break; case GUESS_LIN8: if (s->sampsize == 2) { s->length *= 2; } s->encoding = LIN8; s->sampsize = 1; if (s->guessRate) { s->samprate = DEFAULT_LIN8_RATE; } break; case GUESS_LIN24: s->swap = 0; s->encoding = LIN24; s->sampsize = 4; break; case GUESS_LIN24S: s->swap = 1; s->encoding = LIN24; s->sampsize = 4; break; } if (s->guessRate && s->encoding == LIN16) { for (i = 0; i < GUESS_FFT_LENGTH; i++) { totfft[i] = 0.0; } Snack_InitFFT(GUESS_FFT_LENGTH); Snack_InitWindow(hamwin, GUESS_FFT_LENGTH, GUESS_FFT_LENGTH / 2, SNACK_DEFAULT_GFWINTYPE); for (i = 0; i < (len / s->sampsize) / (GUESS_FFT_LENGTH + 1); i++) { for (j = 0; j < GUESS_FFT_LENGTH; j++) { short sample = ((short *)buf)[j + i * (GUESS_FFT_LENGTH / 2)]; if (s->swap) { sample = Snack_SwapShort(sample); } fft[j] = (float) sample * hamwin[j]; } Snack_DBPowerSpectrum(fft); for (j = 0; j < GUESS_FFT_LENGTH / 2; j++) { totfft[j] += fft[j]; } } for (i = 0; i < GUESS_FFT_LENGTH / 2; i++) { if (totfft[i] < minBin) minBin = totfft[i]; } for (i = 0; i < GUESS_FFT_LENGTH / 2; i++) { toterg += (totfft[i] - minBin); } for (i = 0; i < GUESS_FFT_LENGTH / 2; i++) { cmperg += (totfft[i] - minBin); if (cmperg > toterg / 2.0) break; } if (i > 100) { /* Silence, don't guess */ } else if (i > 64) { s->samprate = 8000; } else if (i > 46) { s->samprate = 11025; } else if (i > 32) { s->samprate = 16000; } else if (i > 23) { s->samprate = 22050; } else if (i > 16) { s->samprate = 32000; } else if (i > 11) { s->samprate = 44100; } } if (s->debug > 2) Snack_WriteLogInt(" Exit GuessEncoding", s->encoding); return TCL_OK; } static int GetRawHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { if (s->debug > 2) Snack_WriteLog(" Reading RAW header\n"); if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); s->length = (TCL_TELL(ch) - s->skipBytes) / (s->sampsize * s->nchannels); } if (obj != NULL) { if (useOldObjAPI) { s->length = (obj->length - s->skipBytes) / (s->sampsize * s->nchannels); } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); s->length = (length - s->skipBytes) / (s->sampsize * s->nchannels); #endif } } s->headSize = s->skipBytes; return TCL_OK; } static int PutRawHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { s->headSize = 0; return TCL_OK; } #define NIST_HEADERSIZE 1024 static int GetSmpHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { char s1[100], s2[100]; int i = 0, cont = 1; if (s->debug > 2) Snack_WriteLog(" Reading SMP header\n"); if (s->firstNRead < NIST_HEADERSIZE) { if (Tcl_Read(ch, (char *)&buf[s->firstNRead], NIST_HEADERSIZE-s->firstNRead) < 0) { return TCL_ERROR; } } do { sscanf(&buf[i], "%s", s1); if (strncmp(s1, "sftot", 5) == 0) { sscanf(&buf[i+6], "%d", &s->samprate); if (s->debug > 3) { Snack_WriteLogInt(" Setting rate", s->samprate); } } else if (strncmp(s1, "msb", 3) == 0) { sscanf(&buf[i+4], "%s", s2); if (s->debug > 3) { Snack_WriteLog(" "); Snack_WriteLog(s2); Snack_WriteLog(" byte order\n"); } } else if (strncmp(s1, "nchans", 6) == 0) { sscanf(&buf[i+7], "%d", &s->nchannels); if (s->debug > 3) { Snack_WriteLogInt(" Setting number of channels", s->nchannels); } } else if (buf[i] == 0) { cont = 0; } while (buf[i] != 10 && buf[i] != 0) i++; i++; } while (cont); s->encoding = LIN16; s->sampsize = 2; s->swap = 0; if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); s->length = (TCL_TELL(ch) - NIST_HEADERSIZE) / (s->sampsize * s->nchannels); } if (obj != NULL) { if (useOldObjAPI) { s->length = (obj->length - NIST_HEADERSIZE) / (s->sampsize * s->nchannels); } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); s->length = (length - NIST_HEADERSIZE) / (s->sampsize * s->nchannels); #endif } } s->headSize = NIST_HEADERSIZE; if (strcmp(s2, "first") == 0) { if (littleEndian) { SwapIfLE(s); } } else { if (!littleEndian) { SwapIfBE(s); } } return TCL_OK; } static int PutSmpHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { int i = 0; char buf[HEADBUF]; if (s->encoding != LIN16) { Tcl_AppendResult(interp, "Unsupported encoding format", NULL); return -1; } i += (int) sprintf(&buf[i], "file=samp\r\n"); i += (int) sprintf(&buf[i], "sftot=%d\r\n", s->samprate); if (littleEndian) { i += (int) sprintf(&buf[i], "msb=last\r\n"); } else { i += (int) sprintf(&buf[i], "msb=first\r\n"); } i += (int) sprintf(&buf[i], "nchans=%d\r\n", s->nchannels); i += (int) sprintf(&buf[i],"preemph=none\r\nborn=snack\r\n=\r\n%c%c%c", 0,4,26); for (;i < NIST_HEADERSIZE; i++) buf[i] = 0; if (ch != NULL) { if (Tcl_Write(ch, buf, NIST_HEADERSIZE) == -1) { Tcl_AppendResult(interp, "Error while writing header", NULL); return -1; } } else { if (useOldObjAPI) { Tcl_SetObjLength(obj, NIST_HEADERSIZE); memcpy(obj->bytes, buf, NIST_HEADERSIZE); } else { #ifdef TCL_81_API unsigned char *p = Tcl_SetByteArrayLength(obj, NIST_HEADERSIZE); memcpy(p, buf, NIST_HEADERSIZE); #endif } } s->inByteOrder = SNACK_NATIVE; s->swap = 0; s->headSize = NIST_HEADERSIZE; return TCL_OK; } #define SNACK_SD_INT 20 static int GetSdHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { int datastart, len, i, j; double freq = 16000.0; double start = 0.0; int first = 1; if (s->debug > 2) Snack_WriteLog(" Reading SD header\n"); datastart = GetBELong(buf, 8); s->nchannels = GetBELong(buf, 144); for (i = 0; i < s->firstNRead; i++) { if (strncasecmp("record_freq", &buf[i], strlen("record_freq")) == 0) { i = i + 18; if (littleEndian) { for (j = 0; j < 4; j++) { char c = buf[i+j]; buf[i+j] = buf[i+7-j]; buf[i+7-j] = c; } } memcpy(&freq, &buf[i], 8); } if (strncasecmp("start_time", &buf[i], strlen("start_time")) == 0 && first) { first = 0; i = i + 18; if (littleEndian) { for (j = 0; j < 4; j++) { char c = buf[i+j]; buf[i+j] = buf[i+7-j]; buf[i+7-j] = c; } } memcpy(&start, &buf[i], 8); if (s->extHead != NULL && s->extHeadType != SNACK_SD_INT) { Snack_FileFormat *ff; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { if (ff->freeHeaderProc != NULL) { (ff->freeHeaderProc)(s); } } } } if (s->extHead == NULL) { s->extHead = (char *) ckalloc(sizeof(double)); memcpy(s->extHead, &buf[i], sizeof(double)); s->extHeadType = SNACK_SD_INT; } } } s->encoding = LIN16; s->sampsize = 2; s->samprate = (int) freq; s->loadOffset = 0; /*(int) (start * s->samprate + 0.5);*/ if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); len = TCL_TELL(ch); if (len == 0 || len < datastart) { Tcl_AppendResult(interp, "Failed reading SD header", NULL); return TCL_ERROR; } s->length = (len - datastart) / s->sampsize + s->loadOffset; } if (obj != NULL) { if (useOldObjAPI) { s->length = obj->length / s->sampsize + s->loadOffset; } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); s->length = length / s->sampsize + s->loadOffset; #endif } } s->length /= s->nchannels; s->headSize = datastart; SwapIfLE(s); return TCL_OK; } static int ConfigSdHeader(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { int index; static CONST84 char *optionStrings[] = { "-start_time", NULL }; enum options { STARTTIME }; if (s->extHeadType != SNACK_SD_INT || objc < 3) return 0; if (objc == 3) { /* get option */ if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0, &index) != TCL_OK) { Tcl_AppendResult(interp, ", or\n", NULL); return 0; } switch ((enum options) index) { case STARTTIME: { double *start = (double *) s->extHead; Tcl_SetObjResult(interp, Tcl_NewDoubleObj(*start)); break; } } } return 1; } static void FreeSdHeader(Sound *s) { if (s->debug > 2) Snack_WriteLog(" Enter FreeSdHeader\n"); if (s->extHead != NULL) { ckfree((char *)s->extHead); s->extHead = NULL; s->extHeadType = 0; } if (s->debug > 2) Snack_WriteLog(" Exit FreeSdHeader\n"); } #define SND_FORMAT_MULAW_8 1 #define SND_FORMAT_LINEAR_8 2 #define SND_FORMAT_LINEAR_16 3 #define SND_FORMAT_LINEAR_24 4 #define SND_FORMAT_LINEAR_32 5 #define SND_FORMAT_FLOAT 6 #define SND_FORMAT_DOUBLE 7 #define SND_FORMAT_ALAW_8 27 #define AU_HEADERSIZE 28 static int GetAuHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { int fmt, hlen, nsamp, nsampfile; if (s->debug > 2) Snack_WriteLog(" Reading AU/SND header\n"); if (s->firstNRead < AU_HEADERSIZE) { if (Tcl_Read(ch, (char *)&buf[s->firstNRead], AU_HEADERSIZE-s->firstNRead) < 0) { return TCL_ERROR; } } hlen = GetBELong(buf, 4); fmt = GetBELong(buf, 12); switch (fmt) { case SND_FORMAT_MULAW_8: s->encoding = MULAW; s->sampsize = 1; break; case SND_FORMAT_LINEAR_8: s->encoding = LIN8; s->sampsize = 1; break; case SND_FORMAT_LINEAR_16: s->encoding = LIN16; s->sampsize = 2; break; case SND_FORMAT_LINEAR_24: s->encoding = LIN24; s->sampsize = 3; break; case SND_FORMAT_LINEAR_32: s->encoding = LIN32; s->sampsize = 4; break; case SND_FORMAT_FLOAT: s->encoding = SNACK_FLOAT; s->sampsize = 4; break; case SND_FORMAT_DOUBLE: s->encoding = SNACK_DOUBLE; s->sampsize = 4; break; case SND_FORMAT_ALAW_8: s->encoding = ALAW; s->sampsize = 1; break; default: Tcl_AppendResult(interp, "Unsupported AU format", NULL); return TCL_ERROR; } s->samprate = GetBELong(buf, 16); s->nchannels = GetBELong(buf, 20); if (hlen < 24) { hlen = 24; } s->headSize = hlen; nsamp = GetBELong(buf, 8) / (s->sampsize * s->nchannels); if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); nsampfile = (TCL_TELL(ch) - hlen) / (s->sampsize * s->nchannels); if (nsampfile < nsamp || nsamp <= 0) { nsamp = nsampfile; } } if (obj != NULL) { if (useOldObjAPI) { nsamp = (obj->length - hlen) / (s->sampsize * s->nchannels); } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); nsamp = (length - hlen) / (s->sampsize * s->nchannels); #endif } } if (s->encoding != SNACK_DOUBLE) { s->length = nsamp; } else { s->length = nsamp/2; } SwapIfLE(s); return TCL_OK; } static int PutAuHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { int fmt = 0; char buf[HEADBUF]; if (s->debug > 2) Snack_WriteLog(" Saving AU/SND\n"); PutBELong(buf, 0, 0x2E736E64); PutBELong(buf, 4, AU_HEADERSIZE); PutBELong(buf, 8, len * s->sampsize * s->nchannels); switch (s->encoding) { case MULAW: fmt = SND_FORMAT_MULAW_8; break; case LIN8: fmt = SND_FORMAT_LINEAR_8; break; case LIN16: fmt = SND_FORMAT_LINEAR_16; break; case LIN24: fmt = SND_FORMAT_LINEAR_24; break; case LIN32: fmt = SND_FORMAT_LINEAR_32; break; case SNACK_FLOAT: case SNACK_DOUBLE: fmt = SND_FORMAT_FLOAT; break; case ALAW: fmt = SND_FORMAT_ALAW_8; break; default: Tcl_AppendResult(interp, "Unsupported AU format", NULL); return -1; } PutBELong(buf, 12, fmt); PutBELong(buf, 16, s->samprate); PutBELong(buf, 20, s->nchannels); PutBELong(buf, 24, 0); if (ch != NULL) { if (Tcl_Write(ch, buf, AU_HEADERSIZE) == -1) { Tcl_AppendResult(interp, "Error while writing header", NULL); return -1; } } else { if (useOldObjAPI) { Tcl_SetObjLength(obj, AU_HEADERSIZE); memcpy(obj->bytes, buf, AU_HEADERSIZE); } else { #ifdef TCL_81_API unsigned char *p = Tcl_SetByteArrayLength(obj, AU_HEADERSIZE); memcpy(p, buf, AU_HEADERSIZE); #endif } } if (len == -1) { SwapIfLE(s); } s->inByteOrder = SNACK_BIGENDIAN; s->headSize = AU_HEADERSIZE; return TCL_OK; } #define WAVE_FORMAT_PCM 1 #ifndef WIN # define WAVE_FORMAT_IEEE_FLOAT 3 # define WAVE_FORMAT_ALAW 6 # define WAVE_FORMAT_MULAW 7 #endif #define WAVE_EX (-2) /* (OxFFFE) in a 2-byte word */ static int GetHeaderBytes(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, char *buf, int len) { int rlen = Tcl_Read(ch, &buf[s->firstNRead], len - s->firstNRead); if (rlen < len - s->firstNRead){ Tcl_AppendResult(interp, "Failed reading header bytes", NULL); return TCL_ERROR; } s->firstNRead += rlen; return TCL_OK; } static int GetWavHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { int fmt, nsamp = 0, nsampfile, i = 12, chunkLen; if (s->debug > 2) Snack_WriteLog(" Reading WAV header\n"); /* buf[] = "RIFFxxxxWAVE" */ while (1) { if (strncasecmp("fmt ", &buf[i], strlen("fmt ")) == 0) { chunkLen = GetLELong(buf, i + 4) + 8; if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } fmt = GetLEShort(buf, i+8); s->nchannels = GetLEShort(buf, i+10); s->samprate = GetLELong(buf, i+12); s->sampsize = GetLEShort(buf, i+22) / 8; /* For WAVE-EX, the format is the first two bytes of the GUID */ if (fmt == WAVE_EX) fmt = GetLEShort(buf, i+32); switch (fmt) { case WAVE_FORMAT_PCM: if (s->sampsize == 1) { s->encoding = LIN8OFFSET; } else if (s->sampsize == 2) { s->encoding = LIN16; } else if (s->sampsize == 3) { s->encoding = LIN24; } else if (s->sampsize == 4) { s->encoding = LIN32; } break; case WAVE_FORMAT_IEEE_FLOAT: if (s->sampsize == 4) { s->encoding = SNACK_FLOAT; } else { s->encoding = SNACK_DOUBLE; } s->sampsize = 4; break; case WAVE_FORMAT_ALAW: s->encoding = ALAW; break; case WAVE_FORMAT_MULAW: s->encoding = MULAW; break; default: Tcl_AppendResult(interp, "Unsupported WAV format", NULL); return TCL_ERROR; } if (s->debug > 3) { Snack_WriteLogInt(" fmt chunk parsed", chunkLen); } } else if (strncasecmp("data", &buf[i], strlen("data")) == 0) { nsamp = GetLELong(buf, i + 4) / (s->sampsize * s->nchannels); if (s->debug > 3) { Snack_WriteLogInt(" data chunk parsed", nsamp); } break; } else { /* unknown chunk */ chunkLen = GetLELong(buf, i + 4) + 8; if (chunkLen < 0) { Tcl_AppendResult(interp, "Failed parsing WAV header", NULL); return TCL_ERROR; } while (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } if (s->debug > 3) { Snack_WriteLogInt(" Skipping unknown chunk", chunkLen); } } i += chunkLen; if (s->firstNRead < i + 8) { if (GetHeaderBytes(s, interp, ch, buf, i + 8) != TCL_OK) { return TCL_ERROR; } } if (i >= HEADBUF) { Tcl_AppendResult(interp, "Failed parsing WAV header", NULL); return TCL_ERROR; } } s->headSize = i + 8; if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); nsampfile = (TCL_TELL(ch) - s->headSize) / (s->sampsize * s->nchannels); if (nsampfile < nsamp || nsamp == 0) { nsamp = nsampfile; } } if (obj != NULL) { if (useOldObjAPI) { nsampfile = (obj->length - s->headSize) / (s->sampsize * s->nchannels); } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); nsampfile = (length - s->headSize) / (s->sampsize * s->nchannels); #endif } if (nsampfile < nsamp || nsamp == 0) { nsamp = nsampfile; } } if (s->encoding != SNACK_DOUBLE) { s->length = nsamp; } else { s->length = nsamp/2; } if (s->sampsize == 4 && s->encoding == LIN32) { double energyLIN32 = 0.0, energyFLOAT = 0.0; for (i = s->headSize; i < s->firstNRead / 4; i++) { int sampleLIN32 = ((int *)buf)[i]; float sampleFLOAT = ((float *)buf)[i]; if (!littleEndian) { sampleLIN32 = Snack_SwapLong(sampleLIN32); sampleFLOAT = Snack_SwapFloat(sampleFLOAT); } energyLIN32 += (double) (sampleLIN32 * sampleLIN32); energyFLOAT += (double) (sampleFLOAT * sampleFLOAT); } if (fabs(energyLIN32) > fabs(energyFLOAT)) { s->encoding = SNACK_FLOAT; } } SwapIfBE(s); return TCL_OK; } #define SNACK_WAV_HEADERSIZE 44 static int PutWavHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { char buf[HEADBUF]; sprintf(&buf[0], "RIFF"); if (len != -1) { PutLELong(buf, 4, len * s->sampsize * s->nchannels + 36); } else { SwapIfBE(s); PutLELong(buf, 4, 0x7FFFFFFF); } sprintf(&buf[8], "WAVEfmt "); PutLELong(buf, 16, 16); switch (s->encoding) { case ALAW: PutLEShort(buf, 20, WAVE_FORMAT_ALAW); break; case MULAW: PutLEShort(buf, 20, WAVE_FORMAT_MULAW); break; case SNACK_FLOAT: case SNACK_DOUBLE: PutLEShort(buf, 20, WAVE_FORMAT_IEEE_FLOAT); break; default: PutLEShort(buf, 20, WAVE_FORMAT_PCM); } PutLEShort(buf, 22, (short)s->nchannels); PutLELong(buf, 24, s->samprate); PutLELong(buf, 28, (s->samprate * s->nchannels * s->sampsize * 8 + 7) / 8); PutLEShort(buf, 32, (short)((s->nchannels * s->sampsize * 8 + 7) / 8)); PutLEShort(buf, 34, (short) (s->sampsize * 8)); sprintf(&buf[36], "data"); if (len != -1) { PutLELong(buf, 40, len * s->sampsize * s->nchannels); } else { PutLELong(buf, 40, 0x7FFFFFDB); } if (ch != NULL) { if (Tcl_Write(ch, buf, SNACK_WAV_HEADERSIZE) == -1) { Tcl_AppendResult(interp, "Error while writing header", NULL); return -1; } } else { if (useOldObjAPI) { Tcl_SetObjLength(obj, SNACK_WAV_HEADERSIZE); memcpy(obj->bytes, buf, SNACK_WAV_HEADERSIZE); } else { #ifdef TCL_81_API unsigned char *p = Tcl_SetByteArrayLength(obj, SNACK_WAV_HEADERSIZE); memcpy(p, buf, SNACK_WAV_HEADERSIZE); #endif } } s->inByteOrder = SNACK_LITTLEENDIAN; s->headSize = SNACK_WAV_HEADERSIZE; return TCL_OK; } /* See http://www.borg.com/~jglatt/tech/aiff.htm */ static uint32_t ConvertFloat(unsigned char *buffer) { uint32_t mantissa; uint32_t last = 0; unsigned char exp; memcpy(&mantissa, buffer + 2, sizeof(int32_t)); if (littleEndian) { mantissa = Snack_SwapLong(mantissa); } exp = 30 - *(buffer+1); while (exp--) { last = mantissa; mantissa >>= 1; } if (last & 0x00000001) mantissa++; return(mantissa); } static void StoreFloat(unsigned char * buffer, uint32_t value) { uint32_t exp; unsigned char i; memset(buffer, 0, 10); exp = value; exp >>= 1; for (i=0; i<32; i++) { exp >>= 1; if (!exp) break; } *(buffer+1) = i; for (i=32; i; i--) { if (value & 0x80000000) break; value <<= 1; } if (littleEndian) { value = Snack_SwapLong(value); } buffer[0] = 0x40; memcpy(buffer + 2, &value, sizeof(int32_t)); } #define ICEILV(n,m) (((n) + ((m) - 1)) / (m)) /* int n,m >= 0 */ #define RNDUPV(n,m) ((m) * ICEILV (n, m)) /* Round up */ static int GetAiffHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { int bits = 0, offset = 0, i = 12, chunkLen = 4; if (s->debug > 2) Snack_WriteLog(" Reading AIFF header\n"); /* buf[] = "FORMxxxxAIFF" */ while (1) { if (strncasecmp("COMM", &buf[i], strlen("COMM")) == 0) { chunkLen = GetBELong(buf, i + 4) + 8; if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } s->nchannels = GetBEShort(buf, i + 8); bits = GetBEShort(buf, i + 14); bits = RNDUPV (bits, 8); switch (bits) { case 8: s->encoding = LIN8; s->sampsize = 1; break; case 16: s->encoding = LIN16; s->sampsize = 2; break; case 24: s->encoding = LIN24; s->sampsize = 3; break; case 32: s->encoding = LIN32; s->sampsize = 4; break; default: Tcl_AppendResult(interp, "Unsupported AIFF format", NULL); return TCL_ERROR; } s->samprate = ConvertFloat((unsigned char *)&buf[i+16]); if (s->debug > 3) { Snack_WriteLogInt(" COMM chunk parsed", chunkLen); } } else if (strncasecmp("SSND", &buf[i], strlen("SSND")) == 0) { chunkLen = 16; if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + 8) != TCL_OK) { return TCL_ERROR; } } s->length = (GetBELong(buf, i + 4) - 8) / (s->sampsize * s->nchannels); offset = GetBELong(buf, i + 8); i += chunkLen; if (s->debug > 3) { Snack_WriteLogInt(" SSND chunk parsed", chunkLen); } break; } else { if (i > HEADBUF - 4) { Tcl_AppendResult(interp, "Missing chunk in AIFF header", NULL); return TCL_ERROR; } else { if (s->debug > 3) { char chunkStr[5]; strncpy(chunkStr, &buf[i], 4); chunkStr[4] = '\0'; Snack_WriteLog(chunkStr); Snack_WriteLog(" chunk skipped\n"); } chunkLen = GetBELong(buf, i + 4) + 8; } } i += chunkLen; if (s->firstNRead < i + 8) { if (GetHeaderBytes(s, interp, ch, buf, i + 8) != TCL_OK) { return TCL_ERROR; } } } s->headSize = i + offset; SwapIfLE(s); return TCL_OK; } #define SNACK_AIFF_HEADERSIZE 54 int PutAiffHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { char buf[HEADBUF]; if (s->encoding == LIN8OFFSET || s->encoding == ALAW || s->encoding == MULAW || s->encoding == SNACK_FLOAT) { Tcl_AppendResult(interp, "Unsupported encoding format", NULL); return -1; } sprintf(&buf[0], "FORM"); if (len != -1) { PutBELong(buf, 4, len * s->sampsize * s->nchannels + 46); } else { SwapIfLE(s); PutBELong(buf, 4, 0x7FFFFFFF); } sprintf(&buf[8], "AIFFCOMM"); PutBELong(buf, 16, 18); PutBEShort(buf, 20, (short) s->nchannels); PutBELong(buf, 22, s->length); PutBEShort(buf, 26, (short) (s->sampsize * 8)); StoreFloat((unsigned char *) &buf[28], (int32_t) s->samprate); sprintf(&buf[38], "SSND"); if (len != -1) { PutBELong(buf, 42, 8 + s->length * s->sampsize * s->nchannels); } else { PutBELong(buf, 42, 8 + 0x7FFFFFD1); } PutBELong(buf, 46, 0); PutBELong(buf, 50, 0); if (ch != NULL) { if (Tcl_Write(ch, buf, SNACK_AIFF_HEADERSIZE) == -1) { Tcl_AppendResult(interp, "Error while writing header", NULL); return -1; } } else { if (useOldObjAPI) { Tcl_SetObjLength(obj, SNACK_AIFF_HEADERSIZE); memcpy(obj->bytes, buf, SNACK_AIFF_HEADERSIZE); } else { #ifdef TCL_81_API unsigned char *p = Tcl_SetByteArrayLength(obj, SNACK_AIFF_HEADERSIZE); memcpy(p, buf, SNACK_AIFF_HEADERSIZE); #endif } } s->inByteOrder = SNACK_BIGENDIAN; s->headSize = SNACK_AIFF_HEADERSIZE; return TCL_OK; } static int GetCslHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, char *buf) { int tmp1, tmp2, nsamp = 0, nsampfile, i = 12, chunkLen; if (s->debug > 2) Snack_WriteLog(" Reading CSL header\n"); /* buf[] = "FORMDS16xxxxHEDR" */ while (1) { if (strncasecmp("HEDR", &buf[i], strlen("HEDR")) == 0) { chunkLen = GetLELong(buf, i + 4) + 8; if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } s->encoding = LIN16; s->sampsize = 2; s->nchannels = 1; s->samprate = GetLELong(buf, i+28); tmp1 = GetLEShort(buf, i+36); tmp2 = GetLEShort(buf, i+38); if (tmp1 != -1 && tmp2 != -1) { s->nchannels = 2; } if (s->debug > 3) { Snack_WriteLogInt(" HEDR block parsed", chunkLen); } } else if (strncasecmp("HDR8", &buf[i], strlen("HDR8")) == 0) { chunkLen = GetLELong(buf, i + 4) + 8; if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } s->encoding = LIN16; s->sampsize = 2; s->nchannels = 1; s->samprate = GetLELong(buf, i+28); tmp1 = GetLEShort(buf, i+36); tmp2 = GetLEShort(buf, i+38); if (tmp1 != -1 && tmp2 != -1) { s->nchannels = 2; } if (s->debug > 3) { Snack_WriteLogInt(" HDR8 block parsed", chunkLen); } } else if (strncasecmp("SDA_", &buf[i], strlen("SDA_")) == 0) { s->nchannels = 1; nsamp = GetLELong(buf, i + 4) / (s->sampsize * s->nchannels); if (s->debug > 3) { Snack_WriteLogInt(" SDA_ block parsed", nsamp); } break; } else if (strncasecmp("SD_B", &buf[i], strlen("SD_B")) == 0) { s->nchannels = 1; nsamp = GetLELong(buf, i + 4) / (s->sampsize * s->nchannels); if (s->debug > 3) { Snack_WriteLogInt(" SD_B block parsed", nsamp); } break; } else if (strncasecmp("SDAB", &buf[i], strlen("SDAB")) == 0) { nsamp = GetLELong(buf, i + 4) / (s->sampsize * s->nchannels); if (s->debug > 3) { Snack_WriteLogInt(" SDAB block parsed", nsamp); } break; } else { /* unknown block */ chunkLen = GetLELong(buf, i + 4) + 8; if (chunkLen & 1) chunkLen++; if (chunkLen < 0 || chunkLen > HEADBUF) { Tcl_AppendResult(interp, "Failed parsing CSL header", NULL); return TCL_ERROR; } if (s->firstNRead < i + chunkLen) { if (GetHeaderBytes(s, interp, ch, buf, i + chunkLen) != TCL_OK) { return TCL_ERROR; } } if (s->debug > 3) { Snack_WriteLogInt(" Skipping unknown block", chunkLen); } } i += chunkLen; if (s->firstNRead < i + 8) { if (GetHeaderBytes(s, interp, ch, buf, i + 8) != TCL_OK) { return TCL_ERROR; } } if (i >= HEADBUF) { Tcl_AppendResult(interp, "Failed parsing CSL header", NULL); return TCL_ERROR; } } s->headSize = i + 8; if (ch != NULL) { TCL_SEEK(ch, 0, SEEK_END); nsampfile = (TCL_TELL(ch) - s->headSize) / (s->sampsize * s->nchannels); if (nsampfile < nsamp || nsamp == 0) { nsamp = nsampfile; } } if (obj != NULL) { if (useOldObjAPI) { nsampfile = (obj->length - s->headSize) / (s->sampsize * s->nchannels); } else { #ifdef TCL_81_API int length = 0; Tcl_GetByteArrayFromObj(obj, &length); nsampfile = (length - s->headSize) / (s->sampsize * s->nchannels); #endif } if (nsampfile < nsamp || nsamp == 0) { nsamp = nsampfile; } } s->length = nsamp; SwapIfBE(s); return TCL_OK; } #define SNACK_CSL_HEADERSIZE 88 #define CSL_DATECOMMAND "clock format [clock seconds] -format {%b %d %T %Y}" static int PutCslHeader(Sound *s, Tcl_Interp *interp, Tcl_Channel ch, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int len) { char buf[HEADBUF]; if (s->encoding != LIN16) { Tcl_AppendResult(interp, "Unsupported encoding format", NULL); return -1; } sprintf(&buf[0], "FORMDS16"); if (len != -1) { PutLELong(buf, 8, len * s->sampsize * s->nchannels + 76); } else { SwapIfBE(s); PutLELong(buf, 8, 0); } sprintf(&buf[12], "HEDR"); PutLELong(buf, 16, 32); Tcl_GlobalEvalObj(s->interp, Tcl_NewStringObj(CSL_DATECOMMAND, -1)); sprintf(&buf[20], Tcl_GetStringResult(s->interp)); PutLELong(buf, 40, s->samprate); PutLELong(buf, 44, s->length); PutLEShort(buf, 48, (short) s->abmax); if (s->nchannels == 1) { PutLEShort(buf, 50, (short) -1); } else { PutLEShort(buf, 50, (short) s->abmax); } sprintf(&buf[52], "NOTE"); PutLELong(buf, 56, 19); sprintf(&buf[60], "Created by Snack "); if (s->nchannels == 1) { sprintf(&buf[80], "SDA_"); } else { sprintf(&buf[80], "SDAB"); } if (len != -1) { PutLELong(buf, 84, len * s->sampsize * s->nchannels); } else { PutLELong(buf, 84, 0); } if (ch != NULL) { if (Tcl_Write(ch, buf, SNACK_CSL_HEADERSIZE) == -1) { Tcl_AppendResult(interp, "Error while writing header", NULL); return -1; } } else { if (useOldObjAPI) { Tcl_SetObjLength(obj, SNACK_CSL_HEADERSIZE); memcpy(obj->bytes, buf, SNACK_CSL_HEADERSIZE); } else { #ifdef TCL_81_API unsigned char *p = Tcl_SetByteArrayLength(obj, SNACK_CSL_HEADERSIZE); memcpy(p, buf, SNACK_CSL_HEADERSIZE); #endif } } s->inByteOrder = SNACK_LITTLEENDIAN; s->headSize = SNACK_CSL_HEADERSIZE; return TCL_OK; } int SnackOpenFile(openProc *openProc, Sound *s, Tcl_Interp *interp, Tcl_Channel *ch, char *mode) { int permissions; if (strcmp(mode, "r") == 0) { permissions = 0; } else { permissions = 420; } if (openProc == NULL) { if ((*ch = Tcl_OpenFileChannel(interp, s->fcname, mode, permissions))==0) { return TCL_ERROR; } Tcl_SetChannelOption(interp, *ch, "-translation", "binary"); #ifdef TCL_81_API Tcl_SetChannelOption(interp, *ch, "-encoding", "binary"); #endif } else { return((openProc)(s, interp, ch, mode)); } return TCL_OK; } int SnackCloseFile(closeProc *closeProc, Sound *s, Tcl_Interp *interp, Tcl_Channel *ch) { if (closeProc == NULL) { Tcl_Close(interp, *ch); *ch = NULL; } else { return((closeProc)(s, interp, ch)); } return TCL_OK; } int SnackSeekFile(seekProc *seekProc, Sound *s, Tcl_Interp *interp, Tcl_Channel ch, int pos) { if (seekProc == NULL) { return(TCL_SEEK(ch, s->headSize + pos * s->sampsize * s->nchannels, SEEK_SET)); } else { return((seekProc)(s, interp, ch, pos)); } } char * LoadSound(Sound *s, Tcl_Interp *interp, Tcl_Obj *obj, int startpos, int endpos) { Tcl_Channel ch = NULL; int status = TCL_OK; Snack_FileFormat *ff; int oldsampfmt = s->encoding; if (s->debug > 1) Snack_WriteLog(" Enter LoadSound\n"); if (GetHeader(s, interp, obj) != TCL_OK) { return NULL; } for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { int pos = 0; if (obj == NULL) { status = SnackOpenFile(ff->openProc, s, interp, &ch, "r"); } if (status == TCL_OK) { if (obj == NULL) { pos = SnackSeekFile(ff->seekProc, s, interp, ch, startpos); if (pos < 0) { SnackCloseFile(ff->closeProc, s, interp, &ch); return NULL; } } } if (status == TCL_OK && pos >= 0) { if (s->writeStatus == WRITE && s->encoding != oldsampfmt) { Snack_StopSound(s, NULL); } status = ReadSound(ff->readProc, s, interp, ch, obj, startpos, endpos); } if (status == TCL_OK && obj == NULL) { status = SnackCloseFile(ff->closeProc, s, interp, &ch); } if (status == TCL_ERROR) { return NULL; } break; } } if (s->debug > 1) Snack_WriteLog(" Exit LoadSound\n"); return(s->fileType); } int SaveSound(Sound *s, Tcl_Interp *interp, char *filename, Tcl_Obj *obj, int objc, Tcl_Obj *CONST objv[], int startpos, int len, char *type) { Tcl_Channel ch = NULL; Snack_FileFormat *ff; char *tmp = s->fcname; if (s->debug > 1) Snack_WriteLog(" Enter SaveSound\n"); for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(type, ff->name) == 0) { if (ff->putHeaderProc != NULL) { s->fcname = filename; if (filename != NULL) { if (SnackOpenFile(ff->openProc, s, interp, &ch, "w") != TCL_OK) { return TCL_ERROR; } } if ((ff->putHeaderProc)(s, interp, ch, obj, objc, objv, len) != TCL_OK) { return TCL_ERROR; } if (WriteSound(ff->writeProc, s, interp, ch, obj, startpos, len) != TCL_OK) { Tcl_AppendResult(interp, "Error while writing", NULL); s->fcname = tmp; return TCL_ERROR; } s->fcname = tmp; } else { Tcl_AppendResult(interp, "Unsupported save format", NULL); return TCL_ERROR; } break; } } if (ch != NULL) { SnackCloseFile(ff->closeProc, s, interp, &ch); } if (s->debug > 1) Snack_WriteLog(" Exit SaveSound\n"); return TCL_OK; } int readCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { char *filetype; int arg, startpos = 0, endpos = -1; static CONST84 char *subOptionStrings[] = { "-rate", "-frequency", "-skiphead", "-byteorder", "-channels", "-encoding", "-format", "-start", "-end", "-fileformat", "-guessproperties", "-progress", NULL }; enum subOptions { RATE, FREQUENCY, SKIPHEAD, BYTEORDER, CHANNELS, ENCODING, FORMAT, START, END, FILEFORMAT, GUESSPROPS, PROGRESS }; if (s->debug > 0) Snack_WriteLog("Enter readCmd\n"); if (objc < 3) { Tcl_AppendResult(interp, "No file name given", NULL); return TCL_ERROR; } if (s->storeType != SOUND_IN_MEMORY) { Tcl_AppendResult(interp, "read only works with in-memory sounds", (char *) NULL); return TCL_ERROR; } if (Tcl_IsSafe(interp)) { Tcl_AppendResult(interp, "can not read sound from a file in a safe", " interpreter", (char *) NULL); return TCL_ERROR; } s->guessEncoding = -1; s->guessRate = -1; s->swap = 0; s->forceFormat = 0; if (s->cmdPtr != NULL) { Tcl_DecrRefCount(s->cmdPtr); s->cmdPtr = NULL; } for (arg = 3; arg < objc; arg+=2) { int index; if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } if (arg + 1 == objc) { Tcl_AppendResult(interp, "No argument given for ", subOptionStrings[index], " option", (char *) NULL); return TCL_ERROR; } switch ((enum subOptions) index) { case RATE: case FREQUENCY: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &s->samprate) != TCL_OK) return TCL_ERROR; s->guessRate = 0; break; } case SKIPHEAD: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &s->skipBytes) != TCL_OK) return TCL_ERROR; break; } case BYTEORDER: { int length; char *str = Tcl_GetStringFromObj(objv[arg+1], &length); if (strncasecmp(str, "littleEndian", length) == 0) { SwapIfBE(s); } else if (strncasecmp(str, "bigEndian", length) == 0) { SwapIfLE(s); } else { Tcl_AppendResult(interp, "-byteorder option should be bigEndian", " or littleEndian", NULL); return TCL_ERROR; } s->guessEncoding = 0; break; } case CHANNELS: { if (GetChannels(interp, objv[arg+1], &s->nchannels) != TCL_OK) return TCL_ERROR; break; } case ENCODING: case FORMAT: { if (GetEncoding(interp, objv[arg+1], &s->encoding, &s->sampsize) != TCL_OK) return TCL_ERROR; s->guessEncoding = 0; break; } case START: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) return TCL_ERROR; break; } case END: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) return TCL_ERROR; break; } case FILEFORMAT: { if (strlen(Tcl_GetStringFromObj(objv[arg+1], NULL)) > 0) { if (GetFileFormat(interp, objv[arg+1], &s->fileType) != TCL_OK) { return TCL_ERROR; } s->forceFormat = 1; } break; } case GUESSPROPS: { int guessProps; if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &guessProps) != TCL_OK) return TCL_ERROR; if (guessProps) { if (s->guessEncoding == -1) s->guessEncoding = 1; if (s->guessRate == -1) s->guessRate = 1; } break; } case PROGRESS: { char *str = Tcl_GetStringFromObj(objv[arg+1], NULL); if (strlen(str) > 0) { Tcl_IncrRefCount(objv[arg+1]); s->cmdPtr = objv[arg+1]; } break; } } } if (s->guessEncoding == -1) s->guessEncoding = 0; if (s->guessRate == -1) s->guessRate = 0; if (startpos < 0) startpos = 0; if (startpos > endpos && endpos != -1) return TCL_OK; if (SetFcname(s, interp, objv[2]) != TCL_OK) { return TCL_ERROR; } if (strlen(s->fcname) == 0) { return TCL_OK; } filetype = LoadSound(s, interp, NULL, startpos, endpos); if (filetype == NULL) { return TCL_ERROR; } else { Snack_UpdateExtremes(s, 0, s->length, SNACK_NEW_SOUND); Snack_ExecCallbacks(s, SNACK_NEW_SOUND); Tcl_SetObjResult(interp, Tcl_NewStringObj(filetype, -1)); } if (s->debug > 0) Snack_WriteLog("Exit readCmd\n"); return TCL_OK; } void Snack_RemoveOptions(int objc, Tcl_Obj *CONST objv[], CONST84 char **subOptionStrings, int *newobjc, Tcl_Obj **newobjv) { int arg, n = 0; Tcl_Obj **new = NULL; if ((new = (Tcl_Obj **) ckalloc(sizeof(Tcl_Obj *) * objc)) == NULL) { return; } for (arg = 0; arg < objc; arg+=2) { int index; if (Tcl_GetIndexFromObj(NULL, objv[arg], subOptionStrings, NULL, 0, &index) != TCL_OK) { new[n++] = Tcl_DuplicateObj(objv[arg]); if (n < objc) new[n++] = Tcl_DuplicateObj(objv[arg+1]); } } *newobjc = n; *newobjv = (Tcl_Obj *) new; } int writeCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { int startpos = 0, endpos = s->length, arg, len, newobjc; char *string, *filetype = NULL; Tcl_Obj **newobjv = NULL; static CONST84 char *subOptionStrings[] = { "-start", "-end", "-fileformat", "-progress", "-byteorder", NULL }; enum subOptions { START, END, FILEFORMAT, PROGRESS, BYTEORDER }; if (s->debug > 0) { Snack_WriteLog("Enter writeCmd\n"); } if (Tcl_IsSafe(interp)) { Tcl_AppendResult(interp, "can not write sound to a file in a safe", " interpreter", (char *) NULL); return TCL_ERROR; } s->inByteOrder = SNACK_NATIVE; if (s->cmdPtr != NULL) { Tcl_DecrRefCount(s->cmdPtr); s->cmdPtr = NULL; } for (arg = 3; arg < objc; arg+=2) { int index; if (Tcl_GetIndexFromObj(NULL, objv[arg], subOptionStrings, "option", 0, &index) != TCL_OK) { continue; } if (arg + 1 == objc) { Tcl_AppendResult(interp, "No argument given for ", subOptionStrings[index], " option", (char *) NULL); return TCL_ERROR; } switch ((enum subOptions) index) { case START: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) return TCL_ERROR; break; } case END: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) return TCL_ERROR; break; } case FILEFORMAT: { if (GetFileFormat(interp, objv[arg+1], &filetype) != TCL_OK) return TCL_ERROR; break; } case PROGRESS: { char *str = Tcl_GetStringFromObj(objv[arg+1], NULL); if (strlen(str) > 0) { Tcl_IncrRefCount(objv[arg+1]); s->cmdPtr = objv[arg+1]; } break; } case BYTEORDER: { int length; char *str = Tcl_GetStringFromObj(objv[arg+1], &length); if (strncasecmp(str, "littleEndian", length) == 0) { s->inByteOrder = SNACK_LITTLEENDIAN; } else if (strncasecmp(str, "bigEndian", length) == 0) { s->inByteOrder = SNACK_BIGENDIAN; } else { Tcl_AppendResult(interp, "-byteorder option should be bigEndian", " or littleEndian", NULL); return TCL_ERROR; } break; } } } len = s->length; if (endpos >= len) endpos = len; if (endpos < 0) endpos = len; if (endpos > startpos) len -= (len - endpos); if (startpos > endpos) return TCL_OK; if (startpos > 0) len -= startpos; else startpos = 0; Snack_RemoveOptions(objc-3, objv+3, subOptionStrings, &newobjc, (Tcl_Obj **) &newobjv); if (objc < 3) { Tcl_AppendResult(interp, "No file name given", NULL); return TCL_ERROR; } string = Tcl_GetStringFromObj(objv[2], NULL); if (filetype == NULL) { filetype = NameGuessFileType(string); } if (strlen(string) == 0) { return TCL_OK; } if (s->storeType != SOUND_IN_MEMORY) { if (s->linkInfo.linkCh == NULL) { OpenLinkedFile(s, &s->linkInfo); } } if (SaveSound(s, interp, string, NULL, newobjc, (Tcl_Obj **CONST) newobjv, startpos, len, filetype) == TCL_ERROR) { return TCL_ERROR; } for (arg = 0; arg debug > 0) { Snack_WriteLog("Exit writeCmd\n"); } return TCL_OK; } /* writeCmd */ int dataCmd(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { if (s->storeType != SOUND_IN_MEMORY) { Tcl_AppendResult(interp, "data only works with in-memory sounds", (char *) NULL); return TCL_ERROR; } if ((objc % 2) == 0) { /* sound -> variable */ Tcl_Obj *new = Tcl_NewObj(); char *filetype = s->fileType; int arg, len, startpos = 0, endpos = s->length; static CONST84 char *subOptionStrings[] = { "-fileformat", "-start", "-end", "-byteorder", NULL }; enum subOptions { FILEFORMAT, START, END, BYTEORDER }; s->swap = 0; for (arg = 2; arg < objc; arg += 2) { int index; char *str; if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } if (arg + 1 == objc) { Tcl_AppendResult(interp, "No argument given for ", subOptionStrings[index], " option", (char *) NULL); return TCL_ERROR; } switch ((enum subOptions) index) { case FILEFORMAT: { if (GetFileFormat(interp, objv[arg+1], &filetype) != TCL_OK) return TCL_ERROR; break; } case START: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) return TCL_ERROR; break; } case END: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) return TCL_ERROR; break; } case BYTEORDER: { str = Tcl_GetStringFromObj(objv[arg+1], &len); if (strncasecmp(str, "littleEndian", len) == 0) { SwapIfBE(s); } else if (strncasecmp(str, "bigEndian", len) == 0) { SwapIfLE(s); } else { Tcl_AppendResult(interp, "-byteorder option should be bigEndian or littleEndian", NULL); return TCL_ERROR; } break; } } } len = s->length; if (endpos >= len) endpos = len; if (endpos < 0) endpos = len; if (endpos > startpos) len -= (len - endpos); if (startpos > endpos) return TCL_OK; if (startpos > 0) len -= startpos; else startpos = 0; if (SaveSound(s, interp, NULL, new, objc-2, objv+2, startpos, len,filetype) == TCL_ERROR) { return TCL_ERROR; } Tcl_SetObjResult(interp, new); } else { /* variable -> sound */ int arg, startpos = 0, endpos = -1; char *filetype; static CONST84 char *subOptionStrings[] = { "-rate", "-frequency", "-skiphead", "-byteorder", "-channels", "-encoding", "-format", "-start", "-end", "-fileformat", "-guessproperties", NULL }; enum subOptions { RATE, FREQUENCY, SKIPHEAD, BYTEORDER, CHANNELS, ENCODING, FORMAT, START, END, FILEFORMAT, GUESSPROPS }; s->guessEncoding = -1; s->guessRate = -1; s->swap = 0; s->forceFormat = 0; for (arg = 3; arg < objc; arg += 2) { int index; if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } if (arg + 1 == objc) { Tcl_AppendResult(interp, "No argument given for ", subOptionStrings[index], " option", (char *) NULL); return TCL_ERROR; } switch ((enum subOptions) index) { case RATE: case FREQUENCY: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &s->samprate) != TCL_OK) return TCL_ERROR; s->guessRate = 0; break; } case SKIPHEAD: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &s->skipBytes) != TCL_OK) { return TCL_ERROR; } break; } case BYTEORDER: { int length; char *str = Tcl_GetStringFromObj(objv[arg+1], &length); if (strncasecmp(str, "littleEndian", length) == 0) { SwapIfBE(s); } else if (strncasecmp(str, "bigEndian", length) == 0) { SwapIfLE(s); } else { Tcl_AppendResult(interp, "-byteorder option should be bigEndian", " or littleEndian", NULL); return TCL_ERROR; } s->guessEncoding = 0; break; } case CHANNELS: { if (GetChannels(interp, objv[arg+1], &s->nchannels) != TCL_OK) return TCL_ERROR; break; } case ENCODING: case FORMAT: { if (GetEncoding(interp, objv[arg+1], &s->encoding, &s->sampsize) != TCL_OK) return TCL_ERROR; s->guessEncoding = 0; break; } case START: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) return TCL_ERROR; break; } case END: { if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) return TCL_ERROR; break; } case FILEFORMAT: { if (strlen(Tcl_GetStringFromObj(objv[arg+1], NULL)) > 0) { if (GetFileFormat(interp, objv[arg+1], &s->fileType) != TCL_OK) return TCL_ERROR; s->forceFormat = 1; break; } } case GUESSPROPS: { int guessProps; if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &guessProps) !=TCL_OK) return TCL_ERROR; if (guessProps) { if (s->guessEncoding == -1) s->guessEncoding = 1; if (s->guessRate == -1) s->guessRate = 1; } break; } } } if (s->guessEncoding == -1) s->guessEncoding = 0; if (s->guessRate == -1) s->guessRate = 0; if (startpos < 0) startpos = 0; if (startpos > endpos && endpos != -1) return TCL_OK; filetype = LoadSound(s, interp, objv[2], startpos, endpos); if (filetype == NULL) { return TCL_ERROR; } else { Snack_UpdateExtremes(s, 0, s->length, SNACK_NEW_SOUND); Snack_ExecCallbacks(s, SNACK_NEW_SOUND); Tcl_SetObjResult(interp, Tcl_NewStringObj(filetype, -1)); } } return TCL_OK; } /* dataCmd */ int GetHeader(Sound *s, Tcl_Interp *interp, Tcl_Obj *obj) { Snack_FileFormat *ff; Tcl_Channel ch = NULL; int status = TCL_OK, openedOk = 0; int buflen = max(HEADBUF, CHANNEL_HEADER_BUFFER), len = 0; if (s->guessEncoding) { s->swap = 0; } if (s->tmpbuf != NULL) { ckfree((char *)s->tmpbuf); } if ((s->tmpbuf = (short *) ckalloc(buflen)) == NULL) { Tcl_AppendResult(interp, "Could not allocate buffer!", NULL); return TCL_ERROR; } if (obj == NULL) { ch = Tcl_OpenFileChannel(interp, s->fcname, "r", 0); if (ch != NULL) { Tcl_SetChannelOption(interp, ch, "-translation", "binary"); #ifdef TCL_81_API Tcl_SetChannelOption(interp, ch, "-encoding", "binary"); #endif if ((len = Tcl_Read(ch, (char *)s->tmpbuf, buflen)) > 0) { Tcl_Close(interp, ch); ch = NULL; } } else { ckfree((char *)s->tmpbuf); s->tmpbuf = NULL; return TCL_ERROR; } } else { unsigned char *ptr = NULL; if (useOldObjAPI) { len = min(obj->length, buflen); memcpy((char *)s->tmpbuf, obj->bytes, len); } else { #ifdef TCL_81_API int length = 0; ptr = Tcl_GetByteArrayFromObj(obj, &length); len = min(length, buflen); memcpy((char *)s->tmpbuf, ptr, len); #endif } } if (s->forceFormat == 0) { s->fileType = GuessFileType((char *)s->tmpbuf, len, 1); } s->firstNRead = len; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { if (obj == NULL) { status = SnackOpenFile(ff->openProc, s, interp, &ch, "r"); if (status == TCL_OK) openedOk = 1; } if (status == TCL_OK) { status = (ff->getHeaderProc)(s, interp, ch, obj, (char *)s->tmpbuf); } if (strcmp(s->fileType, RAW_STRING) == 0 && s->guessEncoding) { GuessEncoding(s, (unsigned char *)s->tmpbuf, len); } if (obj == NULL && openedOk == 1) { status = SnackCloseFile(ff->closeProc, s, interp, &ch); } ckfree((char *)s->tmpbuf); s->tmpbuf = NULL; return(status); } } ckfree((char *)s->tmpbuf); s->tmpbuf = NULL; return TCL_OK; } int PutHeader(Sound *s, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], int length) { Snack_FileFormat *ff; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { if (ff->putHeaderProc != NULL) { return (ff->putHeaderProc)(s, interp, s->rwchan, NULL, objc, objv, length); } break; } } return 0; } int GetFileFormat(Tcl_Interp *interp, Tcl_Obj *obj, char **filetype) { int length; char *str = Tcl_GetStringFromObj(obj, &length); Snack_FileFormat *ff; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcasecmp(str, ff->name) == 0) { *filetype = ff->name; return TCL_OK; } } if (strcasecmp(str, RAW_STRING) == 0) { *filetype = RAW_STRING; return TCL_OK; } Tcl_AppendResult(interp, "Unknown file format", NULL); return TCL_ERROR; } void Snack_CreateFileFormat(Snack_FileFormat *typePtr) { Snack_FileFormat *typePtr2, *prevPtr; /* * If there's already a filter type with the given name, remove it. */ for (typePtr2 = snackFileFormats, prevPtr = NULL; typePtr2 != NULL; prevPtr = typePtr2, typePtr2 = typePtr2->nextPtr) { if (strcmp(typePtr2->name, typePtr->name) == 0) { if (prevPtr == NULL) { snackFileFormats = typePtr2->nextPtr; } else { prevPtr->nextPtr = typePtr2->nextPtr; } break; } } typePtr->nextPtr = snackFileFormats; snackFileFormats = typePtr; } /* Deprecated, use Snack_CreateFileFormat() instead */ int Snack_AddFileFormat(char *name, guessFileTypeProc *guessProc, getHeaderProc *getHeadProc, extensionFileTypeProc *extProc, putHeaderProc *putHeadProc, openProc *openProc, closeProc *closeProc, readSamplesProc *readProc, writeSamplesProc *writeProc, seekProc *seekProc) { Snack_FileFormat *ff = (Snack_FileFormat *)ckalloc(sizeof(Snack_FileFormat)); if (ff == NULL) { return TCL_ERROR; } ff->name = name; ff->guessProc = guessProc; ff->getHeaderProc = getHeadProc; ff->extProc = extProc; ff->putHeaderProc = putHeadProc; ff->openProc = openProc; ff->closeProc = closeProc; ff->readProc = readProc; ff->writeProc = writeProc; ff->seekProc = seekProc; ff->nextPtr = snackFileFormats; snackFileFormats = ff; return TCL_OK; } Snack_FileFormat snackRawFormat = { RAW_STRING, GuessRawFile, GetRawHeader, NULL, PutRawHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; Snack_FileFormat snackMp3Format = { MP3_STRING, GuessMP3File, GetMP3Header, ExtMP3File, NULL, OpenMP3File, CloseMP3File, ReadMP3Samples, NULL, SeekMP3File, FreeMP3Header, ConfigMP3Header, (Snack_FileFormat *) NULL }; Snack_FileFormat snackSmpFormat = { SMP_STRING, GuessSmpFile, GetSmpHeader, ExtSmpFile, PutSmpHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; Snack_FileFormat snackCslFormat = { CSL_STRING, GuessCslFile, GetCslHeader, ExtCslFile, PutCslHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; Snack_FileFormat snackSdFormat = { SD_STRING, GuessSdFile, GetSdHeader, ExtSdFile, NULL, NULL, NULL, NULL, NULL, NULL, FreeSdHeader, ConfigSdHeader, (Snack_FileFormat *) NULL }; Snack_FileFormat snackAiffFormat = { AIFF_STRING, GuessAiffFile, GetAiffHeader, ExtAiffFile, PutAiffHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; Snack_FileFormat snackAuFormat = { AU_STRING, GuessAuFile, GetAuHeader, ExtAuFile, PutAuHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; Snack_FileFormat snackWavFormat = { WAV_STRING, GuessWavFile, GetWavHeader, ExtWavFile, PutWavHeader, NULL, NULL, NULL, NULL, NULL, NULL, NULL, (Snack_FileFormat *) NULL }; void SnackDefineFileFormats(Tcl_Interp *interp) /* { snackFileFormats = &snackWavFormat; snackWavFormat.nextPtr = &snackAiffFormat; snackAiffFormat.nextPtr = &snackAuFormat; snackAuFormat.nextPtr = &snackSmpFormat; snackSmpFormat.nextPtr = &snackCslFormat; snackCslFormat.nextPtr = &snackSdFormat; snackSdFormat.nextPtr = &snackMp3Format; snackMp3Format.nextPtr = &snackRawFormat; snackRawFormat.nextPtr = NULL; } */ { snackFileFormats = &snackWavFormat; snackWavFormat.nextPtr = &snackMp3Format; snackMp3Format.nextPtr = &snackAiffFormat; snackAiffFormat.nextPtr = &snackAuFormat; snackAuFormat.nextPtr = &snackSmpFormat; snackSmpFormat.nextPtr = &snackCslFormat; snackCslFormat.nextPtr = &snackSdFormat; snackSdFormat.nextPtr = &snackRawFormat; snackRawFormat.nextPtr = NULL; } #define BACKLOGSAMPS 1 int OpenLinkedFile(Sound *s, SnackLinkedFileInfo *infoPtr) { Snack_FileFormat *ff; infoPtr->sound = s; if (strlen(s->fcname) == 0) { return TCL_OK; } if (s->itemRefCnt && s->readStatus == READ) { return TCL_OK; } infoPtr->buffer = (float *) ckalloc(ITEMBUFFERSIZE); infoPtr->filePos = -1; infoPtr->validSamples = 0; infoPtr->eof = 0; for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { if (SnackOpenFile(ff->openProc, s, s->interp, &infoPtr->linkCh, "r") != TCL_OK) { return TCL_ERROR; } return TCL_OK; } } return TCL_ERROR; } void CloseLinkedFile(SnackLinkedFileInfo *infoPtr) { Sound *s = infoPtr->sound; Snack_FileFormat *ff; if (strlen(s->fcname) == 0) { return; } if (s->itemRefCnt && s->readStatus == READ) { return; } ckfree((char *) infoPtr->buffer); for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { SnackCloseFile(ff->closeProc, s, s->interp, &infoPtr->linkCh); return; } } } float junkBuffer[PBSIZE]; float GetSample(SnackLinkedFileInfo *infoPtr, int index) { Sound *s = infoPtr->sound; Snack_FileFormat *ff; int nRead = 0, size = ITEMBUFFERSIZE / sizeof(float), i; if (s->itemRefCnt && s->readStatus == READ) { return FSAMPLE(s, index); } if (index < infoPtr->filePos + ITEMBUFFERSIZE / (int) sizeof(float) && index >= infoPtr->filePos && infoPtr->filePos != -1) { if (index < infoPtr->filePos + infoPtr->validSamples) { return(infoPtr->buffer[index-infoPtr->filePos]); } else { infoPtr->eof = 1; return(0.0f); } } else { int pos = 0, doSeek = 1; if (index == infoPtr->filePos + ITEMBUFFERSIZE / (int) sizeof(float)) { doSeek = 0; } /* Keep BACKLOGSAMPS old samples in the buffer */ if (index > BACKLOGSAMPS * s->nchannels) { index -= BACKLOGSAMPS * s->nchannels; } for (ff = snackFileFormats; ff != NULL; ff = ff->nextPtr) { if (strcmp(s->fileType, ff->name) == 0) { char *b = &((char *)infoPtr->buffer)[ITEMBUFFERSIZE - size * s->sampsize]; if (doSeek || ff->readProc == NULL) { SnackSeekFile(ff->seekProc, s, s->interp, infoPtr->linkCh, index / s->nchannels); } if (s->nchannels > 1 && index % s->nchannels > 0) { pos = index % s->nchannels + s->nchannels; index = s->nchannels * (int)(index / s->nchannels); } else { if (index > 0) { pos = s->nchannels; } } if (ff->readProc == NULL) { nRead = Tcl_Read(infoPtr->linkCh, b, size * s->sampsize); infoPtr->validSamples = nRead / s->sampsize; } else { int tries=10,maxt=tries; /* TFW: Workaround for streaming issues: * Make sure we get something from the channel if possible * on some (e.g. ogg) streams, we sometime get a -1 back for length * typically on the second retry we get it right. */ for (;tries>0;tries--) { nRead = (ff->readProc)(s, s->interp, infoPtr->linkCh, NULL, junkBuffer, size); if (nRead > 0) break; } if (s->debug > 1 && tries < maxt) { Snack_WriteLogInt(" Read Tries", maxt-tries); Snack_WriteLogInt(" Read Samples", nRead); } infoPtr->validSamples = nRead; memcpy(infoPtr->buffer, junkBuffer, nRead * sizeof(float)); } if (ff->readProc == NULL) { /* unpack block */ unsigned char *q = (unsigned char *) b; char *sc = (char *) b; short *r = (short *) b; int *is = (int *) b; float *fs = (float *) b; float *f = infoPtr->buffer; for (i = 0; i < size; i++) { switch (s->encoding) { case LIN16: if (s->swap) *r = Snack_SwapShort(*r); *f++ = (float) *r++; break; case LIN32: if (s->swap) *is = Snack_SwapLong(*is); *f++ = (float) *is++; break; case SNACK_FLOAT: if (s->swap) *fs = (float) Snack_SwapLong((int)*fs); *f++ = (float) *fs++; break; case ALAW: *f++ = (float) Snack_Alaw2Lin(*q++); break; case MULAW: *f++ = (float) Snack_Mulaw2Lin(*q++); break; case LIN8: *f++ = (float) *sc++; break; case LIN8OFFSET: *f++ = (float) *q++; break; case LIN24: case LIN24PACKED: { int ee; if (s->swap) { if (littleEndian) { ee = 0; } else { ee = 1; } } else { if (littleEndian) { ee = 1; } else { ee = 0; } } if (ee) { int t = *q++; t |= *q++ << 8; t |= *q++ << 16; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } *f++ = (float) t; } else { int t = *q++ << 16; t |= *q++ << 8; t |= *q++; if (t & 0x00800000) { t |= (unsigned int) 0xff000000; } *f++ = (float) t; } break; } } } } break; } } infoPtr->filePos = index; return(infoPtr->buffer[pos]); } } Snack_FileFormat * Snack_GetFileFormats() { return snackFileFormats; }