GConf backend for settings
[tunertool] / src / gstpitch.c
index 1cf1f07..0250a5a 100644 (file)
@@ -1,3 +1,4 @@
+/* vim: set sts=2 sw=2 et: */
 /*
  * GStreamer
  * Copyright (C) 2006 Josep Torra <j.torra@telefonica.net>
@@ -29,6 +30,9 @@
 GST_DEBUG_CATEGORY_STATIC (gst_pitch_debug);
 #define GST_CAT_DEFAULT gst_pitch_debug
 
+#define RATE    8000
+#define WANTED  RATE * 2
+
 /* Filter signals and args */
 enum
 {
@@ -37,15 +41,16 @@ enum
   PROP_SIGNAL_INTERVAL,
   PROP_SIGNAL_MINFREQ,
   PROP_SIGNAL_MAXFREQ,
-  PROP_NFFT
+  PROP_NFFT,
+  PROP_ALGORITHM
 };
 
 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("audio/x-raw-int, "
-        "rate = (int) [ 1, MAX ], "
-        "channels = (int) [1, MAX], "
+        "rate = (int) 8000, "
+        "channels = (int) 1, "
         "endianness = (int) BYTE_ORDER, "
         "width = (int) 16, " "depth = (int) 16, " "signed = (boolean) true")
     );
@@ -79,6 +84,27 @@ static gboolean gst_pitch_start (GstBaseTransform * trans);
 static GstFlowReturn gst_pitch_transform_ip (GstBaseTransform * trans,
     GstBuffer * in);
 
+#define DEFAULT_PROP_ALGORITHM GST_PITCH_ALGORITHM_FFT
+
+#define GST_TYPE_PITCH_ALGORITHM (gst_pitch_algorithm_get_type())
+static GType
+gst_pitch_algorithm_get_type (void)
+{
+  static GType pitch_algorithm_type = 0;
+  static const GEnumValue pitch_algorithm[] = {
+    {GST_PITCH_ALGORITHM_FFT, "fft", "fft"},
+    {GST_PITCH_ALGORITHM_HPS, "hps", "hps"},
+    {0, NULL, NULL},
+  };
+
+  if (!pitch_algorithm_type) {
+    pitch_algorithm_type =
+        g_enum_register_static ("GstPitchAlgorithm",
+        pitch_algorithm);
+  }
+  return pitch_algorithm_type;
+}
+
 /* GObject vmethod implementations */
 
 static void
@@ -120,11 +146,6 @@ gst_pitch_class_init (GstPitchClass * klass)
           "Post a fundamental frequency message for each passed interval",
           TRUE, G_PARAM_READWRITE));
 
-  g_object_class_install_property (gobject_class, PROP_SIGNAL_INTERVAL,
-      g_param_spec_uint64 ("interval", "Interval",
-          "Interval of time between message posts (in nanoseconds)",
-          1, G_MAXUINT64, GST_SECOND / 10, G_PARAM_READWRITE));
-
   g_object_class_install_property (gobject_class, PROP_SIGNAL_MINFREQ,
       g_param_spec_int ("minfreq", "MinFreq",
           "Initial scan frequency, default 30 Hz",
@@ -135,31 +156,41 @@ gst_pitch_class_init (GstPitchClass * klass)
           "Final scan frequency, default 1500 Hz",
           1, G_MAXINT, 1500, G_PARAM_READWRITE));
 
-  g_object_class_install_property (gobject_class, PROP_NFFT,
-      g_param_spec_int ("nfft", "NFFT",
-          "Number of samples taken for FFT",
-          1, G_MAXINT, 1024, G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_ALGORITHM,
+      g_param_spec_enum ("algorithm", "Algorithm",
+          "Pitch detection algorithm to use",
+          GST_TYPE_PITCH_ALGORITHM, DEFAULT_PROP_ALGORITHM,
+          G_PARAM_READWRITE));
+
 
   GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
       GST_DEBUG_FUNCPTR (gst_pitch_transform_ip);
 }
 
 static void
