@@ -75,6 +75,7 @@ version <next>:
- PFM decoder
- dblur video filter
- Real War KVAG muxer
+- basicplay filter source
version 4.2:
@@ -6021,6 +6021,56 @@ anullsrc=r=48000:cl=mono
All the parameters need to be explicitly defined.
+@section basicplay
+
+An audio source that reimplments the command PLAY from Microsoft BASIC 1.0.
+It generates the samples for a tune described in a very simple text
+description, with electronic-sounding notes.
+
+@table @command
+
+@item DO, RE, MI, FA, SO, LA, SI
+Note, named in fixed-do solfège. The note can possibly be followed by "#" or
+"B".
+Note: the "B" suffix will not change octave: "DOB" is a half-tone below the
+next "DO", not the current one.
+
+@item P
+Silent pause.
+
+@item O@var{o} (1-5; default 4)
+Set the octave for the following notes. "O1DO" is 33 Hz, "O1SI" is 62 Hz,
+"O5DO" is 523 Hz, "O5SI" is 988 Hz.
+
+@item A@var{a} (0-255; default 0)
+Set the "attack" for the following notes: 0 means continuous sound, @var{a}
+values mean the duration of the sound is bounded by the duration of a whole
+note divided by exp(1)×@var{a}.
+The default is 0.
+
+@item L@var{l} (1-96; default 24)
+Set the note duration for the following notes: 96 is a whole note /
+semibreve / ronde, other values give a proportional duration.
+The default is 24, meaning a quarter note / crochet / noire.
+
+@item T@var{t} (1-255; default 5)
+Set the tempo period for the following notes. The default 5 causes 5 quarter
+notes per second, and everything else is proportional.
+Put it simply, T@var{t}L@var{l} sets the duration of the notes to
+@var{t}×@var{l}/600 second.
+
+@end table
+
+@subsection Examples
+
+@itemize
+@item
+Generate the beginning of Für Elise:
+@example
+basicplay=T20L6O4MIRE#MIRE#MIO3SIO4REDOL12O3LA
+@end example
+@end itemize
+
@section flite
Synthesize a voice utterance using the libflite library.
@@ -148,6 +148,7 @@ OBJS-$(CONFIG_AEVALSRC_FILTER) += aeval.o
OBJS-$(CONFIG_AFIRSRC_FILTER) += asrc_afirsrc.o
OBJS-$(CONFIG_ANOISESRC_FILTER) += asrc_anoisesrc.o
OBJS-$(CONFIG_ANULLSRC_FILTER) += asrc_anullsrc.o
+OBJS-$(CONFIG_BASICPLAY_FILTER) += asrc_basicplay.o intsine.o
OBJS-$(CONFIG_FLITE_FILTER) += asrc_flite.o
OBJS-$(CONFIG_HILBERT_FILTER) += asrc_hilbert.o
OBJS-$(CONFIG_SINC_FILTER) += asrc_sinc.o
@@ -142,6 +142,7 @@ extern AVFilter ff_asrc_aevalsrc;
extern AVFilter ff_asrc_afirsrc;
extern AVFilter ff_asrc_anoisesrc;
extern AVFilter ff_asrc_anullsrc;
+extern AVFilter ff_asrc_basicplay;
extern AVFilter ff_asrc_flite;
extern AVFilter ff_asrc_hilbert;
extern AVFilter ff_asrc_sinc;
new file mode 100644
@@ -0,0 +1,309 @@
+/*
+ * Copyright (c) 2013 Nicolas George
+ *
+ * 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/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "audio.h"
+#include "avfilter.h"
+#include "filters.h"
+#include "internal.h"
+#include "intsine.h"
+
+static const unsigned time_base = 960000;
+
+typedef struct Note {
+ unsigned duration;
+ uint8_t note;
+ uint8_t octave;
+} Note;
+
+typedef struct PlayContext {
+ const AVClass *class;
+ const char *tune;
+ Note *notes;
+ int64_t pts;
+ uint32_t phi;
+ unsigned nb_notes;
+ unsigned cur_note;
+ int sample_rate;
+ int16_t sin[SINE_PERIOD];
+} PlayContext;
+
+#define OFFSET(x) offsetof(PlayContext, x)
+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption basicplay_options[] = {
+ { "tune", "tune description", OFFSET(tune), AV_OPT_TYPE_STRING,
+ {.str = ""}, 0, 0, FLAGS },
+ { "sample_rate", "sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT,
+ {.i64 = 44100}, 1, INT_MAX, FLAGS },
+ { "r", "sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT,
+ {.i64 = 44100}, 1, INT_MAX, FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(basicplay);
+
+static void
+add_note(Note **notes, unsigned *nb_notes, uint64_t *pos,
+ unsigned note, unsigned octave, unsigned period, unsigned length, unsigned attack)
+{
+ unsigned whole_note, duration, sound;
+
+ octave += note / 12;
+ note %= 12;
+ whole_note = time_base / 600 * 96 * period;
+ duration = time_base / 600 * period * length;
+ sound = !attack ? duration : /* 325368125/119696244 =~ exp(1) */
+ av_rescale(whole_note, 119696244, (uint64_t)325368125 * attack);
+ sound = FFMIN(sound, duration);
+ if (sound > 0) {
+ if (*notes) {
+ (*notes)->duration = sound;
+ (*notes)->note = note;
+ (*notes)->octave = octave;
+ (*notes)++;
+ }
+ (*nb_notes)++;
+ }
+ if (sound < duration) {
+ (*nb_notes)++;
+ if (*notes) {
+ (*notes)->duration = duration - sound;
+ (*notes)->note = 0;
+ (*notes)->octave = 0;
+ (*notes)++;
+ }
+ }
+ (*pos) += duration;
+}
+
+#define LC(c) ((c) | ('a' - 'A'))
+
+static int parse_letter_number(const char **rt, const char *end, char letter,
+ unsigned min, unsigned max, unsigned *rv)
+{
+ const char *t = *rt;
+ unsigned v = 0;
+
+ if (LC(*t) != letter)
+ return 0;
+ t++;
+ if (!av_isdigit(*t))
+ return 0;
+ while (av_isdigit(*t)) {
+ v = v * 10 + (*(t++) - '0');
+ if (v > max)
+ return 0;
+ }
+ if (v < min)
+ return 0;
+ *rt = t;
+ *rv = v;
+ return 1;
+}
+
+static int parse_tune(AVFilterContext *ctx, Note *notes)
+{
+ PlayContext *s = ctx->priv;
+ const char *t = s->tune;
+ const char *end = t + strlen(t);
+ unsigned nb_notes = 0;
+ unsigned octave = 4, period = 5, length = 24, attack = 0;
+ uint64_t pos = 0;
+ int note;
+
+ while (t < end) {
+ if (av_isspace(*t)) {
+ t++;
+ continue;
+
+ }
+ note = end - t < 2 ? -1 :
+ LC(t[0]) == 'd' && LC(t[1]) == 'o' ? 0 :
+ LC(t[0]) == 'r' && LC(t[1]) == 'e' ? 2 :
+ LC(t[0]) == 'm' && LC(t[1]) == 'i' ? 4 :
+ LC(t[0]) == 'f' && LC(t[1]) == 'a' ? 5 :
+ LC(t[0]) == 's' && LC(t[1]) == 'o' ? 7 :
+ LC(t[0]) == 'l' && LC(t[1]) == 'a' ? 9 :
+ LC(t[0]) == 's' && LC(t[1]) == 'i' ? 11 : -1;
+ if (note >= 0) {
+ t += 2;
+ if (t < end && *t == '#') {
+ t++;
+ note++;
+ }
+ if (t < end && *t == 'b') {
+ t++;
+ note = note ? note - 1 : 11;
+ }
+ add_note(¬es, &nb_notes, &pos,
+ note, octave, period, length, attack);
+ continue;
+ }
+ if (LC(*t) == 'p') {
+ t++;
+ add_note(¬es, &nb_notes, &pos, 0, 0, period, length, 0);
+ continue;
+ }
+ if (parse_letter_number(&t, end, 'o', 1, 5, &octave))
+ continue;
+ if (parse_letter_number(&t, end, 'l', 1, 96, &length))
+ continue;
+ if (parse_letter_number(&t, end, 't', 1, 255, &period))
+ continue;
+ if (parse_letter_number(&t, end, 'a', 0, 255, &attack))
+ continue;
+ av_log(ctx, AV_LOG_ERROR, "Syntax error in tune, near \"%s\"\n", t);
+ return AVERROR(EINVAL);
+ }
+ return nb_notes;
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+ PlayContext *s = ctx->priv;
+ int ret;
+
+ ff_make_sin_table(s->sin);
+ ret = parse_tune(ctx, NULL);
+ if (ret < 0)
+ return ret;
+ s->nb_notes = ret;
+ s->notes = av_malloc_array(sizeof(*s->notes), s->nb_notes);
+ if (!s->notes)
+ return AVERROR(ENOMEM);
+ ret = parse_tune(ctx, s->notes);
+ av_assert0(ret == s->nb_notes);
+ return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+ PlayContext *s = ctx->priv;
+
+ av_freep(&s->notes);
+}
+
+static av_cold int query_formats(AVFilterContext *ctx)
+{
+ PlayContext *s = ctx->priv;
+ static const int64_t chlayouts[] = { AV_CH_LAYOUT_MONO, -1 };
+ int sample_rates[] = { s->sample_rate, -1 };
+ static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16,
+ AV_SAMPLE_FMT_NONE };
+ AVFilterFormats *formats;
+ AVFilterChannelLayouts *layouts;
+ int 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;
+
+ layouts = avfilter_make_format64_list(chlayouts);
+ if (!layouts)
+ return AVERROR(ENOMEM);
+ ret = ff_set_common_channel_layouts(ctx, layouts);
+ if (ret < 0)
+ return ret;
+
+ formats = ff_make_format_list(sample_rates);
+ if (!formats)
+ return AVERROR(ENOMEM);
+ ret = ff_set_common_samplerates(ctx, formats);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static av_cold int config_props(AVFilterLink *outlink)
+{
+ outlink->time_base = av_make_q(1, outlink->sample_rate);
+ return 0;
+}
+
+static int activate(AVFilterContext *ctx)
+{
+ PlayContext *s = ctx->priv;
+ AVFilterLink *outlink = ctx->outputs[0];
+ AVFrame *frame;
+ Note *note;
+ unsigned nb_samples, i;
+ uint32_t dphi;
+ int16_t *samples;
+ const uint32_t freq[12] = { /* octave 27, from DO to SI, in Hz */
+ 2194674310, 2325176436, 2463438621, 2609922305,
+ 2765116361, 2929538736, 3103738174, 3288296050,
+ 3483828309, 3690987520, 3910465059, 4142993412,
+ };
+
+ if (!ff_outlink_frame_wanted(outlink))
+ return FFERROR_NOT_READY;
+ if (s->cur_note == s->nb_notes) {
+ ff_outlink_set_status(outlink, AVERROR_EOF, s->pts);
+ return 0;
+ }
+ note = &s->notes[s->cur_note++];
+ nb_samples = av_rescale(note->duration, outlink->sample_rate, time_base);
+ frame = ff_get_audio_buffer(outlink, nb_samples);
+ if (!frame)
+ return AVERROR(ENOMEM);
+ frame->pts = s->pts;
+ samples = (int16_t *)frame->data[0];
+ if (note->octave) {
+ dphi = ((uint64_t)freq[note->note] << (note->octave + 5)) / outlink->sample_rate;
+ } else {
+ dphi = 0;
+ s->phi = 0;
+ }
+ for (i = 0; i < nb_samples; i++) {
+ samples[i] = s->sin[s->phi >> SINE_SHIFT_PHI];
+ s->phi += dphi;
+ }
+ s->pts += nb_samples;
+ return ff_filter_frame(outlink, frame);
+}
+
+static const AVFilterPad basicplay_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_AUDIO,
+ .config_props = config_props,
+ },
+ { NULL }
+};
+
+AVFilter ff_asrc_basicplay = {
+ .name = "basicplay",
+ .description = NULL_IF_CONFIG_SMALL("Generate a tune like PLAY in Microsoft BASIC."),
+ .query_formats = query_formats,
+ .init = init,
+ .uninit = uninit,
+ .activate = activate,
+ .priv_size = sizeof(PlayContext),
+ .inputs = NULL,
+ .outputs = basicplay_outputs,
+ .priv_class = &basicplay_class,
+};
@@ -30,7 +30,7 @@
#include "libavutil/version.h"
#define LIBAVFILTER_VERSION_MAJOR 7
-#define LIBAVFILTER_VERSION_MINOR 84
+#define LIBAVFILTER_VERSION_MINOR 85
#define LIBAVFILTER_VERSION_MICRO 100
@@ -99,6 +99,9 @@ fate-filter-asetrate: tests/data/asynth-44100-2.wav
fate-filter-asetrate: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
fate-filter-asetrate: CMD = framecrc -i $(SRC) -frames:a 20 -af asetrate=20000
+FATE_AFILTER-$(call ALLYES, BASICPLAY_FILTER) += fate-filter-basicplay
+fate-filter-basicplay: CMD = framecrc -lavfi basicplay="DOREbSIPO1DOREbSIO5DOREbSIO4A2DOREbSIT2DOREbSIL48DOREbSI"
+
FATE_AFILTER-$(call FILTERDEMDECENCMUX, CHORUS, WAV, PCM_S16LE, PCM_S16LE, WAV) += fate-filter-chorus
fate-filter-chorus: tests/data/asynth-22050-1.wav
fate-filter-chorus: SRC = $(TARGET_PATH)/tests/data/asynth-22050-1.wav
new file mode 100644
@@ -0,0 +1,34 @@
+#tb 0: 1/44100
+#media_type 0: audio
+#codec_id 0: pcm_s16le
+#sample_rate 0: 44100
+#channel_layout 0: 4
+#channel_layout_name 0: mono
+0, 0, 0, 8820, 17640, 0x42b3494a
+0, 8820, 8820, 8820, 17640, 0x93a75587
+0, 17640, 17640, 8820, 17640, 0xef3b4efa
+0, 26460, 26460, 8820, 17640, 0x00000000
+0, 35280, 35280, 8820, 17640, 0xc0064c32
+0, 44100, 44100, 8820, 17640, 0x8c7e4cb4
+0, 52920, 52920, 8820, 17640, 0x35d2a2ff
+0, 61740, 61740, 8820, 17640, 0x9b354e12
+0, 70560, 70560, 8820, 17640, 0x35125b2d
+0, 79380, 79380, 8820, 17640, 0xc4a352b4
+0, 88200, 88200, 6489, 12978, 0x7415347d
+0, 94689, 94689, 2331, 4662, 0x00000000
+0, 97020, 97020, 6489, 12978, 0x74494189
+0, 103509, 103509, 2331, 4662, 0x00000000
+0, 105840, 105840, 6489, 12978, 0xb8b13337
+0, 112329, 112329, 2331, 4662, 0x00000000
+0, 114660, 114660, 2596, 5192, 0xc39a0594
+0, 117256, 117256, 932, 1864, 0x00000000
+0, 118188, 118188, 2596, 5192, 0x19851628
+0, 120784, 120784, 932, 1864, 0x00000000
+0, 121716, 121716, 2596, 5192, 0x54fa0d97
+0, 124312, 124312, 932, 1864, 0x00000000
+0, 125244, 125244, 2596, 5192, 0xc39a0594
+0, 127840, 127840, 4460, 8920, 0x00000000
+0, 132300, 132300, 2596, 5192, 0x19851628
+0, 134896, 134896, 4460, 8920, 0x00000000
+0, 139356, 139356, 2596, 5192, 0x54fa0d97
+0, 141952, 141952, 4460, 8920, 0x00000000
Signed-off-by: Nicolas George <george@nsup.org> --- Changelog | 1 + doc/filters.texi | 50 ++++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/asrc_basicplay.c | 309 ++++++++++++++++++++++++++++++++ libavfilter/version.h | 2 +- tests/fate/filter-audio.mak | 3 + tests/ref/fate/filter-basicplay | 34 ++++ 8 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 libavfilter/asrc_basicplay.c create mode 100644 tests/ref/fate/filter-basicplay