| /* |
| * GStreamer |
| * Copyright (C) 2011 Stefan Sauer <ensonic@users.sf.net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| /* |
| * Freeverb |
| * |
| * Written by Jezar at Dreampoint, June 2000 |
| * http://www.dreampoint.co.uk |
| * This code is public domain |
| * |
| * Translated to C by Peter Hanappe, Mai 2001 |
| * Transformed into a GStreamer plugin by Stefan Sauer, Nov 2011 |
| */ |
| |
| /** |
| * SECTION:element-freeverb |
| * |
| * Reverberation/room effect. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch audiotestsrc wave=saw ! freeverb ! autoaudiosink |
| * gst-launch filesrc location="melo1.ogg" ! decodebin ! audioconvert ! freeverb ! autoaudiosink |
| * ]| |
| * </refsect2> |
| */ |
| |
| /* FIXME: |
| * - add mono-to-mono, then we might also need stereo-to-mono ? |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <math.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <gst/gst.h> |
| #include <gst/base/gstbasetransform.h> |
| |
| #include "gstfreeverb.h" |
| |
| #define GST_CAT_DEFAULT gst_freeverb_debug |
| GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
| |
| enum |
| { |
| PROP_0, |
| PROP_ROOM_SIZE, |
| PROP_DAMPING, |
| PROP_PAN_WIDTH, |
| PROP_LEVEL |
| }; |
| |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-raw, " |
| "format = (string) { " GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (S16) "}, " |
| "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ], " |
| "layout = (string) interleaved") |
| ); |
| |
| static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-raw, " |
| "format = (string) { " GST_AUDIO_NE (F32) ", " GST_AUDIO_NE (S16) "}, " |
| "rate = (int) [ 1, MAX ], " "channels = (int) 2, " |
| "layout = (string) interleaved") |
| ); |
| |
| G_DEFINE_TYPE_WITH_CODE (GstFreeverb, gst_freeverb, GST_TYPE_BASE_TRANSFORM, |
| G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL)); |
| |
| static void gst_freeverb_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_freeverb_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static void gst_freeverb_finalize (GObject * object); |
| |
| static gboolean gst_freeverb_get_unit_size (GstBaseTransform * base, |
| GstCaps * caps, gsize * size); |
| static GstCaps *gst_freeverb_transform_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps, GstCaps * filter); |
| static gboolean gst_freeverb_set_caps (GstBaseTransform * base, |
| GstCaps * incaps, GstCaps * outcaps); |
| |
| static GstFlowReturn gst_freeverb_transform (GstBaseTransform * base, |
| GstBuffer * inbuf, GstBuffer * outbuf); |
| |
| static gboolean gst_freeverb_transform_m2s_int (GstFreeverb * filter, |
| gint16 * idata, gint16 * odata, guint num_samples); |
| static gboolean gst_freeverb_transform_s2s_int (GstFreeverb * filter, |
| gint16 * idata, gint16 * odata, guint num_samples); |
| static gboolean gst_freeverb_transform_m2s_float (GstFreeverb * filter, |
| gfloat * idata, gfloat * odata, guint num_samples); |
| static gboolean gst_freeverb_transform_s2s_float (GstFreeverb * filter, |
| gfloat * idata, gfloat * odata, guint num_samples); |
| |
| |
| /* Table with processing functions: [channels][format] */ |
| static GstFreeverbProcessFunc process_functions[2][2] = { |
| { |
| (GstFreeverbProcessFunc) gst_freeverb_transform_m2s_int, |
| (GstFreeverbProcessFunc) gst_freeverb_transform_m2s_float, |
| }, |
| { |
| (GstFreeverbProcessFunc) gst_freeverb_transform_s2s_int, |
| (GstFreeverbProcessFunc) gst_freeverb_transform_s2s_float, |
| } |
| }; |
| |
| /*************************************************************** |
| * |
| * REVERB |
| */ |
| |
| /* Denormalising: |
| * |
| * Another method fixes the problem cheaper: Use a small DC-offset in |
| * the filter calculations. Now the signals converge not against 0, |
| * but against the offset. The constant offset is invisible from the |
| * outside world (i.e. it does not appear at the output. There is a |
| * very small turn-on transient response, which should not cause |
| * problems. |
| */ |
| |
| //#define DC_OFFSET 0 |
| #define DC_OFFSET 1e-8 |
| //#define DC_OFFSET 0.001f |
| |
| /* all pass filter */ |
| |
| typedef struct _freeverb_allpass |
| { |
| gfloat feedback; |
| gfloat *buffer; |
| gint bufsize; |
| gint bufidx; |
| } freeverb_allpass; |
| |
| static void |
| freeverb_allpass_setbuffer (freeverb_allpass * allpass, gint size) |
| { |
| allpass->bufidx = 0; |
| allpass->buffer = g_new (gfloat, size); |
| allpass->bufsize = size; |
| } |
| |
| static void |
| freeverb_allpass_release (freeverb_allpass * allpass) |
| { |
| g_free (allpass->buffer); |
| } |
| |
| static void |
| freeverb_allpass_init (freeverb_allpass * allpass) |
| { |
| gint i, len = allpass->bufsize; |
| gfloat *buf = allpass->buffer; |
| |
| for (i = 0; i < len; i++) { |
| buf[i] = DC_OFFSET; /* this is not 100 % correct. */ |
| } |
| } |
| |
| static void |
| freeverb_allpass_setfeedback (freeverb_allpass * allpass, gfloat val) |
| { |
| allpass->feedback = val; |
| } |
| |
| /* |
| static gfloat |
| freeverb_allpass_getfeedback(freeverb_allpass* allpass) |
| { |
| return allpass->feedback; |
| }*/ |
| |
| #define freeverb_allpass_process(_allpass, _input_1) \ |
| { \ |
| gfloat output; \ |
| gfloat bufout; \ |
| bufout = _allpass.buffer[_allpass.bufidx]; \ |
| output = bufout-_input_1; \ |
| _allpass.buffer[_allpass.bufidx] = _input_1 + (bufout * _allpass.feedback); \ |
| if (++_allpass.bufidx >= _allpass.bufsize) { \ |
| _allpass.bufidx = 0; \ |
| } \ |
| _input_1 = output; \ |
| } |
| |
| /* comb filter */ |
| |
| typedef struct _freeverb_comb |
| { |
| gfloat feedback; |
| gfloat filterstore; |
| gfloat damp1; |
| gfloat damp2; |
| gfloat *buffer; |
| gint bufsize; |
| gint bufidx; |
| } freeverb_comb; |
| |
| static void |
| freeverb_comb_setbuffer (freeverb_comb * comb, gint size) |
| { |
| comb->filterstore = 0; |
| comb->bufidx = 0; |
| comb->buffer = g_new (gfloat, size); |
| comb->bufsize = size; |
| } |
| |
| static void |
| freeverb_comb_release (freeverb_comb * comb) |
| { |
| g_free (comb->buffer); |
| } |
| |
| static void |
| freeverb_comb_init (freeverb_comb * comb) |
| { |
| gint i, len = comb->bufsize; |
| gfloat *buf = comb->buffer; |
| |
| for (i = 0; i < len; i++) { |
| buf[i] = DC_OFFSET; /* This is not 100 % correct. */ |
| } |
| } |
| |
| static void |
| freeverb_comb_setdamp (freeverb_comb * comb, gfloat val) |
| { |
| comb->damp1 = val; |
| comb->damp2 = 1 - val; |
| } |
| |
| /* |
| static gfloat |
| freeverb_comb_getdamp(freeverb_comb* comb) |
| { |
| return comb->damp1; |
| }*/ |
| |
| static void |
| freeverb_comb_setfeedback (freeverb_comb * comb, gfloat val) |
| { |
| comb->feedback = val; |
| } |
| |
| /* |
| static gfloat |
| freeverb_comb_getfeedback(freeverb_comb* comb) |
| { |
| return comb->feedback; |
| }*/ |
| |
| #define freeverb_comb_process(_comb, _input_1, _output) \ |
| { \ |
| gfloat _tmp = _comb.buffer[_comb.bufidx]; \ |
| _comb.filterstore = (_tmp * _comb.damp2) + (_comb.filterstore * _comb.damp1); \ |
| _comb.buffer[_comb.bufidx] = _input_1 + (_comb.filterstore * _comb.feedback); \ |
| if (++_comb.bufidx >= _comb.bufsize) { \ |
| _comb.bufidx = 0; \ |
| } \ |
| _output += _tmp; \ |
| } |
| |
| #define numcombs 8 |
| #define numallpasses 4 |
| #define fixedgain 0.015f |
| #define scalewet 1.0f |
| #define scaledry 1.0f |
| #define scaledamp 1.0f |
| #define scaleroom 0.28f |
| #define offsetroom 0.7f |
| #define stereospread 23 |
| |
| /* These values assume 44.1KHz sample rate |
| * they will need scaling for 96KHz (or other) sample rates. |
| * The values were obtained by listening tests. |
| */ |
| #define combtuningL1 1116 |
| #define combtuningR1 (1116 + stereospread) |
| #define combtuningL2 1188 |
| #define combtuningR2 (1188 + stereospread) |
| #define combtuningL3 1277 |
| #define combtuningR3 (1277 + stereospread) |
| #define combtuningL4 1356 |
| #define combtuningR4 (1356 + stereospread) |
| #define combtuningL5 1422 |
| #define combtuningR5 (1422 + stereospread) |
| #define combtuningL6 1491 |
| #define combtuningR6 (1491 + stereospread) |
| #define combtuningL7 1557 |
| #define combtuningR7 (1557 + stereospread) |
| #define combtuningL8 1617 |
| #define combtuningR8 (1617 + stereospread) |
| #define allpasstuningL1 556 |
| #define allpasstuningR1 (556 + stereospread) |
| #define allpasstuningL2 441 |
| #define allpasstuningR2 (441 + stereospread) |
| #define allpasstuningL3 341 |
| #define allpasstuningR3 (341 + stereospread) |
| #define allpasstuningL4 225 |
| #define allpasstuningR4 (225 + stereospread) |
| |
| struct _GstFreeverbPrivate |
| { |
| gfloat roomsize; |
| gfloat damp; |
| gfloat wet, wet1, wet2, dry; |
| gfloat width; |
| gfloat gain; |
| /* |
| The following are all declared inline |
| to remove the need for dynamic allocation |
| with its subsequent error-checking messiness |
| */ |
| /* Comb filters */ |
| freeverb_comb combL[numcombs]; |
| freeverb_comb combR[numcombs]; |
| /* Allpass filters */ |
| freeverb_allpass allpassL[numallpasses]; |
| freeverb_allpass allpassR[numallpasses]; |
| }; |
| |
| static void |
| freeverb_revmodel_init (GstFreeverb * filter) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i; |
| |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_init (&priv->combL[i]); |
| freeverb_comb_init (&priv->combR[i]); |
| } |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_init (&priv->allpassL[i]); |
| freeverb_allpass_init (&priv->allpassR[i]); |
| } |
| } |
| |
| static void |
| freeverb_revmodel_free (GstFreeverb * filter) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i; |
| |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_release (&priv->combL[i]); |
| freeverb_comb_release (&priv->combR[i]); |
| } |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_release (&priv->allpassL[i]); |
| freeverb_allpass_release (&priv->allpassR[i]); |
| } |
| } |
| |
| /* GObject vmethod implementations */ |
| |
| static void |
| gst_freeverb_class_init (GstFreeverbClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *element_class; |
| |
| g_type_class_add_private (klass, sizeof (GstFreeverbPrivate)); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_freeverb_debug, "freeverb", 0, |
| "freeverb element"); |
| |
| gobject_class = (GObjectClass *) klass; |
| element_class = (GstElementClass *) klass; |
| |
| gobject_class->set_property = gst_freeverb_set_property; |
| gobject_class->get_property = gst_freeverb_get_property; |
| gobject_class->finalize = gst_freeverb_finalize; |
| |
| g_object_class_install_property (gobject_class, PROP_ROOM_SIZE, |
| g_param_spec_float ("room-size", "Room size", |
| "Size of the simulated room", 0.0, 1.0, 0.5, |
| G_PARAM_CONSTRUCT | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | |
| G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_DAMPING, |
| g_param_spec_float ("damping", "Damping", "Damping of high frequencies", |
| 0.0, 1.0, 0.2, |
| G_PARAM_CONSTRUCT | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | |
| G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_PAN_WIDTH, |
| g_param_spec_float ("width", "Width", "Stereo panorama width", 0.0, 1.0, |
| 1.0, |
| G_PARAM_CONSTRUCT | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | |
| G_PARAM_STATIC_STRINGS)); |
| g_object_class_install_property (gobject_class, PROP_LEVEL, |
| g_param_spec_float ("level", "Level", "dry/wet level", 0.0, 1.0, 0.5, |
| G_PARAM_CONSTRUCT | G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | |
| G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "Reverberation/room effect", "Filter/Effect/Audio", |
| "Add reverberation to audio streams", |
| "Stefan Sauer <ensonic@users.sf.net>"); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&src_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&sink_template)); |
| |
| GST_BASE_TRANSFORM_CLASS (klass)->get_unit_size = |
| GST_DEBUG_FUNCPTR (gst_freeverb_get_unit_size); |
| GST_BASE_TRANSFORM_CLASS (klass)->transform_caps = |
| GST_DEBUG_FUNCPTR (gst_freeverb_transform_caps); |
| GST_BASE_TRANSFORM_CLASS (klass)->set_caps = |
| GST_DEBUG_FUNCPTR (gst_freeverb_set_caps); |
| GST_BASE_TRANSFORM_CLASS (klass)->transform = |
| GST_DEBUG_FUNCPTR (gst_freeverb_transform); |
| } |
| |
| static void |
| gst_freeverb_init (GstFreeverb * filter) |
| { |
| filter->priv = |
| G_TYPE_INSTANCE_GET_PRIVATE (filter, GST_TYPE_FREEVERB, |
| GstFreeverbPrivate); |
| |
| gst_audio_info_init (&filter->info); |
| filter->process = NULL; |
| |
| gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE); |
| |
| freeverb_revmodel_init (filter); |
| } |
| |
| static void |
| gst_freeverb_finalize (GObject * object) |
| { |
| GstFreeverb *filter = GST_FREEVERB (object); |
| |
| freeverb_revmodel_free (filter); |
| |
| G_OBJECT_CLASS (gst_freeverb_parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| gst_freeverb_set_process_function (GstFreeverb * filter, GstAudioInfo * info) |
| { |
| gint channel_index, format_index; |
| const GstAudioFormatInfo *finfo = info->finfo; |
| |
| /* set processing function */ |
| channel_index = GST_AUDIO_INFO_CHANNELS (info) - 1; |
| if (channel_index > 1 || channel_index < 0) { |
| filter->process = NULL; |
| return FALSE; |
| } |
| |
| format_index = GST_AUDIO_FORMAT_INFO_IS_FLOAT (finfo) ? 1 : 0; |
| |
| filter->process = process_functions[channel_index][format_index]; |
| return TRUE; |
| } |
| |
| static void |
| gst_freeverb_init_rev_model (GstFreeverb * filter) |
| { |
| gfloat srfactor = GST_AUDIO_INFO_RATE (&filter->info) / 44100.0f; |
| GstFreeverbPrivate *priv = filter->priv; |
| |
| freeverb_revmodel_free (filter); |
| |
| priv->gain = fixedgain; |
| |
| freeverb_comb_setbuffer (&priv->combL[0], combtuningL1 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[0], combtuningR1 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[1], combtuningL2 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[1], combtuningR2 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[2], combtuningL3 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[2], combtuningR3 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[3], combtuningL4 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[3], combtuningR4 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[4], combtuningL5 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[4], combtuningR5 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[5], combtuningL6 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[5], combtuningR6 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[6], combtuningL7 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[6], combtuningR7 * srfactor); |
| freeverb_comb_setbuffer (&priv->combL[7], combtuningL8 * srfactor); |
| freeverb_comb_setbuffer (&priv->combR[7], combtuningR8 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassL[0], allpasstuningL1 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassR[0], allpasstuningR1 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassL[1], allpasstuningL2 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassR[1], allpasstuningR2 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassL[2], allpasstuningL3 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassR[2], allpasstuningR3 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassL[3], allpasstuningL4 * srfactor); |
| freeverb_allpass_setbuffer (&priv->allpassR[3], allpasstuningR4 * srfactor); |
| |
| /* clear buffers */ |
| freeverb_revmodel_init (filter); |
| |
| /* set default values */ |
| freeverb_allpass_setfeedback (&priv->allpassL[0], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassR[0], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassL[1], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassR[1], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassL[2], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassR[2], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassL[3], 0.5f); |
| freeverb_allpass_setfeedback (&priv->allpassR[3], 0.5f); |
| } |
| |
| static void |
| gst_freeverb_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstFreeverb *filter = GST_FREEVERB (object); |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i; |
| |
| switch (prop_id) { |
| case PROP_ROOM_SIZE: |
| filter->room_size = g_value_get_float (value); |
| priv->roomsize = (filter->room_size * scaleroom) + offsetroom; |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_setfeedback (&priv->combL[i], priv->roomsize); |
| freeverb_comb_setfeedback (&priv->combR[i], priv->roomsize); |
| } |
| break; |
| case PROP_DAMPING: |
| filter->damping = g_value_get_float (value); |
| priv->damp = filter->damping * scaledamp; |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_setdamp (&priv->combL[i], priv->damp); |
| freeverb_comb_setdamp (&priv->combR[i], priv->damp); |
| } |
| break; |
| case PROP_PAN_WIDTH: |
| filter->pan_width = g_value_get_float (value); |
| priv->width = filter->pan_width; |
| priv->wet1 = priv->wet * (priv->width / 2.0f + 0.5f); |
| priv->wet2 = priv->wet * ((1.0f - priv->width) / 2.0f); |
| break; |
| case PROP_LEVEL: |
| filter->level = g_value_get_float (value); |
| priv->wet = filter->level * scalewet; |
| priv->dry = (1.0 - filter->level) * scaledry; |
| priv->wet1 = priv->wet * (priv->width / 2.0f + 0.5f); |
| priv->wet2 = priv->wet * ((1.0f - priv->width) / 2.0f); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_freeverb_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstFreeverb *filter = GST_FREEVERB (object); |
| |
| switch (prop_id) { |
| case PROP_ROOM_SIZE: |
| g_value_set_float (value, filter->room_size); |
| break; |
| case PROP_DAMPING: |
| g_value_set_float (value, filter->damping); |
| break; |
| case PROP_PAN_WIDTH: |
| g_value_set_float (value, filter->pan_width); |
| break; |
| case PROP_LEVEL: |
| g_value_set_float (value, filter->level); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| /* GstBaseTransform vmethod implementations */ |
| |
| static gboolean |
| gst_freeverb_get_unit_size (GstBaseTransform * base, GstCaps * caps, |
| gsize * size) |
| { |
| GstAudioInfo info; |
| |
| g_assert (size); |
| |
| if (!gst_audio_info_from_caps (&info, caps)) |
| return FALSE; |
| |
| *size = GST_AUDIO_INFO_BPF (&info); |
| |
| GST_INFO_OBJECT (base, "unit size: %" G_GSIZE_FORMAT, *size); |
| |
| return TRUE; |
| } |
| |
| static GstCaps * |
| gst_freeverb_transform_caps (GstBaseTransform * base, |
| GstPadDirection direction, GstCaps * caps, GstCaps * filter) |
| { |
| GstCaps *res; |
| GstStructure *structure; |
| gint i; |
| |
| /* replace the channel property with our range. */ |
| res = gst_caps_copy (caps); |
| for (i = 0; i < gst_caps_get_size (res); i++) { |
| structure = gst_caps_get_structure (res, i); |
| if (direction == GST_PAD_SRC) { |
| GST_INFO_OBJECT (base, "[%d] allow 1-2 channels", i); |
| gst_structure_set (structure, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); |
| } else { |
| GST_INFO_OBJECT (base, "[%d] allow 2 channels", i); |
| gst_structure_set (structure, "channels", G_TYPE_INT, 2, NULL); |
| } |
| gst_structure_remove_field (structure, "channel-mask"); |
| } |
| GST_DEBUG_OBJECT (base, "transformed %" GST_PTR_FORMAT, res); |
| |
| if (filter) { |
| GstCaps *intersection; |
| |
| GST_DEBUG_OBJECT (base, "Using filter caps %" GST_PTR_FORMAT, filter); |
| intersection = |
| gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (res); |
| res = intersection; |
| GST_DEBUG_OBJECT (base, "Intersection %" GST_PTR_FORMAT, res); |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_freeverb_set_caps (GstBaseTransform * base, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| GstFreeverb *filter = GST_FREEVERB (base); |
| GstAudioInfo info; |
| |
| /*GST_INFO ("incaps are %" GST_PTR_FORMAT, incaps); */ |
| if (!gst_audio_info_from_caps (&info, incaps)) |
| goto no_format; |
| |
| GST_DEBUG ("try to process %d input with %d channels", |
| GST_AUDIO_INFO_FORMAT (&info), GST_AUDIO_INFO_CHANNELS (&info)); |
| |
| if (!gst_freeverb_set_process_function (filter, &info)) |
| goto no_format; |
| |
| filter->info = info; |
| |
| gst_freeverb_init_rev_model (filter); |
| filter->drained = FALSE; |
| GST_INFO_OBJECT (base, "model configured"); |
| |
| return TRUE; |
| |
| no_format: |
| { |
| GST_DEBUG ("invalid caps"); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_freeverb_transform_m2s_int (GstFreeverb * filter, |
| gint16 * idata, gint16 * odata, guint num_samples) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i, k; |
| gfloat out_l1, out_r1, input_1; |
| gfloat out_l2, out_r2, input_2; |
| gboolean drained = TRUE; |
| |
| for (k = 0; k < num_samples; k++) { |
| out_l1 = out_r1 = 0.0; |
| |
| /* The original Freeverb code expects a stereo signal and 'input_1' |
| * is set to the sum of the left and right input_1 sample. Since |
| * this code works on a mono signal, 'input_1' is set to twice the |
| * input_1 sample. */ |
| input_2 = (gfloat) * idata++; |
| input_1 = (2.0f * input_2 + DC_OFFSET) * priv->gain; |
| |
| /* Accumulate comb filters in parallel */ |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_process (priv->combL[i], input_1, out_l1); |
| freeverb_comb_process (priv->combR[i], input_1, out_r1); |
| } |
| /* Feed through allpasses in series */ |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_process (priv->allpassL[i], out_l1); |
| freeverb_allpass_process (priv->allpassR[i], out_r1); |
| } |
| |
| /* Remove the DC offset */ |
| out_l1 -= DC_OFFSET; |
| out_r1 -= DC_OFFSET; |
| |
| /* Calculate output */ |
| out_l2 = out_l1 * priv->wet1 + out_r1 * priv->wet2 + input_2 * priv->dry; |
| out_r2 = out_r1 * priv->wet1 + out_l1 * priv->wet2 + input_2 * priv->dry; |
| out_l2 = CLAMP (out_l2, G_MININT16, G_MAXINT16); |
| out_r2 = CLAMP (out_r2, G_MININT16, G_MAXINT16); |
| *odata++ = (gint16) out_l2; |
| *odata++ = (gint16) out_r2; |
| |
| if (abs ((gint16) out_l2) > 0 || abs ((gint16) out_r2) > 0) |
| drained = FALSE; |
| } |
| return drained; |
| } |
| |
| static gboolean |
| gst_freeverb_transform_s2s_int (GstFreeverb * filter, |
| gint16 * idata, gint16 * odata, guint num_samples) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i, k; |
| gfloat out_l1, out_r1, input_1l, input_1r; |
| gfloat out_l2, out_r2, input_2l, input_2r; |
| gboolean drained = TRUE; |
| |
| for (k = 0; k < num_samples; k++) { |
| out_l1 = out_r1 = 0.0; |
| |
| input_2l = (gfloat) * idata++; |
| input_2r = (gfloat) * idata++; |
| input_1l = (input_2l + DC_OFFSET) * priv->gain; |
| input_1r = (input_2r + DC_OFFSET) * priv->gain; |
| |
| /* Accumulate comb filters in parallel */ |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_process (priv->combL[i], input_1l, out_l1); |
| freeverb_comb_process (priv->combR[i], input_1r, out_r1); |
| } |
| /* Feed through allpasses in series */ |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_process (priv->allpassL[i], out_l1); |
| freeverb_allpass_process (priv->allpassR[i], out_r1); |
| } |
| |
| /* Remove the DC offset */ |
| out_l1 -= DC_OFFSET; |
| out_r1 -= DC_OFFSET; |
| |
| /* Calculate output */ |
| out_l2 = out_l1 * priv->wet1 + out_r1 * priv->wet2 + input_2l * priv->dry; |
| out_r2 = out_r1 * priv->wet1 + out_l1 * priv->wet2 + input_2r * priv->dry; |
| out_l2 = CLAMP (out_l2, G_MININT16, G_MAXINT16); |
| out_r2 = CLAMP (out_r2, G_MININT16, G_MAXINT16); |
| *odata++ = (gint16) out_l2; |
| *odata++ = (gint16) out_r2; |
| |
| if (abs ((gint16) out_l2) > 0 || abs ((gint16) out_r2) > 0) |
| drained = FALSE; |
| } |
| return drained; |
| } |
| |
| static gboolean |
| gst_freeverb_transform_m2s_float (GstFreeverb * filter, |
| gfloat * idata, gfloat * odata, guint num_samples) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i, k; |
| gfloat out_l1, out_r1, input_1; |
| gfloat out_l2, out_r2, input_2; |
| gboolean drained = TRUE; |
| |
| for (k = 0; k < num_samples; k++) { |
| out_l1 = out_r1 = 0.0; |
| |
| /* The original Freeverb code expects a stereo signal and 'input_1' |
| * is set to the sum of the left and right input_1 sample. Since |
| * this code works on a mono signal, 'input_1' is set to twice the |
| * input_1 sample. */ |
| input_2 = *idata++; |
| input_1 = (2.0f * input_2 + DC_OFFSET) * priv->gain; |
| |
| /* Accumulate comb filters in parallel */ |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_process (priv->combL[i], input_1, out_l1); |
| freeverb_comb_process (priv->combR[i], input_1, out_r1); |
| } |
| /* Feed through allpasses in series */ |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_process (priv->allpassL[i], out_l1); |
| freeverb_allpass_process (priv->allpassR[i], out_r1); |
| } |
| |
| /* Remove the DC offset */ |
| out_l1 -= DC_OFFSET; |
| out_r1 -= DC_OFFSET; |
| |
| /* Calculate output */ |
| out_l2 = out_l1 * priv->wet1 + out_r1 * priv->wet2 + input_2 * priv->dry; |
| out_r2 = out_r1 * priv->wet1 + out_l1 * priv->wet2 + input_2 * priv->dry; |
| *odata++ = out_l2; |
| *odata++ = out_r2; |
| |
| if (fabs (out_l2) > 0 || fabs (out_r2) > 0) |
| drained = FALSE; |
| } |
| return drained; |
| } |
| |
| static gboolean |
| gst_freeverb_transform_s2s_float (GstFreeverb * filter, |
| gfloat * idata, gfloat * odata, guint num_samples) |
| { |
| GstFreeverbPrivate *priv = filter->priv; |
| gint i, k; |
| gfloat out_l1, out_r1, input_1l, input_1r; |
| gfloat out_l2, out_r2, input_2l, input_2r; |
| gboolean drained = TRUE; |
| |
| for (k = 0; k < num_samples; k++) { |
| out_l1 = out_r1 = 0.0; |
| |
| input_2l = *idata++; |
| input_2r = *idata++; |
| input_1l = (input_2l + DC_OFFSET) * priv->gain; |
| input_1r = (input_2r + DC_OFFSET) * priv->gain; |
| |
| /* Accumulate comb filters in parallel */ |
| for (i = 0; i < numcombs; i++) { |
| freeverb_comb_process (priv->combL[i], input_1l, out_l1); |
| freeverb_comb_process (priv->combR[i], input_1r, out_r1); |
| } |
| /* Feed through allpasses in series */ |
| for (i = 0; i < numallpasses; i++) { |
| freeverb_allpass_process (priv->allpassL[i], out_l1); |
| freeverb_allpass_process (priv->allpassR[i], out_r1); |
| } |
| |
| /* Remove the DC offset */ |
| out_l1 -= DC_OFFSET; |
| out_r1 -= DC_OFFSET; |
| |
| /* Calculate output */ |
| out_l2 = out_l1 * priv->wet1 + out_r1 * priv->wet2 + input_2l * priv->dry; |
| out_r2 = out_r1 * priv->wet1 + out_l1 * priv->wet2 + input_2r * priv->dry; |
| *odata++ = out_l2; |
| *odata++ = out_r2; |
| |
| if (fabs (out_l2) > 0 || fabs (out_r2) > 0) |
| drained = FALSE; |
| } |
| return drained; |
| } |
| |
| /* this function does the actual processing |
| */ |
| static GstFlowReturn |
| gst_freeverb_transform (GstBaseTransform * base, GstBuffer * inbuf, |
| GstBuffer * outbuf) |
| { |
| GstFreeverb *filter = GST_FREEVERB (base); |
| guint num_samples; |
| GstClockTime timestamp; |
| GstMapInfo inmap, outmap; |
| |
| timestamp = GST_BUFFER_TIMESTAMP (inbuf); |
| timestamp = |
| gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp); |
| |
| gst_buffer_map (inbuf, &inmap, GST_MAP_READ); |
| gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE); |
| num_samples = outmap.size / (2 * GST_AUDIO_INFO_BPS (&filter->info)); |
| |
| GST_DEBUG_OBJECT (filter, "processing %u samples at %" GST_TIME_FORMAT, |
| num_samples, GST_TIME_ARGS (timestamp)); |
| |
| if (GST_CLOCK_TIME_IS_VALID (timestamp)) |
| gst_object_sync_values (GST_OBJECT (filter), timestamp); |
| |
| if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT))) { |
| filter->drained = FALSE; |
| } |
| if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP))) { |
| if (filter->drained) { |
| memset (outmap.data, 0, outmap.size); |
| } |
| } else { |
| filter->drained = FALSE; |
| } |
| |
| if (!filter->drained) { |
| filter->drained = |
| filter->process (filter, inmap.data, outmap.data, num_samples); |
| } |
| |
| if (filter->drained) { |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP); |
| } |
| |
| gst_buffer_unmap (inbuf, &inmap); |
| gst_buffer_unmap (outbuf, &outmap); |
| |
| return GST_FLOW_OK; |
| } |
| |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "freeverb", |
| GST_RANK_NONE, GST_TYPE_FREEVERB); |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| freeverb, |
| "Reverberation/room effect", |
| plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |