1/* 2 * Copyright (C) 2000 Claude Barras 3 * contribution to the Snack Sound Toolkit 4 */ 5 6#include <stdlib.h> 7#include <stdio.h> 8#include <string.h> 9#include <math.h> 10#include "snack.h" 11 12extern int littleEndian; 13extern int useOldObjAPI; 14 15typedef struct { 16 short max; 17 short min; 18} Maxmin; 19 20/* --------------------------------------------------------------------- */ 21 22static short GetShortSample(Sound *s, long i, int c) { 23 short res = 0; 24 if (i >= Snack_GetLength(s) || s->storeType == SOUND_IN_CHANNEL) 25 return 0; 26 i = i * Snack_GetNumChannels(s) + c; 27 if (s->storeType == SOUND_IN_MEMORY) { 28 res = (short) FSAMPLE(s, i); 29 } else { 30 if (s->linkInfo.linkCh == NULL) { 31 OpenLinkedFile(s, &s->linkInfo); 32 } 33 res = (short) GetSample(&s->linkInfo, i); 34 } 35 if (Snack_GetSampleEncoding(s) == LIN8) { 36 res <<= 8; 37 } 38 return res; 39} 40 41static void SetShortSample(Sound *s, long i, int c, short val) { 42 if (i >= Snack_GetLength(s) || s->storeType != SOUND_IN_MEMORY) 43 return; 44 i = i * Snack_GetNumChannels(s) + c; 45 if (Snack_GetSampleEncoding(s) == LIN8) { 46 val /= 256; 47 } 48 FSAMPLE(s, i) = (float) val; 49} 50 51/* --------------------------------------------------------------------- */ 52 53int shapeCmd(Sound *s, Tcl_Interp *interp, int objc, 54 Tcl_Obj *CONST objv[]) 55{ 56 int arg, width = 0, pps = 0, startpos = 0, endpos = -1, check = 0; 57 int byteOrder = SNACK_NATIVE; 58 int encoding, sampsize; 59 Sound *shp = NULL, *preshp = NULL; 60 long k0, k1; 61 int i, c, first, nc, nbytes = 0, mn, mx; 62 double begin, len, Fe, hRatio, pos; 63 Maxmin *q = NULL, *p = NULL; 64 Tcl_Obj *resObj = NULL; 65 char *string; 66 67 if (objc < 3) { 68 Tcl_WrongNumArgs(interp, 1, objv, "shape ?sound? ?options?"); 69 return TCL_ERROR; 70 } 71 72 /* Decide where to store the 'shape' */ 73 string = Tcl_GetStringFromObj(objv[2], NULL); 74 if (string[0] != '-') { 75 if ((shp = Snack_GetSound(interp, string)) == NULL) { 76 return TCL_ERROR; 77 } 78 if (shp == s) { 79 Tcl_AppendResult(interp, "source and target must differ", NULL); 80 return TCL_ERROR; 81 } 82 } 83 84 if (shp) { 85 86 /* Store shape into sound */ 87 static CONST84 char *subOptionStrings[] = { 88 "-start", "-end", "-pixelspersecond", "-format", "-encoding", "-check", 89 NULL 90 }; 91 enum subOptions { 92 START, END, PPS, FORMAT, ENCODING, CHECK 93 }; 94 int index; 95 96 /* default values for the sound target case */ 97 pps = 100; 98 encoding = Snack_GetSampleEncoding(s); 99 sampsize = Snack_GetBytesPerSample(s); 100 101 for (arg = 3; arg < objc; arg += 2) { 102 if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, 103 "option", 0, &index) != TCL_OK) { 104 return TCL_ERROR; 105 } 106 switch ((enum subOptions) index) { 107 case START: 108 { 109 if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) 110 return TCL_ERROR; 111 break; 112 } 113 case END: 114 { 115 if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) 116 return TCL_ERROR; 117 break; 118 } 119 case PPS: 120 { 121 if (Tcl_GetIntFromObj(interp, objv[arg+1], &pps) != TCL_OK) 122 return TCL_ERROR; 123 break; 124 } 125 case FORMAT: 126 case ENCODING: 127 { 128 if (GetEncoding(interp, objv[arg+1], &encoding, &sampsize) != TCL_OK) 129 return TCL_ERROR; 130 break; 131 } 132 case CHECK: 133 { 134 if (Tcl_GetBooleanFromObj(interp, objv[arg+1], &check) != TCL_OK) 135 return TCL_ERROR; 136 break; 137 } 138 } 139 } 140 } else { 141 142 /* Return shape as binary data */ 143 static CONST84 char *subOptionStrings[] = { 144 "-start", "-end", "-width", "-pixelspersecond", 145 "-shape", "-byteorder", NULL 146 }; 147 enum subOptions { 148 START, END, WIDTH, PPS, SHAPE, BYTEORDER 149 }; 150 int index; 151 152 for (arg = 2; arg < objc; arg += 2) { 153 154 if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, 155 "option", 0, &index) != TCL_OK) { 156 return TCL_ERROR; 157 } 158 switch ((enum subOptions) index) { 159 case START: 160 { 161 if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) 162 return TCL_ERROR; 163 break; 164 } 165 case END: 166 { 167 if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) 168 return TCL_ERROR; 169 break; 170 } 171 case WIDTH: 172 { 173 if (Tcl_GetIntFromObj(interp, objv[arg+1], &width) != TCL_OK) 174 return TCL_ERROR; 175 break; 176 } 177 case PPS: 178 { 179 if (Tcl_GetIntFromObj(interp, objv[arg+1], &pps) != TCL_OK) 180 return TCL_ERROR; 181 break; 182 } 183 case SHAPE: 184 { 185 int nchar = 0; 186 char *str = Tcl_GetStringFromObj(objv[arg+1], &nchar); 187 if (nchar > 0 && (preshp = Snack_GetSound(interp, str)) == NULL) { 188 return TCL_ERROR; 189 } 190 break; 191 } 192 case BYTEORDER: 193 { 194 int length; 195 char *str = Tcl_GetStringFromObj(objv[arg+1], &length); 196 197 if (strncasecmp(str, "littleEndian", length) == 0) { 198 byteOrder = SNACK_LITTLEENDIAN; 199 } else if (strncasecmp(str, "bigEndian", length) == 0) { 200 byteOrder = SNACK_BIGENDIAN; 201 } else { 202 Tcl_AppendResult(interp, "-byteorder option should be bigEndian or littleEndian", NULL); 203 return TCL_ERROR; 204 } 205 break; 206 } 207 } 208 } 209 } 210 211 /* Characteristics of 'shaped' sound */ 212 nc = Snack_GetNumChannels(s); 213 Fe = Snack_GetSampleRate(s); 214 215 /* Adjust boundaries to fit the sound and satisfy the constraint: */ 216 /* len = (endpos-startpos+1)/Fe = width/pps */ 217 if (startpos < 0) startpos = 0; 218 if (width > 0 && pps > 0) { 219 endpos = startpos + (int) (Fe * width / (float) pps - 1); 220 } 221 if (/* endpos >= (Snack_GetLength(s) - 1) || */ endpos == -1) 222 endpos = Snack_GetLength(s) - 1; 223 begin = (double) startpos / Fe; 224 len = (double) (endpos - startpos + 1) / Fe; 225 if (width <= 0 && pps > 0) { 226 width = (int) ceil(len * pps); 227 } 228 if (width <= 0 || len <= 0) { 229 Tcl_SetResult(interp, "Bad boundaries for shape", TCL_STATIC); 230 return TCL_ERROR; 231 } 232 233 /* Only checks if existing shape seems compatible with sound then exits */ 234 if (shp && check) { 235 float shpLen = (float) Snack_GetLength(shp) / Snack_GetSampleRate(shp); 236 if (Snack_GetNumChannels(shp) == nc 237 && fabs(len - shpLen) < 0.05 * len 238 && Snack_GetSampleRate(shp) < 2000) { 239 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1)); 240 } else { 241 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0)); 242 } 243 return TCL_OK; 244 } 245 246 /* Try to use precomputed shape instead of original sound */ 247 if (preshp != NULL 248 && Snack_GetNumChannels(preshp) == nc 249 && Snack_GetSampleRate(preshp) * len / width > 4) { 250 Fe = Snack_GetSampleRate(preshp) / 2; 251 } else { 252 Fe = Snack_GetSampleRate(s); 253 preshp = NULL; 254 } 255 256 /* number of samples per point */ 257 if (pps > 0) { 258 hRatio = Fe / pps; 259 } else { 260 hRatio = Fe * len / width; 261 } 262 263 /* round first sample to be a multiple of hRatio */ 264 /* in order to avoid distortions during scrolling */ 265 pos = floor(begin * Fe / hRatio) * hRatio; 266 267 if (shp) { 268 269 /* update shape parameters and get free space into sound target */ 270 Tcl_Obj *empty = Tcl_NewStringObj("",-1); 271 if (Snack_GetSoundWriteStatus(shp) != IDLE && 272 Snack_GetSoundReadStatus(shp) != IDLE) { 273 Snack_StopSound(shp, interp); 274 } 275 SetFcname(shp, interp, empty); 276 Tcl_DecrRefCount(empty); 277 shp->storeType = SOUND_IN_MEMORY; 278 Snack_SetSampleRate(shp, 2 * pps); 279 Snack_SetSampleEncoding(shp, encoding); 280 Snack_SetBytesPerSample(shp, sampsize); 281 Snack_SetNumChannels(shp, nc); 282 Snack_SetLength(shp, (int) (2 * ceil((endpos+1-pos)/hRatio))); 283 if (Snack_ResizeSoundStorage(shp, Snack_GetLength(shp)) != TCL_OK) { 284 return TCL_ERROR; 285 } 286 287 } else { 288 289 /* Get memory for binary shape */ 290 resObj = Tcl_NewObj(); 291 nbytes = sizeof(Maxmin) * nc * width; 292 if (useOldObjAPI) { 293 Tcl_SetObjLength(resObj, nbytes); 294 p = (Maxmin *) resObj->bytes; 295 } else { 296#ifdef TCL_81_API 297 p = (Maxmin *) Tcl_SetByteArrayLength(resObj, nbytes); 298#endif 299 } 300 } 301 302 /* compute min/max for each point */ 303 q = (Maxmin *) Tcl_Alloc(sizeof(Maxmin) * nc); 304 for (i=0; i<width; i++) { 305 k0 = (long) pos; pos += hRatio; k1 = (long) pos; 306 first = 1; 307 while (k0 < k1 && k0 <= endpos) { 308 for (c=0; c<nc; c++) { 309 if (preshp) { 310 mx = GetShortSample(preshp, 2*k0, c); 311 mn = GetShortSample(preshp, 2*k0+1, c); 312 } else { 313 mn = mx = GetShortSample(s, k0, c); 314 } 315 if (first || (mn < q[c].min)) 316 q[c].min = mn; 317 if (first || (mx > q[c].max)) 318 q[c].max = mx; 319 } 320 first = 0; 321 k0++; 322 } 323 if (first && k0 > endpos) break; 324 if (shp) { 325 for (c=0; c<nc; c++) { 326 SetShortSample(shp, 2*i, c, q[c].max); 327 SetShortSample(shp, 2*i+1, c, q[c].min); 328 /* Snack_SetSample(shp, 2*c, i, q[c].max); 329 Snack_SetSample(shp, 2*c+1, i, q[c].min); */ 330 } 331 } else { 332 for (c=0; c<nc; c++) { 333 p[c+i*nc] = q[c]; 334 } 335 } 336 } 337 Tcl_Free((char *)q); 338 339 /* return result */ 340 if (shp) { 341 Snack_UpdateExtremes(shp, 0, Snack_GetLength(shp), SNACK_NEW_SOUND); 342 } else { 343 344 /* Use correct byte order */ 345 if (littleEndian) { 346 if (byteOrder == SNACK_BIGENDIAN) { 347 for (i = 0; i < (int) (nbytes / sizeof(short)); i++) 348 ((short *)p)[i] = Snack_SwapShort(((short *)p)[i]); 349 } 350 } else { 351 if (byteOrder == SNACK_LITTLEENDIAN) { 352 for (i = 0; i < (int) (nbytes / sizeof(short)); i++) 353 ((short *)p)[i] = Snack_SwapShort(((short *)p)[i]); 354 } 355 } 356 357 Tcl_SetObjResult( interp, resObj); 358 } 359 return TCL_OK; 360} 361 362/* --------------------------------------------------------------------- */ 363 364int dataSamplesCmd(Sound *s, Tcl_Interp *interp, int objc, 365 Tcl_Obj *CONST objv[]) 366{ 367 int arg, startpos = 0, endpos = -1; 368 int byteOrder = SNACK_NATIVE; 369 int pos, i, c, nbytes; 370 Tcl_Obj *resObj = NULL; 371 short *p = NULL; 372 373 /* Get options */ 374 for (arg = 2; arg < objc; arg += 2) { 375 static CONST84 char *subOptionStrings[] = { 376 "-start", "-end", "-byteorder", NULL 377 }; 378 enum subOptions { 379 START, END, BYTEORDER 380 }; 381 int index; 382 383 if (Tcl_GetIndexFromObj(interp, objv[arg], subOptionStrings, 384 "option", 0, &index) != TCL_OK) { 385 return TCL_ERROR; 386 } 387 switch ((enum subOptions) index) { 388 case START: 389 { 390 if (Tcl_GetIntFromObj(interp, objv[arg+1], &startpos) != TCL_OK) 391 return TCL_ERROR; 392 break; 393 } 394 case END: 395 { 396 if (Tcl_GetIntFromObj(interp, objv[arg+1], &endpos) != TCL_OK) 397 return TCL_ERROR; 398 break; 399 } 400 case BYTEORDER: 401 { 402 int length; 403 char *str = Tcl_GetStringFromObj(objv[arg+1], &length); 404 405 if (strncasecmp(str, "littleEndian", length) == 0) { 406 byteOrder = SNACK_LITTLEENDIAN; 407 } else if (strncasecmp(str, "bigEndian", length) == 0) { 408 byteOrder = SNACK_BIGENDIAN; 409 } else { 410 Tcl_AppendResult(interp, "-byteorder option should be bigEndian or littleEndian", NULL); 411 return TCL_ERROR; 412 } 413 break; 414 } 415 } 416 } 417 418 /* Adjust boundaries */ 419 if (startpos < 0) startpos = 0; 420 if (/* endpos >= (Snack_GetLength(s) - 1) || */ endpos == -1) 421 endpos = Snack_GetLength(s) - 1; 422 if (startpos > endpos) return TCL_OK; 423 424 /* Get memory */ 425 resObj = Tcl_NewObj(); 426 nbytes = sizeof(short) * Snack_GetNumChannels(s) * (endpos - startpos + 1); 427 if (useOldObjAPI) { 428 Tcl_SetObjLength(resObj, nbytes); 429 p = (short *) resObj->bytes; 430 } else { 431#ifdef TCL_81_API 432 p = (short *) Tcl_SetByteArrayLength(resObj, nbytes); 433#endif 434 } 435 436 /* Put samples into array as binary shorts */ 437 i = 0; 438 for (pos = startpos; pos <= endpos; pos++) { 439 for (c = 0; c < Snack_GetNumChannels(s); c++) { 440 p[i++] = GetShortSample(s, pos, c); 441 } 442 } 443 444 /* Use correct byte order */ 445 if (littleEndian) { 446 if (byteOrder == SNACK_BIGENDIAN) { 447 for (i = 0; i < (int) (nbytes / sizeof(short)); i++) 448 p[i] = Snack_SwapShort(p[i]); 449 } 450 } else { 451 if (byteOrder == SNACK_LITTLEENDIAN) { 452 for (i = 0; i < (int) (nbytes / sizeof(short)); i++) 453 p[i] = Snack_SwapShort(p[i]); 454 } 455 } 456 457 Tcl_SetObjResult( interp, resObj); 458 return TCL_OK; 459} 460 461/* --------------------------------------------------------------------- */ 462