ref: b3cb872a0070c189552eafd8a51f4d74998eeb44
dir: /src/bend.c/
/* libSoX effect: Pitch Bend (c) 2008 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 */ /* Portions based on http://www.dspdimension.com/download smbPitchShift.cpp: * * COPYRIGHT 1999-2006 Stephan M. Bernsee <smb [AT] dspdimension [DOT] com> * * The Wide Open License (WOL) * * Permission to use, copy, modify, distribute and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice and this license appear in all source copies. * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF * ANY KIND. See http://www.dspguru.com/wol.htm for more information. */ #ifdef NDEBUG /* Enable assert always. */ #undef NDEBUG /* Must undef above assert.h or other that might include it. */ #endif #include "sox_i.h" #include "getopt.h" #include <assert.h> #define MAX_FRAME_LENGTH 8192 typedef struct { unsigned nbends; /* Number of bends requested */ struct { char *str; /* Command-line argument to parse for this bend */ size_t start; /* Start bending when in_pos equals this */ double cents; size_t duration; /* Number of samples to bend */ } *bends; unsigned frame_rate; size_t in_pos; /* Number of samples read from the input stream */ unsigned bends_pos; /* Number of bends completed so far */ double shift; float gInFIFO[MAX_FRAME_LENGTH]; float gOutFIFO[MAX_FRAME_LENGTH]; double gFFTworksp[2 * MAX_FRAME_LENGTH]; float gLastPhase[MAX_FRAME_LENGTH / 2 + 1]; float gSumPhase[MAX_FRAME_LENGTH / 2 + 1]; float gOutputAccum[2 * MAX_FRAME_LENGTH]; float gAnaFreq[MAX_FRAME_LENGTH]; float gAnaMagn[MAX_FRAME_LENGTH]; float gSynFreq[MAX_FRAME_LENGTH]; float gSynMagn[MAX_FRAME_LENGTH]; long gRover; int fftFrameSize, ovsamp; } priv_t; static int parse(sox_effect_t * effp, char **argv, sox_rate_t rate) { priv_t *p = (priv_t *) effp->priv; size_t i, time = 0, delay; char const *next; for (i = 0; i < p->nbends; ++i) { if (argv) /* 1st parse only */ p->bends[i].str = lsx_strdup(argv[i]); next = lsx_parsesamples(rate, p->bends[i].str, &delay, 't'); if (next == NULL || *next != ',') break; p->bends[i].start = time += delay; p->bends[i].cents = strtod(next + 1, (char **)&next); if (p->bends[i].cents == 0 || *next != ',') break; next = lsx_parsesamples(rate, next + 1, &p->bends[i].duration, 't'); if (next == NULL || *next != '\0') break; time += p->bends[i].duration; } if (i < p->nbends) return lsx_usage(effp); return SOX_SUCCESS; } static int create(sox_effect_t * effp, int argc, char **argv) { priv_t *p = (priv_t *) effp->priv; char const * opts = "f:o:"; int c; p->frame_rate = 25; p->ovsamp = 16; while ((c = getopt(argc, argv, opts)) != -1) switch (c) { GETOPT_NUMERIC('f', frame_rate, 10 , 80) GETOPT_NUMERIC('o', ovsamp, 4 , 32) default: lsx_fail("unknown option `-%c'", optopt); return lsx_usage(effp); } argc -= optind, argv += optind; p->bends = lsx_calloc(p->nbends = argc, sizeof(*p->bends)); return parse(effp, argv, 1e6); /* No rate yet; parse with dummy */ } static int start(sox_effect_t * effp) { priv_t *p = (priv_t *) effp->priv; unsigned i; int n = effp->in_signal.rate / p->frame_rate + .5; for (p->fftFrameSize = 2; n > 2; p->fftFrameSize <<= 1, n >>= 1); assert(p->fftFrameSize <= MAX_FRAME_LENGTH); p->shift = 1; parse(effp, 0, effp->in_signal.rate); /* Re-parse now rate is known */ p->in_pos = p->bends_pos = 0; for (i = 0; i < p->nbends; ++i) if (p->bends[i].duration) return SOX_SUCCESS; return SOX_EFF_NULL; } static int flow(sox_effect_t * effp, const sox_sample_t * ibuf, sox_sample_t * obuf, size_t * isamp, size_t * osamp) { priv_t *p = (priv_t *) effp->priv; size_t i, len = *isamp = *osamp = min(*isamp, *osamp); double magn, phase, tmp, window, real, imag; double freqPerBin, expct; long k, qpd, index, inFifoLatency, stepSize, fftFrameSize2; float pitchShift = p->shift; /* set up some handy variables */ fftFrameSize2 = p->fftFrameSize / 2; stepSize = p->fftFrameSize / p->ovsamp; freqPerBin = effp->in_signal.rate / p->fftFrameSize; expct = 2. * M_PI * (double) stepSize / (double) p->fftFrameSize; inFifoLatency = p->fftFrameSize - stepSize; if (!p->gRover) p->gRover = inFifoLatency; /* main processing loop */ for (i = 0; i < len; i++) { ++p->in_pos; /* As long as we have not yet collected enough data just read in */ p->gInFIFO[p->gRover] = SOX_SAMPLE_TO_FLOAT_32BIT(ibuf[i], effp->clips); obuf[i] = SOX_FLOAT_32BIT_TO_SAMPLE( p->gOutFIFO[p->gRover - inFifoLatency], effp->clips); p->gRover++; /* now we have enough data for processing */ if (p->gRover >= p->fftFrameSize) { if (p->bends_pos != p->nbends && p->in_pos >= p->bends[p->bends_pos].start + p->bends[p->bends_pos].duration) { pitchShift = p->shift *= pow(2., p->bends[p->bends_pos].cents / 1200); ++p->bends_pos; } if (p->bends_pos != p->nbends && p->in_pos >= p->bends[p->bends_pos].start) { double progress = (double)(p->in_pos - p->bends[p->bends_pos].start) / p->bends[p->bends_pos].duration; progress = 1 - cos(M_PI * progress); progress *= p->bends[p->bends_pos].cents * (.5 / 1200); pitchShift = p->shift * pow(2., progress); } p->gRover = inFifoLatency; /* do windowing and re,im interleave */ for (k = 0; k < p->fftFrameSize; k++) { window = -.5 * cos(2 * M_PI * k / (double) p->fftFrameSize) + .5; p->gFFTworksp[2 * k] = p->gInFIFO[k] * window; p->gFFTworksp[2 * k + 1] = 0.; } /* ***************** ANALYSIS ******************* */ lsx_safe_cdft(2 * p->fftFrameSize, 1, p->gFFTworksp); /* this is the analysis step */ for (k = 0; k <= fftFrameSize2; k++) { /* de-interlace FFT buffer */ real = p->gFFTworksp[2 * k]; imag = - p->gFFTworksp[2 * k + 1]; /* compute magnitude and phase */ magn = 2. * sqrt(real * real + imag * imag); phase = atan2(imag, real); /* compute phase difference */ tmp = phase - p->gLastPhase[k]; p->gLastPhase[k] = phase; tmp -= (double) k *expct; /* subtract expected phase difference */ /* map delta phase into +/- Pi interval */ qpd = tmp / M_PI; if (qpd >= 0) qpd += qpd & 1; else qpd -= qpd & 1; tmp -= M_PI * (double) qpd; /* get deviation from bin frequency from the +/- Pi interval */ tmp = p->ovsamp * tmp / (2. * M_PI); /* compute the k-th partials' true frequency */ tmp = (double) k *freqPerBin + tmp * freqPerBin; /* store magnitude and true frequency in analysis arrays */ p->gAnaMagn[k] = magn; p->gAnaFreq[k] = tmp; } /* this does the actual pitch shifting */ memset(p->gSynMagn, 0, p->fftFrameSize * sizeof(float)); memset(p->gSynFreq, 0, p->fftFrameSize * sizeof(float)); for (k = 0; k <= fftFrameSize2; k++) { index = k * pitchShift; if (index <= fftFrameSize2) { p->gSynMagn[index] += p->gAnaMagn[k]; p->gSynFreq[index] = p->gAnaFreq[k] * pitchShift; } } for (k = 0; k <= fftFrameSize2; k++) { /* SYNTHESIS */ /* get magnitude and true frequency from synthesis arrays */ magn = p->gSynMagn[k], tmp = p->gSynFreq[k]; tmp -= (double) k *freqPerBin; /* subtract bin mid frequency */ tmp /= freqPerBin; /* get bin deviation from freq deviation */ tmp = 2. * M_PI * tmp / p->ovsamp; /* take p->ovsamp into account */ tmp += (double) k *expct; /* add the overlap phase advance back in */ p->gSumPhase[k] += tmp; /* accumulate delta phase to get bin phase */ phase = p->gSumPhase[k]; /* get real and imag part and re-interleave */ p->gFFTworksp[2 * k] = magn * cos(phase); p->gFFTworksp[2 * k + 1] = - magn * sin(phase); } for (k = p->fftFrameSize + 2; k < 2 * p->fftFrameSize; k++) p->gFFTworksp[k] = 0.; /* zero negative frequencies */ lsx_safe_cdft(2 * p->fftFrameSize, -1, p->gFFTworksp); /* do windowing and add to output accumulator */ for (k = 0; k < p->fftFrameSize; k++) { window = -.5 * cos(2. * M_PI * (double) k / (double) p->fftFrameSize) + .5; p->gOutputAccum[k] += 2. * window * p->gFFTworksp[2 * k] / (fftFrameSize2 * p->ovsamp); } for (k = 0; k < stepSize; k++) p->gOutFIFO[k] = p->gOutputAccum[k]; memmove(p->gOutputAccum, /* shift accumulator */ p->gOutputAccum + stepSize, p->fftFrameSize * sizeof(float)); for (k = 0; k < inFifoLatency; k++) /* move input FIFO */ p->gInFIFO[k] = p->gInFIFO[k + stepSize]; } } return SOX_SUCCESS; } static int stop(sox_effect_t * effp) { priv_t *p = (priv_t *) effp->priv; if (p->bends_pos != p->nbends) lsx_warn("Input audio too short; bends not applied: %u", p->nbends - p->bends_pos); return SOX_SUCCESS; } static int kill(sox_effect_t * effp) { priv_t *p = (priv_t *) effp->priv; unsigned i; for (i = 0; i < p->nbends; ++i) free(p->bends[i].str); free(p->bends); return SOX_SUCCESS; } sox_effect_handler_t const *sox_bend_effect_fn(void) { static sox_effect_handler_t handler = { "bend", "[-f frame-rate(25)] [-o over-sample(16)] {delay,cents,duration}", SOX_EFF_GETOPT, create, start, flow, 0, stop, kill, sizeof(priv_t) }; return &handler; }