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