From fd746e33d8a1edb0b6d93e8a2827042092a442c9 Mon Sep 17 00:00:00 2001 From: Dru Moore Date: Thu, 18 Nov 2010 09:19:05 +0000 Subject: [PATCH] Initial project commit 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 | 19 +++ src/AcmPopUp.vala | 291 ++++++++++++++++++++++++++++++++++++++++++ src/AudioCaptureMonitor.vala | 161 +++++++++++++++++++++++ src/Constants.vala | 62 +++++++++ src/DbMonitorWidget.vala | 218 +++++++++++++++++++++++++++++++ src/DecibelMeter.vala | 193 ++++++++++++++++++++++++++++ src/InputBin.vala | 132 +++++++++++++++++++ src/MeterWidget.vala | 113 ++++++++++++++++ src/Program.vala | 31 +++++ www/index.html | 10 ++ 10 files changed, 1230 insertions(+) create mode 100644 build.sh create mode 100644 src/AcmPopUp.vala create mode 100644 src/AudioCaptureMonitor.vala create mode 100644 src/Constants.vala create mode 100644 src/DbMonitorWidget.vala create mode 100644 src/DecibelMeter.vala create mode 100644 src/InputBin.vala create mode 100644 src/MeterWidget.vala create mode 100644 src/Program.vala create mode 100644 www/index.html diff --git a/build.sh b/build.sh new file mode 100644 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 index 0000000..939e4c9 --- /dev/null +++ b/src/AcmPopUp.vala @@ -0,0 +1,291 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..e2b76fe --- /dev/null +++ b/src/AudioCaptureMonitor.vala @@ -0,0 +1,161 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..b947208 --- /dev/null +++ b/src/Constants.vala @@ -0,0 +1,62 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..f0ca23c --- /dev/null +++ b/src/DbMonitorWidget.vala @@ -0,0 +1,218 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..d16b406 --- /dev/null +++ b/src/DecibelMeter.vala @@ -0,0 +1,193 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..a5974ad --- /dev/null +++ b/src/InputBin.vala @@ -0,0 +1,132 @@ +/* Decibel Meter for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..0a17e34 --- /dev/null +++ b/src/MeterWidget.vala @@ -0,0 +1,113 @@ +/* Demo Recorder for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..e303bc9 --- /dev/null +++ b/src/Program.vala @@ -0,0 +1,31 @@ +/* Demo Recorder for MAEMO 5 +* Copyright (C) 2010 Dru Moore +* 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 index 0000000..d7bb090 --- /dev/null +++ b/www/index.html @@ -0,0 +1,10 @@ + + +Decibel Meter for Maemo 5 N900 + + +

Decibel Meter

+

A simple audio meter for Maemo5 and the N900

+

Visita the project page at https://garage.maemo.org/projects/decibelmeter/

+ + -- 1.7.9.5