ref: 76caa137725af8725d869657b9c91f7a1523ea0e
dir: /src/compand.c/
/* * Compander effect * * Written by Nick Bailey (nick@polonius.demon.co.uk or * N.J.Bailey@leeds.ac.uk). Hope page for this effect: * http://www.ee.keeds.ac.uk/homes/NJB/Softwere/Compand/compand.html * * Copyright 1999 Chris Bagwell And Nick Bailey * This source code is freely redistributable and may be used for * any purpose. This copyright notice must be maintained. * Chris Bagwell And Nick Bailey are not responsible for * the consequences of using this software. */ #include <string.h> #include <stdlib.h> #include <math.h> #include "st.h" /* Private data for SKEL file */ typedef struct { int expectedChannels; /* Also flags that channels aren't to be treated individually when = 1 and input not mono */ int transferPoints; /* Number of points specified on the transfer function */ double *attackRate; /* An array of attack rates */ double *decayRate; /* ... and of decay rates */ double *transferIns; /* ... and points on the transfer function */ double *transferOuts; double *volume; /* Current "volume" of each channel */ LONG *lastSamp; /* Remeber the value of the previous sample */ double outgain; /* Post processor gain */ } *compand_t; /* * Process options * * Don't do initialization now. * The 'info' fields are not yet filled in. */ void compand_getopts(effp, n, argv) eff_t effp; int n; char **argv; { compand_t l = (compand_t) effp->priv; if (n < 2 || n > 4) fail("Wrong number of arguments for the compander effect\n" "Use: {<attack_time>,<decay_time>}+ {<dB_in>,<db_out>}+ " "[<dB_postamp>]\n" "where {}+ means `one or more in a comma-separated, " "white-space-free list'\n" "and [] indications possible omission. dB values are floating\n" "point or `-inf'; times are in seconds."); else { /* Right no. of args, but are they well formed? */ char *s; int rates, tfers, i, commas; /* Start by checking the attack and decay rates */ for (s = argv[0], commas = 0; *s; ++s) if (*s == ',') ++commas; if (commas % 2 == 0) /* There must be an even number of attack/decay parameters */ fail("compander: Odd number of attack & decay rate parameters"); rates = 1 + commas/2; if ((l->attackRate = malloc(sizeof(double) * rates)) == NULL || (l->decayRate = malloc(sizeof(double) * rates)) == NULL || (l->volume = malloc(sizeof(double) * rates)) == NULL || (l->lastSamp = calloc(rates, sizeof(LONG))) == NULL) fail("Out of memory"); l->expectedChannels = rates; /* Now tokenise the rates string and set up these arrays. Keep them in seconds at the moment: we don't know the sample rate yet. */ s = strtok(argv[0], ","); i = 0; do { l->attackRate[i] = atof(s); s = strtok(NULL, ","); l->decayRate[i] = atof(s); s = strtok(NULL, ","); ++i; } while (s != NULL); /* Same business, but this time for the transfer function */ for (s = argv[1], commas = 0; *s; ++s) if (*s == ',') ++commas; if (commas % 2 == 0) /* There must be an even number of transfer parameters */ fail("compander: Odd number of transfer function parameters\n" "Each input value in dB must have a corresponding output value"); tfers = 3 + commas/2; /* 0, 0 at start; 1, 1 at end */ if ((l->transferIns = malloc(sizeof(double) * tfers)) == NULL || (l->transferOuts = malloc(sizeof(double) * tfers)) == NULL) fail("Out of memory"); l->transferPoints = tfers; l->transferIns[0] = 0.0; l->transferOuts[0] = 0.0; l->transferIns[tfers-1] = 1.0; l->transferOuts[tfers-1] = 1.0; s = strtok(argv[1], ","); i = 1; do { if (!strcmp(s, "-inf")) fail("Input signals of zero level must always generate zero output"); l->transferIns[i] = pow(10.0, atof(s)/20.0); if (l->transferIns[i] > 1.0) fail("dB values are relative to maximum input, and, ipso facto, " "cannot exceed 0"); if (l->transferIns[i] == 1.0) /* Final point was explicit */ --(l->transferPoints); if (i > 0 && l->transferIns[i] <= l->transferIns[i-1]) fail("Transfer function points don't have strictly ascending " "input amplitude"); s = strtok(NULL, ","); l->transferOuts[i] = strcmp(s, "-inf") ? pow(10.0, atof(s)/20.0) : 0; s = strtok(NULL, ","); ++i; } while (s != NULL); /* If there is a postprocessor gain, store it */ if (n >= 3) l->outgain = pow(10.0, atof(argv[2])/20.0); else l->outgain = 1.0; /* Set the initial "volume" to be attibuted to the input channels. Unless specified, choose 1.0 (maximum) otherwise clipping will result if the user has seleced a long attack time */ for (i = 0; i < l->expectedChannels; ++i) { double v = n>=4 ? pow(10.0, atof(argv[3])/20) : 1.0; l->volume[i] = v; } } } /* * Prepare processing. * Do all initializations. */ void compand_start(effp) eff_t effp; { compand_t l = (compand_t) effp->priv; int i; # ifdef DEBUG { printf("Starting compand effect\n"); printf("\nRate %ld, size %d, style %d, output gain %g.\n", effp->outinfo.rate, effp->outinfo.size, effp->outinfo.style, l->outgain); printf("%d input channel(s) expected: actually %d\n", l->expectedChannels, effp->outinfo.channels); printf("\nAttack and decay rates\n" "======================\n"); for (i = 0; i < l->expectedChannels; ++i) printf("Channel %d: attack = %-12g decay = %-12g\n", i, l->attackRate[i], l->decayRate[i]); printf("\nTransfer function (linear values)\n" "================= =============\n"); for (i = 0; i < l->transferPoints; ++i) printf("%12g -> %-12g\n", l->transferIns[i], l->transferOuts[i]); } # endif /* Convert attack and decay rates using number of samples */ for (i = 0; i < l->expectedChannels; ++i) { if (l->attackRate[i] > 1.0/effp->outinfo.rate) l->attackRate[i] = 1.0 - exp(-1.0/(effp->outinfo.rate * l->attackRate[i])); else l->attackRate[i] = 1.0; if (l->decayRate[i] > 1.0/effp->outinfo.rate) l->decayRate[i] = 1.0 - exp(-1.0/(effp->outinfo.rate * l->decayRate[i])); else l->decayRate[i] = 1.0; } } /* * Update a volume value using the given sample * value, the attack rate and decay rate */ static void doVolume(double *v, double samp, compand_t l, int chan) { double s = samp/(~((LONG)1<<31)); double delta = s - *v; if (delta > 0.0) /* increase volume according to attack rate */ *v += delta * l->attackRate[chan]; else /* reduce volume according to decay rate */ *v += delta * l->decayRate[chan]; } /* * Processed signed long samples from ibuf to obuf. * Return number of samples processed. */ void compand_flow(effp, ibuf, obuf, isamp, osamp) eff_t effp; LONG *ibuf, *obuf; int *isamp, *osamp; { compand_t l = (compand_t) effp->priv; int len = (*isamp > *osamp) ? *osamp : *isamp; int filechans = effp->outinfo.channels; int done; for (done = 0; done < len; done += filechans, obuf += filechans, ibuf += filechans) { int chan; /* Maintain the volume fields by simulating a leaky pump circuit */ for (chan = 0; chan < filechans; ++chan) { if (l->expectedChannels == 1 && filechans > 1) { /* User is expecting same compander for all channels */ int i; double maxsamp = 0.0; for (i = 0; i < filechans; ++i) { double rect = fabs(ibuf[i]); if (rect > maxsamp) maxsamp = rect; } doVolume(&l->volume[0], maxsamp, l, 0); break; } else doVolume(&l->volume[chan], fabs(ibuf[chan]), l, chan); } /* Volume memory is updated: perform compand */ for (chan = 0; chan < filechans; ++chan) { double v = l->expectedChannels > 1 ? l->volume[chan] : l->volume[0]; double outv; int piece; for (piece = 1 /* yes, 1 */; piece < l->transferPoints; ++piece) if (v >= l->transferIns[piece - 1] && v < l->transferIns[piece]) break; outv = l->transferOuts[piece-1] + (l->transferOuts[piece] - l->transferOuts[piece-1]) * (v - l->transferIns[piece-1]) / (l->transferIns[piece] - l->transferIns[piece-1]); obuf[chan] = ibuf[chan]*(outv/v)*l->outgain; } } *isamp = len; *osamp = len; }