/* -*- c-basic-offset: 2 -*- * * GStreamer * Copyright (C) 1999-2001 Erik Walthinsen * 2006 Dreamlab Technologies Ltd. * 2007-2009 Sebastian Dröge * * 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. * * * this windowed sinc filter is taken from the freely downloadable DSP book, * "The Scientist and Engineer's Guide to Digital Signal Processing", * chapter 16 * available at http://www.dspguide.com/ * * For the window functions see * http://en.wikipedia.org/wiki/Window_function */ /** * SECTION:element-audiowsincband * * Attenuates all frequencies outside (bandpass) or inside (bandreject) of a frequency * band. The length parameter controls the rolloff, the window parameter * controls rolloff and stopband attenuation. The Hamming window provides a faster rolloff but a bit * worse stopband attenuation, the other way around for the Blackman window. * * This element has the advantage over the Chebyshev bandpass and bandreject filter that it has * a much better rolloff when using a larger kernel size and almost linear phase. The only * disadvantage is the much slower execution time with larger kernels. * * * Example launch line * |[ * gst-launch audiotestsrc freq=1500 ! audioconvert ! audiosincband mode=band-pass lower-frequency=3000 upper-frequency=10000 length=501 window=blackman ! audioconvert ! alsasink * gst-launch filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiowsincband mode=band-reject lower-frequency=59 upper-frequency=61 length=10001 window=hamming ! audioconvert ! alsasink * gst-launch audiotestsrc wave=white-noise ! audioconvert ! audiowsincband mode=band-pass lower-frequency=1000 upper-frequency=2000 length=31 ! audioconvert ! alsasink * ]| * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "audiowsincband.h" #include "gst/glib-compat-private.h" #define GST_CAT_DEFAULT gst_gst_audio_wsincband_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); enum { PROP_0, PROP_LENGTH, PROP_LOWER_FREQUENCY, PROP_UPPER_FREQUENCY, PROP_MODE, PROP_WINDOW }; enum { MODE_BAND_PASS = 0, MODE_BAND_REJECT }; #define GST_TYPE_AUDIO_WSINC_BAND_MODE (gst_gst_audio_wsincband_mode_get_type ()) static GType gst_gst_audio_wsincband_mode_get_type (void) { static GType gtype = 0; if (gtype == 0) { static const GEnumValue values[] = { {MODE_BAND_PASS, "Band pass (default)", "band-pass"}, {MODE_BAND_REJECT, "Band reject", "band-reject"}, {0, NULL, NULL} }; gtype = g_enum_register_static ("GstAudioWSincBandMode", values); } return gtype; } enum { WINDOW_HAMMING = 0, WINDOW_BLACKMAN, WINDOW_GAUSSIAN, WINDOW_COSINE, WINDOW_HANN }; #define GST_TYPE_AUDIO_WSINC_BAND_WINDOW (gst_gst_audio_wsincband_window_get_type ()) static GType gst_gst_audio_wsincband_window_get_type (void) { static GType gtype = 0; if (gtype == 0) { static const GEnumValue values[] = { {WINDOW_HAMMING, "Hamming window (default)", "hamming"}, {WINDOW_BLACKMAN, "Blackman window", "blackman"}, {WINDOW_GAUSSIAN, "Gaussian window", "gaussian"}, {WINDOW_COSINE, "Cosine window", "cosine"}, {WINDOW_HANN, "Hann window", "hann"}, {0, NULL, NULL} }; gtype = g_enum_register_static ("GstAudioWSincBandWindow", values); } return gtype; } #define DEBUG_INIT(bla) \ GST_DEBUG_CATEGORY_INIT (gst_gst_audio_wsincband_debug, "audiowsincband", 0, \ "Band-pass and Band-reject Windowed sinc filter plugin"); GST_BOILERPLATE_FULL (GstAudioWSincBand, gst_audio_wsincband, GstAudioFilter, GST_TYPE_AUDIO_FX_BASE_FIR_FILTER, DEBUG_INIT); static void gst_audio_wsincband_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_audio_wsincband_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_audio_wsincband_finalize (GObject * object); static gboolean gst_audio_wsincband_setup (GstAudioFilter * base, GstRingBufferSpec * format); #define POW2(x) (x)*(x) /* Element class */ static void gst_audio_wsincband_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); gst_element_class_set_details_simple (element_class, "Band pass & band reject filter", "Filter/Effect/Audio", "Band pass and band reject windowed sinc filter", "Thomas Vander Stichele , " "Steven W. Smith, " "Dreamlab Technologies Ltd. , " "Sebastian Dröge "); } static void gst_audio_wsincband_class_init (GstAudioWSincBandClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstAudioFilterClass *filter_class = (GstAudioFilterClass *) klass; gobject_class->set_property = gst_audio_wsincband_set_property; gobject_class->get_property = gst_audio_wsincband_get_property; gobject_class->finalize = gst_audio_wsincband_finalize; /* FIXME: Don't use the complete possible range but restrict the upper boundary * so automatically generated UIs can use a slider */ g_object_class_install_property (gobject_class, PROP_LOWER_FREQUENCY, g_param_spec_float ("lower-frequency", "Lower Frequency", "Cut-off lower frequency (Hz)", 0.0, 100000.0, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_UPPER_FREQUENCY, g_param_spec_float ("upper-frequency", "Upper Frequency", "Cut-off upper frequency (Hz)", 0.0, 100000.0, 0, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_LENGTH, g_param_spec_int ("length", "Length", "Filter kernel length, will be rounded to the next odd number", 3, 256000, 101, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MODE, g_param_spec_enum ("mode", "Mode", "Band pass or band reject mode", GST_TYPE_AUDIO_WSINC_BAND_MODE, MODE_BAND_PASS, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_WINDOW, g_param_spec_enum ("window", "Window", "Window function to use", GST_TYPE_AUDIO_WSINC_BAND_WINDOW, WINDOW_HAMMING, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); filter_class->setup = GST_DEBUG_FUNCPTR (gst_audio_wsincband_setup); } static void gst_audio_wsincband_init (GstAudioWSincBand * self, GstAudioWSincBandClass * g_class) { self->kernel_length = 101; self->lower_frequency = 0.0; self->upper_frequency = 0.0; self->mode = MODE_BAND_PASS; self->window = WINDOW_HAMMING; self->lock = g_mutex_new (); } static void gst_audio_wsincband_build_kernel (GstAudioWSincBand * self) { gint i = 0; gdouble sum = 0.0; gint len = 0; gdouble *kernel_lp, *kernel_hp; gdouble w; gdouble *kernel; len = self->kernel_length; if (GST_AUDIO_FILTER (self)->format.rate == 0) { GST_DEBUG ("rate not set yet"); return; } if (GST_AUDIO_FILTER (self)->format.channels == 0) { GST_DEBUG ("channels not set yet"); return; } /* Clamp frequencies */ self->lower_frequency = CLAMP (self->lower_frequency, 0.0, GST_AUDIO_FILTER (self)->format.rate / 2); self->upper_frequency = CLAMP (self->upper_frequency, 0.0, GST_AUDIO_FILTER (self)->format.rate / 2); if (self->lower_frequency > self->upper_frequency) { gint tmp = self->lower_frequency; self->lower_frequency = self->upper_frequency; self->upper_frequency = tmp; } GST_DEBUG ("gst_audio_wsincband: initializing filter kernel of length %d " "with lower frequency %.2lf Hz " ", upper frequency %.2lf Hz for mode %s", len, self->lower_frequency, self->upper_frequency, (self->mode == MODE_BAND_PASS) ? "band-pass" : "band-reject"); /* fill the lp kernel */ w = 2 * G_PI * (self->lower_frequency / GST_AUDIO_FILTER (self)->format.rate); kernel_lp = g_new (gdouble, len); for (i = 0; i < len; ++i) { if (i == (len - 1) / 2.0) kernel_lp[i] = w; else kernel_lp[i] = sin (w * (i - (len - 1) / 2.0)) / (i - (len - 1) / 2.0); /* windowing */ switch (self->window) { case WINDOW_HAMMING: kernel_lp[i] *= (0.54 - 0.46 * cos (2 * G_PI * i / (len - 1))); break; case WINDOW_BLACKMAN: kernel_lp[i] *= (0.42 - 0.5 * cos (2 * G_PI * i / (len - 1)) + 0.08 * cos (4 * G_PI * i / (len - 1))); break; case WINDOW_GAUSSIAN: kernel_lp[i] *= exp (-0.5 * POW2 (3.0 / len * (2 * i - (len - 1)))); break; case WINDOW_COSINE: kernel_lp[i] *= cos (G_PI * i / (len - 1) - G_PI / 2); break; case WINDOW_HANN: kernel_lp[i] *= 0.5 * (1 - cos (2 * G_PI * i / (len - 1))); break; } } /* normalize for unity gain at DC */ sum = 0.0; for (i = 0; i < len; ++i) sum += kernel_lp[i]; for (i = 0; i < len; ++i) kernel_lp[i] /= sum; /* fill the hp kernel */ w = 2 * G_PI * (self->upper_frequency / GST_AUDIO_FILTER (self)->format.rate); kernel_hp = g_new (gdouble, len); for (i = 0; i < len; ++i) { if (i == (len - 1) / 2.0) kernel_hp[i] = w; else kernel_hp[i] = sin (w * (i - (len - 1) / 2.0)) / (i - (len - 1) / 2.0); /* Windowing */ switch (self->window) { case WINDOW_HAMMING: kernel_hp[i] *= (0.54 - 0.46 * cos (2 * G_PI * i / (len - 1))); break; case WINDOW_BLACKMAN: kernel_hp[i] *= (0.42 - 0.5 * cos (2 * G_PI * i / (len - 1)) + 0.08 * cos (4 * G_PI * i / (len - 1))); break; case WINDOW_GAUSSIAN: kernel_hp[i] *= exp (-0.5 * POW2 (3.0 / len * (2 * i - (len - 1)))); break; case WINDOW_COSINE: kernel_hp[i] *= cos (G_PI * i / (len - 1) - G_PI / 2); break; case WINDOW_HANN: kernel_hp[i] *= 0.5 * (1 - cos (2 * G_PI * i / (len - 1))); break; } } /* normalize for unity gain at DC */ sum = 0.0; for (i = 0; i < len; ++i) sum += kernel_hp[i]; for (i = 0; i < len; ++i) kernel_hp[i] /= sum; /* do spectral inversion to go from lowpass to highpass */ for (i = 0; i < len; ++i) kernel_hp[i] = -kernel_hp[i]; if (len % 2 == 1) { kernel_hp[(len - 1) / 2] += 1.0; } else { kernel_hp[len / 2 - 1] += 0.5; kernel_hp[len / 2] += 0.5; } /* combine the two kernels */ kernel = g_new (gdouble, len); for (i = 0; i < len; ++i) kernel[i] = kernel_lp[i] + kernel_hp[i]; /* free the helper kernels */ g_free (kernel_lp); g_free (kernel_hp); /* do spectral inversion to go from bandreject to bandpass * if specified */ if (self->mode == MODE_BAND_PASS) { for (i = 0; i < len; ++i) kernel[i] = -kernel[i]; kernel[len / 2] += 1; } gst_audio_fx_base_fir_filter_set_kernel (GST_AUDIO_FX_BASE_FIR_FILTER (self), kernel, self->kernel_length, (len - 1) / 2); } /* GstAudioFilter vmethod implementations */ /* get notified of caps and plug in the correct process function */ static gboolean gst_audio_wsincband_setup (GstAudioFilter * base, GstRingBufferSpec * format) { GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (base); gst_audio_wsincband_build_kernel (self); return GST_AUDIO_FILTER_CLASS (parent_class)->setup (base, format); } static void gst_audio_wsincband_finalize (GObject * object) { GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); g_mutex_free (self->lock); self->lock = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_audio_wsincband_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); g_return_if_fail (GST_IS_AUDIO_WSINC_BAND (self)); switch (prop_id) { case PROP_LENGTH:{ gint val; g_mutex_lock (self->lock); val = g_value_get_int (value); if (val % 2 == 0) val++; if (val != self->kernel_length) { gst_audio_fx_base_fir_filter_push_residue (GST_AUDIO_FX_BASE_FIR_FILTER (self)); self->kernel_length = val; gst_audio_wsincband_build_kernel (self); } g_mutex_unlock (self->lock); break; } case PROP_LOWER_FREQUENCY: g_mutex_lock (self->lock); self->lower_frequency = g_value_get_float (value); gst_audio_wsincband_build_kernel (self); g_mutex_unlock (self->lock); break; case PROP_UPPER_FREQUENCY: g_mutex_lock (self->lock); self->upper_frequency = g_value_get_float (value); gst_audio_wsincband_build_kernel (self); g_mutex_unlock (self->lock); break; case PROP_MODE: g_mutex_lock (self->lock); self->mode = g_value_get_enum (value); gst_audio_wsincband_build_kernel (self); g_mutex_unlock (self->lock); break; case PROP_WINDOW: g_mutex_lock (self->lock); self->window = g_value_get_enum (value); gst_audio_wsincband_build_kernel (self); g_mutex_unlock (self->lock); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_audio_wsincband_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); switch (prop_id) { case PROP_LENGTH: g_value_set_int (value, self->kernel_length); break; case PROP_LOWER_FREQUENCY: g_value_set_float (value, self->lower_frequency); break; case PROP_UPPER_FREQUENCY: g_value_set_float (value, self->upper_frequency); break; case PROP_MODE: g_value_set_enum (value, self->mode); break; case PROP_WINDOW: g_value_set_enum (value, self->window); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }