/* scale.c: Copyright (C) 1994 John ffitch 2005 John ffitch modifications to utility This file is part of Csound. The Csound Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. Csound 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Csound; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /*******************************************************\ * scale.c * * scale a sound file by a float factor * * jpff 3 Sep 1994 after code by dpwe 19sep90 * * and a certain amount of lifting from Csound itself * \*******************************************************/ #include "std_util.h" #include "soundio.h" #include /* Constants */ #define FIND(MSG) if (*s == '\0') \ if (UNLIKELY(!(--argc) || ((s = *++argv) && *s == '-'))) \ csound->Die(csound, "%s", MSG); static const char *usage_txt[] = { Str_noop("Usage:\tscale [-flags] soundfile"), Str_noop("Legal flags are:"), Str_noop("-o fnam\tsound output filename"), Str_noop("-A\tcreate an AIFF format output soundfile"), Str_noop("-W\tcreate a WAV format output soundfile"), Str_noop("-h\tno header on output soundfile"), Str_noop("-c\t8-bit signed_char sound samples"), Str_noop("-a\talaw sound samples"), Str_noop("-u\tulaw sound samples"), Str_noop("-s\tshort_int sound samples"), Str_noop("-l\tlong_int sound samples"), Str_noop("-f\tfloat sound samples"), Str_noop("-F fpnum\tamount to scale amplitude"), Str_noop("-F file \tfile of scaling information (alternative)"), Str_noop("-M fpnum\tScale file to given maximum"), Str_noop("-P fpnum\tscale file to given percentage of full"), Str_noop("-R\tcontinually rewrite header while writing soundfile (WAV/AIFF)"), Str_noop("-H#\tprint a heartbeat style 1, 2 or 3 at each soundfile write"), Str_noop("-N\tnotify (ring the bell) when score or miditrack is done"), Str_noop("-- fnam\tlog output to file"), Str_noop("flag defaults: scale -s -otest -F 0.0"), Str_noop("If scale is 0.0 then reports maximum possible scaling"), NULL }; static void usage(CSOUND *csound, char *mesg) { int32_t i; for (i = 0; usage_txt[i] != NULL; i++) csound->Message(csound, "%s\n", Str(usage_txt[i])); csound->Die(csound, "\n%s", mesg); } static char set_output_format(OPARMS *p, char c, char outformch) { switch (c) { case 'a': p->outformat = AE_ALAW; /* a-law soundfile */ break; case 'c': p->outformat = AE_CHAR; /* signed 8-bit soundfile */ break; case '8': p->outformat = AE_UNCH; /* unsigned 8-bit soundfile */ break; case 'f': p->outformat = AE_FLOAT; /* float soundfile */ break; case 's': p->outformat = AE_SHORT; /* short_int soundfile*/ break; case 'l': p->outformat = AE_LONG; /* long_int soundfile */ break; case 'u': p->outformat = AE_ULAW; /* mu-law soundfile */ break; case '3': p->outformat = AE_24INT; /* 24bit packed soundfile*/ break; case 'e': p->outformat = AE_FLOAT; /* float soundfile (for rescaling) */ break; default: return outformch; /* do nothing */ }; return c; } typedef struct scalepoint { double y0; double y1; double yr; int32_t x0; int32_t x1; struct scalepoint *next; } scalepoint; static const scalepoint stattab = { 0.0, 0.0, 0.0, 0, 0, NULL }; typedef struct { double ff; int32_t table_used; scalepoint scale_table; scalepoint *end_table; SOUNDIN *p; } SCALE; /* Static function prototypes */ static void InitScaleTable(CSOUND *,SCALE *, double, char *); static SNDFILE *SCsndgetset(CSOUND *, SCALE *, char *); static void ScaleSound(CSOUND *, SCALE *, SNDFILE *, SNDFILE *, OPARMS *); static float FindAndReportMax(CSOUND *, SCALE *, SNDFILE *, OPARMS *); static int32_t scale(CSOUND *csound, int32_t argc, char **argv) { char *inputfile = NULL; double factor = 0.0; double maximum = 0.0; char *factorfile = NULL; SNDFILE *infile = NULL, *outfile; void *fd; char outformch = 's', c, *s; const char *envoutyp; SF_INFO sfinfo; OPARMS O; SCALE sc; unsigned outbufsiz; memset(&sc, 0, sizeof(SCALE)); sc.ff = 0.0; sc.table_used = 0; sc.scale_table = stattab; sc.end_table = &sc.scale_table; O.filetyp = O.outformat = 0; O.ringbell = O.heartbeat = 0; /* Check arguments */ if ((envoutyp = csound->GetEnv(csound, "SFOUTYP")) != NULL) { if (strcmp(envoutyp, "AIFF") == 0) O.filetyp = TYP_AIFF; else if (strcmp(envoutyp, "WAV") == 0) O.filetyp = TYP_WAV; else if (strcmp(envoutyp, "IRCAM") == 0) O.filetyp = TYP_IRCAM; else { csound->Die(csound, Str("%s not a recognized SFOUTYP env setting"), envoutyp); } } if (UNLIKELY(!(--argc))) usage(csound, Str("Insufficient arguments")); do { s = *++argv; if (*s++ == '-') /* read all flags: */ while ((c = *s++) != '\0') switch(c) { case 'o': FIND(Str("no outfilename")) O.outfilename = s; /* soundout name */ for ( ; *s != '\0'; s++) ; if (UNLIKELY(strcmp(O.outfilename, "stdin") == 0)) csound->Die(csound, "%s", Str("-o cannot be stdin")); #if defined(WIN32) if (UNLIKELY(strcmp(O.outfilename, "stdout") == 0)) { csound->Die(csound, "%s", Str("stdout audio not supported")); } #endif break; case 'A': O.filetyp = TYP_AIFF; /* AIFF output request */ break; case 'J': O.filetyp = TYP_IRCAM; /* IRCAM output request */ break; case 'W': O.filetyp = TYP_WAV; /* WAV output request */ break; case 'F': FIND(Str("no scale factor")); if (isdigit(*s) || *s == '-' || *s == '+') factor = atof(s); else factorfile = s; while (*++s); break; case 'M': FIND(Str("No maximum")); maximum = atof(s); while (*++s); break; case 'P': /* Percentage */ FIND(Str("No maximum")); maximum = atof(s) * 0.01 * csound->Get0dBFS(csound); while (*++s); break; case 'h': O.filetyp = TYP_RAW; /* skip sfheader */ break; case 'c': case 'a': case 'u': case '8': case 's': case '3': case 'l': case 'f': outformch = set_output_format(&O, c, outformch); break; case 'R': O.rewrt_hdr = 1; break; case 'H': if (isdigit(*s)) { int32_t n; sscanf(s, "%d%n", &O.heartbeat, &n); s += n; } else O.heartbeat = 1; break; case 'N': O.ringbell = 1; /* notify on completion */ break; default: { char err_msg[64]; snprintf(err_msg, 64, Str("unknown flag -%c"), c); usage(csound, err_msg); } } else if (inputfile == NULL) { inputfile = --s; } else usage(csound, Str("too many arguments")); } while (--argc); retry: /* Read sound file */ if (UNLIKELY(inputfile == NULL)) return -1; if (UNLIKELY(!(infile = SCsndgetset(csound, &sc, inputfile)))) { csound->Message(csound, Str("%s: error while opening %s"), argv[0], inputfile); return -1; } if (factor != 0.0 || factorfile != NULL) { /* perform scaling */ if (!O.filetyp) O.filetyp = sc.p->filetyp; if (!O.outformat) O.outformat = sc.p->format; O.sfheader = (O.filetyp == TYP_RAW ? 0 : 1); O.sfsampsize = csound->sfsampsize(FORMAT2SF(O.outformat)); if (!O.sfheader) O.rewrt_hdr = 0; if (O.outfilename == NULL) O.outfilename = "test"; csound->SetUtilSr(csound, (MYFLT)sc.p->sr); csound->SetUtilNchnls(csound, sc.p->nchanls); memset(&sfinfo, 0, sizeof(SF_INFO)); //sfinfo.frames = 0/*was -1*/; sfinfo.samplerate = (int32_t) ( sc.p->sr); // p->sr is int already sfinfo.channels = sc.p->nchanls; sfinfo.format = TYPE2SF(O.filetyp) | FORMAT2SF(O.outformat); /* open file for write */ fd = NULL; if (strcmp(O.outfilename, "stdout") == 0 || strcmp(O.outfilename, "-") == 0) { outfile = sf_open_fd(1, SFM_WRITE, &sfinfo, 0); if (outfile != NULL) { if (UNLIKELY((fd = csound->CreateFileHandle(csound, &outfile, CSFILE_SND_W, "stdout")) == NULL)) { sf_close(outfile); csound->Die(csound, "%s", Str("Memory allocation failure")); } } } else fd = csound->FileOpen2(csound, &outfile, CSFILE_SND_W, O.outfilename, &sfinfo, "SFDIR", csound->type2csfiletype(O.filetyp, O.outformat), 0); if (UNLIKELY(fd == NULL)) csound->Die(csound, Str("Failed to open output file %s: %s"), O.outfilename, Str(sf_strerror(NULL))); outbufsiz = 1024 * O.sfsampsize; /* calc outbuf size */ csound->Message(csound, Str("writing %d-byte blks of %s to %s %s\n"), (int32_t) outbufsiz, csound->getstrformat(O.outformat), O.outfilename, csound->type2string(O.filetyp)); InitScaleTable(csound, &sc, factor, factorfile); ScaleSound(csound, &sc, infile, outfile, &O); } else if (maximum != 0.0) { float mm = FindAndReportMax(csound, &sc, infile, &O); factor = maximum / mm; goto retry; } else FindAndReportMax(csound, &sc, infile, &O); if (O.ringbell) csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c", '\007'); return 0; } static void InitScaleTable(CSOUND *csound, SCALE *thissc, double factor, char *factorfile) { if (factor != 0.0) thissc->ff = factor; else { FILE *f; double samplepert = (double)thissc->p->sr; double x, y; if (UNLIKELY(csound->FileOpen2(csound, &f, CSFILE_STD, factorfile, "r", NULL, CSFTYPE_FLOATS_TEXT, 0) == NULL)) csound->Die(csound, Str("Failed to open %s"), factorfile); while (fscanf(f, "%lf %lf\n", &x, &y) == 2) { scalepoint *newpoint = (scalepoint*) csound->Malloc(csound, sizeof(scalepoint)); thissc->end_table->next = newpoint; newpoint->x0 = thissc->end_table->x1; newpoint->y0 = thissc->end_table->y1; newpoint->x1 = (int32_t) (x*samplepert); newpoint->y1 = y; newpoint->yr = (x == newpoint->x0 ? y - newpoint->y0 : (y - newpoint->y0)/((double)(newpoint->x1 - newpoint->x0))); newpoint->next = NULL; thissc->end_table = newpoint; } { scalepoint *newpoint = (scalepoint*) csound->Malloc(csound,sizeof(scalepoint)); thissc->end_table->next = newpoint; newpoint->x0 = thissc->end_table->x1; newpoint->y0 = thissc->end_table->y1; newpoint->x1 = 0x7fffffff; newpoint->y1 = 0.0; newpoint->next = NULL; newpoint->yr = (x == newpoint->x0 ? -newpoint->y0 : -newpoint->y0/((double)(0x7fffffff-newpoint->x0))); } thissc->end_table = &thissc->scale_table; /* { */ /* scalepoint *tt = &thissc->scale_table; */ /* csound->Message(csound, "Scale table is\n"); */ /* while (tt != NULL) { */ /* csound->Message(csound, "(%d %f) -> %d %f [%f]\n", */ /* tt->x0, tt->y0, tt->x1, tt->y1, tt->yr); */ /* tt = tt->next; */ /* } */ /* csound->Message(csound, "END of Table\n"); */ /* } */ thissc->table_used = 1; } } static double gain(SCALE *thissc, int32_t i) { if (!thissc->table_used) return thissc->ff; while (iend_table->x0 || i>thissc->end_table->x1) {/* Get correct segment */ /* csound->Message(csound, "Next table: %d (%d %f) -> %d %f [%f]\n", */ /* i, thissc->end_table->x0, thissc->end_table->y0, */ /* thissc->end_table->x1, thissc->end_table->y1, */ /* thissc->end_table->yr); */ thissc->end_table = thissc->end_table->next; } return thissc->end_table->y0 + thissc->end_table->yr * (double)(i - thissc->end_table->x0); } static SNDFILE * SCsndgetset(CSOUND *csound, SCALE *thissc, char *inputfile) { SNDFILE *infile; double dur; SOUNDIN *p; csound->SetUtilSr(csound, FL(0.0)); /* set esr 0. with no orchestra */ thissc->p = p = (SOUNDIN *) csound->Calloc(csound, sizeof(SOUNDIN)); p->channel = ALLCHNLS; p->skiptime = FL(0.0); p->analonly = 1; strNcpy(p->sfname, inputfile, MAXSNDNAME-1);//p->sfname[MAXSNDNAME-1]='\0'; if ((infile = csound->sndgetset(csound, p)) == 0) /*open sndfil, do skptim*/ return(0); p->getframes = p->framesrem; dur = (double) p->getframes / p->sr; csound->Message(csound, "%s %" PRId64 " %s (%3.1f secs)\n", Str("scaling"), p->getframes, Str("sample frame"), dur); return(infile); } #define BUFFER_LEN (1024) static void ScaleSound(CSOUND *csound, SCALE *thissc, SNDFILE *infile, SNDFILE *outfd, OPARMS *oparms) { MYFLT buffer[BUFFER_LEN]; long read_in; double tpersample; double max, min; long mxpos, minpos; int32_t maxtimes, mintimes; int32_t i, j, chans = thissc->p->nchanls; int32_t block = 0; int32_t bufferLenFrames = (int32_t) BUFFER_LEN / chans; int32_t bufferLenSamples = bufferLenFrames * chans; tpersample = 1.0 / (double) thissc->p->sr; max = 0.0; mxpos = 0; maxtimes = 0; min = 0.0; minpos = 0; mintimes = 0; while ((read_in = csound->getsndin(csound, infile, buffer, bufferLenSamples, thissc->p)) > 0) { for (i = 0; i < read_in; i++) { j = (i / chans) + (bufferLenFrames * block); buffer[i] = buffer[i] * gain(thissc, j); if (buffer[i] >= max) ++maxtimes; if (buffer[i] <= min) ++mintimes; if (buffer[i] > max) max = buffer[i], mxpos = i + bufferLenSamples * block, maxtimes = 1; if (buffer[i] < min) min = buffer[i], minpos = i + bufferLenSamples * block, mintimes = 1; buffer[i] *= (1.0/csound->Get0dBFS(csound)); } sf_write_MYFLT(outfd, buffer, read_in); block++; if (oparms->heartbeat) { csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c\b", "|/-\\"[block&3]); } } csound->Message(csound, Str("Max val %.3f at index %ld (time %.4f, chan %d) " "%d times\n"), max, (long) mxpos / (long) chans, tpersample * (double) mxpos / (double) chans, ((int32_t) mxpos % chans) + 1, (int32_t) maxtimes); csound->Message(csound, Str("Min val %.3f at index %ld (time %.4f, chan %d) " "%d times\n"), min, (long) minpos / (long) chans, tpersample * (double) minpos / (double) chans, ((int32_t) minpos % chans) + 1, (int32_t) mintimes); csound->Message(csound, Str("Max scale factor = %.3f\n"), (double) csound->Get0dBFS(csound) / (max > -min ? max:-min)); } static float FindAndReportMax(CSOUND *csound, SCALE *thissc, SNDFILE *infile, OPARMS *oparms) { MYFLT buffer[BUFFER_LEN]; long read_in; double tpersample; double max, min; long mxpos, minpos; int32_t maxtimes, mintimes; int32_t i, chans = thissc->p->nchanls; int32_t block = 0; int32_t bufferLenFrames = (int32_t) BUFFER_LEN / chans; int32_t bufferLenSamples = bufferLenFrames * chans; tpersample = 1.0 / (double) thissc->p->sr; max = 0.0; mxpos = 0; maxtimes = 0; min = 0.0; minpos = 0; mintimes = 0; while ((read_in = csound->getsndin(csound, infile, buffer, bufferLenSamples, thissc->p)) > 0) { for (i = 0; i < read_in; i++) { //j = (i / chans) + (bufferLenFrames * block); if (buffer[i] >= max) ++maxtimes; if (buffer[i] <= min) ++mintimes; if (buffer[i] > max) max = buffer[i], mxpos = i + bufferLenSamples * block, maxtimes = 1; if (buffer[i] < min) min = buffer[i], minpos = i + bufferLenSamples * block, mintimes = 1; } block++; if (oparms->heartbeat) { csound->MessageS(csound, CSOUNDMSG_REALTIME, "%c\b", "|/-\\"[block&3]); } } csound->Message(csound, Str("Max val %.3f at index %ld (time %.4f, chan %d) " "%d times\n"), max, (long) mxpos / (long) chans, tpersample * (double) mxpos / (double) chans, ((int32_t) mxpos % chans) + 1, (int32_t) maxtimes); csound->Message(csound, Str("Min val %.3f at index %ld (time %.4f, chan %d) " "%d times\n"), min, (long) minpos / (long) chans, tpersample * (double) minpos / (double) chans, ((int32_t) minpos % chans) + 1, (int32_t) mintimes); csound->Message(csound, Str("Max scale factor = %.3f\n"), (double) csound->Get0dBFS(csound)/ (max > -min ? max:-min)); return (float) (max > -min ? max : -min); } /* module interface */ int32_t scale_init_(CSOUND *csound) { int32_t retval = csound->AddUtility(csound, "scale", scale); if (retval) return retval; return csound->SetUtilityDescription(csound, "scale", Str("Reports and/or adjusts maximum gain")); }