ref: 4f128442d49e9f07699a06238230c1727b126d8c
dir: /src/effects.c/
/* SoX Effects chain (c) 2007 robs@users.sourceforge.net * * This 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. * * This library 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 this library; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define LSX_EFF_ALIAS #include "sox_i.h" #include <assert.h> #include <string.h> #ifdef HAVE_STRINGS_H #include <strings.h> #endif #define DEBUG_EFFECTS_CHAIN 0 /* Default effect handler functions for do-nothing situations: */ static int default_function(sox_effect_t * effp UNUSED) { return SOX_SUCCESS; } /* Pass through samples verbatim */ int lsx_flow_copy(sox_effect_t * effp UNUSED, const sox_sample_t * ibuf, sox_sample_t * obuf, size_t * isamp, size_t * osamp) { *isamp = *osamp = min(*isamp, *osamp); memcpy(obuf, ibuf, *isamp * sizeof(*obuf)); return SOX_SUCCESS; } /* Inform no more samples to drain */ static int default_drain(sox_effect_t * effp UNUSED, sox_sample_t *obuf UNUSED, size_t *osamp) { *osamp = 0; return SOX_EOF; } /* Check that no parameters have been given */ static int default_getopts(sox_effect_t * effp, int argc, char **argv UNUSED) { return --argc? lsx_usage(effp) : SOX_SUCCESS; } /* Partially initialise the effect structure; signal info will come later */ sox_effect_t * sox_create_effect(sox_effect_handler_t const * eh) { sox_effect_t * effp = lsx_calloc(1, sizeof(*effp)); effp->obuf = NULL; effp->global_info = sox_get_effects_globals(); effp->handler = *eh; if (!effp->handler.getopts) effp->handler.getopts = default_getopts; if (!effp->handler.start ) effp->handler.start = default_function; if (!effp->handler.flow ) effp->handler.flow = lsx_flow_copy; if (!effp->handler.drain ) effp->handler.drain = default_drain; if (!effp->handler.stop ) effp->handler.stop = default_function; if (!effp->handler.kill ) effp->handler.kill = default_function; effp->priv = lsx_calloc(1, effp->handler.priv_size); return effp; } /* sox_create_effect */ int sox_effect_options(sox_effect_t *effp, int argc, char * const argv[]) { int result; char * * argv2 = lsx_malloc((argc + 1) * sizeof(*argv2)); argv2[0] = (char *)effp->handler.name; memcpy(argv2 + 1, argv, argc * sizeof(*argv2)); result = effp->handler.getopts(effp, argc + 1, argv2); free(argv2); return result; } /* sox_effect_options */ /* Effects chain: */ sox_effects_chain_t * sox_create_effects_chain( sox_encodinginfo_t const * in_enc, sox_encodinginfo_t const * out_enc) { sox_effects_chain_t * result = lsx_calloc(1, sizeof(sox_effects_chain_t)); result->global_info = *sox_get_effects_globals(); result->in_enc = in_enc; result->out_enc = out_enc; return result; } /* sox_create_effects_chain */ void sox_delete_effects_chain(sox_effects_chain_t *ecp) { if (ecp && ecp->length) sox_delete_effects(ecp); free(ecp->effects); free(ecp); } /* sox_delete_effects_chain */ /* Effect can call in start() or flow() to set minimum input size to flow() */ int lsx_effect_set_imin(sox_effect_t * effp, size_t imin) { if (imin > sox_globals.bufsiz / effp->flows) { lsx_fail("sox_bufsiz not big enough"); return SOX_EOF; } effp->imin = imin; return SOX_SUCCESS; } /* Effects table to be extended in steps of EFF_TABLE_STEP */ #define EFF_TABLE_STEP 8 /* Add an effect to the chain. *in is the input signal for this effect. *out is * a suggestion as to what the output signal should be, but depending on its * given options and *in, the effect can choose to do differently. Whatever * output rate and channels the effect does produce are written back to *in, * ready for the next effect in the chain. */ int sox_add_effect(sox_effects_chain_t * chain, sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t const * out) { int ret, (*start)(sox_effect_t * effp) = effp->handler.start; size_t f; sox_effect_t eff0; /* Copy of effect for flow 0 before calling start */ effp->global_info = &chain->global_info; effp->in_signal = *in; effp->out_signal = *out; effp->in_encoding = chain->in_enc; effp->out_encoding = chain->out_enc; if (!(effp->handler.flags & SOX_EFF_CHAN)) effp->out_signal.channels = in->channels; if (!(effp->handler.flags & SOX_EFF_RATE)) effp->out_signal.rate = in->rate; if (!(effp->handler.flags & SOX_EFF_PREC)) effp->out_signal.precision = (effp->handler.flags & SOX_EFF_MODIFY)? in->precision : SOX_SAMPLE_PRECISION; if (!(effp->handler.flags & SOX_EFF_GAIN)) effp->out_signal.mult = in->mult; effp->flows = (effp->handler.flags & SOX_EFF_MCHAN)? 1 : effp->in_signal.channels; effp->clips = 0; effp->imin = 0; eff0 = *effp, eff0.priv = lsx_memdup(eff0.priv, eff0.handler.priv_size); eff0.in_signal.mult = NULL; /* Only used in channel 0 */ ret = start(effp); if (ret == SOX_EFF_NULL) { lsx_report("has no effect in this configuration"); free(eff0.priv); free(effp->priv); effp->priv = NULL; return SOX_SUCCESS; } if (ret != SOX_SUCCESS) { free(eff0.priv); return SOX_EOF; } if (in->mult) lsx_debug("mult=%g", *in->mult); if (!(effp->handler.flags & SOX_EFF_LENGTH)) { effp->out_signal.length = in->length; if (effp->out_signal.length != SOX_UNKNOWN_LEN) { if (effp->handler.flags & SOX_EFF_CHAN) effp->out_signal.length = effp->out_signal.length / in->channels * effp->out_signal.channels; if (effp->handler.flags & SOX_EFF_RATE) effp->out_signal.length = effp->out_signal.length / in->rate * effp->out_signal.rate + .5; } } *in = effp->out_signal; if (chain->length == chain->table_size) { chain->table_size += EFF_TABLE_STEP; lsx_debug_more("sox_add_effect: extending effects table, " "new size = %" PRIuPTR, chain->table_size); lsx_revalloc(chain->effects, chain->table_size); } chain->effects[chain->length] = lsx_calloc(effp->flows, sizeof(chain->effects[chain->length][0])); chain->effects[chain->length][0] = *effp; for (f = 1; f < effp->flows; ++f) { chain->effects[chain->length][f] = eff0; chain->effects[chain->length][f].flow = f; chain->effects[chain->length][f].priv = lsx_memdup(eff0.priv, eff0.handler.priv_size); if (start(&chain->effects[chain->length][f]) != SOX_SUCCESS) { free(eff0.priv); return SOX_EOF; } } ++chain->length; free(eff0.priv); return SOX_SUCCESS; } /* An effect's output buffer (effp->obuf) generally has this layout: * |. . . A1A2A3B1B2B3C1C2C3. . . . . . . . . . . . . . . . . . | * ^0 ^obeg ^oend ^bufsiz * (where A1 is the first sample of channel 1, A2 the first sample of * channel 2, etc.), i.e. the channels are interleaved. * However, while sox_flow_effects() is running, output buffers are * adapted to how the following effect expects its input, to avoid * back-and-forth conversions. If the following effect operates on * each of several channels separately (flows > 1), the layout is * changed to this uninterleaved form: * |. A1B1C1. . . . . . . A2B2C2. . . . . . . A3B3C3. . . . . . | * ^0 ^obeg ^oend ^bufsiz * <--- channel 1 ----><--- channel 2 ----><--- channel 3 ----> * The buffer is logically subdivided into channel buffers of size * bufsiz/flows each, starting at offsets 0, bufsiz/flows, * 2*(bufsiz/flows) etc. Within the channel buffers, the data starts * at position obeg/flows and ends before oend/flows. In case bufsiz * is not evenly divisible by flows, there will be an unused area at * the very end of the output buffer. * The interleave() and deinterleave() functions convert between these * two representations. */ static void interleave(size_t flows, size_t length, sox_sample_t *from, size_t bufsiz, size_t offset, sox_sample_t *to); static void deinterleave(size_t flows, size_t length, sox_sample_t *from, sox_sample_t *to, size_t bufsiz, size_t offset); static int flow_effect(sox_effects_chain_t * chain, size_t n) { sox_effect_t *effp1 = chain->effects[n - 1]; sox_effect_t *effp = chain->effects[n]; int effstatus = SOX_SUCCESS; size_t f = 0; size_t idone = effp1->oend - effp1->obeg; size_t obeg = sox_globals.bufsiz - effp->oend; sox_bool il_change = (effp->flows == 1) != (chain->length == n + 1 || chain->effects[n+1]->flows == 1); #if DEBUG_EFFECTS_CHAIN size_t pre_idone = idone; size_t pre_odone = obeg; #endif if (effp->flows == 1) { /* Run effect on all channels at once */ idone -= idone % effp->in_signal.channels; effstatus = effp->handler.flow(effp, effp1->obuf + effp1->obeg, il_change ? chain->il_buf : effp->obuf + effp->oend, &idone, &obeg); if (obeg % effp->out_signal.channels != 0) { lsx_fail("multi-channel effect flowed asymmetrically!"); effstatus = SOX_EOF; } if (il_change) deinterleave(chain->effects[n+1]->flows, obeg, chain->il_buf, effp->obuf, sox_globals.bufsiz, effp->oend); } else { /* Run effect on each channel individually */ sox_sample_t *obuf = il_change ? chain->il_buf : effp->obuf; size_t flow_offs = sox_globals.bufsiz/effp->flows; size_t idone_last = 0, odone_last = 0; /* Initialised to prevent warning */ #ifdef HAVE_OPENMP if (sox_globals.use_threads && effp->flows > 1) { #pragma omp parallel for for (f = 0; f < effp->flows; ++f) { size_t idonec = idone / effp->flows; size_t odonec = obeg / effp->flows; int eff_status_c = effp->handler.flow(&chain->effects[n][f], effp1->obuf + f*flow_offs + effp1->obeg/effp->flows, obuf + f*flow_offs + effp->oend/effp->flows, &idonec, &odonec); if (!f) { idone_last = idonec; odone_last = odonec; } if (eff_status_c != SOX_SUCCESS) effstatus = SOX_EOF; } } else /* sox_globals.use_threads */ #endif { for (f = 0; f < effp->flows; ++f) { size_t idonec = idone / effp->flows; size_t odonec = obeg / effp->flows; int eff_status_c = effp->handler.flow(&chain->effects[n][f], effp1->obuf + f*flow_offs + effp1->obeg/effp->flows, obuf + f*flow_offs + effp->oend/effp->flows, &idonec, &odonec); if (f && (idonec != idone_last || odonec != odone_last)) { lsx_fail("flowed asymmetrically!"); effstatus = SOX_EOF; } idone_last = idonec; odone_last = odonec; if (eff_status_c != SOX_SUCCESS) effstatus = SOX_EOF; } } idone = effp->flows * idone_last; obeg = effp->flows * odone_last; if (il_change) interleave(effp->flows, obeg, chain->il_buf, sox_globals.bufsiz, effp->oend, effp->obuf + effp->oend); } effp1->obeg += idone; if (effp1->obeg == effp1->oend) effp1->obeg = effp1->oend = 0; else if (effp1->oend - effp1->obeg < effp->imin) { /* Need to refill? */ size_t flow_offs = sox_globals.bufsiz/effp->flows; for (f = 0; f < effp->flows; ++f) memcpy(effp1->obuf + f * flow_offs, effp1->obuf + f * flow_offs + effp1->obeg/effp->flows, (effp1->oend - effp1->obeg)/effp->flows * sizeof(*effp1->obuf)); effp1->oend -= effp1->obeg; effp1->obeg = 0; } effp->oend += obeg; #if DEBUG_EFFECTS_CHAIN lsx_report("\t" "flow: %2" PRIuPTR " (%1" PRIuPTR ") " "%5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR " " "%5" PRIuPTR " [%" PRIuPTR "-%" PRIuPTR "]", n, effp->flows, pre_idone, pre_odone, idone, obeg, effp1->oend - effp1->obeg, effp1->obeg, effp1->oend); #endif return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF; } /* The same as flow_effect but with no input */ static int drain_effect(sox_effects_chain_t * chain, size_t n) { sox_effect_t *effp = chain->effects[n]; int effstatus = SOX_SUCCESS; size_t f = 0; size_t obeg = sox_globals.bufsiz - effp->oend; sox_bool il_change = (effp->flows == 1) != (chain->length == n + 1 || chain->effects[n+1]->flows == 1); #if DEBUG_EFFECTS_CHAIN size_t pre_odone = obeg; #endif if (effp->flows == 1) { /* Run effect on all channels at once */ effstatus = effp->handler.drain(effp, il_change ? chain->il_buf : effp->obuf + effp->oend, &obeg); if (obeg % effp->out_signal.channels != 0) { lsx_fail("multi-channel effect drained asymmetrically!"); effstatus = SOX_EOF; } if (il_change) deinterleave(chain->effects[n+1]->flows, obeg, chain->il_buf, effp->obuf, sox_globals.bufsiz, effp->oend); } else { /* Run effect on each channel individually */ sox_sample_t *obuf = il_change ? chain->il_buf : effp->obuf; size_t flow_offs = sox_globals.bufsiz/effp->flows; size_t odone_last = 0; /* Initialised to prevent warning */ for (f = 0; f < effp->flows; ++f) { size_t odonec = obeg / effp->flows; int eff_status_c = effp->handler.drain(&chain->effects[n][f], obuf + f*flow_offs + effp->oend/effp->flows, &odonec); if (f && (odonec != odone_last)) { lsx_fail("drained asymmetrically!"); effstatus = SOX_EOF; } odone_last = odonec; if (eff_status_c != SOX_SUCCESS) effstatus = SOX_EOF; } obeg = effp->flows * odone_last; if (il_change) interleave(effp->flows, obeg, chain->il_buf, sox_globals.bufsiz, effp->oend, effp->obuf + effp->oend); } if (!obeg) /* This is the only thing that drain has and flow hasn't */ effstatus = SOX_EOF; effp->oend += obeg; #if DEBUG_EFFECTS_CHAIN lsx_report("\t" "drain: %2" PRIuPTR " (%1" PRIuPTR ") " "%5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR " %5" PRIuPTR, n, effp->flows, (size_t)0, pre_odone, (size_t)0, obeg); #endif return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF; } /* Flow data through the effects chain until an effect or callback gives EOF */ int sox_flow_effects(sox_effects_chain_t * chain, int (* callback)(sox_bool all_done, void * client_data), void * client_data) { int flow_status = SOX_SUCCESS; size_t e, source_e = 0; /* effect indices */ size_t max_flows = 0; sox_bool draining = sox_true; for (e = 0; e < chain->length; ++e) { sox_effect_t *effp = chain->effects[e]; effp->obuf = lsx_realloc(effp->obuf, sox_globals.bufsiz * sizeof(*effp->obuf)); /* Memory will be freed by sox_delete_effect() later. */ /* Possibly there was already a buffer, if this is a used effect; it may still contain samples in that case. */ if (effp->oend > sox_globals.bufsiz) { lsx_warn("buffer size insufficient; buffered samples were dropped"); /* can only happen if bufsize has been reduced since the last run */ effp->obeg = effp->oend = 0; } max_flows = max(max_flows, effp->flows); } if (max_flows > 1) /* might need interleave buffer */ chain->il_buf = lsx_malloc(sox_globals.bufsiz * sizeof(sox_sample_t)); else chain->il_buf = NULL; /* Go through the effects, and if there are samples in one of the buffers, deinterleave it (if necessary). */ for (e = 0; e + 1 < chain->length; e++) { sox_effect_t *effp = chain->effects[e]; if (effp->oend > effp->obeg && chain->effects[e+1]->flows > 1) { sox_sample_t *sw = chain->il_buf; chain->il_buf = effp->obuf; effp->obuf = sw; deinterleave(chain->effects[e+1]->flows, effp->oend - effp->obeg, chain->il_buf, effp->obuf, sox_globals.bufsiz, effp->obeg); } } e = chain->length - 1; while (source_e < chain->length) { #define have_imin (e > 0 && e < chain->length && chain->effects[e - 1]->oend - chain->effects[e - 1]->obeg >= chain->effects[e]->imin) size_t osize = chain->effects[e]->oend - chain->effects[e]->obeg; if (e == source_e && (draining || !have_imin)) { if (drain_effect(chain, e) == SOX_EOF) { ++source_e; draining = sox_false; } } else if (have_imin && flow_effect(chain, e) == SOX_EOF) { flow_status = SOX_EOF; if (e == chain->length - 1) break; source_e = e; draining = sox_true; } if (e < chain->length && chain->effects[e]->oend - chain->effects[e]->obeg > osize) /* False for output */ ++e; else if (e == source_e) draining = sox_true; else if (e < source_e) e = source_e; else --e; if (callback && callback(source_e == chain->length, client_data) != SOX_SUCCESS) { flow_status = SOX_EOF; /* Client has requested to stop the flow. */ break; } } /* If an effect's output buffer still has samples, and if it is uninterleaved, then re-interleave it. Necessary since it might be reused, and at that time possibly followed by an MCHAN effect. */ for (e = 0; e + 1 < chain->length; e++) { sox_effect_t *effp = chain->effects[e]; if (effp->oend > effp->obeg && chain->effects[e+1]->flows > 1) { sox_sample_t *sw = chain->il_buf; chain->il_buf = effp->obuf; effp->obuf = sw; interleave(chain->effects[e+1]->flows, effp->oend - effp->obeg, chain->il_buf, sox_globals.bufsiz, effp->obeg, effp->obuf); } } free(chain->il_buf); return flow_status; } sox_uint64_t sox_effects_clips(sox_effects_chain_t * chain) { size_t i, f; uint64_t clips = 0; for (i = 1; i < chain->length - 1; ++i) for (f = 0; f < chain->effects[i][0].flows; ++f) clips += chain->effects[i][f].clips; return clips; } sox_uint64_t sox_stop_effect(sox_effect_t *effp) { size_t f; uint64_t clips = 0; for (f = 0; f < effp->flows; ++f) { effp[f].handler.stop(&effp[f]); clips += effp[f].clips; } return clips; } void sox_push_effect_last(sox_effects_chain_t *chain, sox_effect_t *effp) { if (chain->length == chain->table_size) { chain->table_size += EFF_TABLE_STEP; lsx_debug_more("sox_push_effect_last: extending effects table, " "new size = %" PRIuPTR, chain->table_size); lsx_revalloc(chain->effects, chain->table_size); } chain->effects[chain->length++] = effp; } /* sox_push_effect_last */ sox_effect_t *sox_pop_effect_last(sox_effects_chain_t *chain) { if (chain->length > 0) { sox_effect_t *effp; chain->length--; effp = chain->effects[chain->length]; chain->effects[chain->length] = NULL; return effp; } else return NULL; } /* sox_pop_effect_last */ /* Free resources related to effect. * Note: This currently closes down the effect which might * not be obvious from name. */ void sox_delete_effect(sox_effect_t *effp) { uint64_t clips; size_t f; if ((clips = sox_stop_effect(effp)) != 0) lsx_warn("%s clipped %" PRIu64 " samples; decrease volume?", effp->handler.name, clips); if (effp->obeg != effp->oend) lsx_debug("output buffer still held %" PRIuPTR " samples; dropped.", (effp->oend - effp->obeg)/effp->out_signal.channels); /* May or may not indicate a problem; it is normal if the user aborted processing, or if an effect like "trim" stopped early. */ effp->handler.kill(effp); /* N.B. only one kill; not one per flow */ for (f = 0; f < effp->flows; ++f) free(effp[f].priv); free(effp->obuf); free(effp); } void sox_delete_effect_last(sox_effects_chain_t *chain) { if (chain->length > 0) { chain->length--; sox_delete_effect(chain->effects[chain->length]); chain->effects[chain->length] = NULL; } } /* sox_delete_effect_last */ /* Remove all effects from the chain. * Note: This currently closes down the effect which might * not be obvious from name. */ void sox_delete_effects(sox_effects_chain_t * chain) { size_t e; for (e = 0; e < chain->length; ++e) { sox_delete_effect(chain->effects[e]); chain->effects[e] = NULL; } chain->length = 0; } /*----------------------------- Effects library ------------------------------*/ static sox_effect_fn_t s_sox_effect_fns[] = { #define EFFECT(f) lsx_##f##_effect_fn, #include "effects.h" #undef EFFECT NULL }; const sox_effect_fn_t* sox_get_effect_fns(void) { return s_sox_effect_fns; } /* Find a named effect in the effects library */ sox_effect_handler_t const * sox_find_effect(char const * name) { int e; sox_effect_fn_t const * fns = sox_get_effect_fns(); for (e = 0; fns[e]; ++e) { const sox_effect_handler_t *eh = fns[e] (); if (eh && eh->name && strcasecmp(eh->name, name) == 0) return eh; /* Found it. */ } return NULL; } /*----------------------------- Helper functions -----------------------------*/ /* interleave() parameters: * flows: number of samples per wide sample * length: number of samples to copy * [pertaining to the (non-interleaved) source buffer:] * from: start address * bufsiz: total size * offset: position at which to start reading * [pertaining to the (interleaved) destination buffer:] * to: start address */ static void interleave(size_t flows, size_t length, sox_sample_t *from, size_t bufsiz, size_t offset, sox_sample_t *to) { size_t i, f; size_t wide_samples = length/flows; size_t flow_offs = bufsiz/flows; from += offset/flows; for (i = 0; i < wide_samples; i++) for (f = 0; f < flows; f++) *to++ = from[f*flow_offs + i]; } /* deinterleave() parameters: * flows: number of samples per wide sample * length: number of samples to copy * [pertaining to the (interleaved) source buffer:] * from: start address * [pertaining to the (non-interleaved) destination buffer:] * to: start address * bufsiz: total size * offset: position at which to start writing */ static void deinterleave(size_t flows, size_t length, sox_sample_t *from, sox_sample_t *to, size_t bufsiz, size_t offset) { size_t i, f; size_t wide_samples = length/flows; size_t flow_offs = bufsiz/flows; to += offset/flows; for (i = 0; i < wide_samples; i++) for (f = 0; f < flows; f++) to[f*flow_offs + i] = *from++; }