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