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