1 /*
2     scale.c:
3 
4     Copyright (C) 1994  John ffitch
5                   2005  John ffitch modifications to utility
6 
7     This file is part of Csound.
8 
9     The Csound Library is free software; you can redistribute it
10     and/or modify it under the terms of the GNU Lesser General Public
11     License as published by the Free Software Foundation; either
12     version 2.1 of the License, or (at your option) any later version.
13 
14     Csound is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU Lesser General Public License for more details.
18 
19     You should have received a copy of the GNU Lesser General Public
20     License along with Csound; if not, write to the Free Software
21     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22     02110-1301 USA
23 */
24 
25 /*******************************************************\
26 *   scale.c                                             *
27 *   scale a sound file by a float factor                *
28 *   jpff 3 Sep 1994 after code by dpwe 19sep90          *
29 *   and a certain amount of lifting from Csound itself  *
30 \*******************************************************/
31 
32 #include "std_util.h"
33 #include "soundio.h"
34 #include <ctype.h>
35 
36 /* Constants */
37 
38 #define FIND(MSG)   if (*s == '\0')  \
39     if (UNLIKELY(!(--argc) || ((s = *++argv) && *s == '-')))    \
40       csound->Die(csound, "%s", MSG);
41 
42 static const char *usage_txt[] = {
43   Str_noop("Usage:\tscale [-flags] soundfile"),
44   Str_noop("Legal flags are:"),
45   Str_noop("-o fnam\tsound output filename"),
46   Str_noop("-A\tcreate an AIFF format output soundfile"),
47   Str_noop("-W\tcreate a WAV format output soundfile"),
48   Str_noop("-h\tno header on output soundfile"),
49   Str_noop("-c\t8-bit signed_char sound samples"),
50   Str_noop("-a\talaw sound samples"),
51   Str_noop("-u\tulaw sound samples"),
52   Str_noop("-s\tshort_int sound samples"),
53   Str_noop("-l\tlong_int sound samples"),
54   Str_noop("-f\tfloat sound samples"),
55   Str_noop("-F fpnum\tamount to scale amplitude"),
56   Str_noop("-F file \tfile of scaling information (alternative)"),
57   Str_noop("-M fpnum\tScale file to given maximum"),
58   Str_noop("-P fpnum\tscale file to given percentage of full"),
59   Str_noop("-R\tcontinually rewrite header while writing soundfile (WAV/AIFF)"),
60   Str_noop("-H#\tprint a heartbeat style 1, 2 or 3 at each soundfile write"),
61   Str_noop("-N\tnotify (ring the bell) when score or miditrack is done"),
62   Str_noop("-- fnam\tlog output to file"),
63   Str_noop("flag defaults: scale -s -otest -F 0.0"),
64   Str_noop("If scale is 0.0 then reports maximum possible scaling"),
65     NULL
66 };
67 
usage(CSOUND * csound,char * mesg)68 static void usage(CSOUND *csound, char *mesg)
69 {
70     int32_t i;
71     for (i = 0; usage_txt[i] != NULL; i++)
72       csound->Message(csound, "%s\n", Str(usage_txt[i]));
73     csound->Die(csound, "\n%s", mesg);
74 }
75 
set_output_format(OPARMS * p,char c,char outformch)76 static char set_output_format(OPARMS *p, char c, char outformch)
77 {
78     switch (c) {
79       case 'a': p->outformat = AE_ALAW;   /* a-law soundfile */
80         break;
81       case 'c': p->outformat = AE_CHAR;   /* signed 8-bit soundfile */
82         break;
83       case '8': p->outformat = AE_UNCH;   /* unsigned 8-bit soundfile */
84         break;
85       case 'f': p->outformat = AE_FLOAT;  /* float soundfile */
86         break;
87       case 's': p->outformat = AE_SHORT;  /* short_int soundfile*/
88         break;
89       case 'l': p->outformat = AE_LONG;   /* long_int soundfile */
90         break;
91       case 'u': p->outformat = AE_ULAW;   /* mu-law soundfile */
92         break;
93       case '3': p->outformat = AE_24INT;  /* 24bit packed soundfile*/
94         break;
95       case 'e': p->outformat = AE_FLOAT;  /* float soundfile (for rescaling) */
96         break;
97       default:
98         return outformch; /* do nothing */
99     };
100 
101     return c;
102 }
103 
104 typedef struct scalepoint {
105   double y0;
106   double y1;
107   double yr;
108   int32_t x0;
109   int32_t x1;
110   struct scalepoint *next;
111 } scalepoint;
112 
113 static const scalepoint stattab = { 0.0, 0.0, 0.0, 0, 0, NULL };
114 
115 typedef struct {
116   double     ff;
117   int32_t        table_used;
118   scalepoint scale_table;
119   scalepoint *end_table;
120   SOUNDIN    *p;
121 } SCALE;
122 
123 /* Static function prototypes */
124 
125 static void  InitScaleTable(CSOUND *,SCALE *, double, char *);
126 static SNDFILE *SCsndgetset(CSOUND *, SCALE *, char *);
127 static void  ScaleSound(CSOUND *, SCALE *, SNDFILE *, SNDFILE *, OPARMS *);
128 static float FindAndReportMax(CSOUND *, SCALE *, SNDFILE *, OPARMS *);
129 
scale(CSOUND * csound,int32_t argc,char ** argv)130 static int32_t scale(CSOUND *csound, int32_t argc, char **argv)
131 {
132     char        *inputfile = NULL;
133     double      factor = 0.0;
134     double      maximum = 0.0;
135     char        *factorfile = NULL;
136     SNDFILE     *infile = NULL, *outfile;
137     void        *fd;
138     char        outformch = 's', c, *s;
139     const char  *envoutyp;
140     SF_INFO     sfinfo;
141     OPARMS      O;
142     SCALE       sc;
143     unsigned    outbufsiz;
144 
145     memset(&sc, 0, sizeof(SCALE));
146     sc.ff = 0.0;
147     sc.table_used = 0;
148     sc.scale_table = stattab;
149     sc.end_table = &sc.scale_table;
150 
151     O.filetyp = O.outformat = 0;
152     O.ringbell = O.heartbeat = 0;
153     /* Check arguments */
154     if ((envoutyp = csound->GetEnv(csound, "SFOUTYP")) != NULL) {
155       if (strcmp(envoutyp, "AIFF") == 0)
156         O.filetyp = TYP_AIFF;
157       else if (strcmp(envoutyp, "WAV") == 0)
158         O.filetyp = TYP_WAV;
159       else if (strcmp(envoutyp, "IRCAM") == 0)
160         O.filetyp = TYP_IRCAM;
161       else {
162         csound->Die(csound, Str("%s not a recognized SFOUTYP env setting"),
163                             envoutyp);
164       }
165     }
166     if (UNLIKELY(!(--argc)))
167       usage(csound, Str("Insufficient arguments"));
168     do {
169       s = *++argv;
170       if (*s++ == '-')                  /* read all flags:  */
171         while ((c = *s++) != '\0')
172           switch(c) {
173           case 'o':
174             FIND(Str("no outfilename"))
175             O.outfilename = s;         /* soundout name */
176             for ( ; *s != '\0'; s++) ;
177             if (UNLIKELY(strcmp(O.outfilename, "stdin") == 0))
178               csound->Die(csound, "%s", Str("-o cannot be stdin"));
179 #if defined(WIN32)
180             if (UNLIKELY(strcmp(O.outfilename, "stdout") == 0)) {
181               csound->Die(csound, "%s", Str("stdout audio not supported"));
182             }
183 #endif
184             break;
185           case 'A':
186             O.filetyp = TYP_AIFF;      /* AIFF output request  */
187             break;
188           case 'J':
189             O.filetyp = TYP_IRCAM;     /* IRCAM output request */
190             break;
191           case 'W':
192             O.filetyp = TYP_WAV;       /* WAV output request  */
193             break;
194           case 'F':
195             FIND(Str("no scale factor"));
196             if (isdigit(*s) || *s == '-' || *s == '+')
197               factor = atof(s);
198             else
199               factorfile = s;
200             while (*++s);
201             break;
202           case 'M':
203             FIND(Str("No maximum"));
204             maximum = atof(s);
205             while (*++s);
206             break;
207           case 'P':       /* Percentage */
208             FIND(Str("No maximum"));
209             maximum = atof(s) * 0.01 * csound->Get0dBFS(csound);
210             while (*++s);
211             break;
212           case 'h':
213             O.filetyp = TYP_RAW;       /* skip sfheader  */
214             break;
215           case 'c':
216           case 'a':
217           case 'u':
218           case '8':
219           case 's':
220           case '3':
221           case 'l':
222           case 'f':
223             outformch = set_output_format(&O, c, outformch);
224             break;
225           case 'R':
226             O.rewrt_hdr = 1;
227             break;
228           case 'H':
229             if (isdigit(*s)) {
230               int32_t n;
231               sscanf(s, "%d%n", &O.heartbeat, &n);
232               s += n;
233             }
234             else O.heartbeat = 1;
235             break;
236           case 'N':
237             O.ringbell = 1;             /* notify on completion */
238             break;
239           default:
240             {
241               char  err_msg[64];
242               snprintf(err_msg, 64, Str("unknown flag -%c"), c);
243               usage(csound, err_msg);
244             }
245           }
246       else if (inputfile == NULL) {
247         inputfile = --s;
248       }
249       else usage(csound, Str("too many arguments"));
250     } while (--argc);
251 
252  retry:
253     /* Read sound file */
254     if (UNLIKELY(inputfile == NULL)) return -1;
255     if (UNLIKELY(!(infile = SCsndgetset(csound, &sc, inputfile)))) {
256       csound->Message(csound, Str("%s: error while opening %s"),
257                               argv[0], inputfile);
258       return -1;
259     }
260     if (factor != 0.0 || factorfile != NULL) {          /* perform scaling */
261       if (!O.filetyp)
262         O.filetyp = sc.p->filetyp;
263       if (!O.outformat)
264         O.outformat = sc.p->format;
265       O.sfheader = (O.filetyp == TYP_RAW ? 0 : 1);
266       O.sfsampsize = csound->sfsampsize(FORMAT2SF(O.outformat));
267       if (!O.sfheader)
268         O.rewrt_hdr = 0;
269       if (O.outfilename == NULL)
270         O.outfilename = "test";
271       csound->SetUtilSr(csound, (MYFLT)sc.p->sr);
272       csound->SetUtilNchnls(csound, sc.p->nchanls);
273 
274       memset(&sfinfo, 0, sizeof(SF_INFO));
275       //sfinfo.frames = 0/*was -1*/;
276       sfinfo.samplerate = (int32_t) ( sc.p->sr); // p->sr is int already
277       sfinfo.channels = sc.p->nchanls;
278       sfinfo.format = TYPE2SF(O.filetyp) | FORMAT2SF(O.outformat);
279       /* open file for write */
280       fd = NULL;
281       if (strcmp(O.outfilename, "stdout") == 0 ||
282           strcmp(O.outfilename, "-") == 0) {
283         outfile = sf_open_fd(1, SFM_WRITE, &sfinfo, 0);
284         if (outfile != NULL) {
285           if (UNLIKELY((fd =
286                         csound->CreateFileHandle(csound, &outfile,
287                                                  CSFILE_SND_W, "stdout")) == NULL)) {
288             sf_close(outfile);
289             csound->Die(csound, "%s", Str("Memory allocation failure"));
290           }
291         }
292       }
293       else
294         fd = csound->FileOpen2(csound, &outfile, CSFILE_SND_W,
295                        O.outfilename, &sfinfo, "SFDIR",
296                        csound->type2csfiletype(O.filetyp, O.outformat), 0);
297       if (UNLIKELY(fd == NULL))
298         csound->Die(csound, Str("Failed to open output file %s: %s"),
299                     O.outfilename, Str(sf_strerror(NULL)));
300       outbufsiz = 1024 * O.sfsampsize;    /* calc outbuf size  */
301       csound->Message(csound, Str("writing %d-byte blks of %s to %s %s\n"),
302                               (int32_t) outbufsiz,
303                               csound->getstrformat(O.outformat),
304                               O.outfilename,
305                               csound->type2string(O.filetyp));
306       InitScaleTable(csound, &sc, factor, factorfile);
307       ScaleSound(csound, &sc, infile, outfile, &O);
308     }
309     else if (maximum != 0.0) {
310       float mm = FindAndReportMax(csound, &sc, infile, &O);
311       factor = maximum / mm;
312       goto retry;
313     }
314     else
315       FindAndReportMax(csound, &sc, infile, &O);
316     if (O.ringbell)
317       csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c", '\007');
318     return 0;
319 }
320 
InitScaleTable(CSOUND * csound,SCALE * thissc,double factor,char * factorfile)321 static void InitScaleTable(CSOUND *csound, SCALE *thissc,
322                            double factor, char *factorfile)
323 {
324     if (factor != 0.0) thissc->ff = factor;
325     else {
326       FILE    *f;
327       double  samplepert = (double)thissc->p->sr;
328       double  x, y;
329       if (UNLIKELY(csound->FileOpen2(csound, &f, CSFILE_STD, factorfile, "r", NULL,
330                                      CSFTYPE_FLOATS_TEXT, 0) == NULL))
331         csound->Die(csound, Str("Failed to open %s"), factorfile);
332       while (fscanf(f, "%lf %lf\n", &x, &y) == 2) {
333         scalepoint *newpoint =
334           (scalepoint*) csound->Malloc(csound, sizeof(scalepoint));
335         thissc->end_table->next = newpoint;
336         newpoint->x0 = thissc->end_table->x1;
337         newpoint->y0 = thissc->end_table->y1;
338         newpoint->x1 = (int32_t) (x*samplepert);
339         newpoint->y1 = y;
340         newpoint->yr =
341           (x == newpoint->x0 ?
342            y - newpoint->y0 :
343            (y - newpoint->y0)/((double)(newpoint->x1 - newpoint->x0)));
344         newpoint->next = NULL;
345         thissc->end_table = newpoint;
346       }
347       {
348         scalepoint *newpoint = (scalepoint*)
349           csound->Malloc(csound,sizeof(scalepoint));
350         thissc->end_table->next = newpoint;
351         newpoint->x0 = thissc->end_table->x1;
352         newpoint->y0 = thissc->end_table->y1;
353         newpoint->x1 = 0x7fffffff;
354         newpoint->y1 = 0.0;
355         newpoint->next = NULL;
356         newpoint->yr = (x == newpoint->x0 ?
357                         -newpoint->y0 :
358                         -newpoint->y0/((double)(0x7fffffff-newpoint->x0)));
359       }
360       thissc->end_table = &thissc->scale_table;
361 /*      { */
362 /*          scalepoint *tt = &thissc->scale_table; */
363 /*          csound->Message(csound, "Scale table is\n"); */
364 /*          while (tt != NULL) { */
365 /*              csound->Message(csound, "(%d %f) -> %d %f [%f]\n", */
366 /*                      tt->x0, tt->y0, tt->x1, tt->y1, tt->yr); */
367 /*              tt = tt->next; */
368 /*          } */
369 /*          csound->Message(csound, "END of Table\n"); */
370 /*      } */
371       thissc->table_used = 1;
372     }
373 }
374 
gain(SCALE * thissc,int32_t i)375 static double gain(SCALE *thissc, int32_t i)
376 {
377     if (!thissc->table_used) return thissc->ff;
378     while (i<thissc->end_table->x0 ||
379            i>thissc->end_table->x1) {/* Get correct segment */
380 /*      csound->Message(csound, "Next table: %d (%d %f) -> %d %f [%f]\n", */
381       /*            i, thissc->end_table->x0, thissc->end_table->y0, */
382       /*            thissc->end_table->x1, thissc->end_table->y1, */
383 /*            thissc->end_table->yr); */
384         thissc->end_table = thissc->end_table->next;
385     }
386     return thissc->end_table->y0 +
387       thissc->end_table->yr * (double)(i - thissc->end_table->x0);
388 }
389 
390 static SNDFILE *
SCsndgetset(CSOUND * csound,SCALE * thissc,char * inputfile)391 SCsndgetset(CSOUND *csound, SCALE *thissc, char *inputfile)
392 {
393     SNDFILE *infile;
394     double  dur;
395     SOUNDIN *p;
396 
397     csound->SetUtilSr(csound, FL(0.0));         /* set esr 0. with no orchestra */
398     thissc->p = p = (SOUNDIN *) csound->Calloc(csound, sizeof(SOUNDIN));
399     p->channel = ALLCHNLS;
400     p->skiptime = FL(0.0);
401     p->analonly = 1;
402     strNcpy(p->sfname, inputfile, MAXSNDNAME-1);//p->sfname[MAXSNDNAME-1]='\0';
403     if ((infile = csound->sndgetset(csound, p)) == 0) /*open sndfil, do skptim*/
404       return(0);
405     p->getframes = p->framesrem;
406     dur = (double) p->getframes / p->sr;
407     csound->Message(csound, "%s %" PRId64 " %s (%3.1f secs)\n",
408                     Str("scaling"), p->getframes, Str("sample frame"), dur);
409     return(infile);
410 }
411 
412 #define BUFFER_LEN (1024)
413 
414 static void
ScaleSound(CSOUND * csound,SCALE * thissc,SNDFILE * infile,SNDFILE * outfd,OPARMS * oparms)415 ScaleSound(CSOUND *csound, SCALE *thissc, SNDFILE *infile,
416            SNDFILE *outfd, OPARMS *oparms)
417 {
418     MYFLT buffer[BUFFER_LEN];
419     long  read_in;
420     double tpersample;
421     double max, min;
422     long  mxpos, minpos;
423     int32_t   maxtimes, mintimes;
424     int32_t   i, j, chans = thissc->p->nchanls;
425     int32_t   block = 0;
426     int32_t   bufferLenFrames = (int32_t) BUFFER_LEN / chans;
427     int32_t   bufferLenSamples = bufferLenFrames * chans;
428 
429     tpersample = 1.0 / (double) thissc->p->sr;
430     max = 0.0;  mxpos = 0; maxtimes = 0;
431     min = 0.0;  minpos = 0; mintimes = 0;
432     while ((read_in = csound->getsndin(csound, infile, buffer,
433                                        bufferLenSamples, thissc->p)) > 0) {
434       for (i = 0; i < read_in; i++) {
435         j = (i / chans) + (bufferLenFrames * block);
436         buffer[i] = buffer[i] * gain(thissc, j);
437         if (buffer[i] >= max) ++maxtimes;
438         if (buffer[i] <= min) ++mintimes;
439         if (buffer[i] > max)
440           max = buffer[i], mxpos = i + bufferLenSamples * block, maxtimes = 1;
441         if (buffer[i] < min)
442           min = buffer[i], minpos = i + bufferLenSamples * block, mintimes = 1;
443         buffer[i] *= (1.0/csound->Get0dBFS(csound));
444       }
445       sf_write_MYFLT(outfd, buffer, read_in);
446       block++;
447       if (oparms->heartbeat) {
448         csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c\b", "|/-\\"[block&3]);
449       }
450     }
451     csound->Message(csound, Str("Max val %.3f at index %ld (time %.4f, chan %d) "
452                                 "%d times\n"), max, (long) mxpos / (long) chans,
453                             tpersample * (double) mxpos / (double) chans,
454                             ((int32_t) mxpos % chans) + 1, (int32_t) maxtimes);
455     csound->Message(csound, Str("Min val %.3f at index %ld (time %.4f, chan %d) "
456                                 "%d times\n"), min, (long) minpos / (long) chans,
457                             tpersample * (double) minpos / (double) chans,
458                             ((int32_t) minpos % chans) + 1, (int32_t) mintimes);
459     csound->Message(csound, Str("Max scale factor = %.3f\n"),
460                             (double) csound->Get0dBFS(csound) / (max > -min ?
461                                                                  max:-min));
462 }
463 
FindAndReportMax(CSOUND * csound,SCALE * thissc,SNDFILE * infile,OPARMS * oparms)464 static float FindAndReportMax(CSOUND *csound, SCALE *thissc,
465                               SNDFILE *infile, OPARMS *oparms)
466 {
467     MYFLT   buffer[BUFFER_LEN];
468     long    read_in;
469     double  tpersample;
470     double  max, min;
471     long    mxpos, minpos;
472     int32_t     maxtimes, mintimes;
473     int32_t     i, chans = thissc->p->nchanls;
474     int32_t     block = 0;
475     int32_t     bufferLenFrames = (int32_t) BUFFER_LEN / chans;
476     int32_t     bufferLenSamples = bufferLenFrames * chans;
477 
478     tpersample = 1.0 / (double) thissc->p->sr;
479     max = 0.0;  mxpos = 0; maxtimes = 0;
480     min = 0.0;  minpos = 0; mintimes = 0;
481     while ((read_in = csound->getsndin(csound, infile, buffer,
482                                        bufferLenSamples, thissc->p)) > 0) {
483       for (i = 0; i < read_in; i++) {
484         //j = (i / chans) + (bufferLenFrames * block);
485         if (buffer[i] >= max) ++maxtimes;
486         if (buffer[i] <= min) ++mintimes;
487         if (buffer[i] > max)
488           max = buffer[i], mxpos = i + bufferLenSamples * block, maxtimes = 1;
489         if (buffer[i] < min)
490           min = buffer[i], minpos = i + bufferLenSamples * block, mintimes = 1;
491       }
492       block++;
493       if (oparms->heartbeat) {
494         csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c\b", "|/-\\"[block&3]);
495       }
496     }
497     csound->Message(csound, Str("Max val %.3f at index %ld (time %.4f, chan %d) "
498                                 "%d times\n"), max, (long) mxpos / (long) chans,
499                             tpersample * (double) mxpos / (double) chans,
500                             ((int32_t) mxpos % chans) + 1, (int32_t) maxtimes);
501     csound->Message(csound, Str("Min val %.3f at index %ld (time %.4f, chan %d) "
502                                 "%d times\n"), min, (long) minpos / (long) chans,
503                             tpersample * (double) minpos / (double) chans,
504                             ((int32_t) minpos % chans) + 1, (int32_t) mintimes);
505     csound->Message(csound, Str("Max scale factor = %.3f\n"),
506                             (double) csound->Get0dBFS(csound)/ (max > -min ?
507                                                                 max:-min));
508     return (float) (max > -min ? max : -min);
509 }
510 
511 /* module interface */
512 
scale_init_(CSOUND * csound)513 int32_t scale_init_(CSOUND *csound)
514 {
515     int32_t retval = csound->AddUtility(csound, "scale", scale);
516     if (retval)
517       return retval;
518     return
519       csound->SetUtilityDescription(csound, "scale",
520                                     Str("Reports and/or adjusts maximum gain"));
521 }
522