summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristoph Reiter <reiter.christoph@gmail.com>2015-01-10 21:41:12 +0100
committerStefan Sauer <ensonic@users.sf.net>2015-01-15 19:59:03 +0100
commit75f8cca325065685bfd31d3907f4af051f373572 (patch)
treee83a0a7f4c34b015a6eed15bfdab7ab25497afab
parentefb74ca0df86954d2cbb51109e3bc214d814f874 (diff)
bs2b: add new plugin (Effect/Audio, crossfeed)
https://bugzilla.gnome.org/show_bug.cgi?id=611689
-rw-r--r--configure.ac12
-rw-r--r--ext/Makefile.am8
-rw-r--r--ext/bs2b/Makefile.am15
-rw-r--r--ext/bs2b/gstbs2b.c415
-rw-r--r--ext/bs2b/gstbs2b.h63
5 files changed, 513 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 71ca13c27..042b82e98 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1820,6 +1820,16 @@ AG_GST_CHECK_FEATURE(APEXSINK, [AirPort Express Wireless sink], apexsink, [
])
])
+dnl *** bs2b ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_BS2B, true)
+AG_GST_CHECK_FEATURE(BS2B, [bs2b], bs2b, [
+ PKG_CHECK_MODULES(BS2B, libbs2b >= 3.1.0, HAVE_BS2B="yes", [
+ HAVE_BS2B="no"
+ ])
+ AC_SUBST(BS2B_CFLAGS)
+ AC_SUBST(BS2B_LIBS)
+])
+
dnl *** BZ2 ***
translit(dnm, m, l) AM_CONDITIONAL(USE_BZ2, true)
AG_GST_CHECK_FEATURE(BZ2, [bz2 library], bz2, [
@@ -2998,6 +3008,7 @@ AM_CONDITIONAL(USE_ASSRENDER, false)
AM_CONDITIONAL(USE_VOAMRWBENC, false)
AM_CONDITIONAL(USE_VOAACENC, false)
AM_CONDITIONAL(USE_APEXSINK, false)
+AM_CONDITIONAL(USE_BS2B, false)
AM_CONDITIONAL(USE_BZ2, false)
AM_CONDITIONAL(USE_CHROMAPRINT, false)
AM_CONDITIONAL(USE_CURL, false)
@@ -3308,6 +3319,7 @@ ext/voamrwbenc/Makefile
ext/voaacenc/Makefile
ext/assrender/Makefile
ext/apexsink/Makefile
+ext/bs2b/Makefile
ext/bz2/Makefile
ext/chromaprint/Makefile
ext/curl/Makefile
diff --git a/ext/Makefile.am b/ext/Makefile.am
index 3859cf773..273758e7b 100644
--- a/ext/Makefile.am
+++ b/ext/Makefile.am
@@ -22,6 +22,12 @@ endif
AUDIOFILE_DIR=
# endif
+if USE_BS2B
+BS2B_DIR=bs2b
+else
+BS2B_DIR=
+endif
+
if USE_BZ2
BZ2_DIR=bz2
else
@@ -418,6 +424,7 @@ SUBDIRS=\
$(VOAMRWBENC_DIR) \
$(APEXSINK_DIR) \
$(AUDIOFILE_DIR) \
+ $(BS2B_DIR) \
$(BZ2_DIR) \
$(CHROMAPRINT_DIR) \
$(CURL_DIR) \
@@ -485,6 +492,7 @@ SUBDIRS=\
DIST_SUBDIRS = \
assrender \
apexsink \
+ bs2b \
bz2 \
chromaprint \
curl \
diff --git a/ext/bs2b/Makefile.am b/ext/bs2b/Makefile.am
new file mode 100644
index 000000000..8516ec9dc
--- /dev/null
+++ b/ext/bs2b/Makefile.am
@@ -0,0 +1,15 @@
+plugin_LTLIBRARIES = libgstbs2b.la
+
+libgstbs2b_la_SOURCES = gstbs2b.c gstbs2b.h
+
+libgstbs2b_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \
+ $(BS2B_CFLAGS)
+libgstbs2b_la_LIBADD = \
+ $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \
+ $(GST_BASE_LIBS) $(GST_LIBS) \
+ $(BS2B_LIBS)
+libgstbs2b_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstbs2b_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = gstbs2b.h
diff --git a/ext/bs2b/gstbs2b.c b/ext/bs2b/gstbs2b.c
new file mode 100644
index 000000000..cfa702d8b
--- /dev/null
+++ b/ext/bs2b/gstbs2b.c
@@ -0,0 +1,415 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2003> David Schleef <ds@schleef.org>
+ * Copyright (C) <2011,2014> Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+/**
+ * SECTION:element-bs2b
+ *
+ * Improve headphone listening of stereo audio records using the bs2b library.
+ * It does so by mixing the left and right channel in a way that simulates
+ * a stereo speaker setup while using headphones.
+ *
+ * <refsect2>
+ * <title>Example pipelines</title>
+ * |[
+ * gst-launch-1.0 audiotestsrc ! "audio/x-raw,channel-mask=(bitmask)0x1" ! interleave name=i ! bs2b ! autoaudiosink audiotestsrc freq=330 ! "audio/x-raw,channel-mask=(bitmask)0x2" ! i.
+ * ]| Play two independent sine test sources and crossfeed them.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <gst/audio/gstaudiofilter.h>
+
+#include "gstbs2b.h"
+
+#define GST_BS2B_DP_LOCK(obj) g_mutex_lock (&obj->bs2b_lock)
+#define GST_BS2B_DP_UNLOCK(obj) g_mutex_unlock (&obj->bs2b_lock)
+
+#define SUPPORTED_FORMAT \
+ "(string) { S8, U8, S16LE, S16BE, U16LE, U16BE, S32LE, S32BE, U32LE, " \
+ "U32BE, S24LE, S24BE, U24LE, U24BE, F32LE, F32BE, F64LE, F64BE }"
+
+#define SUPPORTED_RATE \
+ "(int) [ " G_STRINGIFY (BS2B_MINSRATE) ", " G_STRINGIFY (BS2B_MAXSRATE) " ]"
+
+#define FRONT_L_FRONT_R "(bitmask) 0x3"
+
+#define PAD_CAPS \
+ "audio/x-raw, " \
+ "format = " SUPPORTED_FORMAT ", " \
+ "rate = " SUPPORTED_RATE ", " \
+ "channels = (int) 2, " \
+ "channel-mask = " FRONT_L_FRONT_R ", " \
+ "layout = (string) interleaved" \
+ "; " \
+ "audio/x-raw, " \
+ "channels = (int) 1" \
+
+enum
+{
+ PROP_FCUT = 1,
+ PROP_FEED,
+ PROP_PRESET,
+ PROP_LAST,
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+enum
+{
+ PRESET_DEFAULT,
+ PRESET_CMOY,
+ PRESET_JMEIER,
+ PRESET_NONE
+};
+
+G_DEFINE_TYPE (GstBs2b, gst_bs2b, GST_TYPE_AUDIO_FILTER);
+
+static GType gst_bs2b_preset_get_type (void);
+
+static void gst_bs2b_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_bs2b_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_bs2b_finalize (GObject * object);
+
+static GstFlowReturn gst_bs2b_transform_inplace (GstBaseTransform *
+ base_transform, GstBuffer * buffer);
+static gboolean gst_bs2b_setup (GstAudioFilter * self,
+ const GstAudioInfo * audio_info);
+
+
+static void
+gst_bs2b_class_init (GstBs2bClass * klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
+ GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
+ GstCaps *caps;
+
+ gobject_class->set_property = gst_bs2b_set_property;
+ gobject_class->get_property = gst_bs2b_get_property;
+ gobject_class->finalize = gst_bs2b_finalize;
+
+ trans_class->transform_ip = gst_bs2b_transform_inplace;
+ trans_class->transform_ip_on_passthrough = FALSE;
+
+ filter_class->setup = gst_bs2b_setup;
+
+ properties[PROP_FCUT] = g_param_spec_int ("fcut", "Frequency cut",
+ "Low-pass filter cut frequency (Hz)",
+ BS2B_MINFCUT, BS2B_MAXFCUT, BS2B_DEFAULT_CLEVEL & 0xFFFF,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_FEED] =
+ g_param_spec_int ("feed", "Feed level", "Feed Level (dB/10)",
+ BS2B_MINFEED, BS2B_MAXFEED, BS2B_DEFAULT_CLEVEL >> 16,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_PRESET] =
+ g_param_spec_enum ("preset", "Preset", "Bs2b filter preset",
+ gst_bs2b_preset_get_type (), PRESET_DEFAULT,
+ G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, properties);
+
+ gst_element_class_set_metadata (element_class,
+ "Crossfeed effect",
+ "Filter/Effect/Audio",
+ "Improve headphone listening of stereo audio records using the bs2b "
+ "library.", "Christoph Reiter <reiter.christoph@gmail.com>");
+
+ caps = gst_caps_from_string (PAD_CAPS);
+ gst_audio_filter_class_add_pad_templates (filter_class, caps);
+ gst_caps_unref (caps);
+}
+
+static void
+gst_bs2b_init (GstBs2b * element)
+{
+ g_mutex_init (&element->bs2b_lock);
+ element->bs2bdp = bs2b_open ();
+}
+
+static gboolean
+gst_bs2b_setup (GstAudioFilter * filter, const GstAudioInfo * audio_info)
+{
+ GstBaseTransform *base_transform = GST_BASE_TRANSFORM (filter);
+ GstBs2b *element = GST_BS2B (filter);
+ gint channels = GST_AUDIO_INFO_CHANNELS (audio_info);
+
+ element->func = NULL;
+
+ if (channels == 1) {
+ gst_base_transform_set_passthrough (base_transform, TRUE);
+ return TRUE;
+ }
+
+ g_assert (channels == 2);
+ gst_base_transform_set_passthrough (base_transform, FALSE);
+
+ switch (GST_AUDIO_INFO_FORMAT (audio_info)) {
+ case GST_AUDIO_FORMAT_S8:
+ element->func = &bs2b_cross_feed_s8;
+ break;
+ case GST_AUDIO_FORMAT_U8:
+ element->func = &bs2b_cross_feed_u8;
+ break;
+ case GST_AUDIO_FORMAT_S16BE:
+ element->func = &bs2b_cross_feed_s16be;
+ break;
+ case GST_AUDIO_FORMAT_S16LE:
+ element->func = &bs2b_cross_feed_s16le;
+ break;
+ case GST_AUDIO_FORMAT_U16BE:
+ element->func = &bs2b_cross_feed_u16be;
+ break;
+ case GST_AUDIO_FORMAT_U16LE:
+ element->func = &bs2b_cross_feed_u16le;
+ break;
+ case GST_AUDIO_FORMAT_S24BE:
+ element->func = &bs2b_cross_feed_s24be;
+ break;
+ case GST_AUDIO_FORMAT_S24LE:
+ element->func = &bs2b_cross_feed_s24le;
+ break;
+ case GST_AUDIO_FORMAT_U24BE:
+ element->func = &bs2b_cross_feed_u24be;
+ break;
+ case GST_AUDIO_FORMAT_U24LE:
+ element->func = &bs2b_cross_feed_u24le;
+ break;
+ case GST_AUDIO_FORMAT_S32BE:
+ element->func = &bs2b_cross_feed_s32be;
+ break;
+ case GST_AUDIO_FORMAT_S32LE:
+ element->func = &bs2b_cross_feed_s32le;
+ break;
+ case GST_AUDIO_FORMAT_U32BE:
+ element->func = &bs2b_cross_feed_u32be;
+ break;
+ case GST_AUDIO_FORMAT_U32LE:
+ element->func = &bs2b_cross_feed_u32le;
+ break;
+ case GST_AUDIO_FORMAT_F32BE:
+ element->func = &bs2b_cross_feed_fbe;
+ break;
+ case GST_AUDIO_FORMAT_F32LE:
+ element->func = &bs2b_cross_feed_fle;
+ break;
+ case GST_AUDIO_FORMAT_F64BE:
+ element->func = &bs2b_cross_feed_dbe;
+ break;
+ case GST_AUDIO_FORMAT_F64LE:
+ element->func = &bs2b_cross_feed_dle;
+ break;
+ default:
+ return FALSE;
+ }
+
+ g_assert (element->func);
+ element->bytes_per_sample =
+ (GST_AUDIO_INFO_WIDTH (audio_info) * channels) / 8;
+
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_srate (element->bs2bdp, GST_AUDIO_INFO_RATE (audio_info));
+ GST_BS2B_DP_UNLOCK (element);
+
+ return TRUE;
+}
+
+static void
+gst_bs2b_finalize (GObject * object)
+{
+ GstBs2b *element = GST_BS2B (object);
+
+ bs2b_close (element->bs2bdp);
+ element->bs2bdp = NULL;
+
+ G_OBJECT_CLASS (gst_bs2b_parent_class)->finalize (object);
+}
+
+static GstFlowReturn
+gst_bs2b_transform_inplace (GstBaseTransform * base_transform,
+ GstBuffer * buffer)
+{
+ GstBs2b *element = GST_BS2B (base_transform);
+ GstMapInfo map_info;
+
+ if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_WRITE))
+ return GST_FLOW_ERROR;
+
+ GST_BS2B_DP_LOCK (element);
+ if (GST_BUFFER_IS_DISCONT (buffer))
+ bs2b_clear (element->bs2bdp);
+ element->func (element->bs2bdp, map_info.data,
+ map_info.size / element->bytes_per_sample);
+ GST_BS2B_DP_UNLOCK (element);
+
+ gst_buffer_unmap (buffer, &map_info);
+
+ return GST_FLOW_OK;
+}
+
+static GType
+gst_bs2b_preset_get_type (void)
+{
+ static GType bs2b_preset_type = 0;
+
+ if (!bs2b_preset_type) {
+ static GEnumValue types[] = {
+ {
+ PRESET_DEFAULT,
+ "Closest to virtual speaker placement (30°, 3 meter) [700Hz, 4.5dB]",
+ "default"},
+ {
+ PRESET_CMOY,
+ "Close to Chu Moy's crossfeeder (popular) [700Hz, 6.0dB]",
+ "cmoy"},
+ {
+ PRESET_JMEIER,
+ "Close to Jan Meier's CORDA amplifiers (little change) [650Hz, 9.0dB]",
+ "jmeier"},
+ {
+ PRESET_NONE,
+ "No preset",
+ "none"},
+ {0, NULL, NULL},
+ };
+
+ bs2b_preset_type = g_enum_register_static ("GstBs2bPreset", types);
+ }
+
+ return bs2b_preset_type;
+}
+
+static void
+gst_bs2b_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstBs2b *element = GST_BS2B (object);
+
+ switch (prop_id) {
+ case PROP_FCUT:
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_level_fcut (element->bs2bdp, g_value_get_int (value));
+ bs2b_clear (element->bs2bdp);
+ GST_BS2B_DP_UNLOCK (element);
+ g_object_notify_by_pspec (object, properties[PROP_PRESET]);
+ break;
+ case PROP_FEED:
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_level_feed (element->bs2bdp, g_value_get_int (value));
+ bs2b_clear (element->bs2bdp);
+ GST_BS2B_DP_UNLOCK (element);
+ g_object_notify_by_pspec (object, properties[PROP_PRESET]);
+ break;
+ case PROP_PRESET:
+ switch (g_value_get_enum (value)) {
+ case PRESET_DEFAULT:
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_level (element->bs2bdp, BS2B_DEFAULT_CLEVEL);
+ bs2b_clear (element->bs2bdp);
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ case PRESET_CMOY:
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_level (element->bs2bdp, BS2B_CMOY_CLEVEL);
+ bs2b_clear (element->bs2bdp);
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ case PRESET_JMEIER:
+ GST_BS2B_DP_LOCK (element);
+ bs2b_set_level (element->bs2bdp, BS2B_JMEIER_CLEVEL);
+ bs2b_clear (element->bs2bdp);
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ return;
+ }
+ g_object_notify_by_pspec (object, properties[PROP_FCUT]);
+ g_object_notify_by_pspec (object, properties[PROP_FEED]);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_bs2b_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstBs2b *element = GST_BS2B (object);
+
+ switch (prop_id) {
+ case PROP_FCUT:
+ GST_BS2B_DP_LOCK (element);
+ g_value_set_int (value, bs2b_get_level_fcut (element->bs2bdp));
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ case PROP_FEED:
+ GST_BS2B_DP_LOCK (element);
+ g_value_set_int (value, bs2b_get_level_feed (element->bs2bdp));
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ case PROP_PRESET:
+ GST_BS2B_DP_LOCK (element);
+ switch (bs2b_get_level (element->bs2bdp)) {
+ case BS2B_DEFAULT_CLEVEL:
+ g_value_set_enum (value, PRESET_DEFAULT);
+ break;
+ case BS2B_CMOY_CLEVEL:
+ g_value_set_enum (value, PRESET_CMOY);
+ break;
+ case BS2B_JMEIER_CLEVEL:
+ g_value_set_enum (value, PRESET_JMEIER);
+ break;
+ default:
+ g_value_set_enum (value, PRESET_NONE);
+ break;
+ }
+ GST_BS2B_DP_UNLOCK (element);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return gst_element_register (plugin, "bs2b", GST_RANK_NONE, GST_TYPE_BS2B);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ bs2b,
+ "Improve headphone listening of stereo audio records"
+ "using the bs2b library.",
+ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/ext/bs2b/gstbs2b.h b/ext/bs2b/gstbs2b.h
new file mode 100644
index 000000000..8ec7f145c
--- /dev/null
+++ b/ext/bs2b/gstbs2b.h
@@ -0,0 +1,63 @@
+/* GStreamer
+ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
+ * Copyright (C) <2003> David Schleef <ds@schleef.org>
+ * Copyright (C) <2011,2014> Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GST_BS2B_H__
+#define __GST_BS2B_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <gst/audio/gstaudiofilter.h>
+#include <bs2b/bs2b.h>
+
+G_BEGIN_DECLS
+#define GST_TYPE_BS2B \
+ (gst_bs2b_get_type())
+#define GST_BS2B(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BS2B,GstBs2b))
+#define GST_BS2B_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BS2B,GstBs2bClass))
+#define GST_IS_BS2B(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BS2B))
+#define GST_IS_BS2B_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BS2B))
+typedef struct _GstBs2b GstBs2b;
+typedef struct _GstBs2bClass GstBs2bClass;
+
+
+struct _GstBs2b
+{
+ GstAudioFilter element;
+
+ /*< private > */
+ GMutex bs2b_lock;
+ t_bs2bdp bs2bdp;
+ void (*func) ();
+ guint bytes_per_sample;
+};
+
+struct _GstBs2bClass
+{
+ GstAudioFilterClass parent_class;
+};
+
+GType gst_bs2b_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_BS2B_H__ */