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