Change the tap tempo estimator to least-squares regression
authorPavel Potocek <pavelpotocek@gmail.com>
Wed, 13 Jan 2016 14:04:29 +0000 (15:04 +0100)
committerTim Mayberry <mojofunk@gmail.com>
Thu, 18 Feb 2016 00:32:49 +0000 (10:32 +1000)
gtk2_ardour/tempo_dialog.cc
gtk2_ardour/tempo_dialog.h

index 0b570abdaa2fcc780de2b5dc78beb8ee8411db21..a7a6f4ae759e90d00b535404a748d1c01ff0325b 100644 (file)
@@ -267,39 +267,38 @@ TempoDialog::pulse_change ()
 bool
 TempoDialog::tap_tempo_button_press (GdkEventButton *ev)
 {
-       gint64 now;
-       now = g_get_monotonic_time (); // microseconds
+       double t;
 
+       // Linear least-squares regression
        if (tapped) {
-               double interval, bpm;
-               static const double decay = 0.5;
-
-               interval = (now - last_tap) * 1.0e-6;
-               if (interval <= 6.0) {
-                       // <= 6 seconds (say): >= 10 bpm
-                       if (average_interval > 0) {
-                               if (average_interval > interval / 1.2 && average_interval < interval * 1.2) {
-                               average_interval = interval * decay
-                                       + average_interval * (1.0-decay);
-                               } else {
-                                       average_interval = 0;
-                               }
-                       } else {
-                               average_interval = interval;
-                       }
-
-                       if (average_interval > 0) {
-                               bpm = 60.0 / average_interval;
-                               bpm_spinner.set_value (bpm);
-                       }
+               t = 1e-6 * (g_get_monotonic_time () - first_t); // Subtract first_t to avoid precision problems
+
+               double n = tap_count;
+               sum_y += t;
+               sum_x += n;
+               sum_xy += n * t;
+               sum_xx += n * n;
+               double T = (sum_xy/n - sum_x/n * sum_y/n) / (sum_xx/n - sum_x/n * sum_x/n);
+
+               if (t - last_t < T / 1.2 || t - last_t > T * 1.2) {
+                       tapped = false;
                } else {
-                       average_interval = 0;
+                       bpm_spinner.set_value (60.0 / T);
                }
-       } else {
-               average_interval = 0;
+       }
+       if (!tapped) {
+               first_t = g_get_monotonic_time ();
+               t = 0.0;
+               sum_y = 0.0;
+               sum_x = 1.0;
+               sum_xy = 0.0;
+               sum_xx = 1.0;
+               tap_count = 1.0;
+
                tapped = true;
        }
-       last_tap = now;
+       tap_count++;
+       last_t = t;
 
        return true;
 }
index eda4e61f1a27a2145086f1629b3d53c40e9a8cc4..06c5db196da0545aaff24a39b890a412fb7e2642 100644 (file)
@@ -60,8 +60,10 @@ private:
        NoteTypes note_types;
 
        bool tapped;      // whether the tap-tempo button has been clicked
-       gint64 last_tap;
-       double average_interval;
+       double sum_x, sum_xx, sum_xy, sum_y;
+       double tap_count;
+       double last_t;
+       gint64 first_t;
 
        Gtk::ComboBoxText pulse_selector;
        Gtk::Adjustment   bpm_adjustment;