1 /* vim: set sts=2 sw=2 et: */
3 * Copyright (C) 2006 Josep Torra <j.torra@telefonica.net>
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.
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.
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.
26 #define TUNER_VERSION "0.4"
30 # include <hildon/hildon-defines.h>
31 # include <hildon/hildon-program.h>
32 # include <hildon/hildon-number-editor.h>
33 # elif defined(MAEMO1)
34 # include <hildon-widgets/hildon-app.h>
35 # include <hildon-widgets/hildon-appview.h>
37 # include <hildon-widgets/hildon-program.h>
42 #define OSSO_PACKAGE "tuner-tool"
43 #define OSSO_VERSION TUNER_VERSION
45 #endif /* ifdef HILDON */
48 # define DEFAULT_AUDIOSRC "dsppcmsrc"
49 # define DEFAULT_AUDIOSINK "dsppcmsink"
51 # define DEFAULT_AUDIOSRC "alsasrc"
52 # define DEFAULT_AUDIOSINK "alsasink"
61 #define between(x,a,b) (((x)>=(a)) && ((x)<=(b)))
63 #define MAGIC (1.059463094359f) /* 2^(1/2) */
65 extern gboolean plugin_pitch_init (GstPlugin * plugin);
66 extern gboolean plugin_tonesrc_init (GstPlugin * plugin);
76 GtkWidget *targetFrequency;
77 GtkWidget *currentFrequency;
78 GtkWidget *drawingarea1;
79 GtkWidget *drawingarea2;
86 osso_context_t *osso_context;
88 guint display_timer_id;
92 typedef struct app_data AppData;
106 #define NUM_LEDS (66)
107 #define NUM_WKEYS (15) /* # of white keys in the piano keyboard */
108 #define WKEY_WIDTH (45)
110 static Note equal_tempered_scale[] = {
184 {"C#6/Db6", 1108.73},
186 {"D#6/Eb6", 1244.51},
189 {"F#6/Gb6", 1479.98},
191 {"G#6/Ab6", 1661.22},
193 {"A#6/Bb6", 1864.66},
196 {"C#7/Db7", 2217.46},
198 {"D#7/Eb7", 2489.02},
201 {"F#7/Gb7", 2959.96},
203 {"G#7/Ab7", 3322.44},
205 {"A#7/Bb7", 3729.31},
209 static GdkColor ledOnColor = { 0, 0 * 255, 180 * 255, 95 * 255 };
210 static GdkColor ledOnColor2 = { 0, 180 * 255, 180 * 255, 0 * 255 };
211 static GdkColor ledOffColor = { 0, 80 * 255, 80 * 255, 80 * 255 };
214 recalculate_scale (double a4)
218 for (i = 0; i < NUM_NOTES; i++) {
219 equal_tempered_scale[i].frequency = a4 * pow (MAGIC, i - 57);
220 /* fprintf(stdout, "%s: %.2f\n", equal_tempered_scale[i].name, equal_tempered_scale[i].frequency); */
226 fix_hildon_number_editor (GtkWidget * widget, gpointer data)
228 if (GTK_IS_EDITABLE (widget)) {
229 gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE);
230 g_object_set (G_OBJECT (widget), "can-focus", FALSE, NULL);
236 calibration_changed (GObject * object, GParamSpec * pspec, gpointer user_data)
241 value = hildon_number_editor_get_value (HILDON_NUMBER_EDITOR (object));
243 value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (object));
246 if (value >= CALIB_MIN && value <= CALIB_MAX) {
247 recalculate_scale (value);
252 on_window_destroy (GtkObject * object, gpointer user_data)
258 toggle_fullscreen (GtkWindow * window)
260 static gboolean fullscreen = FALSE;
262 fullscreen = !fullscreen;
264 gtk_window_fullscreen (GTK_WINDOW (window));
266 gtk_window_unfullscreen (GTK_WINDOW (window));
270 key_press_event (GtkWidget * widget, GdkEventKey * event, GtkWindow * window)
272 switch (event->keyval) {
274 case HILDON_HARDKEY_FULLSCREEN:
275 toggle_fullscreen (window);
286 draw_leds (AppData * appdata, gint n)
289 static GdkGC *gc = NULL;
290 gint width = appdata->drawingarea1->allocation.width;
291 gint led_width = ((gfloat) width / (gfloat) (NUM_LEDS)) * 0.8;
292 gint led_space = ((gfloat) width / (gfloat) (NUM_LEDS)) * 0.2;
293 gint led_total = led_width + led_space;
294 gint padding = (width - NUM_LEDS * led_total) / 2;
297 gc = gdk_gc_new (appdata->drawingarea1->window);
299 gdk_gc_set_rgb_fg_color (gc, &appdata->drawingarea1->style->fg[0]);
301 gdk_draw_rectangle (appdata->drawingarea1->window, gc, TRUE, 0, 0,
302 appdata->drawingarea1->allocation.width, appdata->drawingarea1->allocation.height);
304 if (abs (n) > (NUM_LEDS / 2))
305 n = n / n * (NUM_LEDS / 2);
308 j = NUM_LEDS / 2 + 1;
309 k = NUM_LEDS / 2 + n;
311 j = NUM_LEDS / 2 + n;
312 k = NUM_LEDS / 2 - 1;
316 for (i = 0; i < NUM_LEDS; i++) {
317 if (i == NUM_LEDS / 2) {
319 gdk_gc_set_rgb_fg_color (gc, &ledOnColor2);
321 gdk_gc_set_rgb_fg_color (gc, &ledOffColor);
323 gdk_draw_rectangle (appdata->drawingarea1->window, gc, TRUE, padding + (i * led_total) + ((led_total - 4) / 2), 2, 4,
326 if ((i >= j) && (i <= k))
327 gdk_gc_set_rgb_fg_color (gc, &ledOnColor);
329 gdk_gc_set_rgb_fg_color (gc, &ledOffColor);
331 gdk_draw_rectangle (appdata->drawingarea1->window, gc, TRUE, padding + (i * led_total), 10, led_width,
337 /* update frequency info */
339 update_frequency (AppData * appdata, gint frequency)
343 gfloat diff, min_diff;
345 min_diff = frequency - (equal_tempered_scale[0].frequency - 10);
346 for (i = j = 0; i < NUM_NOTES; i++) {
347 diff = frequency - equal_tempered_scale[i].frequency;
348 if (fabs (diff) <= fabs (min_diff)) {
357 g_strdup_printf ("Nearest note is %s with %.2f Hz frequency",
358 equal_tempered_scale[j].name, equal_tempered_scale[j].frequency);
359 gtk_label_set_text (GTK_LABEL (appdata->targetFrequency), buffer);
362 buffer = g_strdup_printf ("Played frequency is %d Hz", frequency);
363 gtk_label_set_text (GTK_LABEL (appdata->currentFrequency), buffer);
366 draw_leds (appdata, (gint) roundf (min_diff));
369 /* receive spectral data from element message */
371 message_handler (GstBus * bus, GstMessage * message, gpointer data)
373 if (message->type == GST_MESSAGE_ELEMENT) {
374 const GstStructure *s = gst_message_get_structure (message);
375 const gchar *name = gst_structure_get_name (s);
377 if (strcmp (name, "pitch") == 0) {
380 frequency = g_value_get_int (gst_structure_get_value (s, "frequency"));
381 update_frequency (data, frequency);
384 /* we handled the message we want, and ignored the ones we didn't want.
385 * so the core can unref the message for us */
390 keynote2freq (AppData * appdata, gint x, gint y)
392 gint i, j, height, found;
393 gfloat frequency = 0;
395 height = appdata->drawingarea2->allocation.height;
399 for (i = 0; i < NUM_WKEYS; i++) {
400 // Test for a white key
402 if (between (x, i * WKEY_WIDTH, i * WKEY_WIDTH + (WKEY_WIDTH - 1)) && between (y, 0, height))
404 // Test for a black key
405 if (((i % 7) != 2) && ((i % 7) != 6) && (i != 14)) {
407 if (between (x, 24 + i * 45, 24 + i * 45 + 42)
408 && between (y, 0, height / 2))
412 frequency = equal_tempered_scale[48 + found - 1].frequency;
420 expose_event (GtkWidget * widget, GdkEventExpose * event, gpointer user_data)
422 AppData * appdata = (AppData *) user_data;
424 static GdkGC *gc = NULL;
427 gc = gdk_gc_new (appdata->drawingarea2->window);
429 gdk_gc_set_rgb_fg_color (gc, &appdata->drawingarea2->style->fg[0]);
431 gdk_draw_rectangle (appdata->drawingarea2->window, gc, FALSE, 0, 0,
432 NUM_WKEYS * WKEY_WIDTH, appdata->drawingarea2->allocation.height - 1);
434 for (i = 0; i < NUM_WKEYS - 1; i++)
435 gdk_draw_rectangle (appdata->drawingarea2->window, gc, FALSE, i * WKEY_WIDTH, 0,
436 WKEY_WIDTH, appdata->drawingarea2->allocation.height - 1);
438 for (i = 0; i < NUM_WKEYS - 1; i++) {
439 if (((i % 7) != 2) && ((i % 7) != 6))
440 gdk_draw_rectangle (appdata->drawingarea2->window, gc, TRUE, 24 + i * WKEY_WIDTH, 0,
441 42, appdata->drawingarea2->allocation.height / 2);
447 button_press_event (GtkWidget * widget, GdkEventButton * event, gpointer user_data)
449 AppData * appdata = (AppData *) user_data;
451 if (event->button == 1) {
452 g_object_set (appdata->tonesrc, "freq", (gdouble) keynote2freq (appdata, event->x, event->y),
453 "volume", 0.8, NULL);
460 button_release_event (GtkWidget * widget, GdkEventButton * event,
463 AppData * appdata = (AppData *) user_data;
465 if (event->button == 1) {
466 g_object_set (appdata->tonesrc, "volume", 0.0, NULL);
473 set_pipeline_states (AppData * appdata, GstState state)
476 gst_element_set_state (appdata->bin1, state);
479 gst_element_set_state (appdata->bin2, state);
483 stop_pipelines (gpointer user_data)
485 AppData * appdata = (AppData *) user_data;
487 /* dsppcmsrc needs to go to READY or NULL state to make
488 * the DSP sleep and OMAP reach retention mode */
489 set_pipeline_states (appdata, GST_STATE_READY);
490 appdata->stop_timer_id = 0;
497 osso_hw_state_cb (osso_hw_state_t *state, gpointer user_data)
499 AppData * appdata = (AppData *) user_data;
501 if (state->shutdown_ind) {
506 if (state->system_inactivity_ind) {
507 /* do not stop pipelines if the app is on foreground
508 * and display is kept on */
509 if (appdata->display_timer_id == 0) {
510 if (appdata->stop_timer_id != 0)
511 g_source_remove (appdata->stop_timer_id);
513 appdata->stop_timer_id = g_timeout_add (5000, (GSourceFunc) stop_pipelines, user_data);
518 if (hildon_program_get_is_topmost (HILDON_PROGRAM (appdata->app))) {
519 if (appdata->stop_timer_id != 0) {
520 g_source_remove (appdata->stop_timer_id);
521 appdata->stop_timer_id = 0;
524 set_pipeline_states (appdata, GST_STATE_PLAYING);
526 /* not topmost => topmost_notify will set pipelines to PLAYING
527 * when the application is on the foreground again */
529 if (appdata->stop_timer_id != 0) {
530 g_source_remove (appdata->stop_timer_id);
531 appdata->stop_timer_id = 0;
534 set_pipeline_states (appdata, GST_STATE_PLAYING);
543 display_keepalive (gpointer user_data)
545 AppData * appdata = (AppData *) user_data;
547 /* first (direct) call: call blanking_pause and set up timer */
548 if (appdata->display_timer_id == 0) {
549 osso_display_blanking_pause (appdata->osso_context);
550 appdata->display_timer_id = g_timeout_add (55000, (GSourceFunc) display_keepalive, user_data);
551 return TRUE; /* does not really matter */
554 /* callback from main loop */
555 if (hildon_program_get_is_topmost (HILDON_PROGRAM (appdata->app))) {
556 osso_display_blanking_pause (appdata->osso_context);
560 appdata->display_timer_id = 0;
565 topmost_notify (GObject * object, GParamSpec * pspec, gpointer user_data)
567 AppData * appdata = (AppData *) user_data;
569 if (hildon_program_get_is_topmost (HILDON_PROGRAM (object))) {
570 /* cancel pipeline stop timer if it is ticking */
571 if (appdata->stop_timer_id != 0) {
572 g_source_remove (appdata->stop_timer_id);
573 appdata->stop_timer_id = 0;
576 set_pipeline_states (appdata, GST_STATE_PLAYING);
578 /* keep display on */
579 if (appdata->display_timer_id == 0)
580 display_keepalive (user_data);
583 /* pause pipelines so that we don't update the UI needlessly */
584 set_pipeline_states (appdata, GST_STATE_PAUSED);
585 /* stop pipelines fully if the app stays in the background for 30 seconds */
586 appdata->stop_timer_id = g_timeout_add (30000, (GSourceFunc) stop_pipelines, user_data);
587 /* let display dim and switch off */
588 if (appdata->display_timer_id) {
589 g_source_remove (appdata->display_timer_id);
590 appdata->display_timer_id = 0;
599 main (int argc, char *argv[])
601 AppData * appdata = NULL;
604 HildonApp *app = NULL;
605 HildonAppView *view = NULL;
607 HildonProgram *app = NULL;
608 HildonWindow *view = NULL;
610 osso_hw_state_t hw_state_mask = { TRUE, FALSE, FALSE, TRUE, 0 };
613 GstElement *src1, *src2, *pitch, *sink1;
621 GtkWidget *alignment;
622 GtkWidget *calibrate;
626 GdkPixbuf *icon = NULL;
627 GError *error = NULL;
629 gboolean piano_enabled = TRUE;
631 appdata = g_new0(AppData, 1);
634 gst_init (&argc, &argv);
635 /* Register the GStreamer plugins */
636 plugin_pitch_init (NULL);
637 plugin_tonesrc_init (NULL);
639 recalculate_scale (CALIB_DEFAULT);
641 /* Init the gtk - must be called before any hildon stuff */
642 gtk_init (&argc, &argv);
646 /* Create the hildon application and setup the title */
647 app = HILDON_APP (hildon_app_new ());
648 hildon_app_set_title (app, "Tuner Tool");
649 hildon_app_set_two_part_title (app, TRUE);
651 app = HILDON_PROGRAM (hildon_program_get_instance ());
652 g_set_application_name ("Tuner Tool");
657 /* Initialize maemo application */
658 appdata->osso_context = osso_initialize (OSSO_PACKAGE, OSSO_VERSION, TRUE, NULL);
660 /* Check that initialization was ok */
661 if (appdata->osso_context == NULL) {
662 g_print ("Bummer, osso failed\n");
664 g_assert (appdata->osso_context);
666 /* could use also display_event_cb but it is available only from chinook onwards */
667 if (osso_hw_set_event_cb (appdata->osso_context, &hw_state_mask, osso_hw_state_cb, appdata) != OSSO_OK)
668 g_warning ("setting osso_hw_state_cb failed!");
670 mainBox = gtk_vbox_new (FALSE, 0);
671 gtk_container_set_border_width (GTK_CONTAINER (mainBox), 0);
673 view = HILDON_APPVIEW (hildon_appview_new ("Tuner"));
674 hildon_appview_set_fullscreen_key_allowed (view, TRUE);
675 mainWin = GTK_WIDGET (app);
677 view = HILDON_WINDOW (hildon_window_new ());
678 mainWin = GTK_WIDGET (view);
679 g_signal_connect (G_OBJECT (app), "notify::is-topmost", G_CALLBACK (topmost_notify), appdata);
682 mainWin = gtk_window_new (GTK_WINDOW_TOPLEVEL);
683 gtk_window_set_title (GTK_WINDOW (mainWin), "Tuner " TUNER_VERSION);
684 icon = gdk_pixbuf_new_from_file ("tuner64.png", &error);
686 g_print ("Setting icon\n");
687 gtk_window_set_icon (GTK_WINDOW (mainWin), icon);
689 mainBox = gtk_vbox_new (FALSE, 0);
690 gtk_container_set_border_width (GTK_CONTAINER (mainBox), 0);
693 /* Bin for tuner functionality */
694 appdata->bin1 = gst_pipeline_new ("bin1");
696 src1 = gst_element_factory_make (DEFAULT_AUDIOSRC, "src1");
697 pitch = gst_element_factory_make ("pitch", "pitch");
698 g_object_set (G_OBJECT (pitch), "message", TRUE, "minfreq", 10,
699 "maxfreq", 4000, NULL);
701 sink1 = gst_element_factory_make ("fakesink", "sink1");
702 g_object_set (G_OBJECT (sink1), "silent", 1, NULL);
704 gst_bin_add_many (GST_BIN (appdata->bin1), src1, pitch, sink1, NULL);
705 if (!gst_element_link_many (src1, pitch, sink1, NULL)) {
706 fprintf (stderr, "cant link elements\n");
710 bus = gst_element_get_bus (appdata->bin1);
711 gst_bus_add_watch (bus, message_handler, appdata);
712 gst_object_unref (bus);
714 /* Bin for piano functionality */
715 appdata->bin2 = gst_pipeline_new ("bin2");
717 //src2 = gst_element_factory_make ("audiotestsrc", "src2");
718 //g_object_set (G_OBJECT (src2), "volume", 0.0, "wave", 7, NULL);
719 src2 = gst_element_factory_make ("tonesrc", "src2");
720 g_object_set (G_OBJECT (src2), "volume", 0.0, NULL);
721 sink2 = gst_element_factory_make (DEFAULT_AUDIOSINK, "sink2");
723 gst_bin_add_many (GST_BIN (appdata->bin2), src2, sink2, NULL);
724 if (!gst_element_link_many (src2, sink2, NULL)) {
725 piano_enabled = FALSE;
728 appdata->tonesrc = src2;
731 g_signal_connect (G_OBJECT (mainWin), "destroy",
732 G_CALLBACK (on_window_destroy), NULL);
733 g_signal_connect (G_OBJECT(mainWin), "key_press_event",
734 G_CALLBACK(key_press_event), mainWin);
737 appdata->targetFrequency = gtk_label_new ("");
738 gtk_box_pack_start (GTK_BOX (mainBox), appdata->targetFrequency, FALSE, FALSE, 5);
741 appdata->drawingarea1 = gtk_drawing_area_new ();
742 gtk_widget_set_size_request (appdata->drawingarea1, 636, 40);
743 gtk_box_pack_start (GTK_BOX (mainBox), appdata->drawingarea1, FALSE, FALSE, 5);
745 /* Current frequency lable */
746 appdata->currentFrequency = gtk_label_new ("");
747 gtk_box_pack_start (GTK_BOX (mainBox), appdata->currentFrequency, FALSE, FALSE, 5);
749 /* Calibration spinner */
750 box = gtk_hbox_new (FALSE, 0);
751 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
752 label = gtk_label_new ("Calibration");
753 gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 5);
756 calibrate = hildon_number_editor_new (CALIB_MIN, CALIB_MAX);
757 hildon_number_editor_set_value (HILDON_NUMBER_EDITOR (calibrate),
759 /* we don't want that ugly cursor there */
760 gtk_container_forall (GTK_CONTAINER (calibrate),
761 (GtkCallback) fix_hildon_number_editor, NULL);
762 g_signal_connect (G_OBJECT (calibrate), "notify::value",
763 G_CALLBACK (calibration_changed), NULL);
765 calibrate = gtk_spin_button_new_with_range (CALIB_MIN, CALIB_MAX, 1);
766 gtk_spin_button_set_value (GTK_SPIN_BUTTON (calibrate), CALIB_DEFAULT);
767 g_signal_connect (G_OBJECT (calibrate), "value_changed",
768 G_CALLBACK (calibration_changed), NULL);
770 gtk_box_pack_start (GTK_BOX (box), calibrate, FALSE, FALSE, 5);
771 gtk_container_add (GTK_CONTAINER (alignment), box);
772 gtk_box_pack_start (GTK_BOX (mainBox), alignment, FALSE, FALSE, 5);
775 sep = gtk_hseparator_new ();
778 gtk_box_pack_start (GTK_BOX (mainBox), sep, FALSE, FALSE, 5);
780 label = gtk_label_new ("Tuner Tool developed by Josep Torra.\n"
781 "http://n770galaxy.blogspot.com/");
782 gtk_box_pack_start (GTK_BOX (mainBox), label, FALSE, FALSE, 5);
785 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
786 appdata->drawingarea2 = gtk_drawing_area_new ();
787 gtk_widget_set_size_request (appdata->drawingarea2, NUM_WKEYS * WKEY_WIDTH + 1, 130);
788 gtk_container_add (GTK_CONTAINER (alignment), appdata->drawingarea2);
789 gtk_box_pack_start (GTK_BOX (mainBox), alignment, FALSE, FALSE, 5);
791 g_signal_connect (G_OBJECT (appdata->drawingarea2), "expose_event",
792 G_CALLBACK (expose_event), appdata);
794 g_signal_connect (G_OBJECT (appdata->drawingarea2), "button_press_event",
795 G_CALLBACK (button_press_event), (gpointer) appdata);
797 g_signal_connect (G_OBJECT (appdata->drawingarea2), "button_release_event",
798 G_CALLBACK (button_release_event), (gpointer) appdata);
800 gtk_widget_set_events (appdata->drawingarea2, GDK_EXPOSURE_MASK
801 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
803 gtk_widget_set_events (appdata->drawingarea2, GDK_EXPOSURE_MASK);
806 gtk_container_add (GTK_CONTAINER (view), mainBox);
808 hildon_app_set_appview (app, view);
809 gtk_widget_show_all (GTK_WIDGET (app));
811 hildon_program_add_window (app, view);
812 gtk_widget_show_all (GTK_WIDGET (view));
815 gtk_container_add (GTK_CONTAINER (mainWin), mainBox);
816 gtk_widget_show_all (GTK_WIDGET (mainWin));
819 set_pipeline_states (appdata, GST_STATE_PLAYING);
820 display_keepalive (appdata);
822 set_pipeline_states (appdata, GST_STATE_NULL);
824 gst_object_unref (appdata->bin1);
825 gst_object_unref (appdata->bin2);