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