1 /* -*- c-basic-offset: 2 -*-
4 * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
5 * 2006 Dreamlab Technologies Ltd. <mathis.hofer@dreamlab.net>
6 * 2007 Sebastian Dröge <slomo@circular-chaos.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public
19 * License along with this library; if not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
24 * this windowed sinc filter is taken from the freely downloadable DSP book,
25 * "The Scientist and Engineer's Guide to Digital Signal Processing",
27 * available at http://www.dspguide.com/
29 * TODO: - Implement the convolution in place, probably only makes sense
30 * when using FFT convolution as currently the convolution itself
31 * is probably the bottleneck
32 * - Maybe allow cascading the filter to get a better stopband attenuation.
33 * Can be done by convolving a filter kernel with itself.
43 #include <gst/audio/gstaudiofilter.h>
44 #include <gst/controller/gstcontroller.h>
46 #include "gstlpwsinc.h"
48 #define GST_CAT_DEFAULT gst_lpwsinc_debug
49 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
51 static const GstElementDetails lpwsinc_details = GST_ELEMENT_DETAILS ("LPWSinc",
52 "Filter/Effect/Audio",
53 "Low-pass and High-pass Windowed sinc filter",
54 "Thomas <thomas@apestaart.org>, "
56 "Dreamlab Technologies Ltd. <mathis.hofer@dreamlab.net>, "
57 "Sebastian Dröge <slomo@circular-chaos.org>");
59 /* Filter signals and args */
81 #define GST_TYPE_LPWSINC_MODE (gst_lpwsinc_mode_get_type ())
83 gst_lpwsinc_mode_get_type (void)
85 static GType gtype = 0;
88 static const GEnumValue values[] = {
89 {MODE_LOW_PASS, "Low pass (default)",
91 {MODE_HIGH_PASS, "High pass",
96 gtype = g_enum_register_static ("GstLPWSincMode", values);
107 #define GST_TYPE_LPWSINC_WINDOW (gst_lpwsinc_window_get_type ())
109 gst_lpwsinc_window_get_type (void)
111 static GType gtype = 0;
114 static const GEnumValue values[] = {
115 {WINDOW_HAMMING, "Hamming window (default)",
117 {WINDOW_BLACKMAN, "Blackman window",
122 gtype = g_enum_register_static ("GstLPWSincWindow", values);
127 #define ALLOWED_CAPS \
128 "audio/x-raw-float, " \
129 " width = (int) { 32, 64 }, " \
130 " endianness = (int) BYTE_ORDER, " \
131 " rate = (int) [ 1, MAX ], " \
132 " channels = (int) [ 1, MAX ]"
134 #define DEBUG_INIT(bla) \
135 GST_DEBUG_CATEGORY_INIT (gst_lpwsinc_debug, "lpwsinc", 0, "Low-pass and High-pass Windowed sinc filter plugin");
137 GST_BOILERPLATE_FULL (GstLPWSinc, gst_lpwsinc, GstAudioFilter,
138 GST_TYPE_AUDIO_FILTER, DEBUG_INIT);
140 static void lpwsinc_set_property (GObject * object, guint prop_id,
141 const GValue * value, GParamSpec * pspec);
142 static void lpwsinc_get_property (GObject * object, guint prop_id,
143 GValue * value, GParamSpec * pspec);
145 static GstFlowReturn lpwsinc_transform (GstBaseTransform * base,
146 GstBuffer * inbuf, GstBuffer * outbuf);
147 static gboolean lpwsinc_get_unit_size (GstBaseTransform * base, GstCaps * caps,
149 static gboolean lpwsinc_start (GstBaseTransform * base);
150 static gboolean lpwsinc_setup (GstAudioFilter * base,
151 GstRingBufferSpec * format);
156 gst_lpwsinc_dispose (GObject * object)
158 GstLPWSinc *self = GST_LPWSINC (object);
161 g_free (self->residue);
162 self->residue = NULL;
166 g_free (self->kernel);
170 G_OBJECT_CLASS (parent_class)->dispose (object);
174 gst_lpwsinc_base_init (gpointer g_class)
176 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
179 gst_element_class_set_details (element_class, &lpwsinc_details);
181 caps = gst_caps_from_string (ALLOWED_CAPS);
182 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (g_class),
184 gst_caps_unref (caps);
188 gst_lpwsinc_class_init (GstLPWSincClass * klass)
190 GObjectClass *gobject_class;
191 GstBaseTransformClass *trans_class;
192 GstAudioFilterClass *filter_class;
194 gobject_class = (GObjectClass *) klass;
195 trans_class = (GstBaseTransformClass *) klass;
196 filter_class = (GstAudioFilterClass *) klass;
198 gobject_class->set_property = lpwsinc_set_property;
199 gobject_class->get_property = lpwsinc_get_property;
200 gobject_class->dispose = gst_lpwsinc_dispose;
202 g_object_class_install_property (gobject_class, PROP_FREQUENCY,
203 g_param_spec_double ("frequency", "Frequency",
204 "Cut-off Frequency (Hz)", 0.0, G_MAXDOUBLE, 0.0,
205 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
207 g_object_class_install_property (gobject_class, PROP_LENGTH,
208 g_param_spec_int ("length", "Length",
209 "Filter kernel length, will be rounded to the next odd number",
210 3, G_MAXINT, 101, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
212 g_object_class_install_property (gobject_class, PROP_MODE,
213 g_param_spec_enum ("mode", "Mode",
214 "Low pass or high pass mode", GST_TYPE_LPWSINC_MODE,
215 MODE_LOW_PASS, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
217 g_object_class_install_property (gobject_class, PROP_WINDOW,
218 g_param_spec_enum ("window", "Window",
219 "Window function to use", GST_TYPE_LPWSINC_WINDOW,
220 WINDOW_HAMMING, G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
222 trans_class->transform = GST_DEBUG_FUNCPTR (lpwsinc_transform);
223 trans_class->get_unit_size = GST_DEBUG_FUNCPTR (lpwsinc_get_unit_size);
224 trans_class->start = GST_DEBUG_FUNCPTR (lpwsinc_start);
225 filter_class->setup = GST_DEBUG_FUNCPTR (lpwsinc_setup);
229 gst_lpwsinc_init (GstLPWSinc * self, GstLPWSincClass * g_class)
231 self->mode = MODE_LOW_PASS;
232 self->window = WINDOW_HAMMING;
233 self->kernel_length = 101;
234 self->frequency = 0.0;
236 self->residue = NULL;
238 self->have_kernel = FALSE;
242 process_32 (GstLPWSinc * self, gfloat * src, gfloat * dst, guint input_samples)
244 gint kernel_length = self->kernel_length;
246 gint channels = GST_AUDIO_FILTER (self)->format.channels;
250 for (i = 0; i < input_samples; i++) {
254 for (j = 0; j < kernel_length; j++)
257 self->residue[(kernel_length + l - j) * channels +
258 k] * self->kernel[j];
260 dst[i] += src[(l - j) * channels + k] * self->kernel[j];
263 /* copy the tail of the current input buffer to the residue, while
264 * keeping parts of the residue if the input buffer is smaller than
265 * the kernel length */
266 if (input_samples < kernel_length * channels)
267 res_start = kernel_length * channels - input_samples;
271 for (i = 0; i < res_start; i++)
272 self->residue[i] = self->residue[i + input_samples];
273 for (i = res_start; i < kernel_length * channels; i++)
274 self->residue[i] = src[input_samples - kernel_length * channels + i];
278 process_64 (GstLPWSinc * self, gdouble * src, gdouble * dst,
281 gint kernel_length = self->kernel_length;
283 gint channels = GST_AUDIO_FILTER (self)->format.channels;
287 for (i = 0; i < input_samples; i++) {
291 for (j = 0; j < kernel_length; j++)
294 self->residue[(kernel_length + l - j) * channels +
295 k] * self->kernel[j];
297 dst[i] += src[(l - j) * channels + k] * self->kernel[j];
300 /* copy the tail of the current input buffer to the residue, while
301 * keeping parts of the residue if the input buffer is smaller than
302 * the kernel length */
303 if (input_samples < kernel_length * channels)
304 res_start = kernel_length * channels - input_samples;
308 for (i = 0; i < res_start; i++)
309 self->residue[i] = self->residue[i + input_samples];
310 for (i = res_start; i < kernel_length * channels; i++)
311 self->residue[i] = src[input_samples - kernel_length * channels + i];
315 lpwsinc_build_kernel (GstLPWSinc * self)
322 len = self->kernel_length;
324 if (GST_AUDIO_FILTER (self)->format.rate == 0) {
325 GST_DEBUG ("rate not set yet");
329 if (GST_AUDIO_FILTER (self)->format.channels == 0) {
330 GST_DEBUG ("channels not set yet");
334 /* Clamp cutoff frequency between 0 and the nyquist frequency */
336 CLAMP (self->frequency, 0.0, GST_AUDIO_FILTER (self)->format.rate / 2);
338 GST_DEBUG ("lpwsinc: initializing filter kernel of length %d "
339 "with cutoff %.2lf Hz "
341 len, self->frequency,
342 (self->mode == MODE_LOW_PASS) ? "low-pass" : "high-pass");
344 /* fill the kernel */
345 w = 2 * M_PI * (self->frequency / GST_AUDIO_FILTER (self)->format.rate);
348 g_free (self->kernel);
349 self->kernel = g_new (gdouble, len);
351 for (i = 0; i < len; ++i) {
355 self->kernel[i] = sin (w * (i - len / 2)) / (i - len / 2);
357 if (self->window == WINDOW_HAMMING)
358 self->kernel[i] *= (0.54 - 0.46 * cos (2 * M_PI * i / len));
361 (0.42 - 0.5 * cos (2 * M_PI * i / len) +
362 0.08 * cos (4 * M_PI * i / len));
365 /* normalize for unity gain at DC */
366 for (i = 0; i < len; ++i)
367 sum += self->kernel[i];
368 for (i = 0; i < len; ++i)
369 self->kernel[i] /= sum;
371 /* convert to highpass if specified */
372 if (self->mode == MODE_HIGH_PASS) {
373 for (i = 0; i < len; ++i)
374 self->kernel[i] = -self->kernel[i];
375 self->kernel[len / 2] += 1.0;
378 /* set up the residue memory space */
380 g_free (self->residue);
382 g_new0 (gdouble, len * GST_AUDIO_FILTER (self)->format.channels);
384 self->have_kernel = TRUE;
387 /* GstAudioFilter vmethod implementations */
389 /* get notified of caps and plug in the correct process function */
391 lpwsinc_setup (GstAudioFilter * base, GstRingBufferSpec * format)
393 GstLPWSinc *self = GST_LPWSINC (base);
397 if (format->width == 32)
398 self->process = (GstLPWSincProcessFunc) process_32;
399 else if (format->width == 64)
400 self->process = (GstLPWSincProcessFunc) process_64;
404 self->have_kernel = FALSE;
409 /* GstBaseTransform vmethod implementations */
412 lpwsinc_get_unit_size (GstBaseTransform * base, GstCaps * caps, guint * size)
414 gint width, channels;
415 GstStructure *structure;
420 structure = gst_caps_get_structure (caps, 0);
421 ret = gst_structure_get_int (structure, "width", &width);
422 ret &= gst_structure_get_int (structure, "channels", &channels);
424 *size = width * channels / 8;
430 lpwsinc_transform (GstBaseTransform * base, GstBuffer * inbuf,
433 GstLPWSinc *self = GST_LPWSINC (base);
434 GstClockTime timestamp;
436 GST_BUFFER_SIZE (outbuf) / (GST_AUDIO_FILTER (self)->format.width / 8);
438 /* don't process data in passthrough-mode */
439 if (gst_base_transform_is_passthrough (base))
442 /* FIXME: subdivide GST_BUFFER_SIZE into small chunks for smooth fades */
443 timestamp = GST_BUFFER_TIMESTAMP (outbuf);
444 if (GST_CLOCK_TIME_IS_VALID (timestamp))
445 gst_object_sync_values (G_OBJECT (self), timestamp);
447 if (!self->have_kernel)
448 lpwsinc_build_kernel (self);
450 self->process (self, GST_BUFFER_DATA (inbuf), GST_BUFFER_DATA (outbuf),
457 lpwsinc_start (GstBaseTransform * base)
459 GstLPWSinc *self = GST_LPWSINC (base);
460 gint channels = GST_AUDIO_FILTER (self)->format.channels;
462 /* Reset the residue if already existing */
463 if (channels && self->residue)
464 memset (self->residue, 0, channels *
465 self->kernel_length * sizeof (gdouble));
471 lpwsinc_set_property (GObject * object, guint prop_id, const GValue * value,
474 GstLPWSinc *self = GST_LPWSINC (object);
476 g_return_if_fail (GST_IS_LPWSINC (self));
482 GST_BASE_TRANSFORM_LOCK (self);
483 val = g_value_get_int (value);
486 self->kernel_length = val;
487 lpwsinc_build_kernel (self);
488 GST_BASE_TRANSFORM_UNLOCK (self);
492 GST_BASE_TRANSFORM_LOCK (self);
493 self->frequency = g_value_get_double (value);
494 lpwsinc_build_kernel (self);
495 GST_BASE_TRANSFORM_UNLOCK (self);
498 GST_BASE_TRANSFORM_LOCK (self);
499 self->mode = g_value_get_enum (value);
500 lpwsinc_build_kernel (self);
501 GST_BASE_TRANSFORM_UNLOCK (self);
504 GST_BASE_TRANSFORM_LOCK (self);
505 self->window = g_value_get_enum (value);
506 lpwsinc_build_kernel (self);
507 GST_BASE_TRANSFORM_UNLOCK (self);
510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
516 lpwsinc_get_property (GObject * object, guint prop_id, GValue * value,
519 GstLPWSinc *self = GST_LPWSINC (object);
523 g_value_set_int (value, self->kernel_length);
526 g_value_set_double (value, self->frequency);
529 g_value_set_enum (value, self->mode);
532 g_value_set_enum (value, self->window);
535 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);