freeverb: add a freeverb port
authorStefan Sauer <ensonic@users.sf.net>
Mon, 21 Nov 2011 21:55:40 +0000 (22:55 +0100)
committerStefan Sauer <ensonic@users.sf.net>
Tue, 22 Nov 2011 08:10:57 +0000 (09:10 +0100)
Freeverb is a public domain reverb implementation. Port it as a gstreamer
element and make use of gstreamer specific features (gap aware, disconts,
controller, ...).

configure.ac
gst/freeverb/Makefile.am [new file with mode: 0644]
gst/freeverb/gstfreeverb.c [new file with mode: 0644]
gst/freeverb/gstfreeverb.h [new file with mode: 0644]

index dca8d0be1738234ea8e45ffd0341c7ecd86f21b2..4c40173a330c1e5587e5b1fdcebdb196ab7b24f3 100644 (file)
@@ -315,6 +315,7 @@ AG_GST_CHECK_PLUGIN(faceoverlay)
 AG_GST_CHECK_PLUGIN(festival)
 AG_GST_CHECK_PLUGIN(fieldanalysis)
 AG_GST_CHECK_PLUGIN(freeze)
+AG_GST_CHECK_PLUGIN(freeverb)
 AG_GST_CHECK_PLUGIN(frei0r)
 AG_GST_CHECK_PLUGIN(gaudieffects)
 AG_GST_CHECK_PLUGIN(geometrictransform)
@@ -1909,6 +1910,7 @@ gst/faceoverlay/Makefile
 gst/festival/Makefile
 gst/fieldanalysis/Makefile
 gst/freeze/Makefile
+gst/freeverb/Makefile
 gst/frei0r/Makefile
 gst/gaudieffects/Makefile
 gst/geometrictransform/Makefile
diff --git a/gst/freeverb/Makefile.am b/gst/freeverb/Makefile.am
new file mode 100644 (file)
index 0000000..73706a2
--- /dev/null
@@ -0,0 +1,28 @@
+plugin_LTLIBRARIES = libgstfreeverb.la
+
+# sources used to compile this plug-in
+libgstfreeverb_la_SOURCES = gstfreeverb.c
+
+# flags used to compile this plugin
+# add other _CFLAGS and _LIBS as needed
+libgstfreeverb_la_CFLAGS = $(GST_CFLAGS) $(GST_CONTROLLER_CFLAGS) 
+libgstfreeverb_la_LIBADD = $(GST_BASE_LIBS) $(GST_CONTROLLER_LIBS) $(GST_LIBS)
+libgstfreeverb_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstfreeverb_la_LIBTOOLFLAGS = --tag=disable-static
+
+# headers we need but don't want installed
+noinst_HEADERS = gstfreeverb.h
+
+Android.mk: Makefile.am $(BUILT_SOURCES)
+       androgenizer \
+       -:PROJECT libgstfreeverb -:SHARED libgstfreeverb \
+        -:TAGS eng debug \
+         -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
+        -:SOURCES $(libgstfreeverb_la_SOURCES) \
+        -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstfreeverb_la_CFLAGS) \
+        -:LDFLAGS $(libgstfreeverb_la_LDFLAGS) \
+                  $(libgstfreeverb_la_LIBADD) \
+                  -ldl \
+        -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
+                      LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \
+       > $@
diff --git a/gst/freeverb/gstfreeverb.c b/gst/freeverb/gstfreeverb.c
new file mode 100644 (file)
index 0000000..1255eb8
--- /dev/null
@@ -0,0 +1,972 @@
+/*
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, 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 <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+#include <gst/controller/gstcontroller.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-float, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "endianness = (int) BYTE_ORDER, " "width = (int) 32; "
+        "audio/x-raw-int, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) [ 1, 2 ], "
+        "endianness = (int) BYTE_ORDER, "
+        "width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true")
+    );
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS ("audio/x-raw-float, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) 2, "
+        "endianness = (int) BYTE_ORDER, " "width = (int) 32; "
+        "audio/x-raw-int, "
+        "rate = (int) [ 1, MAX ], "
+        "channels = (int) 2, "
+        "endianness = (int) BYTE_ORDER, "
+        "width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true")
+    );
+
+#define _do_init(type) {                                                       \
+  const GInterfaceInfo preset_interface_info = { NULL, NULL, NULL };           \
+  g_type_add_interface_static (type, GST_TYPE_PRESET, &preset_interface_info); \
+                                                                               \
+  GST_DEBUG_CATEGORY_INIT (gst_freeverb_debug, "freeverb", 0,                  \
+      "freeverb element");                                                     \
+}
+
+GST_BOILERPLATE_FULL (GstFreeverb, gst_freeverb, GstBaseTransform,
+    GST_TYPE_BASE_TRANSFORM, _do_init);
+
+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, guint * size);
+static GstCaps *gst_freeverb_transform_caps (GstBaseTransform * base,
+    GstPadDirection direction, GstCaps * caps);
+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_base_init (gpointer klass)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+  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_element_class_set_details_simple (element_class, "Stereo positioning",
+      "Filter/Effect/Audio",
+      "Reverberation/room effect", "Stefan Sauer <ensonic@users.sf.net>");
+}
+
+static void
+gst_freeverb_class_init (GstFreeverbClass * klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (GstFreeverbPrivate));
+
+  gobject_class = (GObjectClass *) 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_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, GstFreeverbClass * klass)
+{
+  filter->priv =
+      G_TYPE_INSTANCE_GET_PRIVATE (filter, GST_TYPE_FREEVERB,
+      GstFreeverbPrivate);
+
+  filter->width = 0;
+  filter->channels = 0;
+  filter->format_float = FALSE;
+  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 (parent_class)->finalize (object);
+}
+
+static gboolean
+gst_freeverb_set_process_function (GstFreeverb * filter)
+{
+  gint channel_index, format_index;
+
+  /* set processing function */
+  channel_index = filter->channels - 1;
+  if (channel_index > 1 || channel_index < 0) {
+    filter->process = NULL;
+    return FALSE;
+  }
+
+  format_index = (filter->format_float) ? 1 : 0;
+
+  filter->process = process_functions[channel_index][format_index];
+
+  g_assert (filter->process);
+  return TRUE;
+}
+
+static void
+gst_freeverb_init_rev_model (GstFreeverb * filter)
+{
+  gfloat srfactor = filter->rate / 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,
+    guint * size)
+{
+  gint width, channels;
+  GstStructure *structure;
+  gboolean ret;
+
+  g_assert (size);
+
+  /* this works for both float and int */
+  structure = gst_caps_get_structure (caps, 0);
+  ret = gst_structure_get_int (structure, "width", &width);
+  ret &= gst_structure_get_int (structure, "channels", &channels);
+
+  *size = width * channels / 8;
+
+  GST_INFO_OBJECT (base, "unit size: %u", *size);
+
+  return ret;
+}
+
+static GstCaps *
+gst_freeverb_transform_caps (GstBaseTransform * base,
+    GstPadDirection direction, GstCaps * caps)
+{
+  GstCaps *res;
+  GstStructure *structure;
+
+  /* transform caps gives one single caps so we can just replace
+   * the channel property with our range. */
+  res = gst_caps_copy (caps);
+  structure = gst_caps_get_structure (res, 0);
+  if (direction == GST_PAD_SRC) {
+    GST_INFO_OBJECT (base, "allow 1-2 channels");
+    gst_structure_set (structure, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL);
+  } else {
+    GST_INFO_OBJECT (base, "allow 2 channels");
+    gst_structure_set (structure, "channels", G_TYPE_INT, 2, NULL);
+  }
+
+  return res;
+}
+
+static gboolean
+gst_freeverb_set_caps (GstBaseTransform * base, GstCaps * incaps,
+    GstCaps * outcaps)
+{
+  GstFreeverb *filter = GST_FREEVERB (base);
+  const GstStructure *structure;
+  gboolean ret;
+  gint width, rate;
+  const gchar *fmt;
+
+  /*GST_INFO ("incaps are %" GST_PTR_FORMAT, incaps); */
+
+  structure = gst_caps_get_structure (incaps, 0);
+  ret = gst_structure_get_int (structure, "channels", &filter->channels);
+  if (!ret)
+    goto no_channels;
+
+  ret = gst_structure_get_int (structure, "width", &width);
+  if (!ret)
+    goto no_width;
+  filter->width = width / 8;
+
+  ret = gst_structure_get_int (structure, "rate", &rate);
+  if (!ret)
+    goto no_rate;
+  filter->rate = rate;
+
+  fmt = gst_structure_get_name (structure);
+  if (!strcmp (fmt, "audio/x-raw-int"))
+    filter->format_float = FALSE;
+  else
+    filter->format_float = TRUE;
+
+  GST_DEBUG_OBJECT (filter, "try to process %s input_1 with %d channels", fmt,
+      filter->channels);
+
+  ret = gst_freeverb_set_process_function (filter);
+  if (!ret)
+    GST_WARNING_OBJECT (filter, "can't process input_1 with %d channels",
+        filter->channels);
+
+  gst_freeverb_init_rev_model (filter);
+  filter->drained = FALSE;
+  GST_INFO_OBJECT (base, "model configured");
+
+  return ret;
+
+no_channels:
+  GST_DEBUG_OBJECT (filter, "no channels in caps");
+  return ret;
+no_width:
+  GST_DEBUG_OBJECT (filter, "no width in caps");
+  return ret;
+no_rate:
+  GST_DEBUG_OBJECT (filter, "no rate in caps");
+  return ret;
+}
+
+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;
+    *odata++ = (gint16) CLAMP (out_l2, G_MININT16, G_MAXINT16);
+    *odata++ = (gint16) CLAMP (out_r2, G_MININT16, G_MAXINT16);
+
+    if (abs (out_l2) > 0 || abs (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;
+    *odata++ = (gint16) CLAMP (out_l2, G_MININT16, G_MAXINT16);
+    *odata++ = (gint16) CLAMP (out_r2, G_MININT16, G_MAXINT16);
+
+    if (abs (out_l2) > 0 || abs (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 = GST_BUFFER_SIZE (outbuf) / (2 * filter->width);
+  GstClockTime timestamp;
+
+  timestamp = GST_BUFFER_TIMESTAMP (inbuf);
+  timestamp =
+      gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
+
+  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 (G_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) {
+      GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
+      memset (GST_BUFFER_DATA (outbuf), 0, GST_BUFFER_SIZE (outbuf));
+      return GST_FLOW_OK;
+    }
+  } else {
+    filter->drained = FALSE;
+  }
+
+  filter->drained = filter->process (filter, GST_BUFFER_DATA (inbuf),
+      GST_BUFFER_DATA (outbuf), num_samples);
+
+  if (filter->drained) {
+    GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_GAP);
+  }
+
+  return GST_FLOW_OK;
+}
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gst_controller_init (NULL, NULL);
+
+  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)
diff --git a/gst/freeverb/gstfreeverb.h b/gst/freeverb/gstfreeverb.h
new file mode 100644 (file)
index 0000000..23e3940
--- /dev/null
@@ -0,0 +1,71 @@
+/* 
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __GST_FREEVERB_H__
+#define __GST_FREEVERB_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_FREEVERB            (gst_freeverb_get_type())
+#define GST_FREEVERB(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FREEVERB,GstFreeverb))
+#define GST_IS_FREEVERB(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FREEVERB))
+#define GST_FREEVERB_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass) ,GST_TYPE_FREEVERB,GstFreeverbClass))
+#define GST_IS_FREEVERB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) ,GST_TYPE_FREEVERB))
+#define GST_FREEVERB_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_FREEVERB,GstFreeverbClass))
+
+typedef struct _GstFreeverb        GstFreeverb;
+typedef struct _GstFreeverbClass   GstFreeverbClass;
+typedef struct _GstFreeverbPrivate GstFreeverbPrivate;
+
+typedef gboolean (*GstFreeverbProcessFunc)(GstFreeverb*, guint8*, guint8*, guint);
+
+struct _GstFreeverb {
+  GstBaseTransform element;
+  
+  /* < private > */
+  gfloat room_size;
+  gfloat damping;
+  gfloat pan_width;
+  gfloat level;
+
+  GstFreeverbProcessFunc process;
+  gint channels;
+  gboolean format_float;
+  gint width;
+  gint method;
+  gint rate;
+  
+  gboolean drained;
+  
+  GstFreeverbPrivate *priv;
+};
+
+struct _GstFreeverbClass {
+  GstBaseTransformClass parent_class;
+};
+
+GType gst_freeverb_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_FREEVERB_H__ */