On Sat, Nov 28, 2015 at 5:26 PM, Paul B Mahol <one...@gmail.com> wrote: > Signed-off-by: Paul B Mahol <one...@gmail.com> > --- > doc/filters.texi | 57 ++++++++++ > libavfilter/Makefile | 1 + > libavfilter/af_apulsator.c | 270 > +++++++++++++++++++++++++++++++++++++++++++++ > libavfilter/allfilters.c | 1 + > 4 files changed, 329 insertions(+) > create mode 100644 libavfilter/af_apulsator.c > > diff --git a/doc/filters.texi b/doc/filters.texi > index c8471e5..6d10a05 100644 > --- a/doc/filters.texi > +++ b/doc/filters.texi > @@ -1027,6 +1027,63 @@ It accepts the following values: > @end table > @end table > > +@section apulsator > + > +Audio pulsator is something between an autopanner and a tremolo. > +But it can produce funny stereo effects as well. Pulsator changes the volume > +of left and right channel based on a LFO (low frequency oscillator) with
"left and right" -> "the left and right" > +different waveforms and shifted phases. > +This filter have ability to define an offset between left and right channel. "have ability" -> "has the ability", "left and right" -> "the left and right" > +An offset of 0 means that both LFO shapes match each other. Left and right > +channel are altered equally - a conventional tremolo. An offset of 50% means "Left and right channel" -> "The left and right channels" > +that the shape of the right channel is exactly shifted in phase (or moved > +backwards about half of the frequency) - Pulsator acts as an autopanner. "Pulsator" -> "pulsator" > +At 1 both curves match again. Every setting inbetween moves the phaseshift "inbetween" -> "in between" "phaseshift" -> "phase shift" or "phase-shift", prefer 1st > +gapless between all stages and produces some "bypassing" sounds with sine and > +triangle waveform. The more you set the offset near 1 (starting from the "sine and triangle waveform" -> "sine and triangle waveforms", or perhaps based on code "sine, triangle, square, sawup, or sawdown waveforms". Up to you. > +0.5) the faster the signal passes from left to right speaker. "the 0.5" -> "0.5" "left to right speaker" -> "the left to the right speaker". > + > +The filter accepts the following options: > + > +@table @option > +@item level_in > +Set input gain. By default it is 1. Range is between 0.015625 and 64. > + > +@item level_out > +Set output gain. By default it is 1. Range is between 0.015625 and 64. nit: 0.01625 and 64 inclusive or exclusive (i.e open or closed interval) should be clarified since they are exactly representable doubles. > + > +@item mode > +Set waveform shape the LFO will use. Can be one of: sine, triangle, square, > +sawup or sawdown. Default is sine. > + > +@item amount > +Set modulation. Define how much of original signal is affected by the LFO. > + > +@item offset_l > +Set left channel offset. Default is 0. Allowed range is from 0 to 1. > + > +@item offset_r > +Set right channel offset. Default is 0.5. Allowed range is from 0 to 1. Again, inclusive or exclusive. > + > +@item width > +Set pulse width. > + > +@item timing > +Set possible timing mode. Can be one of: bpm, ms or hz. Default is hz. > + > +@item bpm > +Set bpm. Default is 120. Allowed range is from 30 to 300. Only used if timing > +is set to bpm. > + > +@item ms > +Set ms. Default is 500. Allowed range is from 10 to 2000. Only used if timing > +is set to ms Same as above. > + > +@item hz > +Set frequency in Hz. Default is 2. Allowed range is from 0.01 to 100. Only > used > +if timing is set to hz. > +@end table > + > @anchor{aresample} > @section aresample > > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index e31bdaa..b6c0d7b 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -40,6 +40,7 @@ OBJS-$(CONFIG_ANULL_FILTER) += af_anull.o > OBJS-$(CONFIG_APAD_FILTER) += af_apad.o > OBJS-$(CONFIG_APERMS_FILTER) += f_perms.o > OBJS-$(CONFIG_APHASER_FILTER) += af_aphaser.o > generate_wave_table.o > +OBJS-$(CONFIG_APULSATOR_FILTER) += af_apulsator.o > OBJS-$(CONFIG_AREALTIME_FILTER) += f_realtime.o > OBJS-$(CONFIG_ARESAMPLE_FILTER) += af_aresample.o > OBJS-$(CONFIG_AREVERSE_FILTER) += f_reverse.o > diff --git a/libavfilter/af_apulsator.c b/libavfilter/af_apulsator.c > new file mode 100644 > index 0000000..c3579f4 > --- /dev/null > +++ b/libavfilter/af_apulsator.c > @@ -0,0 +1,270 @@ > +/* > + * Copyright (c) 2001-2010 Krzysztof Foltman, Markus Schmidt, Thor Harald > Johansen and others > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg 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. > + * > + * FFmpeg 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 FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 > USA > + */ > + > +#include "libavutil/opt.h" > +#include "avfilter.h" > +#include "internal.h" > +#include "audio.h" > + > +enum PulsatorModes { SINE, TRIANGLE, SQUARE, SAWUP, SAWDOWN, NB_MODES }; > +enum PulsatorTimings { UNIT_BPM, UNIT_MS, UNIT_HZ, NB_TIMINGS }; > + > +typedef struct SimpleLFO { > + double phase; > + double freq; > + double offset; > + double amount; > + double pwidth; > + int mode; > + int srate; > +} SimpleLFO; I don't know the policy towards typedef'ed structures, kernel explicitly forbids them. Seems like FFmpeg freely typedef's structures, so feel free to ignore. > + > +typedef struct AudioPulsatorContext { > + const AVClass *class; > + int mode; > + double level_in; > + double level_out; > + double amount; > + double offset_l; > + double offset_r; > + double pwidth; > + double bpm; > + double hz; > + int ms; > + int timing; > + > + SimpleLFO lfoL, lfoR; > +} AudioPulsatorContext; > + > +#define OFFSET(x) offsetof(AudioPulsatorContext, x) > +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM > + > +static const AVOption apulsator_options[] = { > + { "level_in", "set input gain", OFFSET(level_in), AV_OPT_TYPE_DOUBLE, > {.dbl=1}, 0.015625, 64, FLAGS, }, > + { "level_out", "set output gain", OFFSET(level_out), AV_OPT_TYPE_DOUBLE, > {.dbl=1}, 0.015625, 64, FLAGS, }, > + { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, > {.i64=SINE}, SINE, NB_MODES-1, FLAGS, "mode" }, > + { "sine", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=SINE}, 0, 0, FLAGS, "mode" }, > + { "triangle", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=TRIANGLE},0, 0, FLAGS, "mode" }, > + { "square", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=SQUARE}, 0, 0, FLAGS, "mode" }, > + { "sawup", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=SAWUP}, 0, 0, FLAGS, "mode" }, > + { "sawdown", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=SAWDOWN}, 0, 0, FLAGS, "mode" }, > + { "amount", "set modulation", OFFSET(amount), AV_OPT_TYPE_DOUBLE, > {.dbl=1}, 0, 1, FLAGS }, > + { "offset_l", "set offset L", OFFSET(offset_l), AV_OPT_TYPE_DOUBLE, > {.dbl=0}, 0, 1, FLAGS }, > + { "offset_r", "set offset R", OFFSET(offset_r), AV_OPT_TYPE_DOUBLE, > {.dbl=.5}, 0, 1, FLAGS }, > + { "width", "set pulse width", OFFSET(pwidth), AV_OPT_TYPE_DOUBLE, > {.dbl=1}, 0, 2, FLAGS }, > + { "timing", "set timing", OFFSET(timing), AV_OPT_TYPE_INT, > {.i64=2}, 0, NB_TIMINGS-1, FLAGS, "timing" }, > + { "bpm", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=UNIT_BPM}, 0, 0, FLAGS, "timing" }, > + { "ms", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=UNIT_MS}, 0, 0, FLAGS, "timing" }, > + { "hz", NULL, 0, AV_OPT_TYPE_CONST, > {.i64=UNIT_HZ}, 0, 0, FLAGS, "timing" }, > + { "bpm", "set BPM", OFFSET(bpm), AV_OPT_TYPE_DOUBLE, > {.dbl=120}, 30, 300, FLAGS }, > + { "ms", "set ms", OFFSET(ms), AV_OPT_TYPE_INT, > {.i64=500}, 10, 2000, FLAGS }, > + { "hz", "set frequency", OFFSET(hz), AV_OPT_TYPE_DOUBLE, > {.dbl=2}, 0.01, 100, FLAGS }, > + { NULL } > +}; Please do check a build with "-Wgnu-zero-variadic-macro-arguments" (on clang) if easily available: https://lists.ffmpeg.org/pipermail/ffmpeg-devel/2015-October/181970.html, or verify otherwise that it is not an issue. > + > +AVFILTER_DEFINE_CLASS(apulsator); > + > +static void lfo_advance(SimpleLFO *lfo, unsigned count) > +{ > + lfo->phase = fabs((lfo->phase + count * lfo->freq * (1.0 / lfo->srate))); > + if (lfo->phase >= 1.) > + lfo->phase = fmod(lfo->phase, 1.); Minor nit: change all 1. to 1, they are exactly representable. More useful one: remove 1.0 / , simply do a count * lfo->freq / lfo->srate, the 1.0 is redundant. > +} > + > +static double lfo_get_value(SimpleLFO *lfo) > +{ > + double val; > + double phs = FFMIN(100., lfo->phase / FFMIN(1.99, FFMAX(0.01, > lfo->pwidth)) + lfo->offset); why "magic" 1.99? Usually when I see such things, they are a bad form of 2 - epsilon, and epsilon is picked out of a hat with no rationale. If 1.99, 0.01 is genuinely needed for some reason, be it compatibility or something technical, please add a relevant comment. minor nit: change 100. to 100, this applies in various places throughout this code. I won't point them out below for brevity. > + > + if (phs > 1) > + phs = fmod(phs, 1.); > + > + switch (lfo->mode) { > + case SINE: > + val = sin((phs * 360.) * M_PI / 180); Simplify to sin(phs * 2 * M_PI) > + break; > + case TRIANGLE: > + if (phs > 0.75) > + val = (phs - 0.75) * 4 - 1; > + else if (phs > 0.5) > + val = (phs - 0.5) * 4 * -1; > + else if (phs > 0.25) > + val = 1 - (phs - 0.25) * 4; Squash the two cases for > 0.25, 0.5 into one; it is a straight line segment and does not need an additional branch. > + else > + val = phs * 4; > + break; > + case SQUARE: > + val = (phs < 0.5) ? -1 : +1; > + break; > + case SAWUP: > + val = phs * 2. - 1; > + break; > + case SAWDOWN: > + val = 1 - phs * 2.; > + break; > + } > + > + return val * lfo->amount; > +} > + > +static int filter_frame(AVFilterLink *inlink, AVFrame *in) > +{ > + AVFilterContext *ctx = inlink->dst; > + AVFilterLink *outlink = ctx->outputs[0]; > + AudioPulsatorContext *s = ctx->priv; > + const double *src = (const double *)in->data[0]; > + const int nb_samples = in->nb_samples; > + const double level_out = s->level_out; > + const double level_in = s->level_in; > + const double amount = s->amount; > + AVFrame *out; > + double *dst; > + int n; > + > + if (av_frame_is_writable(in)) { > + out = in; > + } else { > + out = ff_get_audio_buffer(inlink, in->nb_samples); > + if (!out) { > + av_frame_free(&in); > + return AVERROR(ENOMEM); > + } > + av_frame_copy_props(out, in); > + } > + dst = (double *)out->data[0]; > + > + for (n = 0; n < nb_samples; n++) { > + double outL; > + double outR; > + double inL = src[0] * level_in; > + double inR = src[1] * level_in; > + double procL = inL; > + double procR = inR; > + > + procL *= lfo_get_value(&s->lfoL) * 0.5 + amount / 2; > + procR *= lfo_get_value(&s->lfoR) * 0.5 + amount / 2; > + > + outL = procL + inL * (1. - amount); > + outR = procR + inR * (1. - amount); > + > + outL *= level_out; > + outR *= level_out; > + > + dst[0] = outL; > + dst[1] = outR; > + > + lfo_advance(&s->lfoL, 1); > + lfo_advance(&s->lfoR, 1); > + > + dst += 2; > + src += 2; > + } > + > + if (in != out) > + av_frame_free(&in); > + > + return ff_filter_frame(outlink, out); > +} > + > +static int query_formats(AVFilterContext *ctx) > +{ > + AVFilterChannelLayouts *layout = NULL; > + AVFilterFormats *formats; > + static const enum AVSampleFormat sample_fmts[] = { > + AV_SAMPLE_FMT_DBL, > + AV_SAMPLE_FMT_NONE > + }; > + int ret; > + > + ret = ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO); > + if (ret < 0) > + return ret; > + ret = ff_set_common_channel_layouts(ctx, layout); > + if (ret < 0) > + return ret; > + > + formats = ff_make_format_list(sample_fmts); > + if (!formats) > + return AVERROR(ENOMEM); > + ret = ff_set_common_formats(ctx, formats); > + if (ret < 0) > + return ret; > + > + formats = ff_all_samplerates(); > + if (!formats) > + return AVERROR(ENOMEM); > + return ff_set_common_samplerates(ctx, formats); A potential memleak issue: what happens if e.g channel layout stuff succeeds and formats stuff fails? goto fail may be useful. > +} > + > +static int config_input(AVFilterLink *inlink) > +{ > + AVFilterContext *ctx = inlink->dst; > + AudioPulsatorContext *s = ctx->priv; > + double freq; > + > + switch (s->timing) { > + case UNIT_BPM: freq = s->bpm / 60.; break; > + case UNIT_MS: freq = 1. / (s->ms / 1000); break; > + case UNIT_HZ: freq = s->hz; break; > + } > + > + s->lfoL.freq = freq; > + s->lfoR.freq = freq; > + s->lfoL.mode = s->mode; > + s->lfoR.mode = s->mode; > + s->lfoL.offset = s->offset_l; > + s->lfoR.offset = s->offset_r; > + s->lfoL.srate = inlink->sample_rate; > + s->lfoR.srate = inlink->sample_rate; > + s->lfoL.amount = s->amount; > + s->lfoR.amount = s->amount; > + s->lfoL.pwidth = s->pwidth; > + s->lfoR.pwidth = s->pwidth; > + > + return 0; > +} > + > +static const AVFilterPad inputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_AUDIO, > + .config_props = config_input, > + .filter_frame = filter_frame, > + }, > + { NULL } > +}; > + > +static const AVFilterPad outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_AUDIO, > + }, > + { NULL } > +}; > + > +AVFilter ff_af_apulsator = { > + .name = "apulsator", > + .description = NULL_IF_CONFIG_SMALL("Audio pulsator."), > + .priv_size = sizeof(AudioPulsatorContext), > + .priv_class = &apulsator_class, > + .query_formats = query_formats, > + .inputs = inputs, > + .outputs = outputs, > +}; > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index ccd3f35..9502ebf 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -62,6 +62,7 @@ void avfilter_register_all(void) > REGISTER_FILTER(APAD, apad, af); > REGISTER_FILTER(APERMS, aperms, af); > REGISTER_FILTER(APHASER, aphaser, af); > + REGISTER_FILTER(APULSATOR, apulsator, af); > REGISTER_FILTER(AREALTIME, arealtime, af); > REGISTER_FILTER(ARESAMPLE, aresample, af); > REGISTER_FILTER(AREVERSE, areverse, af); > -- > 1.9.1 Note: I have not tested the filter; all the above is purely based on examination of the code. > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel