* AUTHORS:
[tunertool] / src / gstpitch.c
1 /*
2  * GStreamer
3  * Copyright (C) 2006 Josep Torra <j.torra@telefonica.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <gst/audio/audio.h>
26
27 #include "gstpitch.h"
28
29 GST_DEBUG_CATEGORY_STATIC (gst_pitch_debug);
30 #define GST_CAT_DEFAULT gst_pitch_debug
31
32 /* Filter signals and args */
33 enum
34 {
35   PROP_0,
36   PROP_SIGNAL_FFREQ,
37   PROP_SIGNAL_INTERVAL,
38   PROP_SIGNAL_MINFREQ,
39   PROP_SIGNAL_MAXFREQ,
40   PROP_NFFT
41 };
42
43 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
44     GST_PAD_SINK,
45     GST_PAD_ALWAYS,
46     GST_STATIC_CAPS ("audio/x-raw-int, "
47         "rate = (int) [ 1, MAX ], "
48         "channels = (int) [1, MAX], "
49         "endianness = (int) BYTE_ORDER, "
50         "width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true")
51     );
52
53 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
54     GST_PAD_SRC,
55     GST_PAD_ALWAYS,
56     GST_STATIC_CAPS ("audio/x-raw-int, "
57         "rate = (int) [ 1, MAX ], "
58         "channels = (int) [1, MAX], "
59         "endianness = (int) BYTE_ORDER, "
60         "width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true")
61     );
62
63 #define DEBUG_INIT(bla) \
64   GST_DEBUG_CATEGORY_INIT (gst_pitch_debug, "Pitch", 0, "fundamental frequency plugin");
65
66 GST_BOILERPLATE_FULL (GstPitch, gst_pitch, GstBaseTransform,
67     GST_TYPE_BASE_TRANSFORM, DEBUG_INIT);
68
69 static void gst_pitch_set_property (GObject * object, guint prop_id,
70     const GValue * value, GParamSpec * pspec);
71 static void gst_pitch_get_property (GObject * object, guint prop_id,
72     GValue * value, GParamSpec * pspec);
73 static void gst_pitch_dispose (GObject * object);
74
75 static gboolean gst_pitch_set_caps (GstBaseTransform * trans, GstCaps * in,
76     GstCaps * out);
77 static gboolean gst_pitch_start (GstBaseTransform * trans);
78
79 static GstFlowReturn gst_pitch_transform_ip (GstBaseTransform * trans,
80     GstBuffer * in);
81
82 /* GObject vmethod implementations */
83
84 static void
85 gst_pitch_base_init (gpointer klass)
86 {
87   static GstElementDetails element_details = {
88     "Pitch analyzer",
89     "Filter/Analyzer/Audio",
90     "Run an FFT on the audio signal, output fundamental frequency",
91     "Josep Torra <j.torra@telefonica.net>"
92   };
93   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
94
95   gst_element_class_add_pad_template (element_class,
96       gst_static_pad_template_get (&src_template));
97   gst_element_class_add_pad_template (element_class,
98       gst_static_pad_template_get (&sink_template));
99   gst_element_class_set_details (element_class, &element_details);
100 }
101
102 static void
103 gst_pitch_class_init (GstPitchClass * klass)
104 {
105   GObjectClass *gobject_class;
106   GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
107
108   gobject_class = (GObjectClass *) klass;
109   gobject_class->set_property = gst_pitch_set_property;
110   gobject_class->get_property = gst_pitch_get_property;
111   gobject_class->dispose = gst_pitch_dispose;
112
113   trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_pitch_set_caps);
114   trans_class->start = GST_DEBUG_FUNCPTR (gst_pitch_start);
115   trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_pitch_transform_ip);
116   trans_class->passthrough_on_same_caps = TRUE;
117
118   g_object_class_install_property (gobject_class, PROP_SIGNAL_FFREQ,
119       g_param_spec_boolean ("message", "Message",
120           "Post a fundamental frequency message for each passed interval",
121           TRUE, G_PARAM_READWRITE));
122
123   g_object_class_install_property (gobject_class, PROP_SIGNAL_INTERVAL,
124       g_param_spec_uint64 ("interval", "Interval",
125           "Interval of time between message posts (in nanoseconds)",
126           1, G_MAXUINT64, GST_SECOND / 10, G_PARAM_READWRITE));
127
128   g_object_class_install_property (gobject_class, PROP_SIGNAL_MINFREQ,
129       g_param_spec_int ("minfreq", "MinFreq",
130           "Initial scan frequency, default 30 Hz",
131           1, G_MAXINT, 30, G_PARAM_READWRITE));
132
133   g_object_class_install_property (gobject_class, PROP_SIGNAL_MAXFREQ,
134       g_param_spec_int ("maxfreq", "MaxFreq",
135           "Final scan frequency, default 1500 Hz",
136           1, G_MAXINT, 1500, G_PARAM_READWRITE));
137
138   g_object_class_install_property (gobject_class, PROP_NFFT,
139       g_param_spec_int ("nfft", "NFFT",
140           "Number of samples taken for FFT",
141           1, G_MAXINT, 1024, G_PARAM_READWRITE));
142
143   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
144       GST_DEBUG_FUNCPTR (gst_pitch_transform_ip);
145 }
146
147 static void
148 gst_pitch_init (GstPitch * filter, GstPitchClass * klass)
149 {
150   filter->adapter = gst_adapter_new ();
151
152   filter->minfreq = 30;
153   filter->maxfreq = 1500;
154   filter->nfft = 1024;
155   filter->message = TRUE;
156   filter->interval = GST_SECOND / 10;
157
158   filter->fft_cfg = kiss_fft_alloc (filter->nfft, 0, NULL, NULL);
159   filter->signal =
160       (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
161   filter->spectrum =
162       (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
163 }
164
165 static void
166 gst_pitch_dispose (GObject * object)
167 {
168   GstPitch *filter = GST_PITCH (object);
169
170   if (filter->adapter) {
171     g_object_unref (filter->adapter);
172     filter->adapter = NULL;
173   }
174
175   g_free (filter->fft_cfg);
176   g_free (filter->signal);
177   g_free (filter->spectrum);
178
179   kiss_fft_cleanup ();
180
181   G_OBJECT_CLASS (parent_class)->dispose (object);
182 }
183
184 static void
185 gst_pitch_set_property (GObject * object, guint prop_id,
186     const GValue * value, GParamSpec * pspec)
187 {
188   GstPitch *filter = GST_PITCH (object);
189
190   switch (prop_id) {
191     case PROP_SIGNAL_FFREQ:
192       filter->message = g_value_get_boolean (value);
193       break;
194     case PROP_SIGNAL_INTERVAL:
195       filter->interval = gst_guint64_to_gdouble (g_value_get_uint64 (value));
196       break;
197     case PROP_SIGNAL_MINFREQ:
198       filter->minfreq = g_value_get_int (value);
199       break;
200     case PROP_SIGNAL_MAXFREQ:
201       filter->maxfreq = g_value_get_int (value);
202       break;
203     case PROP_NFFT:
204       filter->nfft = g_value_get_int (value);
205       g_free (filter->fft_cfg);
206       g_free (filter->signal);
207       g_free (filter->spectrum);
208       filter->fft_cfg = kiss_fft_alloc (filter->nfft, 0, NULL, NULL);
209       filter->signal =
210           (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
211       filter->spectrum =
212           (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
213       break;
214
215     default:
216       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
217       break;
218   }
219 }
220
221 static void
222 gst_pitch_get_property (GObject * object, guint prop_id,
223     GValue * value, GParamSpec * pspec)
224 {
225   GstPitch *filter = GST_PITCH (object);
226
227   switch (prop_id) {
228     case PROP_SIGNAL_FFREQ:
229       g_value_set_boolean (value, filter->message);
230       break;
231     case PROP_SIGNAL_INTERVAL:
232       g_value_set_uint64 (value, filter->interval);
233       break;
234     case PROP_SIGNAL_MINFREQ:
235       g_value_set_int (value, filter->minfreq);
236       break;
237     case PROP_SIGNAL_MAXFREQ:
238       g_value_set_int (value, filter->maxfreq);
239       break;
240     case PROP_NFFT:
241       g_value_set_int (value, filter->nfft);
242       break;
243
244     default:
245       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
246       break;
247   }
248 }
249
250 static gboolean
251 gst_pitch_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
252 {
253   GstPitch *filter = GST_PITCH (trans);
254   GstStructure *structure;
255
256   structure = gst_caps_get_structure (in, 0);
257   gst_structure_get_int (structure, "rate", &filter->rate);
258   gst_structure_get_int (structure, "width", &filter->width);
259   gst_structure_get_int (structure, "channels", &filter->channels);
260
261   return TRUE;
262 }
263
264 static gboolean
265 gst_pitch_start (GstBaseTransform * trans)
266 {
267   GstPitch *filter = GST_PITCH (trans);
268
269   gst_adapter_clear (filter->adapter);
270   filter->num_frames = 0;
271
272   return TRUE;
273 }
274
275 static GstMessage *
276 gst_pitch_message_new (GstPitch * filter)
277 {
278   GstStructure *s;
279   gint i, min_i, max_i;
280   gint frequency, frequency_module, module;
281
282   /* Extract fundamental frequency */
283   frequency = 0;
284   frequency_module = 0;
285   min_i = filter->minfreq * filter->nfft / filter->rate;
286   max_i = filter->maxfreq * filter->nfft / filter->rate;
287   GST_DEBUG_OBJECT (filter, "min_freq = %d, max_freq = %d", filter->minfreq,
288       filter->maxfreq);
289   GST_DEBUG_OBJECT (filter, "min_i = %d, max_i = %d", min_i, max_i);
290   for (i = min_i; (i <= max_i) && (i < filter->nfft); i++) {
291     module = (filter->spectrum[i].r * filter->spectrum[i].r);
292     module += (filter->spectrum[i].i * filter->spectrum[i].i);
293
294     if (module > 0)
295       GST_DEBUG_OBJECT (filter, "module[%d] = %d", i, module);
296
297     if (module > frequency_module) {
298       frequency_module = module;
299       frequency = i;
300     }
301   }
302
303   frequency = frequency * filter->rate / filter->nfft;
304
305   GST_DEBUG_OBJECT (filter, "preparing message, frequency = %d ", frequency);
306
307   s = gst_structure_new ("pitch", "frequency", G_TYPE_INT, frequency, NULL);
308
309   return gst_message_new_element (GST_OBJECT (filter), s);
310 }
311
312 /* GstBaseTransform vmethod implementations */
313
314 /* this function does the actual processing
315  */
316 static GstFlowReturn
317 gst_pitch_transform_ip (GstBaseTransform * trans, GstBuffer * in)
318 {
319   GstPitch *filter = GST_PITCH (trans);
320   gint16 *samples;
321   gint wanted;
322   gint i, j, k;
323   gint32 acc;
324
325   GST_DEBUG ("transform : %ld bytes", GST_BUFFER_SIZE (in));
326
327   gst_adapter_push (filter->adapter, gst_buffer_ref (in));
328   /* required number of bytes */
329   wanted = filter->channels * filter->nfft * 2;
330
331   while (gst_adapter_available (filter->adapter) > wanted) {
332
333     GST_DEBUG ("  adapter loop");
334     samples = (gint16 *) gst_adapter_take (filter->adapter, wanted);
335
336     for (i = 0, j = 0; i < filter->nfft; i++) {
337       for (k = 0, acc = 0; k < filter->channels; k++)
338         acc += samples[j++];
339       filter->signal[i].r = (kiss_fft_scalar) (acc / filter->channels);
340     }
341
342     GST_DEBUG ("  fft");
343
344     kiss_fft (filter->fft_cfg, filter->signal, filter->spectrum);
345
346     GST_DEBUG ("  send message? %d", filter->num_frames);
347     filter->num_frames += filter->nfft;
348     /* do we need to message ? */
349     if (filter->num_frames >=
350         GST_CLOCK_TIME_TO_FRAMES (filter->interval, filter->rate)) {
351       if (filter->message) {
352         GstMessage *m = gst_pitch_message_new (filter);
353
354         GST_DEBUG ("  sending message");
355         gst_element_post_message (GST_ELEMENT (filter), m);
356       }
357       filter->num_frames = 0;
358     }
359   }
360
361   return GST_FLOW_OK;
362 }
363
364
365 /* entry point to initialize the plug-in
366  * initialize the plug-in itself
367  * register the element factories and pad templates
368  * register the features
369  *
370  * exchange the string 'plugin' with your elemnt name
371  */
372 /* static */ gboolean
373 plugin_pitch_init (GstPlugin * plugin)
374 {
375   return gst_element_register (plugin, "pitch", GST_RANK_NONE, GST_TYPE_PITCH);
376 }
377
378 /* this is the structure that gstreamer looks for to register plugins
379  *
380  * exchange the strings 'plugin' and 'Template plugin' with you plugin name and
381  * description
382  */
383 #if 0
384 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
385     GST_VERSION_MINOR,
386     "pitch",
387     "Run an FFT on the audio signal, output fundamental frequency",
388     plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")
389 #endif