From 4412b8714b7bd2a7bdd3f1ddecb9a596b30b839e Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Tue, 16 Feb 2021 00:08:02 +0100 Subject: [PATCH] RecorderUI: Capture info and transport ctrls --- gtk2_ardour/rec_info_box.cc | 365 ++++++++++++++++++++++++++++ gtk2_ardour/rec_info_box.h | 90 +++++++ gtk2_ardour/recorder_ui.cc | 34 ++- gtk2_ardour/recorder_ui.h | 7 + gtk2_ardour/transport_control_ui.cc | 2 + gtk2_ardour/wscript | 1 + 6 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 gtk2_ardour/rec_info_box.cc create mode 100644 gtk2_ardour/rec_info_box.h diff --git a/gtk2_ardour/rec_info_box.cc b/gtk2_ardour/rec_info_box.cc new file mode 100644 index 0000000000..98a08f879f --- /dev/null +++ b/gtk2_ardour/rec_info_box.cc @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2021 Robin Gareus + * Copyright (C) 2021 Ben Loftis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifdef WAF_BUILD +#include "gtk2ardour-config.h" +#endif + +#include + +#include "ardour/session.h" +#include "ardour/session_route.h" +#include "ardour/track.h" + +#include "gtkmm2ext/utils.h" +#include "temporal/time.h" + +#include "audio_clock.h" +#include "gui_thread.h" +#include "rec_info_box.h" +#include "timers.h" +#include "ui_config.h" + +#include "pbd/i18n.h" + +using namespace ARDOUR; + +RecInfoBox::RecInfoBox () +{ + set_name (X_("RecInfoBox")); + _layout_label = Pango::Layout::create (get_pango_context ()); + _layout_value = Pango::Layout::create (get_pango_context ()); + + UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &RecInfoBox::dpi_reset)); + dpi_reset (); +} + +void +RecInfoBox::on_size_request (Gtk::Requisition* r) +{ + r->width = _width; + r->height = std::max (12, _height); +} + +void +RecInfoBox::on_size_allocate (Gtk::Allocation& a) +{ + CairoWidget::on_size_allocate (a); +} + +void +RecInfoBox::set_session (Session* s) +{ + SessionHandlePtr::set_session (s); + + if (_session) { + update (); + } +} + +void +RecInfoBox::update () +{ + set_dirty (); +} + +/* ****************************************************************************/ + +void +DurationInfoBox::set_session (Session* s) +{ + RecInfoBox::set_session (s); + + if (!_session) { + _rectime_connection.disconnect (); + return; + } + _session->RecordStateChanged.connect (_session_connections, invalidator (*this), boost::bind (&DurationInfoBox::rec_state_changed, this), gui_context()); + _session->UpdateRouteRecordState.connect (_session_connections, invalidator (*this), boost::bind (&DurationInfoBox::update, this), gui_context()); +} + +void +DurationInfoBox::rec_state_changed () +{ + if (_session && _session->actively_recording ()) { + if (!_rectime_connection.connected ()) { + _rectime_connection = Timers::rapid_connect (sigc::mem_fun (*this, &DurationInfoBox::update)); + } + } else { + _rectime_connection.disconnect (); + } + update (); +} + +void +DurationInfoBox::dpi_reset () +{ + int wv, hv; + _layout_value->set_font_description (UIConfiguration::instance ().get_NormalMonospaceFont ()); + _layout_value->set_text ("<00:00:00:0>"); + _layout_value->get_pixel_size (wv, hv); + _width = 8 + wv; + _height = 4 + hv; + queue_resize (); +} + +void +DurationInfoBox::render (Cairo::RefPtr const& cr, cairo_rectangle_t* r) +{ + int ww = get_width (); + int hh = get_height (); + + cr->rectangle (r->x, r->y, r->width, r->height); + cr->clip (); + cr->set_operator (Cairo::OPERATOR_OVER); + + bool recording; + if (_session && _session->actively_recording ()) { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7); + recording = true; + } else { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7); + recording = false; + } + + Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 1, hh - 1, /*_height / 4.0 */ 4); + cr->fill (); + + if (!_session) { + return; + } + + Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground")); + + samplecnt_t capture_duration = _session->capture_duration (); + samplecnt_t sample_rate = _session->nominal_sample_rate (); + + int w, h; + + if (capture_duration > 0) { + char buf[32]; + AudioClock::print_minsec (capture_duration, buf, sizeof (buf), sample_rate, 1); + if (recording) { + _layout_value->set_text (string_compose(" %1 ", std::string(buf).substr(1))); + } else { + _layout_value->set_text (string_compose("<%1>", std::string(buf).substr(1))); + } + } else { + _layout_value->set_text (" --:--:--:- "); + } + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), hh/2 - h/2); + _layout_value->show_in_cairo_context (cr); +} + +/* ****************************************************************************/ + +void +XrunInfoBox::set_session (Session* s) +{ + RecInfoBox::set_session (s); + + if (!_session) { + return; + } + + _session->Xrun.connect (_session_connections, invalidator (*this), boost::bind (&XrunInfoBox::update, this), gui_context()); + _session->RecordStateChanged.connect (_session_connections, invalidator (*this), boost::bind (&XrunInfoBox::update, this), gui_context()); +} + +void +XrunInfoBox::dpi_reset () +{ + int wv, hv; + _layout_value->set_font_description (UIConfiguration::instance ().get_NormalFont ()); + _layout_value->set_text ("<99+>"); + _layout_value->get_pixel_size (wv, hv); + _width = 8 + wv; + _height = 8 + hv; + queue_resize (); +} + +void +XrunInfoBox::render (Cairo::RefPtr const& cr, cairo_rectangle_t* r) +{ + if (!_session) { + return; + } + + int ww = get_width (); + int hh = get_height (); + + cr->rectangle (r->x, r->y, r->width, r->height); + cr->clip (); + cr->set_operator (Cairo::OPERATOR_OVER); + + unsigned int xruns = _session->capture_xruns (); + + if (xruns > 0) { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7); + } else { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7); + } + + Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 2, hh - 2, /*_height / 4.0 */ 4); + cr->fill (); + + if (xruns < 99) { + if (_session->actively_recording ()) { + _layout_value->set_text (string_compose ("%1", xruns)); + } else if (_session->capture_duration () > 0) { + _layout_value->set_text (string_compose ("<%1>", xruns)); + } else { + _layout_value->set_text ("-"); + } + } else { + if (_session->actively_recording ()) { + _layout_value->set_text ("99+"); + } else if (_session->capture_duration () > 0) { + _layout_value->set_text ("<99+>"); + } else { + assert (0); + return; + } + } + + Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground")); + int w, h; + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), .5 * (hh - h)); + _layout_value->show_in_cairo_context (cr); +} + +/* ****************************************************************************/ + +void +RemainInfoBox::set_session (Session* s) +{ + RecInfoBox::set_session (s); + + if (!_session) { + _diskspace_connection.disconnect (); + return; + } + + _diskspace_connection = Timers::second_connect (sigc::mem_fun (*this, &RemainInfoBox::update)); + _session->UpdateRouteRecordState.connect (_session_connections, invalidator (*this), boost::bind (&RemainInfoBox::update, this), gui_context()); +} + +void +RemainInfoBox::dpi_reset () +{ + _layout_label->set_font_description (UIConfiguration::instance ().get_NormalFont ()); + _layout_value->set_font_description (UIConfiguration::instance ().get_NormalMonospaceFont ()); + + int wl, hl, wv, hv; + + _layout_label->set_text (_("Disk Space:")); + _layout_label->get_pixel_size (wl, hl); + + _layout_value->set_text (_(">24h")); + _layout_value->get_pixel_size (wv, hv); + + _width = 8 + std::max (wl, wv); + _height = 2 + hv + 2 + hl + 2; + + queue_resize (); +} + +void +RemainInfoBox::count_recenabled_streams (Route& route) +{ + Track* track = dynamic_cast(&route); + if (track && track->rec_enable_control()->get_value()) { + _rec_enabled_streams += track->n_inputs().n_total(); + } +} + +void +RemainInfoBox::render (Cairo::RefPtr const& cr, cairo_rectangle_t* r) +{ + int ww = get_width (); + int hh = get_height (); + + cr->rectangle (r->x, r->y, r->width, r->height); + cr->clip (); + cr->set_operator (Cairo::OPERATOR_OVER); + + if (!_session) { + return; + } + + samplecnt_t sample_rate = _session->nominal_sample_rate (); + boost::optional opt_samples = _session->available_capture_duration (); + + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7); + + if (!opt_samples) { + /* Available space is unknown */ + _layout_value->set_text (_("Unknown")); + } else if (opt_samples.value_or (0) == max_samplecnt) { + _layout_value->set_text (_(">24h")); + } else { + _rec_enabled_streams = 0; + _session->foreach_route (this, &RemainInfoBox::count_recenabled_streams, false); + + samplecnt_t samples = opt_samples.value_or (0); + + if (_rec_enabled_streams > 0) { + samples /= _rec_enabled_streams; + } + + float remain_sec = samples / (float)sample_rate; + char buf[32]; + + if (remain_sec > 86400) { + _layout_value->set_text (_(">24h")); + } else if (remain_sec > 32400 /* 9 hours */) { + snprintf (buf, sizeof (buf), "%.0f", remain_sec / 3600.f); + _layout_value->set_text (std::string (buf) + S_("hours|h")); + } else if (remain_sec > 5940 /* 99 mins */) { + snprintf (buf, sizeof (buf), "%.1f", remain_sec / 3600.f); + _layout_value->set_text (std::string (buf) + S_("hours|h")); + } else if (remain_sec > 60*3 /* 3 mins */) { + snprintf (buf, sizeof (buf), "%.0f", remain_sec / 60.f); + _layout_value->set_text (std::string (buf) + S_("minutes|m")); + } else { + Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("alert:red"), .7); + snprintf (buf, sizeof (buf), "%.0f", remain_sec / 60.f); + _layout_value->set_text (std::string (buf) + S_("minutes|m")); + } + } + + /* draw box */ + Gtkmm2ext::rounded_rectangle (cr, 1 , 1, ww - 2, hh - 2, /*_height / 4.0 */ 4); + cr->fill (); + + /*draw text */ + Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground")); + cr->set_line_width (1.0); + + int w, h; + _layout_label->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), 4); + _layout_label->show_in_cairo_context (cr); + + _layout_value->get_pixel_size (w, h); + cr->move_to (.5 * (ww - w), hh - 4 - h); + _layout_value->show_in_cairo_context (cr); +} diff --git a/gtk2_ardour/rec_info_box.h b/gtk2_ardour/rec_info_box.h new file mode 100644 index 0000000000..7d16401f53 --- /dev/null +++ b/gtk2_ardour/rec_info_box.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Robin Gareus + * Copyright (C) 2021 Ben Loftis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef _rec_info_box_h_ +#define _rec_info_box_h_ + +#include "ardour/session_handle.h" + +#include "gtkmm2ext/cairo_widget.h" + +namespace ARDOUR { + class Route; +} + +class RecInfoBox : public CairoWidget, public ARDOUR::SessionHandlePtr +{ +public: + RecInfoBox (); + + virtual void set_session (ARDOUR::Session*); + +protected: + virtual void render (Cairo::RefPtr const&, cairo_rectangle_t*) = 0; + virtual void dpi_reset () {} + + void on_size_request (Gtk::Requisition*); + void on_size_allocate (Gtk::Allocation&); + virtual void update (); + + Glib::RefPtr _layout_label; + Glib::RefPtr _layout_value; + int _width; + int _height; +}; + +class DurationInfoBox : public RecInfoBox +{ +public: + virtual void set_session (ARDOUR::Session*); + +protected: + void render (Cairo::RefPtr const&, cairo_rectangle_t*); + void dpi_reset (); + +private: + void rec_state_changed (); + sigc::connection _rectime_connection; +}; + +class XrunInfoBox : public RecInfoBox +{ +public: + virtual void set_session (ARDOUR::Session*); + +protected: + void render (Cairo::RefPtr const&, cairo_rectangle_t*); + void dpi_reset (); +}; + +class RemainInfoBox : public RecInfoBox +{ +public: + virtual void set_session (ARDOUR::Session*); + +protected: + void render (Cairo::RefPtr const&, cairo_rectangle_t*); + void dpi_reset (); +private: + void count_recenabled_streams (ARDOUR::Route&); + uint32_t _rec_enabled_streams; + sigc::connection _diskspace_connection; +}; + +#endif diff --git a/gtk2_ardour/recorder_ui.cc b/gtk2_ardour/recorder_ui.cc index e6c5ba449c..27855dcfd7 100644 --- a/gtk2_ardour/recorder_ui.cc +++ b/gtk2_ardour/recorder_ui.cc @@ -88,6 +88,12 @@ RecorderUI::RecorderUI () load_bindings (); register_actions (); + _transport_ctrl.setup (ARDOUR_UI::instance ()); + _transport_ctrl.map_actions (); + _transport_ctrl.set_no_show_all (); + + signal_tabbed_changed.connect (sigc::mem_fun (*this, &RecorderUI::tabbed_changed)); + /* monitoring */ _auto_input_button.set_related_action (ActionManager::get_action ("Transport", "ToggleAutoInput")); _auto_input_button.set_name ("transport option button"); @@ -142,7 +148,7 @@ RecorderUI::RecorderUI () _rec_area.set_spacing (0); _rec_area.pack_end (_scroller_base, true, true); - _rec_area.pack_end (_ruler_sep, true, true, 0); + _rec_area.pack_end (_ruler_sep, false, false, 0); /* HBox [ groups | tracks] */ _rec_group_tabs = new RecorderGroupTabs (this); @@ -176,6 +182,11 @@ RecorderUI::RecorderUI () int spacepad = 3; int col = 0; + _button_table.attach (_transport_ctrl, col, col + 1, 0, 1, FILL, FILL, hpadding, vpadding); + col += 1; + + _button_table.attach (_duration_info_box, col, col + 1, 0, 1, FILL, FILL, hpadding, vpadding); + _button_table.attach (_xrun_info_box, col + 1, col + 2, 0, 1, FILL, FILL, hpadding, vpadding); _button_table.attach (_btn_rec_forget, col, col + 2, 1, 2, FILL, SHRINK, hpadding, vpadding); col += 2; @@ -197,6 +208,7 @@ RecorderUI::RecorderUI () _toolbar.pack_start (_button_table, false, false); _toolbar.pack_end (_btn_peak_reset, false, false, 4); + _toolbar.pack_end (_remain_info_box, false, false, 4); /* tooltips */ set_tooltip (_btn_rec_all, _("Record enable all tracks")); @@ -205,6 +217,9 @@ RecorderUI::RecorderUI () set_tooltip (_auto_input_button, _("Track Input Monitoring automatically follows transport state")); set_tooltip (_monitor_in_button, _("Force all tracks to monitor Input, unless they are explicitly set to monitor Disk")); set_tooltip (_monitor_disk_button, _("Force all tracks to monitor Disk playback, unless they are explicitly set to Input")); + set_tooltip (_xrun_info_box, _("X-runs: Soundcard buffer under- or over-run occurrences in the last recording take")); + set_tooltip (_remain_info_box, _("Remaining Time: Recording time available on the current disk with currently armed tracks")); + set_tooltip (_duration_info_box, _("Duration: Length of the most recent (or current) recording take")); set_tooltip (_btn_rec_forget, _("Delete the region AND the audio files of the last recording take")); /* show [almost] all */ @@ -225,6 +240,9 @@ RecorderUI::RecorderUI () _rec_groups.show (); _rec_group_tabs->show (); _rec_container.show (); + _duration_info_box.show (); + _xrun_info_box.show (); + _remain_info_box.show (); _meter_table.show (); _meter_area.show (); _meter_scroller.show (); @@ -296,6 +314,16 @@ RecorderUI::use_own_window (bool and_fill_it) return win; } +void +RecorderUI::tabbed_changed (bool tabbed) +{ + if (tabbed) { + _transport_ctrl.hide (); + } else { + _transport_ctrl.show (); + } +} + XMLNode& RecorderUI::get_state () { @@ -329,6 +357,10 @@ RecorderUI::set_session (Session* s) SessionHandlePtr::set_session (s); _ruler.set_session (s); + _duration_info_box.set_session (s); + _xrun_info_box.set_session (s); + _remain_info_box.set_session (s); + _transport_ctrl.set_session (s); _rec_group_tabs->set_session (s); update_sensitivity (); diff --git a/gtk2_ardour/recorder_ui.h b/gtk2_ardour/recorder_ui.h index 8d3cd2bd1a..f6e6e7e6c0 100644 --- a/gtk2_ardour/recorder_ui.h +++ b/gtk2_ardour/recorder_ui.h @@ -45,6 +45,8 @@ #include "widgets/tabbable.h" #include "input_port_monitor.h" +#include "rec_info_box.h" +#include "transport_control_ui.h" namespace ARDOUR { class SoloMuteRelease; @@ -90,6 +92,7 @@ private: void remove_route (TrackRecordAxis*); void update_rec_table_layout (); void update_spacer_width (Gtk::Allocation&, TrackRecordAxis*); + void tabbed_changed (bool); void set_connections (std::string const&); void port_connected_or_disconnected (std::string, std::string); @@ -135,6 +138,10 @@ private: ArdourWidgets::ArdourButton _monitor_in_button; ArdourWidgets::ArdourButton _monitor_disk_button; ArdourWidgets::ArdourButton _auto_input_button; + DurationInfoBox _duration_info_box; + XrunInfoBox _xrun_info_box; + RemainInfoBox _remain_info_box; + TransportControlUI _transport_ctrl; Glib::RefPtr _toolbar_button_height; Glib::RefPtr _toolbar_recarm_width; Glib::RefPtr _toolbar_monitoring_width; diff --git a/gtk2_ardour/transport_control_ui.cc b/gtk2_ardour/transport_control_ui.cc index c3b6fb552a..f7c2a320b3 100644 --- a/gtk2_ardour/transport_control_ui.cc +++ b/gtk2_ardour/transport_control_ui.cc @@ -160,6 +160,8 @@ TransportControlUI::setup (TransportControlProvider* ui) stop_button.set_active (true); + show_all (); + Timers::blink_connect (sigc::mem_fun (*this, &TransportControlUI::blink_rec_enable)); } diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index a3fd7ffed5..684a647ea1 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -226,6 +226,7 @@ gtk2_ardour_sources = [ 'public_editor.cc', 'quantize_dialog.cc', 'rc_option_editor.cc', + 'rec_info_box.cc', 'recorder_group_tabs.cc', 'recorder_ui.cc', 'region_editor.cc',