fe6c47e91e0fdada5df2228730c65fa9297b79a6
[tunertool] / src / tuner.c
1 /* vim: set sts=2 sw=2 et: */
2 /* Tuner
3  * Copyright (C) 2006 Josep Torra <j.torra@telefonica.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #define TUNER_VERSION "0.4"
27
28 #ifdef HILDON
29 #  if HILDON==1
30 #    include <hildon/hildon-program.h>
31 #    include <hildon/hildon-number-editor.h>
32 #  elif defined(MAEMO1)
33 #    include <hildon-widgets/hildon-app.h>
34 #    include <hildon-widgets/hildon-appview.h>
35 #  else
36 #    include <hildon-widgets/hildon-program.h>
37 #  endif
38
39 #include <libosso.h>
40
41 #define OSSO_PACKAGE "tuner-tool"
42 #define OSSO_VERSION TUNER_VERSION
43
44 #endif /* ifdef HILDON */
45
46 #ifdef MAEMO
47 # define DEFAULT_AUDIOSRC "dsppcmsrc"
48 # define DEFAULT_AUDIOSINK "dsppcmsink"
49 #else
50 # define DEFAULT_AUDIOSRC "alsasrc"
51 # define DEFAULT_AUDIOSINK "alsasink"
52 #endif
53
54
55 #include <string.h>
56 #include <math.h>
57 #include <gst/gst.h>
58 #include <gtk/gtk.h>
59
60 #define between(x,a,b) (((x)>=(a)) && ((x)<=(b)))
61
62 #define MAGIC (1.059463094359f) /* 2^(1/2) */
63
64 extern gboolean plugin_pitch_init (GstPlugin * plugin);
65 extern gboolean plugin_tonesrc_init (GstPlugin * plugin);
66
67 typedef struct
68 {
69   const gchar *name;
70   gfloat frequency;
71 } Note;
72
73 enum
74 {
75   NUM_NOTES = 96
76 };
77
78 enum
79 {
80   CALIB_MIN = 430,
81   CALIB_MAX = 450,
82   CALIB_DEFAULT = 440
83 };
84
85 #define NUM_LEDS (66)
86
87 static Note equal_tempered_scale[] = {
88   {"C0", 16.35},
89   {"C#0/Db0", 17.32},
90   {"D0", 18.35},
91   {"D#0/Eb0", 19.45},
92   {"E0", 20.60},
93   {"F0", 21.83},
94   {"F#0/Gb0", 23.12},
95   {"G0", 24.50},
96   {"G#0/Ab0", 25.96},
97   {"A0", 27.50},
98   {"A#0/Bb0", 29.14},
99   {"B0", 30.87},
100   {"C1", 32.70},
101   {"C#1/Db1", 34.65},
102   {"D1", 36.71},
103   {"D#1/Eb1", 38.89},
104   {"E1", 41.20},
105   {"F1", 43.65},
106   {"F#1/Gb1", 46.25},
107   {"G1", 49.00},
108   {"G#1/Ab1", 51.91},
109   {"A1", 55.00},
110   {"A#1/Bb1", 58.27},
111   {"B1", 61.74},
112   {"C2", 65.41},
113   {"C#2/Db2", 69.30},
114   {"D2", 73.42},
115   {"D#2/Eb2", 77.78},
116   {"E2", 82.41},
117   {"F2", 87.31},
118   {"F#2/Gb2", 92.50},
119   {"G2", 98.00},
120   {"G#2/Ab2", 103.83},
121   {"A2", 110.00},
122   {"A#2/Bb2", 116.54},
123   {"B2", 123.47},
124   {"C3", 130.81},
125   {"C#3/Db3", 138.59},
126   {"D3", 146.83},
127   {"D#3/Eb3", 155.56},
128   {"E3", 164.81},
129   {"F3", 174.61},
130   {"F#3/Gb3", 185.00},
131   {"G3", 196.00},
132   {"G#3/Ab3", 207.65},
133   {"A3", 220.00},
134   {"A#3/Bb3", 233.08},
135   {"B3", 246.94},
136   {"C4", 261.63},
137   {"C#4/Db4", 277.18},
138   {"D4", 293.66},
139   {"D#4/Eb4", 311.13},
140   {"E4", 329.63},
141   {"F4", 349.23},
142   {"F#4/Gb4", 369.99},
143   {"G4", 392.00},
144   {"G#4/Ab4", 415.30},
145   {"A4", 440.00},
146   {"A#4/Bb4", 466.16},
147   {"B4", 493.88},
148   {"C5", 523.25},
149   {"C#5/Db5", 554.37},
150   {"D5", 587.33},
151   {"D#5/Eb5", 622.25},
152   {"E5", 659.26},
153   {"F5", 698.46},
154   {"F#5/Gb5", 739.99},
155   {"G5", 783.99},
156   {"G#5/Ab5", 830.61},
157   {"A5", 880.00},
158   {"A#5/Bb5", 932.33},
159   {"B5", 987.77},
160   {"C6", 1046.50},
161   {"C#6/Db6", 1108.73},
162   {"D6", 1174.66},
163   {"D#6/Eb6", 1244.51},
164   {"E6", 1318.51},
165   {"F6", 1396.91},
166   {"F#6/Gb6", 1479.98},
167   {"G6", 1567.98},
168   {"G#6/Ab6", 1661.22},
169   {"A6", 1760.00},
170   {"A#6/Bb6", 1864.66},
171   {"B6", 1975.53},
172   {"C7", 2093.00},
173   {"C#7/Db7", 2217.46},
174   {"D7", 2349.32},
175   {"D#7/Eb7", 2489.02},
176   {"E7", 2637.02},
177   {"F7", 2793.83},
178   {"F#7/Gb7", 2959.96},
179   {"G7", 3135.96},
180   {"G#7/Ab7", 3322.44},
181   {"A7", 3520.00},
182   {"A#7/Bb7", 3729.31},
183   {"B7", 3951.07},
184 };
185
186 static GdkColor ledOnColor = { 0, 0 * 255, 180 * 255, 95 * 255 };
187 static GdkColor ledOnColor2 = { 0, 180 * 255, 180 * 255, 0 * 255 };
188 static GdkColor ledOffColor = { 0, 80 * 255, 80 * 255, 80 * 255 };
189
190 GtkWidget *mainWin;
191 GtkWidget *targetFrequency;
192 GtkWidget *currentFrequency;
193 GtkWidget *drawingarea1;
194 GtkWidget *drawingarea2;
195
196 static void
197 recalculate_scale (double a4)
198 {
199   int i;
200
201   for (i = 0; i < NUM_NOTES; i++) {
202     equal_tempered_scale[i].frequency = a4 * pow (MAGIC, i - 57);
203     /* fprintf(stdout, "%s: %.2f\n", equal_tempered_scale[i].name, equal_tempered_scale[i].frequency); */
204   }
205 }
206
207 #ifdef HILDON
208 static void
209 fix_hildon_number_editor (GtkWidget * widget, gpointer data)
210 {
211   if (GTK_IS_EDITABLE (widget)) {
212     gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE);
213     g_object_set (G_OBJECT (widget), "can-focus", FALSE, NULL);
214   }
215 }
216 #endif
217
218 static void
219 calibration_changed (GObject * object, GParamSpec * pspec, gpointer user_data)
220 {
221   gint value;
222
223 #ifdef HILDON
224   value = hildon_number_editor_get_value (HILDON_NUMBER_EDITOR (object));
225 #else
226   value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (object));
227 #endif
228
229   if (value >= CALIB_MIN && value <= CALIB_MAX) {
230     recalculate_scale (value);
231     g_debug ("Calibration changed to %d Hz", value);
232   }
233 }
234
235 static void
236 on_window_destroy (GtkObject * object, gpointer user_data)
237 {
238   gtk_main_quit ();
239 }
240
241 static void
242 draw_leds (gint n)
243 {
244   gint i, j, k;
245   static GdkGC *gc = NULL;
246
247   if (!gc) {
248     gc = gdk_gc_new (drawingarea1->window);
249   }
250   gdk_gc_set_rgb_fg_color (gc, &drawingarea1->style->fg[0]);
251
252   gdk_draw_rectangle (drawingarea1->window, gc, TRUE, 0, 0,
253       drawingarea1->allocation.width, drawingarea1->allocation.height);
254
255   if (abs (n) > (NUM_LEDS / 2))
256     n = n / n * (NUM_LEDS / 2);
257
258   if (n > 0) {
259     j = NUM_LEDS / 2 + 1;
260     k = NUM_LEDS / 2 + n;
261   } else {
262     j = NUM_LEDS / 2 + n;
263     k = NUM_LEDS / 2 - 1;
264   }
265
266   // Draw all leds
267   for (i = 0; i < NUM_LEDS; i++) {
268     if (i == NUM_LEDS / 2) {
269       if (n == 0)
270         gdk_gc_set_rgb_fg_color (gc, &ledOnColor2);
271       else
272         gdk_gc_set_rgb_fg_color (gc, &ledOffColor);
273
274       gdk_draw_rectangle (drawingarea1->window, gc, TRUE, (i * 10) + 8, 2, 4,
275           36);
276     } else {
277       if ((i >= j) && (i <= k))
278         gdk_gc_set_rgb_fg_color (gc, &ledOnColor);
279       else
280         gdk_gc_set_rgb_fg_color (gc, &ledOffColor);
281
282       gdk_draw_rectangle (drawingarea1->window, gc, TRUE, (i * 10) + 6, 10, 8,
283           20);
284     }
285   }
286 }
287
288 /* update frequency info */
289 static void
290 update_frequency (gint frequency)
291 {
292   gchar *buffer;
293   gint i, j;
294   gfloat diff, min_diff;
295
296   min_diff = fabs (frequency - (equal_tempered_scale[0].frequency - 10));
297   for (i = j = 0; i < NUM_NOTES; i++) {
298     diff = fabs (frequency - equal_tempered_scale[i].frequency);
299     if (diff <= min_diff) {
300       min_diff = diff;
301       j = i;
302     } else {
303       break;
304     }
305   }
306
307   buffer =
308       g_strdup_printf ("Nearest note is %s with %.2f Hz frequency",
309       equal_tempered_scale[j].name, equal_tempered_scale[j].frequency);
310   gtk_label_set_text (GTK_LABEL (targetFrequency), buffer);
311   g_free (buffer);
312
313   buffer = g_strdup_printf ("Played frequency is %d Hz", frequency);
314   gtk_label_set_text (GTK_LABEL (currentFrequency), buffer);
315   g_free (buffer);
316
317   draw_leds ((gint) roundf (min_diff));
318 }
319
320 /* receive spectral data from element message */
321 gboolean
322 message_handler (GstBus * bus, GstMessage * message, gpointer data)
323 {
324   if (message->type == GST_MESSAGE_ELEMENT) {
325     const GstStructure *s = gst_message_get_structure (message);
326     const gchar *name = gst_structure_get_name (s);
327
328     if (strcmp (name, "pitch") == 0) {
329       gint frequency;
330
331       frequency = g_value_get_int (gst_structure_get_value (s, "frequency"));
332       update_frequency (frequency);
333     }
334   }
335   /* we handled the message we want, and ignored the ones we didn't want.
336    * so the core can unref the message for us */
337   return TRUE;
338 }
339
340 gfloat
341 keynote2freq (gint x, gint y)
342 {
343   gint i, j, height, found;
344   gfloat frequency = 0;
345
346   height = drawingarea2->allocation.height;
347
348   j = 0;
349   found = 0;
350   for (i = 0; i < 15; i++) {
351     // Test for a white key  
352     j++;
353     if (between (x, i * 45, i * 45 + 44) && between (y, 0, height))
354       found = j;
355     // Test for a black key
356     if (((i % 7) != 2) && ((i % 7) != 6) && (i != 14)) {
357       j++;
358       if (between (x, 24 + i * 45, 24 + i * 45 + 42)
359           && between (y, 0, height / 2))
360         found = j;
361     }
362     if (found) {
363       frequency = equal_tempered_scale[48 + found - 1].frequency;
364       break;
365     }
366   }
367   return frequency;
368 }
369
370 static gboolean
371 expose_event (GtkWidget * widget, GdkEventExpose * event)
372 {
373   gint i;
374   static GdkGC *gc = NULL;
375
376   if (!gc) {
377     gc = gdk_gc_new (drawingarea2->window);
378   }
379   gdk_gc_set_rgb_fg_color (gc, &drawingarea2->style->fg[0]);
380
381   gdk_draw_rectangle (drawingarea2->window, gc, FALSE, 0, 0,
382       drawingarea2->allocation.width - 1, drawingarea2->allocation.height - 1);
383
384   for (i = 0; i < 14; i++)
385     gdk_draw_rectangle (drawingarea2->window, gc, FALSE, i * 45, 0,
386         45, drawingarea2->allocation.height - 1);
387
388   for (i = 0; i < 14; i++) {
389     if (((i % 7) != 2) && ((i % 7) != 6))
390       gdk_draw_rectangle (drawingarea2->window, gc, TRUE, 24 + i * 45, 0,
391           42, drawingarea2->allocation.height / 2);
392   }
393   return FALSE;
394 }
395
396 static gboolean
397 key_press_event (GtkWidget * widget, GdkEventButton * event, gpointer user_data)
398 {
399   GstElement *piano = GST_ELEMENT (user_data);
400
401   if (event->button == 1) {
402     g_object_set (piano, "freq", (gdouble) keynote2freq (event->x, event->y),
403         "volume", 0.8, NULL);
404   }
405
406   return TRUE;
407 }
408
409 static gboolean
410 key_release_event (GtkWidget * widget, GdkEventButton * event,
411     gpointer user_data)
412 {
413   GstElement *piano = GST_ELEMENT (user_data);
414
415   if (event->button == 1) {
416     g_object_set (piano, "volume", 0.0, NULL);
417   }
418
419   return TRUE;
420 }
421
422 int
423 main (int argc, char *argv[])
424 {
425 #ifdef HILDON
426 #if defined(MAEMO1)
427   HildonApp *app = NULL;
428   HildonAppView *view = NULL;
429 #else
430   HildonProgram *app = NULL;
431   HildonWindow *view = NULL;
432 #endif
433   osso_context_t *osso_context = NULL;  /* handle to osso */
434 #endif
435
436   GstElement *bin1, *bin2;
437   GstElement *src1, *pitch, *sink1;
438   GstElement *src2, *sink2;
439   GstBus *bus;
440
441   GtkWidget *mainBox;
442   GtkWidget *box;
443   GtkWidget *label;
444   GtkWidget *alignment;
445   GtkWidget *calibrate;
446   GtkWidget *sep;
447
448 #ifndef HILDON
449   GdkPixbuf *icon = NULL;
450   GError *error = NULL;
451 #endif
452   gboolean piano_enabled = TRUE;
453
454   /* Init GStreamer */
455   gst_init (&argc, &argv);
456   /* Register the GStreamer plugins */
457   plugin_pitch_init (NULL);
458   plugin_tonesrc_init (NULL);
459
460   recalculate_scale (CALIB_DEFAULT);
461
462   /* Init the gtk - must be called before any hildon stuff */
463   gtk_init (&argc, &argv);
464
465 #ifdef HILDON
466 #if defined(MAEMO1)
467   /* Create the hildon application and setup the title */
468   app = HILDON_APP (hildon_app_new ());
469   hildon_app_set_title (app, "Tuner Tool");
470   hildon_app_set_two_part_title (app, TRUE);
471 #else
472   app = HILDON_PROGRAM (hildon_program_get_instance ());
473   g_set_application_name ("Tuner Tool");
474 #endif
475
476   /* Initialize maemo application */
477   osso_context = osso_initialize (OSSO_PACKAGE, OSSO_VERSION, TRUE, NULL);
478
479   /* Check that initialization was ok */
480   if (osso_context == NULL) {
481     g_print ("Bummer, osso failed\n");
482   }
483   g_assert (osso_context);
484
485   mainBox = gtk_vbox_new (FALSE, 0);
486   gtk_container_set_border_width (GTK_CONTAINER (mainBox), 0);
487 #if defined(MAEMO1)
488   view = HILDON_APPVIEW (hildon_appview_new ("Tuner"));
489   hildon_appview_set_fullscreen_key_allowed (view, TRUE);
490   mainWin = GTK_WIDGET (app);
491 #else
492   view = HILDON_WINDOW (hildon_window_new ());
493   mainWin = GTK_WIDGET (view);
494 #endif
495 #else
496   mainWin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
497   gtk_window_set_title (GTK_WINDOW (mainWin), "Tuner " TUNER_VERSION);
498   icon = gdk_pixbuf_new_from_file ("tuner64.png", &error);
499   if (icon != NULL) {
500     g_print ("Setting icon\n");
501     gtk_window_set_icon (GTK_WINDOW (mainWin), icon);
502   }
503   mainBox = gtk_vbox_new (FALSE, 0);
504   gtk_container_set_border_width (GTK_CONTAINER (mainBox), 0);
505 #endif
506
507   /* Bin for tuner functionality */
508   bin1 = gst_pipeline_new ("bin1");
509
510   src1 = gst_element_factory_make (DEFAULT_AUDIOSRC, "src1");
511   pitch = gst_element_factory_make ("pitch", "pitch");
512   g_object_set (G_OBJECT (pitch), "message", TRUE, "minfreq", 10,
513       "maxfreq", 4000, NULL);
514
515   sink1 = gst_element_factory_make ("fakesink", "sink1");
516   g_object_set (G_OBJECT (sink1), "silent", 1, NULL);
517
518   gst_bin_add_many (GST_BIN (bin1), src1, pitch, sink1, NULL);
519   if (!gst_element_link_many (src1, pitch, sink1, NULL)) {
520     fprintf (stderr, "cant link elements\n");
521     exit (1);
522   }
523
524   bus = gst_element_get_bus (bin1);
525   gst_bus_add_watch (bus, message_handler, NULL);
526   gst_object_unref (bus);
527
528   /* Bin for piano functionality */
529   bin2 = gst_pipeline_new ("bin2");
530
531   //src2 = gst_element_factory_make ("audiotestsrc", "src2");
532   //g_object_set (G_OBJECT (src2), "volume", 0.0, "wave", 7, NULL);
533   src2 = gst_element_factory_make ("tonesrc", "src2");
534   g_object_set (G_OBJECT (src2), "volume", 0.0, NULL);
535   sink2 = gst_element_factory_make (DEFAULT_AUDIOSINK, "sink2");
536
537   gst_bin_add_many (GST_BIN (bin2), src2, sink2, NULL);
538   if (!gst_element_link_many (src2, sink2, NULL)) {
539     piano_enabled = FALSE;
540   }
541
542   /* GUI */
543   g_signal_connect (G_OBJECT (mainWin), "destroy",
544       G_CALLBACK (on_window_destroy), NULL);
545
546   /* Note label */
547   targetFrequency = gtk_label_new ("");
548   gtk_box_pack_start (GTK_BOX (mainBox), targetFrequency, FALSE, FALSE, 5);
549
550   /* Leds */
551   drawingarea1 = gtk_drawing_area_new ();
552   gtk_widget_set_size_request (drawingarea1, 636, 40);
553   gtk_box_pack_start (GTK_BOX (mainBox), drawingarea1, FALSE, FALSE, 5);
554
555   /* Current frequency lable */
556   currentFrequency = gtk_label_new ("");
557   gtk_box_pack_start (GTK_BOX (mainBox), currentFrequency, FALSE, FALSE, 5);
558
559   /* Calibration spinner */
560   box = gtk_hbox_new (FALSE, 0);
561   alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
562   label = gtk_label_new ("Calibration");
563   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 5);
564
565 #ifdef HILDON
566   calibrate = hildon_number_editor_new (CALIB_MIN, CALIB_MAX);
567   hildon_number_editor_set_value (HILDON_NUMBER_EDITOR (calibrate),
568       CALIB_DEFAULT);
569   /* we don't want that ugly cursor there */
570   gtk_container_forall (GTK_CONTAINER (calibrate),
571       (GtkCallback) fix_hildon_number_editor, NULL);
572   g_signal_connect (G_OBJECT (calibrate), "notify::value",
573       G_CALLBACK (calibration_changed), NULL);
574 #else
575   calibrate = gtk_spin_button_new_with_range (CALIB_MIN, CALIB_MAX, 1);
576   gtk_spin_button_set_value (GTK_SPIN_BUTTON (calibrate), CALIB_DEFAULT);
577   g_signal_connect (G_OBJECT (calibrate), "value_changed",
578       G_CALLBACK (calibration_changed), NULL);
579 #endif
580   gtk_box_pack_start (GTK_BOX (box), calibrate, FALSE, FALSE, 5);
581   gtk_container_add (GTK_CONTAINER (alignment), box);
582   gtk_box_pack_start (GTK_BOX (mainBox), alignment, FALSE, FALSE, 5);
583
584   /* Separator */
585   sep = gtk_hseparator_new ();
586
587   /* Credits */
588   gtk_box_pack_start (GTK_BOX (mainBox), sep, FALSE, FALSE, 5);
589
590   label = gtk_label_new ("Tuner Tool developed by Josep Torra.\n"
591       "http://n770galaxy.blogspot.com/");
592   gtk_box_pack_start (GTK_BOX (mainBox), label, FALSE, FALSE, 5);
593
594   /* Piano keyboard */
595   drawingarea2 = gtk_drawing_area_new ();
596   gtk_widget_set_size_request (drawingarea2, 636, 130);
597   gtk_box_pack_start (GTK_BOX (mainBox), drawingarea2, FALSE, FALSE, 5);
598
599   g_signal_connect (G_OBJECT (drawingarea2), "expose_event",
600       G_CALLBACK (expose_event), NULL);
601   if (piano_enabled) {
602     g_signal_connect (G_OBJECT (drawingarea2), "button_press_event",
603         G_CALLBACK (key_press_event), (gpointer) src2);
604
605     g_signal_connect (G_OBJECT (drawingarea2), "button_release_event",
606         G_CALLBACK (key_release_event), (gpointer) src2);
607
608     gtk_widget_set_events (drawingarea2, GDK_EXPOSURE_MASK
609         | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
610   } else {
611     gtk_widget_set_events (drawingarea2, GDK_EXPOSURE_MASK);
612   }
613 #ifdef HILDON
614   gtk_container_add (GTK_CONTAINER (view), mainBox);
615 #if defined(MAEMO1)
616   hildon_app_set_appview (app, view);
617   gtk_widget_show_all (GTK_WIDGET (app));
618 #else
619   hildon_program_add_window (app, view);
620   gtk_widget_show_all (GTK_WIDGET (view));
621 #endif
622 #else
623   gtk_container_add (GTK_CONTAINER (mainWin), mainBox);
624   gtk_widget_show_all (GTK_WIDGET (mainWin));
625 #endif
626
627   gst_element_set_state (bin1, GST_STATE_PLAYING);
628   gst_element_set_state (bin2, GST_STATE_PLAYING);
629   gtk_main ();
630   gst_element_set_state (bin2, GST_STATE_NULL);
631   gst_element_set_state (bin1, GST_STATE_NULL);
632
633   gst_object_unref (bin1);
634   gst_object_unref (bin2);
635
636   return 0;
637 }