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