From 94c090d46fbad8e75383253dbaa31456722ad171 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 13 May 2023 14:44:44 +0900 Subject: [PATCH 01/10] Add recording indication --- meson.build | 1 + src/Services/Recorder.vala | 40 ++++++++++++++++++++++++++++++++++---- src/Views/RecordView.vala | 26 +++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index c0a2a47b..9c6a0eac 100644 --- a/meson.build +++ b/meson.build @@ -65,6 +65,7 @@ executable( dependency('libpulse-mainloop-glib'), dependency('pango') ], + link_args: ['-X', '-lm'], install: true ) diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index cabea039..7f87abb6 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -11,6 +11,21 @@ public class Recorder : Object { public bool is_recording { get; private set; } + public double current_peak { + get { + return _current_peak; + } + set { + double decibel = value; + if (decibel > 0) { + decibel = 0; + } + + _current_peak = Math.pow (10, decibel / 20); + } + } + private double _current_peak = 0; + private PulseAudioManager pam; private string suffix; private string tmp_full_path; @@ -45,11 +60,14 @@ public class Recorder : Object { public void start_recording () throws Gst.ParseError { pipeline = new Gst.Pipeline ("pipeline"); var sink = Gst.ElementFactory.make ("filesink", "sink"); + var level = Gst.ElementFactory.make ("level", "level"); if (pipeline == null) { throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create the GStreamer element \"pipeline\""); } else if (sink == null) { throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create the GStreamer element \"filesink\""); + } else if (level == null) { + throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create the GStreamer element \"level\""); } Source source = (Source) Application.settings.get_enum ("source"); @@ -127,23 +145,25 @@ public class Recorder : Object { "audio/x-raw", "channels", Type.INT, (Channels) Application.settings.get_enum ("channels") )); - pipeline.add_many (caps_filter, encoder, sink); + pipeline.add_many (caps_filter, level, encoder, sink); + + level.set ("post-messages", true); switch (source) { case Source.MIC: pipeline.add_many (mic_sound); - mic_sound.link_many (caps_filter, encoder); + mic_sound.link_many (caps_filter, level, encoder); break; case Source.SYSTEM: pipeline.add_many (sys_sound); - sys_sound.link_many (caps_filter, encoder); + sys_sound.link_many (caps_filter, level, encoder); break; case Source.BOTH: var mixer = Gst.ElementFactory.make ("audiomixer", "mixer"); pipeline.add_many (mic_sound, sys_sound, mixer); mic_sound.get_static_pad ("src").link (mixer.request_pad_simple ("sink_%u")); sys_sound.get_static_pad ("src").link (mixer.request_pad_simple ("sink_%u")); - mixer.link_many (caps_filter, encoder); + mixer.link_many (caps_filter, level, encoder); break; default: assert_not_reached (); @@ -178,6 +198,18 @@ public class Recorder : Object { pipeline.dispose (); save_file (tmp_full_path, suffix); + break; + case Gst.MessageType.ELEMENT: + unowned Gst.Structure? structure = msg.get_structure (); + if (structure == null || !structure.has_name ("level")) { + break; + } + + unowned var peak_arr = (GLib.ValueArray) structure.get_value ("peak").get_boxed (); + if (peak_arr != null) { + current_peak = peak_arr.get_nth (0).get_double (); + } + break; default: break; diff --git a/src/Views/RecordView.vala b/src/Views/RecordView.vala index 8e389db3..4615b853 100644 --- a/src/Views/RecordView.vala +++ b/src/Views/RecordView.vala @@ -4,6 +4,9 @@ */ public class RecordView : Gtk.Box { + private const double PEEK_BAR_MIN = 0; + private const double PEEK_BAR_MAX = 10; + public MainWindow window { get; construct; } private Recorder recorder; @@ -52,6 +55,21 @@ public class RecordView : Gtk.Box { label_grid.attach (time_label, 0, 1, 1, 1); label_grid.attach (remaining_time_label, 0, 2, 1, 1); + var peak_bar = new Gtk.LevelBar.for_interval (PEEK_BAR_MIN, PEEK_BAR_MAX) { + mode = Gtk.LevelBarMode.DISCRETE, + orientation = Gtk.Orientation.VERTICAL, + inverted = true + }; + + var center_grid = new Gtk.Grid () { + column_spacing = 6, + row_spacing = 6, + halign = Gtk.Align.CENTER, + hexpand = true, + vexpand = true + }; + center_grid.attach (peak_bar, 0, 0, 1, 1); + var cancel_button = new Gtk.Button () { icon_name = "user-trash-symbolic", tooltip_text = _("Cancel recording"), @@ -87,6 +105,7 @@ public class RecordView : Gtk.Box { buttons_grid.attach (pause_button, 2, 0, 1, 1); append (label_grid); + append (center_grid); append (buttons_grid); cancel_button.clicked.connect (() => { @@ -129,6 +148,13 @@ public class RecordView : Gtk.Box { pause_button.tooltip_text = _("Pause recording"); } }); + + recorder.bind_property ("current-peak", peak_bar, "value", GLib.BindingFlags.SYNC_CREATE, + (binding, src, ref target) => { + double set_val = ((double) src) * PEEK_BAR_MAX; + target.set_double (set_val); + return true; + }); } public async void trigger_stop_recording () { From 7b92831c859206b757111feebe3f008c31ad932b Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 13 May 2023 21:48:15 +0900 Subject: [PATCH 02/10] Add credits --- src/Services/Recorder.vala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index 066f7f43..fce04c8f 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -2,7 +2,9 @@ * SPDX-License-Identifier: GPL-3.0-or-later * SPDX-FileCopyrightText: 2018-2023 Ryo Nakano * - * GStreamer related codes are inspired from artemanufrij/screencast, src/MainWindow.vala + * GStreamer related codes are inspired from: + * * artemanufrij/screencast, src/MainWindow.vala + * * GNOME/gnome-sound-recorder (gnome-3-38), src/recorder.js */ public class Recorder : Object { @@ -11,6 +13,7 @@ public class Recorder : Object { public bool is_recording { get; private set; } + // current sound level, taking value from 0 to 1 public double current_peak { get { return _current_peak; @@ -152,8 +155,6 @@ public class Recorder : Object { )); pipeline.add_many (caps_filter, level, encoder, sink); - level.set ("post-messages", true); - switch (source) { case SourceID.MIC: pipeline.add_many (mic_sound); From a6055f0bcfa538867b453db0605e4e315a951488 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Fri, 11 Aug 2023 15:57:25 +0900 Subject: [PATCH 03/10] Redesign levelbars --- data/Application.css | 16 +++++++++ meson.build | 1 + src/Services/Recorder.vala | 7 +++- src/Views/RecordView.vala | 27 ++-------------- src/Widgets/LevelBar.vala | 66 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 26 deletions(-) create mode 100644 src/Widgets/LevelBar.vala diff --git a/data/Application.css b/data/Application.css index 3256861e..15574309 100644 --- a/data/Application.css +++ b/data/Application.css @@ -18,3 +18,19 @@ background: none; border: none; } + +levelbar block.low { + background-color: #9bdb4d; +} + +levelbar block.middle { + background-color: #64baff; +} + +levelbar block.high { + background-color: #f9c440; +} + +levelbar block.full { + background-color: #c6262e; +} diff --git a/meson.build b/meson.build index 9c6a0eac..0aa26226 100644 --- a/meson.build +++ b/meson.build @@ -47,6 +47,7 @@ sources = files( 'src' / 'Views' / 'CountDownView.vala', 'src' / 'Views' / 'WelcomeView.vala', 'src' / 'Views' / 'RecordView.vala', + 'src' / 'Widgets' / 'LevelBar.vala', 'src' / 'Application.vala', 'src' / 'MainWindow.vala', 'src' / 'StyleSwitcher.vala' diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index fce04c8f..c723aa13 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -24,7 +24,12 @@ public class Recorder : Object { decibel = 0; } - _current_peak = Math.pow (10, decibel / 20); + double p = Math.pow (10, decibel / 20); + if (p == _current_peak) { + return; + } + + _current_peak = p; } } private double _current_peak = 0; diff --git a/src/Views/RecordView.vala b/src/Views/RecordView.vala index b6838837..cf43d38f 100644 --- a/src/Views/RecordView.vala +++ b/src/Views/RecordView.vala @@ -4,9 +4,6 @@ */ public class RecordView : Gtk.Box { - private const double PEEK_BAR_MIN = 0; - private const double PEEK_BAR_MAX = 10; - public MainWindow window { get; construct; } private Recorder recorder; @@ -55,20 +52,7 @@ public class RecordView : Gtk.Box { label_grid.attach (time_label, 0, 1, 1, 1); label_grid.attach (remaining_time_label, 0, 2, 1, 1); - var peak_bar = new Gtk.LevelBar.for_interval (PEEK_BAR_MIN, PEEK_BAR_MAX) { - mode = Gtk.LevelBarMode.DISCRETE, - orientation = Gtk.Orientation.VERTICAL, - inverted = true - }; - - var center_grid = new Gtk.Grid () { - column_spacing = 6, - row_spacing = 6, - halign = Gtk.Align.CENTER, - hexpand = true, - vexpand = true - }; - center_grid.attach (peak_bar, 0, 0, 1, 1); + var levelbar = new LevelBar (); var cancel_button = new Gtk.Button () { icon_name = "user-trash-symbolic", @@ -105,7 +89,7 @@ public class RecordView : Gtk.Box { buttons_grid.attach (pause_button, 2, 0, 1, 1); append (label_grid); - append (center_grid); + append (levelbar); append (buttons_grid); cancel_button.clicked.connect (() => { @@ -145,13 +129,6 @@ public class RecordView : Gtk.Box { pause_button_set_pause (); } }); - - recorder.bind_property ("current-peak", peak_bar, "value", GLib.BindingFlags.SYNC_CREATE, - (binding, src, ref target) => { - double set_val = ((double) src) * PEEK_BAR_MAX; - target.set_double (set_val); - return true; - }); } public async void trigger_stop_recording () { diff --git a/src/Widgets/LevelBar.vala b/src/Widgets/LevelBar.vala new file mode 100644 index 00000000..42fb9723 --- /dev/null +++ b/src/Widgets/LevelBar.vala @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023 Ryo Nakano + */ + +public class LevelBar : Gtk.Box { + private const int LEVEL_MIN = 0; + private const int LEVEL_LOW_MAX = 2; + private const int LEVEL_MIDDLE_MAX = 5; + private const int LEVEL_HIGH_MAX = 8; + private const int LEVEL_MAX = 10; + private const int BAR_ON = 1; + private const int BAR_OFF = 0; + + private Gtk.LevelBar child_bars[LEVEL_MAX]; + + public LevelBar () { + } + + construct { + orientation = Gtk.Orientation.VERTICAL; + spacing = 6; + margin_top = 6; + margin_bottom = 6; + halign = Gtk.Align.CENTER; + hexpand = true; + vexpand = true; + + var recorder = Recorder.get_default (); + + for (int level = LEVEL_MIN; level < LEVEL_MAX; level++) { + var bar = new Gtk.LevelBar.for_interval (BAR_OFF, BAR_ON) { + mode = Gtk.LevelBarMode.DISCRETE, + inverted = true + }; + + string css_class; + if (level <= LEVEL_LOW_MAX) { + css_class = "low"; + } else if (level <= LEVEL_MIDDLE_MAX) { + css_class = "middle"; + } else if (level <= LEVEL_HIGH_MAX) { + css_class = "high"; + } else { + css_class = "full"; + } + bar.add_offset_value (css_class, BAR_ON); + bar.value = BAR_OFF; + + child_bars[level] = bar; + prepend (bar); + } + + recorder.notify["current-peak"].connect (() => { + int current = (int) (recorder.current_peak * (double) LEVEL_MAX); + + for (int level = LEVEL_MIN; level < LEVEL_MAX; level++) { + if (level < current) { + child_bars[level].value = BAR_ON; + } else { + child_bars[level].value = BAR_OFF; + } + } + }); + } +} From 9d6503f117a07f2119de7c92656b4ff1849ee9e6 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Fri, 11 Aug 2023 23:13:37 +0900 Subject: [PATCH 04/10] Fix deprecated warnings of GLib.ValueArray --- com.github.ryonakano.reco.yml | 29 +++++- ...ay-instead-of-deprecated-GValueArray.patch | 89 +++++++++++++++++++ src/Services/Recorder.vala | 4 +- 3 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch diff --git a/com.github.ryonakano.reco.yml b/com.github.ryonakano.reco.yml index 16e4dbcd..dc987053 100644 --- a/com.github.ryonakano.reco.yml +++ b/com.github.ryonakano.reco.yml @@ -12,15 +12,42 @@ finish-args: - '--env=GST_PLUGIN_PATH_1_0=/app/lib/gstreamer-1.0' - '--metadata=X-DConf=migrate-path=/com/github/ryonakano/reco/' modules: + - name: gstreamer + buildsystem: meson + config_opts: + # Same options with https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/master/elements/components/gstreamer-plugins-good.bst + - '-Dpackage-origin="freedesktop-sdk"' + - '-Dv4l2-libv4l2=enabled' + - '-Dv4l2-gudev=disabled' + - '-Daalib=disabled' + - '-Djack=disabled' + - '-Dlibcaca=disabled' + - '-Ddv=disabled' + - '-Ddv1394=disabled' + - '-Dqt5=disabled' + - '-Dshout2=disabled' + - '-Dtaglib=disabled' + - '-Dtwolame=disabled' + - '-Dexamples=disabled' + - '-Drpicamsrc=disabled' + sources: + - type: archive + # we need to use the same version with gstreamer installed in SDK + url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.20.5.tar.xz + sha256: e83ab4d12ca24959489bbb0ec4fac9b90e32f741d49cda357cb554b2cb8b97f9 + - type: patch + path: patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch + - name: gst-libav buildsystem: meson config_opts: - '-Ddoc=disabled' sources: - type: archive - # we need to use the same version with gstreamer + # we need to use the same version with gstreamer installed in SDK url: https://gstreamer.freedesktop.org/src/gst-libav/gst-libav-1.20.5.tar.xz sha256: b152e3cc49d014899f53c39d8a6224a44e1399b4cf76aa5f9a903fdf9793c3cc + - name: reco buildsystem: meson sources: diff --git a/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch b/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch new file mode 100644 index 00000000..043d4848 --- /dev/null +++ b/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch @@ -0,0 +1,89 @@ +diff -urp a/gst/level/gstlevel.c b/gst/level/gstlevel.c +--- a/gst/level/gstlevel.c 2023-08-11 22:29:55.470060598 +0900 ++++ b/gst/level/gstlevel.c 2023-08-11 22:31:15.321532034 +0900 +@@ -34,13 +34,13 @@ + * * #GstClockTime `duration`: the duration of the buffer. + * * #GstClockTime `endtime`: the end time of the buffer that triggered the message as + * stream time (this is deprecated, as it can be calculated from stream-time + duration) +- * * #GValueArray of #gdouble `peak`: the peak power level in dB for each channel +- * * #GValueArray of #gdouble `decay`: the decaying peak power level in dB for each channel ++ * * #GArray of #gdouble `peak`: the peak power level in dB for each channel ++ * * #GArray of #gdouble `decay`: the decaying peak power level in dB for each channel + * The decaying peak level follows the peak level, but starts dropping if no + * new peak is reached after the time given by the #GstLevel:peak-ttl. + * When the decaying peak level drops, it does so at the decay rate as + * specified by the #GstLevel:peak-falloff. +- * * #GValueArray of #gdouble `rms`: the Root Mean Square (or average power) level in dB ++ * * #GArray of #gdouble `rms`: the Root Mean Square (or average power) level in dB + * for each channel + * + * ## Example application +@@ -53,10 +53,6 @@ + #include "config.h" + #endif + +-/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray +- * with newer GLib versions (>= 2.31.0) */ +-#define GLIB_DISABLE_DEPRECATION_WARNINGS +- + #include + #include + #include +@@ -542,16 +538,16 @@ gst_level_message_new (GstLevel * level, + "running-time", G_TYPE_UINT64, running_time, + "duration", G_TYPE_UINT64, duration, NULL); + +- g_value_init (&v, G_TYPE_VALUE_ARRAY); +- g_value_take_boxed (&v, g_value_array_new (0)); ++ g_value_init (&v, G_TYPE_ARRAY); ++ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); + gst_structure_take_value (s, "rms", &v); + +- g_value_init (&v, G_TYPE_VALUE_ARRAY); +- g_value_take_boxed (&v, g_value_array_new (0)); ++ g_value_init (&v, G_TYPE_ARRAY); ++ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); + gst_structure_take_value (s, "peak", &v); + +- g_value_init (&v, G_TYPE_VALUE_ARRAY); +- g_value_take_boxed (&v, g_value_array_new (0)); ++ g_value_init (&v, G_TYPE_ARRAY); ++ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); + gst_structure_take_value (s, "decay", &v); + + return gst_message_new_element (GST_OBJECT (level), s); +@@ -563,7 +559,7 @@ gst_level_message_append_channel (GstMes + { + const GValue *array_val; + GstStructure *s; +- GValueArray *arr; ++ GArray *arr; + GValue v = { 0, }; + + g_value_init (&v, G_TYPE_DOUBLE); +@@ -571,19 +567,19 @@ gst_level_message_append_channel (GstMes + s = (GstStructure *) gst_message_get_structure (m); + + array_val = gst_structure_get_value (s, "rms"); +- arr = (GValueArray *) g_value_get_boxed (array_val); ++ arr = (GArray *) g_value_get_boxed (array_val); + g_value_set_double (&v, rms); +- g_value_array_append (arr, &v); /* copies by value */ ++ g_array_append_val (arr, v); /* copies by value */ + + array_val = gst_structure_get_value (s, "peak"); +- arr = (GValueArray *) g_value_get_boxed (array_val); ++ arr = (GArray *) g_value_get_boxed (array_val); + g_value_set_double (&v, peak); +- g_value_array_append (arr, &v); /* copies by value */ ++ g_array_append_val (arr, v); /* copies by value */ + + array_val = gst_structure_get_value (s, "decay"); +- arr = (GValueArray *) g_value_get_boxed (array_val); ++ arr = (GArray *) g_value_get_boxed (array_val); + g_value_set_double (&v, decay); +- g_value_array_append (arr, &v); /* copies by value */ ++ g_array_append_val (arr, v); /* copies by value */ + + g_value_unset (&v); + } diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index c723aa13..955a5e29 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -216,9 +216,9 @@ public class Recorder : Object { break; } - unowned var peak_arr = (GLib.ValueArray) structure.get_value ("peak").get_boxed (); + unowned var peak_arr = (GLib.Array) structure.get_value ("peak").get_boxed (); if (peak_arr != null) { - current_peak = peak_arr.get_nth (0).get_double (); + current_peak = peak_arr.index (0).get_double (); } break; From e5e0513ac6a13256654150c4f78a762b4298c07f Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 07:59:15 +0900 Subject: [PATCH 05/10] Revert "Fix deprecated warnings of GLib.ValueArray" This reverts commit 9d6503f117a07f2119de7c92656b4ff1849ee9e6. --- com.github.ryonakano.reco.yml | 29 +----- ...ay-instead-of-deprecated-GValueArray.patch | 89 ------------------- src/Services/Recorder.vala | 4 +- 3 files changed, 3 insertions(+), 119 deletions(-) delete mode 100644 patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch diff --git a/com.github.ryonakano.reco.yml b/com.github.ryonakano.reco.yml index dc987053..16e4dbcd 100644 --- a/com.github.ryonakano.reco.yml +++ b/com.github.ryonakano.reco.yml @@ -12,42 +12,15 @@ finish-args: - '--env=GST_PLUGIN_PATH_1_0=/app/lib/gstreamer-1.0' - '--metadata=X-DConf=migrate-path=/com/github/ryonakano/reco/' modules: - - name: gstreamer - buildsystem: meson - config_opts: - # Same options with https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/master/elements/components/gstreamer-plugins-good.bst - - '-Dpackage-origin="freedesktop-sdk"' - - '-Dv4l2-libv4l2=enabled' - - '-Dv4l2-gudev=disabled' - - '-Daalib=disabled' - - '-Djack=disabled' - - '-Dlibcaca=disabled' - - '-Ddv=disabled' - - '-Ddv1394=disabled' - - '-Dqt5=disabled' - - '-Dshout2=disabled' - - '-Dtaglib=disabled' - - '-Dtwolame=disabled' - - '-Dexamples=disabled' - - '-Drpicamsrc=disabled' - sources: - - type: archive - # we need to use the same version with gstreamer installed in SDK - url: https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.20.5.tar.xz - sha256: e83ab4d12ca24959489bbb0ec4fac9b90e32f741d49cda357cb554b2cb8b97f9 - - type: patch - path: patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch - - name: gst-libav buildsystem: meson config_opts: - '-Ddoc=disabled' sources: - type: archive - # we need to use the same version with gstreamer installed in SDK + # we need to use the same version with gstreamer url: https://gstreamer.freedesktop.org/src/gst-libav/gst-libav-1.20.5.tar.xz sha256: b152e3cc49d014899f53c39d8a6224a44e1399b4cf76aa5f9a903fdf9793c3cc - - name: reco buildsystem: meson sources: diff --git a/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch b/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch deleted file mode 100644 index 043d4848..00000000 --- a/patches/0001-level-use-GArray-instead-of-deprecated-GValueArray.patch +++ /dev/null @@ -1,89 +0,0 @@ -diff -urp a/gst/level/gstlevel.c b/gst/level/gstlevel.c ---- a/gst/level/gstlevel.c 2023-08-11 22:29:55.470060598 +0900 -+++ b/gst/level/gstlevel.c 2023-08-11 22:31:15.321532034 +0900 -@@ -34,13 +34,13 @@ - * * #GstClockTime `duration`: the duration of the buffer. - * * #GstClockTime `endtime`: the end time of the buffer that triggered the message as - * stream time (this is deprecated, as it can be calculated from stream-time + duration) -- * * #GValueArray of #gdouble `peak`: the peak power level in dB for each channel -- * * #GValueArray of #gdouble `decay`: the decaying peak power level in dB for each channel -+ * * #GArray of #gdouble `peak`: the peak power level in dB for each channel -+ * * #GArray of #gdouble `decay`: the decaying peak power level in dB for each channel - * The decaying peak level follows the peak level, but starts dropping if no - * new peak is reached after the time given by the #GstLevel:peak-ttl. - * When the decaying peak level drops, it does so at the decay rate as - * specified by the #GstLevel:peak-falloff. -- * * #GValueArray of #gdouble `rms`: the Root Mean Square (or average power) level in dB -+ * * #GArray of #gdouble `rms`: the Root Mean Square (or average power) level in dB - * for each channel - * - * ## Example application -@@ -53,10 +53,6 @@ - #include "config.h" - #endif - --/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray -- * with newer GLib versions (>= 2.31.0) */ --#define GLIB_DISABLE_DEPRECATION_WARNINGS -- - #include - #include - #include -@@ -542,16 +538,16 @@ gst_level_message_new (GstLevel * level, - "running-time", G_TYPE_UINT64, running_time, - "duration", G_TYPE_UINT64, duration, NULL); - -- g_value_init (&v, G_TYPE_VALUE_ARRAY); -- g_value_take_boxed (&v, g_value_array_new (0)); -+ g_value_init (&v, G_TYPE_ARRAY); -+ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); - gst_structure_take_value (s, "rms", &v); - -- g_value_init (&v, G_TYPE_VALUE_ARRAY); -- g_value_take_boxed (&v, g_value_array_new (0)); -+ g_value_init (&v, G_TYPE_ARRAY); -+ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); - gst_structure_take_value (s, "peak", &v); - -- g_value_init (&v, G_TYPE_VALUE_ARRAY); -- g_value_take_boxed (&v, g_value_array_new (0)); -+ g_value_init (&v, G_TYPE_ARRAY); -+ g_value_take_boxed (&v, g_array_new (TRUE, TRUE, sizeof(GArray))); - gst_structure_take_value (s, "decay", &v); - - return gst_message_new_element (GST_OBJECT (level), s); -@@ -563,7 +559,7 @@ gst_level_message_append_channel (GstMes - { - const GValue *array_val; - GstStructure *s; -- GValueArray *arr; -+ GArray *arr; - GValue v = { 0, }; - - g_value_init (&v, G_TYPE_DOUBLE); -@@ -571,19 +567,19 @@ gst_level_message_append_channel (GstMes - s = (GstStructure *) gst_message_get_structure (m); - - array_val = gst_structure_get_value (s, "rms"); -- arr = (GValueArray *) g_value_get_boxed (array_val); -+ arr = (GArray *) g_value_get_boxed (array_val); - g_value_set_double (&v, rms); -- g_value_array_append (arr, &v); /* copies by value */ -+ g_array_append_val (arr, v); /* copies by value */ - - array_val = gst_structure_get_value (s, "peak"); -- arr = (GValueArray *) g_value_get_boxed (array_val); -+ arr = (GArray *) g_value_get_boxed (array_val); - g_value_set_double (&v, peak); -- g_value_array_append (arr, &v); /* copies by value */ -+ g_array_append_val (arr, v); /* copies by value */ - - array_val = gst_structure_get_value (s, "decay"); -- arr = (GValueArray *) g_value_get_boxed (array_val); -+ arr = (GArray *) g_value_get_boxed (array_val); - g_value_set_double (&v, decay); -- g_value_array_append (arr, &v); /* copies by value */ -+ g_array_append_val (arr, v); /* copies by value */ - - g_value_unset (&v); - } diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index 955a5e29..c723aa13 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -216,9 +216,9 @@ public class Recorder : Object { break; } - unowned var peak_arr = (GLib.Array) structure.get_value ("peak").get_boxed (); + unowned var peak_arr = (GLib.ValueArray) structure.get_value ("peak").get_boxed (); if (peak_arr != null) { - current_peak = peak_arr.index (0).get_double (); + current_peak = peak_arr.get_nth (0).get_double (); } break; From dab315ae7546c712235f86f63bbb44da7cec86d9 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 08:09:00 +0900 Subject: [PATCH 06/10] Leave comment for deprecated warning --- src/Services/Recorder.vala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index c723aa13..9c2eeebf 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -216,6 +216,10 @@ public class Recorder : Object { break; } + // FIXME: GLib.ValueArray is deprecated but used as an I/F structure in the GStreamer side: + // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/1.20.5/subprojects/gst-plugins-good/gst/level/gstlevel.c#L579 + // We would need a patch for GStreamer to replace ValueArray with Array + // when it's removed before GStreamer resolves unowned var peak_arr = (GLib.ValueArray) structure.get_value ("peak").get_boxed (); if (peak_arr != null) { current_peak = peak_arr.get_nth (0).get_double (); From ee1c7a3b0466c464792a01f7370621b696fa732a Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 09:59:49 +0900 Subject: [PATCH 07/10] Use Live Chart for indicator --- README.md | 1 + com.github.ryonakano.reco.yml | 8 ++++ data/Application.css | 16 ------- meson.build | 2 + src/Views/RecordView.vala | 3 +- src/Widgets/LevelBar.vala | 83 +++++++++++++++++------------------ 6 files changed, 53 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index f0d848f6..a0db361f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ You'll need the following dependencies to build: * libgstreamer1.0-dev (>= 1.20) * libpulse-dev * libpulse-mainloop-glib0 +* [livechart](https://github.com/lcallarec/live-chart) (commit 3bd8336bf7ed461c57a28e97efa7115b1 or later) * meson (>= 0.57.0) * valac diff --git a/com.github.ryonakano.reco.yml b/com.github.ryonakano.reco.yml index 16e4dbcd..dcdce4db 100644 --- a/com.github.ryonakano.reco.yml +++ b/com.github.ryonakano.reco.yml @@ -12,6 +12,14 @@ finish-args: - '--env=GST_PLUGIN_PATH_1_0=/app/lib/gstreamer-1.0' - '--metadata=X-DConf=migrate-path=/com/github/ryonakano/reco/' modules: + - name: live-chart + buildsystem: meson + sources: + # TODO: Replace with tarball when new release is published + - type: git + url: https://github.com/lcallarec/live-chart.git + branch: master + commit: d364df43bd8336bf7ed461c57a28e97efa7115b1 - name: gst-libav buildsystem: meson config_opts: diff --git a/data/Application.css b/data/Application.css index 15574309..3256861e 100644 --- a/data/Application.css +++ b/data/Application.css @@ -18,19 +18,3 @@ background: none; border: none; } - -levelbar block.low { - background-color: #9bdb4d; -} - -levelbar block.middle { - background-color: #64baff; -} - -levelbar block.high { - background-color: #f9c440; -} - -levelbar block.full { - background-color: #c6262e; -} diff --git a/meson.build b/meson.build index 0aa26226..8442006b 100644 --- a/meson.build +++ b/meson.build @@ -64,6 +64,8 @@ executable( dependency('gstreamer-1.0', version: '>= 1.20'), dependency('libpulse'), dependency('libpulse-mainloop-glib'), + # TODO: Add limitation for GTK4 supported version + dependency('livechart'), dependency('pango') ], link_args: ['-X', '-lm'], diff --git a/src/Views/RecordView.vala b/src/Views/RecordView.vala index cf43d38f..16b58b15 100644 --- a/src/Views/RecordView.vala +++ b/src/Views/RecordView.vala @@ -46,8 +46,7 @@ public class RecordView : Gtk.Box { var label_grid = new Gtk.Grid () { column_spacing = 6, row_spacing = 6, - halign = Gtk.Align.CENTER, - vexpand = true + halign = Gtk.Align.CENTER }; label_grid.attach (time_label, 0, 1, 1, 1); label_grid.attach (remaining_time_label, 0, 2, 1, 1); diff --git a/src/Widgets/LevelBar.vala b/src/Widgets/LevelBar.vala index 42fb9723..26220ec4 100644 --- a/src/Widgets/LevelBar.vala +++ b/src/Widgets/LevelBar.vala @@ -4,63 +4,62 @@ */ public class LevelBar : Gtk.Box { - private const int LEVEL_MIN = 0; - private const int LEVEL_LOW_MAX = 2; - private const int LEVEL_MIDDLE_MAX = 5; - private const int LEVEL_HIGH_MAX = 8; - private const int LEVEL_MAX = 10; - private const int BAR_ON = 1; - private const int BAR_OFF = 0; + private const double PEAK_MAX = 100.0; - private Gtk.LevelBar child_bars[LEVEL_MAX]; + private LiveChart.Serie serie; + private uint update_graph_timeout; public LevelBar () { } construct { orientation = Gtk.Orientation.VERTICAL; - spacing = 6; - margin_top = 6; - margin_bottom = 6; - halign = Gtk.Align.CENTER; - hexpand = true; - vexpand = true; + spacing = 0; var recorder = Recorder.get_default (); - for (int level = LEVEL_MIN; level < LEVEL_MAX; level++) { - var bar = new Gtk.LevelBar.for_interval (BAR_OFF, BAR_ON) { - mode = Gtk.LevelBarMode.DISCRETE, - inverted = true - }; + serie = new LiveChart.Serie ("Peak", new LiveChart.Bar ()); + serie.line.color = { 0.7f, 0.1f, 0.2f, 1.0f }; + serie.line.width = 1.0; - string css_class; - if (level <= LEVEL_LOW_MAX) { - css_class = "low"; - } else if (level <= LEVEL_MIDDLE_MAX) { - css_class = "middle"; - } else if (level <= LEVEL_HIGH_MAX) { - css_class = "high"; - } else { - css_class = "full"; - } - bar.add_offset_value (css_class, BAR_ON); - bar.value = BAR_OFF; + var config = new LiveChart.Config (); + config.x_axis.tick_interval = 1; + config.y_axis.fixed_max = PEAK_MAX; + config.padding = LiveChart.Padding () { + smart = LiveChart.AutoPadding.NONE, + top = 0, + right = 0, + bottom = 12, + left = 0 + }; + + var chart = new LiveChart.Chart (config) { + hexpand = true, + vexpand = true + }; + // Hide all axis lines, legend, and background; just show the graph + chart.grid.visible = false; + chart.legend.visible = false; + chart.background.color = { 0.0f, 0.0f, 0.0f, 0.0f }; - child_bars[level] = bar; - prepend (bar); - } + chart.add_serie (serie); - recorder.notify["current-peak"].connect (() => { - int current = (int) (recorder.current_peak * (double) LEVEL_MAX); + append (chart); - for (int level = LEVEL_MIN; level < LEVEL_MAX; level++) { - if (level < current) { - child_bars[level].value = BAR_ON; - } else { - child_bars[level].value = BAR_OFF; - } + recorder.notify["is-recording"].connect (() => { + if (recorder.is_recording) { + // Start updating the graph when recording started + update_graph_timeout = Timeout.add (100, () => { + int current = (int) (recorder.current_peak * PEAK_MAX); + serie.add (current); + return GLib.Source.CONTINUE; + }); + } else { + // Stop updating the graph when recording stopped + GLib.Source.remove (update_graph_timeout); + serie.clear (); } }); + } } From 4660bc7bdf3cb76e0a4b36b549c99e37b7d1c1ae Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 10:08:46 +0900 Subject: [PATCH 08/10] Followup fixes --- README.md | 2 +- src/Services/Recorder.vala | 3 ++- src/Widgets/LevelBar.vala | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a0db361f..a71c37d9 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ You'll need the following dependencies to build: * libgstreamer1.0-dev (>= 1.20) * libpulse-dev * libpulse-mainloop-glib0 -* [livechart](https://github.com/lcallarec/live-chart) (commit 3bd8336bf7ed461c57a28e97efa7115b1 or later) +* [livechart](https://github.com/lcallarec/live-chart) (commit `d364df43bd8336bf7ed461c57a28e97efa7115b1` or later) * meson (>= 0.57.0) * valac diff --git a/src/Services/Recorder.vala b/src/Services/Recorder.vala index 9c2eeebf..06073992 100644 --- a/src/Services/Recorder.vala +++ b/src/Services/Recorder.vala @@ -26,6 +26,7 @@ public class Recorder : Object { double p = Math.pow (10, decibel / 20); if (p == _current_peak) { + // No need to renew value return; } @@ -212,7 +213,7 @@ public class Recorder : Object { break; case Gst.MessageType.ELEMENT: unowned Gst.Structure? structure = msg.get_structure (); - if (structure == null || !structure.has_name ("level")) { + if (!structure.has_name ("level")) { break; } diff --git a/src/Widgets/LevelBar.vala b/src/Widgets/LevelBar.vala index 26220ec4..aca0f001 100644 --- a/src/Widgets/LevelBar.vala +++ b/src/Widgets/LevelBar.vala @@ -18,7 +18,7 @@ public class LevelBar : Gtk.Box { var recorder = Recorder.get_default (); - serie = new LiveChart.Serie ("Peak", new LiveChart.Bar ()); + serie = new LiveChart.Serie ("peak-value", new LiveChart.Bar ()); serie.line.color = { 0.7f, 0.1f, 0.2f, 1.0f }; serie.line.width = 1.0; From 397fd463d36ec50403b5d53a5a2c14b0efa83a64 Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 10:24:07 +0900 Subject: [PATCH 09/10] Readable variable name --- src/Widgets/LevelBar.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Widgets/LevelBar.vala b/src/Widgets/LevelBar.vala index aca0f001..d578c695 100644 --- a/src/Widgets/LevelBar.vala +++ b/src/Widgets/LevelBar.vala @@ -4,7 +4,7 @@ */ public class LevelBar : Gtk.Box { - private const double PEAK_MAX = 100.0; + private const double PEAK_PERCENTAGE = 100.0; private LiveChart.Serie serie; private uint update_graph_timeout; @@ -24,7 +24,7 @@ public class LevelBar : Gtk.Box { var config = new LiveChart.Config (); config.x_axis.tick_interval = 1; - config.y_axis.fixed_max = PEAK_MAX; + config.y_axis.fixed_max = PEAK_PERCENTAGE; config.padding = LiveChart.Padding () { smart = LiveChart.AutoPadding.NONE, top = 0, @@ -50,7 +50,7 @@ public class LevelBar : Gtk.Box { if (recorder.is_recording) { // Start updating the graph when recording started update_graph_timeout = Timeout.add (100, () => { - int current = (int) (recorder.current_peak * PEAK_MAX); + int current = (int) (recorder.current_peak * PEAK_PERCENTAGE); serie.add (current); return GLib.Source.CONTINUE; }); From e994ffaf5d3be15395b06e93e85df2794715763f Mon Sep 17 00:00:00 2001 From: Ryo Nakano Date: Sat, 12 Aug 2023 10:24:41 +0900 Subject: [PATCH 10/10] Remove unnecessary newline --- src/Widgets/LevelBar.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Widgets/LevelBar.vala b/src/Widgets/LevelBar.vala index d578c695..6d90f70e 100644 --- a/src/Widgets/LevelBar.vala +++ b/src/Widgets/LevelBar.vala @@ -60,6 +60,5 @@ public class LevelBar : Gtk.Box { serie.clear (); } }); - } }