Initial project commit master
authorDru Moore <usr@dru-id.co.uk>
Thu, 18 Nov 2010 09:19:05 +0000 (09:19 +0000)
committerDru Moore <usr@dru-id.co.uk>
Thu, 18 Nov 2010 09:19:05 +0000 (09:19 +0000)
new file:   build.sh
new file:   src/AcmPopUp.vala
new file:   src/AudioCaptureMonitor.vala
new file:   src/Constants.vala
new file:   src/DbMonitorWidget.vala
new file:   src/DecibelMeter.vala
new file:   src/InputBin.vala
new file:   src/MeterWidget.vala
new file:   src/Program.vala
new file:   www/index.html

build.sh [new file with mode: 0644]
src/AcmPopUp.vala [new file with mode: 0644]
src/AudioCaptureMonitor.vala [new file with mode: 0644]
src/Constants.vala [new file with mode: 0644]
src/DbMonitorWidget.vala [new file with mode: 0644]
src/DecibelMeter.vala [new file with mode: 0644]
src/InputBin.vala [new file with mode: 0644]
src/MeterWidget.vala [new file with mode: 0644]
src/Program.vala [new file with mode: 0644]
www/index.html [new file with mode: 0644]

diff --git a/build.sh b/build.sh
new file mode 100644 (file)
index 0000000..a1e512f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,19 @@
+#! /bin/bash
+if [ ! -d "bin" ] ; then
+  mkdir bin
+fi
+valac \
+--pkg gtk+-2.0 \
+--pkg hildon-1 \
+--pkg hildon-fm-2 \
+--pkg gstreamer-0.10 \
+--pkg libosso \
+--pkg dbus-glib-1 \
+-o bin/decibelmeter \
+src/Program.vala \
+src/AudioCaptureMonitor.vala \
+src/Constants.vala \
+src/DbMonitorWidget.vala \
+src/DecibelMeter.vala \
+src/MeterWidget.vala \
+src/InputBin.vala
diff --git a/src/AcmPopUp.vala b/src/AcmPopUp.vala
new file mode 100644 (file)
index 0000000..939e4c9
--- /dev/null
@@ -0,0 +1,291 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+namespace IdWorks {
+  
+  public class AcmPopUp : Hildon.Dialog {
+    
+    SettingsStructures.MonitorSettings settings;
+    AudioCaptureMonitor acm;
+    Gtk.Label lbl_level_1;
+    Gtk.Label lbl_level_2;
+    Gtk.HScale pregain_slider;
+    Gtk.HScale volume_slider;
+    Hildon.Button dynamics_settings_btn;
+    Hildon.CheckButton dynamics_btn;
+    Hildon.TouchSelector source_selector;
+    DbMonitorWidget monitor_1;
+    DbMonitorWidget monitor_2;
+    const string level_format = "%02.1f/%02.1fdB";
+    
+    public AcmPopUp(string title, Gtk.Widget parent) {
+      this.set_title(title);
+      this.set_parent(parent);
+      this.settings = SettingsStructures.MonitorSettings();
+      this.construct_interface();
+      update_settings();
+    }
+    public AcmPopUp.with_settings(string title, Gtk.Widget parent, SettingsStructures.MonitorSettings settings) {
+      this.set_title(title);
+      this.set_parent(parent);
+      this.settings = settings;
+      this.construct_interface();
+      update_settings();
+    }
+    
+    public void set_settings_structure(SettingsStructures.MonitorSettings settings) {
+      this.settings = settings;
+      update_settings();
+    }
+    public SettingsStructures.MonitorSettings get_settings_structure() {
+      return this.settings;
+    }
+    
+    private void update_settings() {
+      set_volume(settings.input.volume);
+      set_pregain(settings.input.pregain);
+      //set_source(settings.input.source);
+      dynamics_btn.set_active(settings.input.dynamics.active);
+      toggle_dynamics(settings.input.dynamics.active);
+    }
+    
+    private void set_volume(double val) {
+      volume_slider.set_value(val);
+      settings.input.volume = val;
+      if (null != acm) acm.set_volume(val);
+    }
+    private void set_pregain(double val) {
+      pregain_slider.set_value(val);
+      settings.input.pregain = val;
+      if (null != acm) acm.set_pregain(val);
+    }
+    
+    private void set_source(string name) {
+      switch (name) {
+        case "Microphone":
+          source_selector.set_active(0, 0);
+          break;
+        case "Monitor":
+          source_selector.set_active(0, 1);
+          break;
+        case "Bluetooth":
+          source_selector.set_active(0, 2);
+          break;
+        default:
+          source_selector.set_active(0, 0);
+          break;
+      }
+      settings.input.source = name;
+      if (null != acm) acm.set_source(name);
+    }
+    private void set_input_source(string name) {
+      settings.input.source = name;
+      if (null != acm) acm.set_source(name);
+    }
+    
+    private void construct_interface() {
+      this.set_default_response(Gtk.ResponseType.CANCEL);
+      this.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK);
+      this.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
+      
+      Gtk.VBox control_area = (Gtk.VBox)this.get_content_area();
+      
+      acm = new AudioCaptureMonitor("Recorder");
+      acm.update_level.connect(update_level_callback);
+      
+      Gtk.HButtonBox input_bar = new Gtk.HButtonBox();
+      input_bar.set_layout(Gtk.ButtonBoxStyle.START);
+      input_bar.set_homogeneous(true);
+      input_bar.set_spacing(2);
+      
+      source_selector = new Hildon.TouchSelector.text();
+      source_selector.append_text("Microphone");
+      source_selector.append_text("Monitor");
+      source_selector.append_text("Bluetooth");
+      //source_selector.set_active(0, 0);
+      this.set_source(settings.input.source);
+      Hildon.PickerButton source_picker = new Hildon.PickerButton(Hildon.SizeType.FINGER_HEIGHT | Hildon.SizeType.AUTO_WIDTH, Hildon.ButtonArrangement.VERTICAL);
+      source_picker.set_title("Source:");
+      source_picker.set_selector(source_selector);
+      source_picker.set_alignment(0, 0, 1, 1);
+      source_picker.set_image(new Gtk.Image.from_icon_name("camera_volume_unmuted", Gtk.IconSize.BUTTON));
+      source_picker.value_changed.connect( (s) => { set_input_source(s.get_selector().get_current_text()); } );
+      input_bar.pack_start(source_picker, true, true, 2);
+      
+      Hildon.CheckButton audio_btn = new Hildon.CheckButton(Hildon.SizeType.FINGER_HEIGHT | Hildon.SizeType.AUTO_WIDTH);
+      audio_btn.set_label("Enable monitor");
+      //audio_btn.set_image(new Gtk.Image.from_icon_name("statusarea_volume_headset", Gtk.IconSize.BUTTON));
+      audio_btn.toggled.connect( (s) => {toggle_monitor(s.get_active());} );
+      input_bar.pack_start(audio_btn, true, false, 2);
+      
+      control_area.pack_start(input_bar, true, true, 4);
+      
+      Gtk.HBox monitor_1_bar = new Gtk.HBox(false, 2);
+      lbl_level_1 = new Gtk.Label(level_format.printf(0, 0));
+      monitor_1 = new DbMonitorWidget();
+      monitor_1.set_layout(DbMonitorWidget.Layout.HORIZONTAL);
+      monitor_1.set_peak(1);
+      monitor_1.set_decay(1.5);
+      monitor_1_bar.pack_start(new Gtk.Label("L"), false, false, 2);
+      monitor_1_bar.pack_start(monitor_1, true, true, 2);
+      monitor_1_bar.pack_start(lbl_level_1, false, false, 2);
+      control_area.pack_start(monitor_1_bar, true, true, 2);
+      
+      Gtk.HBox monitor_2_bar = new Gtk.HBox(false, 4);
+      lbl_level_2 = new Gtk.Label(level_format.printf(0, 0));
+      monitor_2 = new DbMonitorWidget();
+      monitor_2.set_layout(DbMonitorWidget.Layout.HORIZONTAL);
+      monitor_2.set_peak(1);
+      monitor_2.set_decay(1.5);
+      monitor_2_bar.pack_start(new Gtk.Label("R"), false, false, 2);
+      monitor_2_bar.pack_start(monitor_2, true, true, 2);
+      monitor_2_bar.pack_start(lbl_level_2, false, false, 2);
+      control_area.pack_start(monitor_2_bar, true, true, 2);
+      
+      Gtk.HBox pregain_bar = new Gtk.HBox(false, 4);
+      Gtk.Label pregain_label = new Gtk.Label("Pre-gain");
+      pregain_bar.pack_start(pregain_label, false, false , 2);
+      pregain_slider = new Gtk.HScale.with_range(0.0, 10.0, 0.1);
+      pregain_slider.set_value_pos(Gtk.PositionType.LEFT);
+      pregain_slider.value_changed.connect((s) => {this.set_pregain(s.get_value());});
+      pregain_bar.pack_start(pregain_slider, true, true , 2);
+      
+      control_area.pack_start(pregain_bar, true, true , 2);
+      
+      Gtk.HBox dynamics_bar = new Gtk.HBox(false, 4);
+      dynamics_btn = new Hildon.CheckButton(Hildon.SizeType.FINGER_HEIGHT | Hildon.SizeType.AUTO_WIDTH);
+      dynamics_btn.set_label("Enable dynamics");
+      dynamics_btn.toggled.connect( (s) => {toggle_dynamics(s.get_active());} );
+      dynamics_bar.pack_start(dynamics_btn, true, false, 2);
+      dynamics_settings_btn = new Hildon.Button(Hildon.SizeType.FINGER_HEIGHT | Hildon.SizeType.AUTO_WIDTH, Hildon.ButtonArrangement.HORIZONTAL);
+      dynamics_settings_btn.set_text("Settings", "");
+      dynamics_settings_btn.set_image(new Gtk.Image.from_icon_name("general_settings", Gtk.IconSize.BUTTON));
+      dynamics_settings_btn.set_image_position(Gtk.PositionType.LEFT);
+      dynamics_settings_btn.clicked.connect(do_dynamics_popup);
+      dynamics_bar.pack_start(dynamics_settings_btn, true, false, 2);
+      
+      control_area.pack_start(dynamics_bar, true, true , 2);
+      
+      Gtk.HBox volume_bar = new Gtk.HBox(false, 4);
+      Gtk.Label volume_label = new Gtk.Label("Volume");
+      volume_bar.pack_start(volume_label, false, false , 2);
+      volume_slider = new Gtk.HScale.with_range(0.0, 10.0, 0.1);
+      volume_slider.set_value_pos(Gtk.PositionType.LEFT);
+      volume_slider.value_changed.connect((s) => {this.set_volume(s.get_value());});
+      volume_bar.pack_start(volume_slider, true, true , 2);
+      
+      control_area.pack_start(volume_bar, true, true , 2);
+      
+      this.update_settings();
+      this.show_all();
+    }
+    
+    public void play() {
+      acm.play();
+    }
+    
+    public void pause() {
+      acm.pause();
+    }
+    
+    public void stop() {
+      acm.stop();
+    }
+    
+    private void toggle_monitor(bool state) {
+      if (null != acm) {
+        acm.set_monitor_mute(!state);
+      }
+    }
+    
+    private void toggle_dynamics(bool state) {
+      this.settings.input.dynamics.active = state;
+      this.dynamics_settings_btn.set_sensitive(state);
+      if (null != acm) {
+        if (!state) {
+          SettingsStructures.DynamicsSettings ds = SettingsStructures.DynamicsSettings();
+          acm.set_dynamics(ds.mode, ds.characteristics, ds.threshold, ds.ratio); // shouldn't be applied
+        }
+        else {
+          acm.set_dynamics(settings.input.dynamics.mode, settings.input.dynamics.characteristics, settings.input.dynamics.threshold, settings.input.dynamics.ratio);
+        }
+      }
+    }
+    
+    private void do_dynamics_popup() {
+      stdout.printf("dynamics popup new called\n");
+      stdout.flush();
+      DynamicsPopUp dlg = new DynamicsPopUp.with_settings("Monitoring dynamics settings", this, settings.input.dynamics);
+      dlg.set_transient_for(this.get_transient_for());
+      dlg.mode_updated.connect(this.dynamics_update_mode_callback);
+      dlg.characteristics_updated.connect(this.dynamics_update_characteristics_callback);
+      dlg.ratio_updated.connect(this.dynamics_update_ratio_callback);
+      dlg.threshold_updated.connect(this.dynamics_update_threshold_callback);
+      stdout.printf("dynamics connections made\n");
+      stdout.flush();
+      dlg.run();
+      stdout.printf("dynamics back from run\n");
+      stdout.flush();
+      dlg.destroy();
+    }
+    private void dynamics_update_mode_callback(DynamicsPopUp sender, string val) {
+      settings.input.dynamics.mode = val;
+      if (null != acm) {
+        acm.set_dynamics_mode(val);
+      }
+    }
+    private void dynamics_update_characteristics_callback(DynamicsPopUp sender, string val) {
+      settings.input.dynamics.characteristics = val;
+      if (null != acm) {
+        acm.set_dynamics_characteristics(val);
+      }
+    }
+    private void dynamics_update_ratio_callback(DynamicsPopUp sender, double val) {
+      settings.input.dynamics.ratio = val;
+      if (null != acm) {
+        acm.set_dynamics_ratio(val);
+      }
+    }
+    private void dynamics_update_threshold_callback(DynamicsPopUp sender, double val) {
+      settings.input.dynamics.threshold = val;
+      if (null != acm) {
+        acm.set_dynamics_threshold(val);
+      }
+    }
+    
+    private void update_level_callback(AudioCaptureMonitor sender, int channels, int channel, double rms_dB, double peak_dB, double decay_dB, double rms) {
+      //string text = level_format.printf(channel.to_string(), rms_dB, peak_dB, decay_dB, rms, Math.pow (10, decay_dB / 20));
+      string text = level_format.printf(peak_dB, decay_dB);
+      if (0 == channel) {
+        lbl_level_1.set_text(text);
+        monitor_1.set_peak(Math.pow (10, peak_dB / 20));
+        monitor_1.set_decay(Math.pow (10, decay_dB / 20));
+      }
+      else if (1 == channel) {
+        lbl_level_2.set_text(text);
+        monitor_2.set_peak(Math.pow (10, peak_dB / 20));
+        monitor_2.set_decay(Math.pow (10, decay_dB / 20));
+      }
+      else {
+        stdout.printf("channel %s found\n", channel.to_string());
+        stdout.flush();
+      }
+    }
+    
+  }
+  
+}
diff --git a/src/AudioCaptureMonitor.vala b/src/AudioCaptureMonitor.vala
new file mode 100644 (file)
index 0000000..e2b76fe
--- /dev/null
@@ -0,0 +1,161 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+namespace IdWorks {
+
+public class AudioCaptureMonitor : Gst.Pipeline {
+  InputBin input;
+  Gst.Element level;
+  Gst.Element monitor;
+  Gst.Element sink_element;
+  uint watch_id;
+  
+  public AudioCaptureMonitor(string name) {
+    this.name = name;
+    construct_pipeline("Microphone", 1.0);
+  }
+  public AudioCaptureMonitor.with_settings(string name, string source, double volume) {
+    this.name = name;
+    construct_pipeline(source, volume);
+  }
+  
+  private void construct_pipeline(string source, double volume) {
+    
+    this.input = new InputBin.with_settings("input", source, 1.0);
+    this.add(this.input);
+    
+    this.level = Gst.ElementFactory.make("level", "level");
+    this.level.set_property("interval", 50 * Time.Nanoseconds.MILLISECOND);
+    this.level.set_property("peak-ttl", 2 * Time.Nanoseconds.SECOND);
+    this.add(this.level);
+    
+    this.monitor = Gst.ElementFactory.make("volume", "monitor");
+    this.monitor.set_property("volume", 1);
+    this.monitor.set_property("mute", true);
+    this.add(this.monitor);
+    
+    this.sink_element = Gst.ElementFactory.make("autoaudiosink", "fakesink");
+    this.add(this.sink_element);
+    
+    this.level.set_property("message", true);
+    
+    this.sink_element.set_property("sync", true);
+    
+    this.input.link(level);
+    this.level.link(monitor);
+    this.monitor.link(sink_element);
+    
+    Gst.Bus bus = this.get_bus();
+    
+    watch_id = bus.add_watch(message_handler);
+    
+  }
+  
+  
+  public void set_source(string name) {
+    this.input.set_source(name);
+  }
+  public string get_source() {
+    return this.input.get_source();
+  }
+  
+  public void set_volume(double val)
+  requires (val >= 0.0 && val <= 10.0) {
+    this.input.set_volume(val);
+    //this.volume.set_property("volume", val);
+  }
+  public double get_volume()
+  ensures (result >= 0.0 && result <= 10.0) {
+    return this.input.get_volume();
+    /*GLib.Value val = 0;
+    this.volume.get_property("volume", ref val);
+    return val.get_double();*/
+  }
+  
+  public void set_mute(bool val) {
+    this.input.set_mute(val);
+    //this.volume.set_property("mute", val);
+  }
+  public bool get_mute() {
+    return this.input.get_mute();
+    /*GLib.Value val = 0;
+    this.volume.get_property("mute", ref val);
+    return val.get_boolean();*/
+  }
+  
+  public void set_monitor_mute(bool val) {
+    this.monitor.set_property("mute", val);
+  }
+  public bool get_monitor_mute() {
+    GLib.Value val = 0;
+    this.monitor.get_property("mute", ref val);
+    return val.get_boolean();
+  }
+  
+  public void play() {
+    this.set_state(Gst.State.PLAYING);
+  }
+  
+  public void pause() {
+    this.set_state(Gst.State.READY);
+  }
+  
+  public void stop() {
+    this.set_state(Gst.State.READY);
+  }
+  
+  public signal void update_level(int channels, int channel, double rms_dB, double peak_dB, double decay_dB, double rms);
+  
+  public bool message_handler(Gst.Bus bus, Gst.Message message) {
+    if (Gst.MessageType.ELEMENT == message.type) {
+      Gst.Structure s = message.get_structure();
+      string name = s.get_name();
+      
+      if ("level" == name) {
+        int channels;
+        double rms_dB, peak_dB, decay_dB;
+        double rms;
+        Gst.Value list;
+        Gst.Value value;
+        int i;
+              
+        list = s.get_value("rms"); // any value gives us the channelsby list size
+        channels = 0;
+        channels = (int) list.list_get_size();
+        
+        for (i = 0; i < channels; ++i) {          
+          list = s.get_value ("rms");
+          value = list.list_get_value (i);
+          rms_dB = value.get_double ();
+          list = s.get_value ("peak");
+          value = list.list_get_value (i);
+          peak_dB = value.get_double ();
+          list = s.get_value ("decay");
+          value = list.list_get_value (i);
+          decay_dB = value.get_double ();
+          /* converting from dB to normal gives us a value between 0.0 and 1.0 */
+          rms = Math.pow (10, rms_dB / 20);
+          update_level(channels, i, rms_dB, peak_dB, decay_dB, rms);
+        }
+      }
+    }
+    return true;
+  }
+
+}
+
+}
diff --git a/src/Constants.vala b/src/Constants.vala
new file mode 100644 (file)
index 0000000..b947208
--- /dev/null
@@ -0,0 +1,62 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+namespace IdWorks {
+
+  namespace Time {
+
+    public const string CLOCK_FORMAT = "%02llu:%02llu.%01llu";
+    public static string time_to_string(int64 t) {
+      uint64 ut = (uint64)t / Nanoseconds.MILLISECOND;
+      return CLOCK_FORMAT.printf((ut / Milliseconds.MINUTE), (ut / Milliseconds.SECOND) % Seconds.MINUTE, (ut % Milliseconds.SECOND) / 100);
+    }
+    public static int64 get_current_time() {
+      TimeVal tv = TimeVal();
+      tv.get_current_time();
+      return (((int64)tv.tv_sec * 1000000) + (int64)tv.tv_usec) * 1000;
+    }
+
+    namespace Nanoseconds {
+      public const uint64 NANOSECOND = 1;
+      public const uint64 MILLISECOND = 1000000 * NANOSECOND;
+      public const uint64 SECOND = 1000 * MILLISECOND;
+      public const uint64 MINUTE = 60 * SECOND;
+      public const uint64 HOUR = 60 * MINUTE;
+      public const uint64 DAY = 24 * HOUR;
+      public const uint64 WEEK = 7 * DAY;
+    }
+
+    namespace Milliseconds {
+      public const uint64 MILLISECOND = 1;
+      public const uint64 SECOND = 1000 * MILLISECOND;
+      public const uint64 MINUTE = 60 * SECOND;
+      public const uint64 HOUR = 60 * MINUTE;
+      public const uint64 DAY = 24 * HOUR;
+      public const uint64 WEEK = 7 * DAY;
+    }
+
+    namespace Seconds {
+      public const uint64 SECOND = 1;
+      public const uint64 MINUTE = 60 * SECOND;
+      public const uint64 HOUR = 60 * MINUTE;
+      public const uint64 DAY = 24 * HOUR;
+      public const uint64 WEEK = 7 * DAY;
+    }
+
+  }
+
+}
diff --git a/src/DbMonitorWidget.vala b/src/DbMonitorWidget.vala
new file mode 100644 (file)
index 0000000..f0ca23c
--- /dev/null
@@ -0,0 +1,218 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+namespace IdWorks {
+
+public class DbMonitorWidget : Gtk.Widget {
+  
+  public static enum Layout {
+    VERTICAL = 0,
+    HORIZONTAL
+  }
+  
+  private static const int BORDER_WIDTH = 16;
+  private static const int BORDER_HEIGHT = 8;
+  //private Pango.Layout layout;
+  private static const double MIN_VALUE = 0.0;
+  private static const double MAX_VALUE = 1.5;
+  
+  private static const double ZERO_VALUE = 1.0;
+  
+  private double peak;
+  private double decay;
+  public void set_peak(double val) {
+    this.peak = (MAX_VALUE > val) ? val : MAX_VALUE;
+    this.redraw_canvas();
+  }
+  public void set_decay(double val) {
+    this.decay = (MAX_VALUE > val) ? val : MAX_VALUE;
+    this.redraw_canvas();
+  }
+  private Layout layout = Layout.VERTICAL;
+  public void set_layout(Layout layout) {
+    this.layout = layout;
+  }
+  
+  construct {
+    // nothing TODO
+  }
+  
+  /*
+  * This method Gtk+ is calling on a widget to ask
+  * the widget how large it wishes to be. It's not guaranteed
+  * that Gtk+ will actually give this size to the widget.
+  */
+  public override void size_request (out Gtk.Requisition requisition) {
+    requisition.width = (Layout.VERTICAL == this.layout) ? 40 : 225;
+    requisition.height = (Layout.VERTICAL == this.layout) ? 225 : 40;
+  }
+  
+  /*
+  * This method gets called by Gtk+ when the actual size is known
+  * and the widget is told how much space could actually be allocated.
+  * It is called every time the widget size changes, for example when the
+  * user resizes the window.
+  */
+  public override void size_allocate (Gdk.Rectangle allocation) {
+    // The base method will save the allocation and move/resize the
+    // widget's GDK window if the widget is already realized.
+    base.size_allocate (allocation);
+    
+    // Move/resize other realized windows if necessary
+  }
+  
+  private void redraw_canvas () {
+    if (null == this.window) {
+      return;
+    }    
+    unowned Gdk.Region region = this.window.get_clip_region ();
+    // redraw the cairo canvas completely by exposing it
+    this.window.invalidate_region (region, true);
+    this.window.process_updates (true);
+  }
+  
+  private void draw() {
+    // Cairo context to draw on
+    Cairo.Context cr = Gdk.cairo_create (this.window);
+    // In this example, draw a rectangle in the foreground color
+    Gdk.cairo_set_source_color (cr, this.style.fg[this.state]);
+    cr.rectangle (BORDER_WIDTH, BORDER_HEIGHT,
+                  this.allocation.width - 2 * BORDER_WIDTH,
+                  this.allocation.height - 2 * BORDER_HEIGHT);
+                  cr.set_line_width (1.0);
+                  //cr.set_line_join (LineJoin.ROUND);
+                  cr.stroke ();
+                  
+    // draw the bar for the peak level
+    if (0.0 < this.peak) {
+      if (Layout.VERTICAL == this.layout) {
+        cr.rectangle (BORDER_WIDTH
+        , (this.allocation.height - 2 * BORDER_HEIGHT) - ( (this.allocation.height - 2 * BORDER_HEIGHT) / (MAX_VALUE - MIN_VALUE) ) * this.peak
+        , this.allocation.width - 2 * BORDER_WIDTH
+        , (((this.allocation.height - 2 * BORDER_HEIGHT) / (MAX_VALUE - MIN_VALUE)) * this.peak) + BORDER_HEIGHT
+        );
+      }
+      else {
+        cr.rectangle (BORDER_WIDTH
+        , BORDER_HEIGHT
+        , (((this.allocation.width - 2 * BORDER_WIDTH) / (MAX_VALUE - MIN_VALUE)) * this.peak) + BORDER_WIDTH
+        , this.allocation.height - 2 * BORDER_HEIGHT
+        );
+      }
+      cr.set_line_width (2.0);
+      cr.fill();
+    }
+    
+    // draw a line for the decay level
+    if (0.0 < this.decay) {
+      if (Layout.VERTICAL == this.layout) {
+        cr.rectangle (BORDER_WIDTH
+        , (this.allocation.height - 2 * BORDER_HEIGHT) - ( (this.allocation.height - 2 * BORDER_HEIGHT) / (MAX_VALUE - MIN_VALUE) ) * this.decay
+        , this.allocation.width - 2 * BORDER_WIDTH
+        , BORDER_HEIGHT
+        );
+      }
+      else {
+        cr.rectangle ((((this.allocation.width - 2 * BORDER_WIDTH) / (MAX_VALUE - MIN_VALUE)) * this.decay) + BORDER_WIDTH
+        , BORDER_HEIGHT
+        , BORDER_WIDTH
+        , this.allocation.height - 2 * BORDER_HEIGHT
+        );
+      }
+      cr.set_line_width (2.0);
+      cr.fill();
+    }
+    
+    // draw 0 db marker
+    if (Layout.VERTICAL == this.layout) {
+      cr.rectangle (BORDER_WIDTH
+      , (this.allocation.height) - ( (this.allocation.height) / (MAX_VALUE - MIN_VALUE) ) * ZERO_VALUE
+      , this.allocation.width - 2 * BORDER_WIDTH
+      , 2
+      );
+    }
+    else {
+      cr.rectangle ((((this.allocation.width) / (MAX_VALUE - MIN_VALUE)) * ZERO_VALUE)
+      , BORDER_HEIGHT
+      , 2
+      , this.allocation.height - 2 * BORDER_HEIGHT
+      );
+    }
+    cr.set_line_width (1.0);
+    cr.fill();
+    
+  }
+  
+  /*
+  * This method is responsible for creating GDK (windowing system)
+  * resources. In this example we will create a new GDK window which we
+  * then draw on.
+  */
+  public override void realize () {
+    // Create a new Gdk.Window which we can draw on.
+    // Also say that we want to receive exposure events by setting
+    // the event_mask
+    var attrs = Gdk.WindowAttr () {
+      window_type = Gdk.WindowType.CHILD,
+      wclass = Gdk.WindowClass.INPUT_OUTPUT,
+      event_mask = get_events () | Gdk.EventMask.EXPOSURE_MASK
+    };
+    this.window = new Gdk.Window (get_parent_window (), attrs, 0);
+    this.window.move_resize (this.allocation.x, this.allocation.y,
+                             this.allocation.width, this.allocation.height);
+                             
+    // Associate the GDK window with ourselves, Gtk+ needs a reference
+    // between the widget and the GDK window
+    this.window.set_user_data (this);
+    
+    // Attach the style to the GDK window. A style contains colors and
+    // GC contexts used for drawing
+    this.style = this.style.attach (this.window);
+    
+    // The default color of the background should be what
+    // the style (theme engine) tells us
+    this.style.set_background (this.window, Gtk.StateType.NORMAL);
+    
+    // Set an internal flag telling that we're realized
+    set_flags (Gtk.WidgetFlags.REALIZED);
+  }
+  
+  /*
+  * This method is called when the widget is asked to draw itself.
+  * Remember that this will be called a lot of times, so it's usually
+  * a good idea to write this code as optimized as it can be, don't
+  * create any resources in here.
+  */
+  public override bool expose_event (Gdk.EventExpose event) {
+    this.draw();
+    return true;
+  }
+  
+  /*
+  * This method is responsible for freeing the GDK resources.
+  */
+  public override void unrealize () {
+    // The base method will de-associate the GDK window we created in
+    // method 'realize' with ourselves.
+    base.unrealize ();
+    
+    // De-associate other windows with 'set_user_data (null)' if necessary
+  }
+  
+}
+
+}
diff --git a/src/DecibelMeter.vala b/src/DecibelMeter.vala
new file mode 100644 (file)
index 0000000..d16b406
--- /dev/null
@@ -0,0 +1,193 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+
+namespace IdWorks {
+
+public class DecibelMeter : Hildon.Program {
+
+  Hildon.StackableWindow window;  
+  Osso.Context osso_context;
+  MeterWidget channel_one;
+  MeterWidget channel_two;
+  AudioCaptureMonitor monitor;
+  
+  Hildon.Button play_button;
+  bool is_playing = false;
+  Gtk.Image play_icon;
+  Gtk.Image stop_icon;
+  Gtk.Image mic_icon;
+  Gtk.Image audio_icon;
+  Gtk.Image bt_icon;
+  Hildon.TouchSelector source_selector;
+  Hildon.PickerButton source_picker;
+  
+  construct {
+    osso_context = new Osso.Context("decibelmeter", "0.1", false, null);
+    window = new Hildon.StackableWindow();
+    window.destroy.connect(Gtk.main_quit);
+    window.set_title("Decibel Meter");
+    
+    play_icon = new Gtk.Image.from_icon_name("camera_playback", Gtk.IconSize.BUTTON);
+    stop_icon = new Gtk.Image.from_icon_name("camera_video_stop", Gtk.IconSize.BUTTON);
+    mic_icon = new Gtk.Image.from_icon_name("camera_volume_unmuted", Gtk.IconSize.BUTTON);
+    audio_icon = new Gtk.Image.from_icon_name("mediaplayer_main_button_music_pressed", Gtk.IconSize.BUTTON);
+    bt_icon = new Gtk.Image.from_icon_name("general_bluetooth", Gtk.IconSize.BUTTON);
+    
+    channel_one = new MeterWidget();
+    channel_one.title = "Channel 1";
+    channel_one.set_layout(DbMonitorWidget.Layout.VERTICAL);
+    channel_two = new MeterWidget();
+    channel_two.title = "Channel 2";
+    
+    Gtk.VBox container = new Gtk.VBox(false, 4);
+    
+    Gtk.HBox meters = new Gtk.HBox(true, 4);
+    meters.pack_start(channel_one, true, true, 4);
+    meters.pack_start(channel_two, true, true, 4);
+    
+    container.add(meters);
+    
+    Gtk.HBox control = new Gtk.HBox(false, 4);
+    
+    
+    play_button = new Hildon.Button(Hildon.SizeType.THUMB_HEIGHT | Hildon.SizeType.AUTO_WIDTH, Hildon.ButtonArrangement.HORIZONTAL);
+    play_button.set_image(play_icon);
+    play_button.clicked.connect(on_play);
+    control.pack_start(play_button, false, true, 4);
+    
+    Gtk.HScale volume_slider = new Gtk.HScale.with_range(0.0, 10.0, 0.05);
+    volume_slider.set_value_pos(Gtk.PositionType.LEFT);
+    volume_slider.set_value(1.0);
+    volume_slider.value_changed.connect( (s) => { monitor.set_volume(s.get_value()); } );
+    control.pack_start(new Gtk.Label("Gain:"), false, true, 4);
+    control.pack_start(volume_slider, true, true, 4);
+    
+    source_selector = new Hildon.TouchSelector.text();
+    source_selector.append_text("Microphone");
+    source_selector.append_text("Monitor");
+    source_selector.append_text("Bluetooth");
+    //source_selector.set_active(0, 0);
+    this.set_source("Microphone");
+    source_picker = new Hildon.PickerButton(Hildon.SizeType.THUMB_HEIGHT | Hildon.SizeType.AUTO_WIDTH, Hildon.ButtonArrangement.VERTICAL);
+    source_picker.set_title("Source:");
+    source_picker.set_selector(source_selector);
+    source_picker.set_alignment(0, 0, 1, 1);
+    source_picker.set_image(mic_icon);
+    source_picker.value_changed.connect( (s) => { set_input_source(s.get_selector().get_current_text()); } );
+    control.pack_start(source_picker, true, true, 4);
+    
+    
+    container.add(control);
+    
+    window.add(container);
+    construct_menu();
+    construct_monitor();
+  }
+  
+  private void construct_menu() {
+  
+  
+  }
+  
+  private void set_source(string name) {
+    switch (name) {
+      case "Microphone":
+        source_selector.set_active(0, 0);
+        source_picker.set_image(mic_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      case "Monitor":
+        source_selector.set_active(0, 1);
+        source_picker.set_image(audio_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      case "Bluetooth":
+        source_selector.set_active(0, 2);
+        source_picker.set_image(bt_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      default:
+        source_selector.set_active(0, 0);
+        source_picker.set_image(mic_icon);
+        source_picker.set_text("Source:", name);
+        break;
+    }
+    if (null != monitor) monitor.set_source(name);
+  }
+  private void set_input_source(string name) {
+    switch (name) {
+      case "Microphone":
+        source_picker.set_image(mic_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      case "Monitor":
+        source_picker.set_image(audio_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      case "Bluetooth":
+        source_picker.set_image(bt_icon);
+        source_picker.set_text("Source:", name);
+        break;
+      default:
+        source_picker.set_image(mic_icon);
+        source_picker.set_text("Source:", name);
+        break;
+    }
+    if (null != monitor) monitor.set_source(name);
+  }
+  
+  private void on_play() {
+    if (!is_playing) {
+      play_button.set_image(stop_icon);
+      monitor.play();
+    }
+    else {
+      monitor.stop();      
+      play_button.set_image(play_icon);
+    }
+    is_playing = !is_playing;
+  }
+  
+  private void construct_monitor() {
+    monitor = new AudioCaptureMonitor.with_settings("Monitor", "Microphone", 1.0);
+    monitor.update_level.connect(level_updated);
+  }
+  
+  private void level_updated(int channels, int channel, double rms_dB, double peak_dB, double decay_dB, double rms) {
+    switch(channel) {
+      case 0:
+        channel_one.set_peak(peak_dB);
+        channel_one.set_decay(decay_dB);
+        break;
+      case 1:
+        channel_two.set_peak(peak_dB);
+        channel_two.set_decay(decay_dB);
+        break;
+      default:
+        break;
+    }
+  }
+
+  public void run() {
+    window.show_all();
+    Gtk.main();
+  }
+
+}
+
+}
diff --git a/src/InputBin.vala b/src/InputBin.vala
new file mode 100644 (file)
index 0000000..a5974ad
--- /dev/null
@@ -0,0 +1,132 @@
+/*  Decibel Meter for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+namespace IdWorks {
+
+public class InputBin : Gst.Bin {
+  
+  Gst.Element src;
+  Gst.Element converter;
+  Gst.Element volume;
+  Gst.GhostPad src_pad;
+  Gst.GhostPad sink_pad;
+  
+  public InputBin(string name) {
+    this.set_name(name);
+    construct_pipeline("Microphone", 1.0);
+  }  
+  public InputBin.with_settings(string name, string source, double volume) {
+    this.name = name;
+    construct_pipeline(source, volume);
+  }
+  
+  private string get_source_device(string name) {
+    switch (name) {
+      case "Microphone":
+        return "source.hw0";
+      case "Monitor":
+        return "sink.hw0.monitor";
+      case "Bluetooth":
+        return "source.hw1";
+      default:
+        return "source.hw0";
+    }
+  }
+  private string get_source_name(string device) {
+    switch (device) {
+      case "source.hw0":
+        return "Microphone";
+      case "sink.hw0.monitor":
+        return "Monitor";
+      case "source.hw1":
+        return "Bluetooth";
+      default:
+        return "Microphone";
+    }
+  }
+  
+  private void construct_pipeline(string source, double volume) {
+    
+    this.src = Gst.ElementFactory.make("pulsesrc", "capture");
+    this.src.set_property("device", get_source_device(source)); //"source.hw0"); /// TODO
+    this.add(this.src);
+    
+    this.converter = Gst.ElementFactory.make("audioconvert", "converter");
+    this.add(this.converter);
+    
+    this.volume = Gst.ElementFactory.make("volume", "volume");
+    this.volume.set_property("volume", volume);
+    this.add(this.volume);
+    
+    this.src.link(converter);
+    this.converter.link(this.volume);
+    
+    this.sink_pad = new Gst.GhostPad("sink", this.src.get_static_pad("sink"));
+    this.add_pad(sink_pad);
+    this.src_pad = new Gst.GhostPad("src", this.volume.get_static_pad("src"));
+    this.add_pad(src_pad);
+  }
+  
+  public new Gst.PadTemplate get_pad_template(string name) {
+    switch (name) {
+      case "src":
+        return this.volume.get_pad_template("src");
+      case "sink":
+        return this.src.get_pad_template("sink");
+      default:
+        return base.get_pad_template(name);
+    }
+  }
+  
+  public void set_source(string name) {
+    Gst.State state;
+    Gst.ClockTime time = Gst.util_get_timestamp ();
+    this.get_state (out state, null, time);
+    this.set_state(Gst.State.NULL);
+    this.src.set_property("device", get_source_device(name));
+    this.set_state(state);
+  }
+  public string get_source() {
+    GLib.Value val = 0;
+    this.src.get_property("device", ref val);
+    return get_source_name(val.get_string());
+  }
+  
+  public void set_volume(double val)
+  requires (val >= 0.0 && val <= 10.0) {
+    this.volume.set_property("volume", val);
+  }
+  public double get_volume()
+  ensures (result >= 0.0 && result <= 10.0) {
+    GLib.Value val = 0;
+    this.volume.get_property("volume", ref val);
+    return val.get_double();
+  }
+  
+  public void set_mute(bool val) {
+    this.volume.set_property("mute", val);
+  }
+  public bool get_mute() {
+    GLib.Value val = 0;
+    this.volume.get_property("mute", ref val);
+    return val.get_boolean();
+  }
+  
+
+}
+
+}
diff --git a/src/MeterWidget.vala b/src/MeterWidget.vala
new file mode 100644 (file)
index 0000000..0a17e34
--- /dev/null
@@ -0,0 +1,113 @@
+/*  Demo Recorder for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+
+namespace IdWorks {
+
+public class MeterWidget : Gtk.VBox {
+  
+  private Gtk.Label title_label;
+  private Gtk.Label peak_label;
+  private Gtk.Label decay_label;
+  private DbMonitorWidget meter;  
+  
+  private static const int BORDER_WIDTH = 4;
+  private static const int BORDER_HEIGHT = 4;
+  //private Pango.Layout layout;
+  private static const double MIN_VALUE = 0.0;
+  private static const double MAX_VALUE = 1.5;
+  
+  private static const double ZERO_VALUE = 1.0;
+  
+  private double peak;
+  private double decay;
+  public void set_peak(double val) {
+    this.peak = (MAX_VALUE > val) ? val : MAX_VALUE;
+    this.peak_label.set_text("%02.2f".printf(val));
+    meter.set_peak(Math.pow (10, val / 20));
+  }
+  public void set_decay(double val) {
+    this.decay = (MAX_VALUE > val) ? val : MAX_VALUE;
+    this.decay_label.set_text("%02.2f".printf(val));
+    meter.set_decay(Math.pow (10, val / 20));
+  }
+  private DbMonitorWidget.Layout layout = DbMonitorWidget.Layout.VERTICAL;
+  public void set_layout(DbMonitorWidget.Layout layout) {
+    this.layout = layout;
+    meter.set_layout(layout);
+  }
+  
+  public string title {
+    get {
+      return title_label.get_text();
+    }
+    set {
+      title_label.set_text(value);
+    }
+  }
+  
+  construct {
+    title_label = new Gtk.Label("");
+    title_label.modify_font(Pango.FontDescription.from_string("Sans 20"));
+    title_label.set_padding(4, 2);
+    //title_label.set_alignment(0,0);
+    
+    this.pack_start(title_label, false, true, 4);    
+    
+    meter = new DbMonitorWidget();
+    meter.set_layout(layout);
+    meter.set_peak(0.0);
+    meter.set_decay(0.0);
+    this.pack_start(meter, true, true, 0);
+  
+    Gtk.HBox display = new Gtk.HBox(true, 8);
+    
+    peak_label = new Gtk.Label("0.0");
+    peak_label.modify_font(Pango.FontDescription.from_string("Sans 40"));
+    peak_label.set_padding(4, 2);
+    //peak_label.set_alignment(0,0);
+    decay_label = new Gtk.Label("0.0");
+    decay_label.modify_font(Pango.FontDescription.from_string("Sans 40"));
+    decay_label.set_padding(4, 2);
+    //decay_label.set_alignment(0,0);
+    
+    display.add(peak_label);
+    display.add(decay_label);
+    
+    this.pack_start(display, false, true, 0);    
+    
+    Gtk.HBox display2 = new Gtk.HBox(true, 0);
+    
+    Gtk.Label peak_label2 = new Gtk.Label("dB Peak");
+    peak_label2.modify_font(Pango.FontDescription.from_string("Sans 16"));
+    peak_label2.set_padding(0, 0);
+    //peak_label2.set_alignment(0,0);
+    Gtk.Label decay_label2 = new Gtk.Label("dB Decay");
+    decay_label2.modify_font(Pango.FontDescription.from_string("Sans 16"));
+    decay_label2.set_padding(0, 0);
+    //decay_label2.set_alignment(0,0);
+    
+    display2.add(peak_label2);
+    display2.add(decay_label2);
+    
+    this.pack_start(display2, false, true, 0);    
+    
+  }  
+  
+}
+
+}
diff --git a/src/Program.vala b/src/Program.vala
new file mode 100644 (file)
index 0000000..e303bc9
--- /dev/null
@@ -0,0 +1,31 @@
+/*  Demo Recorder for MAEMO 5
+*   Copyright (C) 2010 Dru Moore <usr@dru-id.co.uk>
+*   This program is free software; you can redistribute it and/or modify
+*   it under the terms of the GNU General Public License version 2,
+*   or (at your option) any later version, as published by the Free
+*   Software Foundation
+*
+*   This program is distributed in the hope that it will be useful,
+*   but WITHOUT ANY WARRANTY; without even the implied warranty of
+*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*   GNU General Public License for more details
+*
+*   You should have received a copy of the GNU General Public
+*   License along with this program; if not, write to the
+*   Free Software Foundation, Inc.,
+*   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+*/
+
+namespace IdWorks {
+
+//extern void exit(int exit_code);
+
+  public static int main(string[] args) {
+    Gtk.init(ref args);
+    Gst.init(ref args);
+    IdWorks.DecibelMeter meter = new IdWorks.DecibelMeter();
+    meter.run();
+    return 0;
+  }
+
+}
diff --git a/www/index.html b/www/index.html
new file mode 100644 (file)
index 0000000..d7bb090
--- /dev/null
@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>Decibel Meter for Maemo 5 N900</title>
+</head>
+<body>
+<h1>Decibel Meter</h1>
+<h2>A simple audio meter for Maemo5 and the N900</h2>
+<p>Visita the project page at <a href="https://garage.maemo.org/projects/decibelmeter/" title="Decibel Meter on maemo Garage">https://garage.maemo.org/projects/decibelmeter/</a></p>
+</body>
+<html>