From patchwork Tue Jun 2 18:35:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 20128 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 8EE2344B1DA for ; Tue, 2 Jun 2020 21:35:16 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 52EA168A7B8; Tue, 2 Jun 2020 21:35:16 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 374E0680D13 for ; Tue, 2 Jun 2020 21:35:10 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 052IZ9rU023809 for ; Tue, 2 Jun 2020 20:35:09 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 2DE0DE0633; Tue, 2 Jun 2020 20:35:09 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Tue, 2 Jun 2020 20:35:04 +0200 Message-Id: <20200602183506.491783-1-george@nsup.org> X-Mailer: git-send-email 2.26.2 MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Tue, 02 Jun 2020 20:35:09 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 1/3] lavfi/asrc_sine: move sine table generation to a separate file. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Nicolas George --- libavfilter/Makefile | 2 +- libavfilter/asrc_sine.c | 53 ++++----------------------------- libavfilter/intsine.c | 65 +++++++++++++++++++++++++++++++++++++++++ libavfilter/intsine.h | 44 ++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 49 deletions(-) create mode 100644 libavfilter/intsine.c create mode 100644 libavfilter/intsine.h diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 5123540653..83d939f0b1 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -151,7 +151,7 @@ OBJS-$(CONFIG_ANULLSRC_FILTER) += asrc_anullsrc.o OBJS-$(CONFIG_FLITE_FILTER) += asrc_flite.o OBJS-$(CONFIG_HILBERT_FILTER) += asrc_hilbert.o OBJS-$(CONFIG_SINC_FILTER) += asrc_sinc.o -OBJS-$(CONFIG_SINE_FILTER) += asrc_sine.o +OBJS-$(CONFIG_SINE_FILTER) += asrc_sine.o intsine.o OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o diff --git a/libavfilter/asrc_sine.c b/libavfilter/asrc_sine.c index 3a87210b4b..8fff1dda8b 100644 --- a/libavfilter/asrc_sine.c +++ b/libavfilter/asrc_sine.c @@ -27,6 +27,7 @@ #include "audio.h" #include "avfilter.h" #include "internal.h" +#include "intsine.h" typedef struct SineContext { const AVClass *class; @@ -81,50 +82,6 @@ static const AVOption sine_options[] = { AVFILTER_DEFINE_CLASS(sine); -#define LOG_PERIOD 15 -#define AMPLITUDE 4095 -#define AMPLITUDE_SHIFT 3 - -static void make_sin_table(int16_t *sin) -{ - unsigned half_pi = 1 << (LOG_PERIOD - 2); - unsigned ampls = AMPLITUDE << AMPLITUDE_SHIFT; - uint64_t unit2 = (uint64_t)(ampls * ampls) << 32; - unsigned step, i, c, s, k, new_k, n2; - - /* Principle: if u = exp(i*a1) and v = exp(i*a2), then - exp(i*(a1+a2)/2) = (u+v) / length(u+v) */ - sin[0] = 0; - sin[half_pi] = ampls; - for (step = half_pi; step > 1; step /= 2) { - /* k = (1 << 16) * amplitude / length(u+v) - In exact values, k is constant at a given step */ - k = 0x10000; - for (i = 0; i < half_pi / 2; i += step) { - s = sin[i] + sin[i + step]; - c = sin[half_pi - i] + sin[half_pi - i - step]; - n2 = s * s + c * c; - /* Newton's method to solve n² * k² = unit² */ - while (1) { - new_k = (k + unit2 / ((uint64_t)k * n2) + 1) >> 1; - if (k == new_k) - break; - k = new_k; - } - sin[i + step / 2] = (k * s + 0x7FFF) >> 16; - sin[half_pi - i - step / 2] = (k * c + 0x8000) >> 16; - } - } - /* Unshift amplitude */ - for (i = 0; i <= half_pi; i++) - sin[i] = (sin[i] + (1 << (AMPLITUDE_SHIFT - 1))) >> AMPLITUDE_SHIFT; - /* Use symmetries to fill the other three quarters */ - for (i = 0; i < half_pi; i++) - sin[half_pi * 2 - i] = sin[i]; - for (i = 0; i < 2 * half_pi; i++) - sin[i + 2 * half_pi] = -sin[i]; -} - static const char *const var_names[] = { "n", "pts", @@ -146,10 +103,10 @@ static av_cold int init(AVFilterContext *ctx) int ret; SineContext *sine = ctx->priv; - if (!(sine->sin = av_malloc(sizeof(*sine->sin) << LOG_PERIOD))) + if (!(sine->sin = av_malloc(sizeof(*sine->sin) << SINE_LOG_PERIOD))) return AVERROR(ENOMEM); sine->dphi = ldexp(sine->frequency, 32) / sine->sample_rate + 0.5; - make_sin_table(sine->sin); + ff_make_sin_table(sine->sin); if (sine->beep_factor) { sine->beep_period = sine->sample_rate; @@ -244,10 +201,10 @@ static int request_frame(AVFilterLink *outlink) samples = (int16_t *)frame->data[0]; for (i = 0; i < nb_samples; i++) { - samples[i] = sine->sin[sine->phi >> (32 - LOG_PERIOD)]; + samples[i] = sine->sin[sine->phi >> (32 - SINE_LOG_PERIOD)]; sine->phi += sine->dphi; if (sine->beep_index < sine->beep_length) { - samples[i] += sine->sin[sine->phi_beep >> (32 - LOG_PERIOD)] << 1; + samples[i] += sine->sin[sine->phi_beep >> (32 - SINE_LOG_PERIOD)] << 1; sine->phi_beep += sine->dphi_beep; } if (++sine->beep_index == sine->beep_period) diff --git a/libavfilter/intsine.c b/libavfilter/intsine.c new file mode 100644 index 0000000000..ad99dc0147 --- /dev/null +++ b/libavfilter/intsine.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 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 "intsine.h" + +#define AMPLITUDE_SHIFT 3 + +void ff_make_sin_table(int16_t *sin) +{ + unsigned half_pi = 1 << (SINE_LOG_PERIOD - 2); + unsigned ampls = SINE_AMPLITUDE << AMPLITUDE_SHIFT; + uint64_t unit2 = (uint64_t)(ampls * ampls) << 32; + unsigned step, i, c, s, k, new_k, n2; + + /* Principle: if u = exp(i*a1) and v = exp(i*a2), then + exp(i*(a1+a2)/2) = (u+v) / length(u+v) */ + sin[0] = 0; + sin[half_pi] = ampls; + for (step = half_pi; step > 1; step /= 2) { + /* k = (1 << 16) * amplitude / length(u+v) + In exact values, k is constant at a given step */ + k = 0x10000; + for (i = 0; i < half_pi / 2; i += step) { + s = sin[i] + sin[i + step]; + c = sin[half_pi - i] + sin[half_pi - i - step]; + n2 = s * s + c * c; + /* Newton's method to solve n² * k² = unit² */ + while (1) { + new_k = (k + unit2 / ((uint64_t)k * n2) + 1) >> 1; + if (k == new_k) + break; + k = new_k; + } + sin[i + step / 2] = (k * s + 0x7FFF) >> 16; + sin[half_pi - i - step / 2] = (k * c + 0x8000) >> 16; + } + } + /* Unshift amplitude */ + for (i = 0; i <= half_pi; i++) + sin[i] = (sin[i] + (1 << (AMPLITUDE_SHIFT - 1))) >> AMPLITUDE_SHIFT; + /* Use symmetries to fill the other three quarters */ + for (i = 0; i < half_pi; i++) + sin[half_pi * 2 - i] = sin[i]; + for (i = 0; i < 2 * half_pi; i++) + sin[i + 2 * half_pi] = -sin[i]; +} + + diff --git a/libavfilter/intsine.h b/libavfilter/intsine.h new file mode 100644 index 0000000000..0723a9f141 --- /dev/null +++ b/libavfilter/intsine.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 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 + */ + +#ifndef AVFILTER_INTSINE_H +#define AVFILTER_INTSINE_H + +#include + +/** + * Make a sin() table with integer arithmetic. + * + * The provided pointer must point to an array of SINE_PERIOD. + * It will be filled with one period ([0,2π[) of sine values with amplitude + * SINE_AMPLITUDE. + * + * A good way to use this table is to use a phase variable phi of type + * uint32_t, with 0 for 0 and 1<<32 for 2π and access the table at + * sin[phi >> SINE_SHIFT_PHI]. + */ +void ff_make_sin_table(int16_t *sin); + +#define SINE_LOG_PERIOD 15 +#define SINE_PERIOD (1U< X-Patchwork-Id: 20129 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 70F6044B1DA for ; Tue, 2 Jun 2020 21:35:17 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 5EF1D68AA35; Tue, 2 Jun 2020 21:35:17 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 58F8B681866 for ; Tue, 2 Jun 2020 21:35:10 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 052IZ9mo023810 for ; Tue, 2 Jun 2020 20:35:09 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id 6947FE977F; Tue, 2 Jun 2020 20:35:09 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Tue, 2 Jun 2020 20:35:05 +0200 Message-Id: <20200602183506.491783-2-george@nsup.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200602183506.491783-1-george@nsup.org> References: <20200602183506.491783-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Tue, 02 Jun 2020 20:35:09 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 2/3] lavfi/sine: switch to activate. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Allow to set the EOF timestamp. Signed-off-by: Nicolas George --- libavfilter/asrc_sine.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/libavfilter/asrc_sine.c b/libavfilter/asrc_sine.c index 8fff1dda8b..947bc9a288 100644 --- a/libavfilter/asrc_sine.c +++ b/libavfilter/asrc_sine.c @@ -26,6 +26,7 @@ #include "libavutil/opt.h" #include "audio.h" #include "avfilter.h" +#include "filters.h" #include "internal.h" #include "intsine.h" @@ -171,9 +172,10 @@ static av_cold int config_props(AVFilterLink *outlink) return 0; } -static int request_frame(AVFilterLink *outlink) +static int activate(AVFilterContext *ctx) { - SineContext *sine = outlink->src->priv; + AVFilterLink *outlink = ctx->outputs[0]; + SineContext *sine = ctx->priv; AVFrame *frame; double values[VAR_VARS_NB] = { [VAR_N] = outlink->frame_count_in, @@ -184,6 +186,8 @@ static int request_frame(AVFilterLink *outlink) int i, nb_samples = lrint(av_expr_eval(sine->samples_per_frame_expr, values, sine)); int16_t *samples; + if (!ff_outlink_frame_wanted(outlink)) + return FFERROR_NOT_READY; if (nb_samples <= 0) { av_log(sine, AV_LOG_WARNING, "nb samples expression evaluated to %d, " "defaulting to 1024\n", nb_samples); @@ -193,8 +197,10 @@ static int request_frame(AVFilterLink *outlink) if (sine->duration) { nb_samples = FFMIN(nb_samples, sine->duration - sine->pts); av_assert1(nb_samples >= 0); - if (!nb_samples) - return AVERROR_EOF; + if (!nb_samples) { + ff_outlink_set_status(outlink, AVERROR_EOF, sine->pts); + return 0; + } } if (!(frame = ff_get_audio_buffer(outlink, nb_samples))) return AVERROR(ENOMEM); @@ -220,7 +226,6 @@ static const AVFilterPad sine_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_AUDIO, - .request_frame = request_frame, .config_props = config_props, }, { NULL } @@ -232,6 +237,7 @@ AVFilter ff_asrc_sine = { .query_formats = query_formats, .init = init, .uninit = uninit, + .activate = activate, .priv_size = sizeof(SineContext), .inputs = NULL, .outputs = sine_outputs, From patchwork Tue Jun 2 18:35:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 20130 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 505B644B1DA for ; Tue, 2 Jun 2020 21:35:18 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 36D0C68ADE6; Tue, 2 Jun 2020 21:35:18 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 902A2681866 for ; Tue, 2 Jun 2020 21:35:10 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 052IZ9DH023812 for ; Tue, 2 Jun 2020 20:35:10 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id A6975EB5C0; Tue, 2 Jun 2020 20:35:09 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Tue, 2 Jun 2020 20:35:06 +0200 Message-Id: <20200602183506.491783-3-george@nsup.org> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200602183506.491783-1-george@nsup.org> References: <20200602183506.491783-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Tue, 02 Jun 2020 20:35:10 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 3/3] lavfi: add basicplay filter. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Nicolas George --- 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 diff --git a/Changelog b/Changelog index 711c843b99..85df371b55 100644 --- a/Changelog +++ b/Changelog @@ -75,6 +75,7 @@ version : - PFM decoder - dblur video filter - Real War KVAG muxer +- basicplay filter source version 4.2: diff --git a/doc/filters.texi b/doc/filters.texi index f76604c51e..08b0707a01 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -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. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 83d939f0b1..849aad05ba 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -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 diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 1183e40267..daddddf159 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -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; diff --git a/libavfilter/asrc_basicplay.c b/libavfilter/asrc_basicplay.c new file mode 100644 index 0000000000..979a07e028 --- /dev/null +++ b/libavfilter/asrc_basicplay.c @@ -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, +}; diff --git a/libavfilter/version.h b/libavfilter/version.h index 980d9baca3..96b14d6794 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -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 diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak index 79b1536df0..da754af9be 100644 --- a/tests/fate/filter-audio.mak +++ b/tests/fate/filter-audio.mak @@ -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 diff --git a/tests/ref/fate/filter-basicplay b/tests/ref/fate/filter-basicplay new file mode 100644 index 0000000000..d00323ff1c --- /dev/null +++ b/tests/ref/fate/filter-basicplay @@ -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