+gst_pitch_setup_algorithm (GstPitch * filter)
+{
+  if (filter->algorithm == GST_PITCH_ALGORITHM_HPS) {
+    filter->module = (gint *) g_malloc (RATE * sizeof (gint));
+  }
+  else {
+    if (filter->module) 
+      g_free (filter->module);
+
+    filter->module = NULL;
+  }
+}
+
+static void
 gst_pitch_init (GstPitch * filter, GstPitchClass * klass)
 {
   filter->adapter = gst_adapter_new ();
 
   filter->minfreq = 30;
   filter->maxfreq = 1500;
-  filter->nfft = 1024;
   filter->message = TRUE;
-  filter->interval = GST_SECOND / 10;
-
-  filter->fft_cfg = kiss_fft_alloc (filter->nfft, 0, NULL, NULL);
-  filter->signal =
-      (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
-  filter->spectrum =
-      (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
+  filter->algorithm = DEFAULT_PROP_ALGORITHM;
+  gst_pitch_setup_algorithm (filter);
 }
 
 static void
@@ -175,6 +206,8 @@ gst_pitch_dispose (GObject * object)
   g_free (filter->fft_cfg);
   g_free (filter->signal);
   g_free (filter->spectrum);
+  if (filter->module)
+    g_free (filter->module);
 
   kiss_fft_cleanup ();
 
@@ -191,27 +224,16 @@ gst_pitch_set_property (GObject * object, guint prop_id,
     case PROP_SIGNAL_FFREQ:
       filter->message = g_value_get_boolean (value);
       break;
-    case PROP_SIGNAL_INTERVAL:
-      filter->interval = gst_guint64_to_gdouble (g_value_get_uint64 (value));
-      break;
     case PROP_SIGNAL_MINFREQ:
       filter->minfreq = g_value_get_int (value);
       break;
     case PROP_SIGNAL_MAXFREQ:
       filter->maxfreq = g_value_get_int (value);
       break;
-    case PROP_NFFT:
-      filter->nfft = g_value_get_int (value);
-      g_free (filter->fft_cfg);
-      g_free (filter->signal);
-      g_free (filter->spectrum);
-      filter->fft_cfg = kiss_fft_alloc (filter->nfft, 0, NULL, NULL);
-      filter->signal =
-          (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
-      filter->spectrum =
-          (kiss_fft_cpx *) g_malloc (filter->nfft * sizeof (kiss_fft_cpx));
+    case PROP_ALGORITHM:
+      filter->algorithm = g_value_get_enum (value);
+      gst_pitch_setup_algorithm (filter);
       break;
-
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -228,19 +250,15 @@ gst_pitch_get_property (GObject * object, guint prop_id,
     case PROP_SIGNAL_FFREQ:
       g_value_set_boolean (value, filter->message);
       break;
-    case PROP_SIGNAL_INTERVAL:
-      g_value_set_uint64 (value, filter->interval);
-      break;
     case PROP_SIGNAL_MINFREQ:
       g_value_set_int (value, filter->minfreq);
       break;
     case PROP_SIGNAL_MAXFREQ:
       g_value_set_int (value, filter->maxfreq);
       break;
-    case PROP_NFFT:
-      g_value_set_int (value, filter->nfft);
+    case PROP_ALGORITHM:
+      g_value_set_enum (value, filter->algorithm);
       break;
-
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -251,12 +269,12 @@ static gboolean
 gst_pitch_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
 {
   GstPitch *filter = GST_PITCH (trans);
-  GstStructure *structure;
 
-  structure = gst_caps_get_structure (in, 0);
-  gst_structure_get_int (structure, "rate", &filter->rate);
-  gst_structure_get_int (structure, "width", &filter->width);
-  gst_structure_get_int (structure, "channels", &filter->channels);
+  filter->fft_cfg = kiss_fft_alloc (RATE, 0, NULL, NULL);
+  filter->signal =
+      (kiss_fft_cpx *) g_malloc (RATE * sizeof (kiss_fft_cpx));
+  filter->spectrum =
+      (kiss_fft_cpx *) g_malloc (RATE * sizeof (kiss_fft_cpx));
 
   return TRUE;
 }
@@ -267,7 +285,6 @@ gst_pitch_start (GstBaseTransform * trans)
   GstPitch *filter = GST_PITCH (trans);
 
   gst_adapter_clear (filter->adapter);
-  filter->num_frames = 0;
 
   return TRUE;
 }
@@ -277,31 +294,94 @@ gst_pitch_message_new (GstPitch * filter)
 {
   GstStructure *s;
   gint i, min_i, max_i;
-  gint frequency, frequency_module, module;
+  gint frequency, frequency_module;
 
   /* Extract fundamental frequency */
   frequency = 0;
   frequency_module = 0;
-  min_i = filter->minfreq * filter->nfft / filter->rate;
-  max_i = filter->maxfreq * filter->nfft / filter->rate;
+  min_i = filter->minfreq;
+  max_i = filter->maxfreq;
+
   GST_DEBUG_OBJECT (filter, "min_freq = %d, max_freq = %d", filter->minfreq,
       filter->maxfreq);
-  GST_DEBUG_OBJECT (filter, "min_i = %d, max_i = %d", min_i, max_i);
-  for (i = min_i; (i <= max_i) && (i < filter->nfft); i++) {
-    module = (filter->spectrum[i].r * filter->spectrum[i].r);
-    module += (filter->spectrum[i].i * filter->spectrum[i].i);
+  /*GST_DEBUG_OBJECT (filter, "min_i = %d, max_i = %d", min_i, max_i); */
 
-    if (module > 0)
-      GST_DEBUG_OBJECT (filter, "module[%d] = %d", i, module);
+  switch (filter->algorithm) {
 
-    if (module > frequency_module) {
-      frequency_module = module;
-      frequency = i;
-    }
-  }
+    case GST_PITCH_ALGORITHM_FFT:
+      {
+        gint module = 0;
+
+        for (i = min_i; i < max_i; i++) {
+          module = (filter->spectrum[i].r * filter->spectrum[i].r);
+          module += (filter->spectrum[i].i * filter->spectrum[i].i);
 
-  frequency = frequency * filter->rate / filter->nfft;
+          if (module > 0)
+            GST_LOG_OBJECT (filter, "module[%d] = %d", i, module);
+
+          /* find strongest peak */
+          if (module > frequency_module) {
+            frequency_module = module;
+            frequency = i;
+          }
+        }
+      }
+      break;
 
+    case GST_PITCH_ALGORITHM_HPS:
+      {
+        gint prev_frequency = 0;
+        gint j, t;
+
+        for (i = min_i; i < RATE; i++) {
+          filter->module[i] = (filter->spectrum[i].r * filter->spectrum[i].r);
+          filter->module[i] += (filter->spectrum[i].i * filter->spectrum[i].i);
+
+          if (filter->module[i] > 0)
+            GST_LOG_OBJECT (filter, "module[%d] = %d", i, filter->module[i]);
+
+        }
+        /* Harmonic Product Spectrum algorithm */
+#define MAX_DS_FACTOR (6)
+        for (i = min_i; (i <= max_i) && (i < RATE); i++) {
+          for (j = 2; j <= MAX_DS_FACTOR; j++) {
+            t = i * j;
+            if (t > RATE)
+              break;
+
+            /* this is not part of the HPS but it seems
+             * there are lots of zeroes in the spectrum ... 
+             */
+            if (filter->module[t] != 0) 
+              filter->module[i] *= filter->module[t];
+          }
+
+          /* find strongest peak */
+          if (filter->module[i] > frequency_module) {
+            prev_frequency = frequency;
+            frequency_module = filter->module[i];
+            frequency = i;
+          }
+        }
+
+        /* try to correct octave error */
+        if (frequency != 0 && prev_frequency != 0) {
+          float ratio = (float) frequency / (float) prev_frequency;
+          if (ratio >= 1.9 && ratio < 2.1 && (float) filter->module[prev_frequency] >= 0.2 * (float) frequency_module ) {
+            g_debug("Chose freq %d[%d] over %d[%d]\n", prev_frequency, filter->module[prev_frequency], frequency, filter->module[frequency]);
+            frequency = prev_frequency;
+            frequency_module = filter->module[prev_frequency];
+          } 
+        }
+      }
+      break;
+    default:
+      break;
+  }
+
+  /*
+  g_debug("freq %d[%d]\n", frequency, frequency_module);
+  */
   GST_DEBUG_OBJECT (filter, "preparing message, frequency = %d ", frequency);
 
   s = gst_structure_new ("pitch", "frequency", G_TYPE_INT, frequency, NULL);
@@ -318,43 +398,34 @@ gst_pitch_transform_ip (GstBaseTransform * trans, GstBuffer * in)
 {
   GstPitch *filter = GST_PITCH (trans);
   gint16 *samples;
-  gint wanted;
-  gint i, j, k;
-  gint32 acc;
-
-  GST_DEBUG ("transform : %ld bytes", GST_BUFFER_SIZE (in));
+  gint i;
+  guint avail;
 
+  GST_DEBUG_OBJECT (filter, "transform : %ld bytes", GST_BUFFER_SIZE (in));
   gst_adapter_push (filter->adapter, gst_buffer_ref (in));
+
   /* required number of bytes */
-  wanted = filter->channels * filter->nfft * 2;
+  avail = gst_adapter_available (filter->adapter);
+  GST_DEBUG_OBJECT (filter, "avail: %d wanted: %d", avail, WANTED);
 
-  while (gst_adapter_available (filter->adapter) > wanted) {
+  if (avail > WANTED) {
 
-    GST_DEBUG ("  adapter loop");
-    samples = (gint16 *) gst_adapter_peek (filter->adapter, wanted);
+    /* copy sample data in the complex vector */
+    samples = (gint16 *) gst_adapter_peek (filter->adapter, WANTED);
 
-    for (i = 0, j = 0; i < filter->nfft; i++) {
-      for (k = 0, acc = 0; k < filter->channels; k++)
-        acc += samples[j++];
-      filter->signal[i].r = (kiss_fft_scalar) (acc / filter->channels);
+    for (i = 0; i < RATE; i++) {
+      filter->signal[i].r = (kiss_fft_scalar) (samples[i]);
     }
-    gst_adapter_flush (filter->adapter, wanted);
 
-    GST_DEBUG ("  fft");
-    kiss_fft (filter->fft_cfg, filter->signal, filter->spectrum);
+    /* flush half second of data to implement sliding window */
+    gst_adapter_flush (filter->adapter, WANTED >> 1);
 
-    GST_DEBUG ("  send message? %d", filter->num_frames);
-    filter->num_frames += filter->nfft;
-    /* do we need to message ? */
-    if (filter->num_frames >=
-        GST_CLOCK_TIME_TO_FRAMES (filter->interval, filter->rate)) {
-      if (filter->message) {
-        GstMessage *m = gst_pitch_message_new (filter);
+    GST_DEBUG ("perform fft");
+    kiss_fft (filter->fft_cfg, filter->signal, filter->spectrum);
 
-        GST_DEBUG ("  sending message");
-        gst_element_post_message (GST_ELEMENT (filter), m);
-      }
-      filter->num_frames = 0;
+    if (filter->message) {
+      GstMessage *m = gst_pitch_message_new (filter);
+      gst_element_post_message (GST_ELEMENT (filter), m);
     }
   }