RecorderUI: Capture info and transport ctrls

This commit is contained in:
Robin Gareus 2021-02-16 00:08:02 +01:00
parent 15361bca55
commit 4412b8714b
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
6 changed files with 498 additions and 1 deletions

365
gtk2_ardour/rec_info_box.cc Normal file
View File

@ -0,0 +1,365 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
* Copyright (C) 2021 Ben Loftis <ben@harrisonconsoles.com>
*
* 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 <cassert>
#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<Cairo::Context> 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<Cairo::Context> 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<Track*>(&route);
if (track && track->rec_enable_control()->get_value()) {
_rec_enabled_streams += track->n_inputs().n_total();
}
}
void
RemainInfoBox::render (Cairo::RefPtr<Cairo::Context> 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<samplecnt_t> 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);
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
* Copyright (C) 2021 Ben Loftis <ben@harrisonconsoles.com>
*
* 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<Cairo::Context> 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<Pango::Layout> _layout_label;
Glib::RefPtr<Pango::Layout> _layout_value;
int _width;
int _height;
};
class DurationInfoBox : public RecInfoBox
{
public:
virtual void set_session (ARDOUR::Session*);
protected:
void render (Cairo::RefPtr<Cairo::Context> 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<Cairo::Context> const&, cairo_rectangle_t*);
void dpi_reset ();
};
class RemainInfoBox : public RecInfoBox
{
public:
virtual void set_session (ARDOUR::Session*);
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void dpi_reset ();
private:
void count_recenabled_streams (ARDOUR::Route&);
uint32_t _rec_enabled_streams;
sigc::connection _diskspace_connection;
};
#endif

View File

@ -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 ();

View File

@ -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<Gtk::SizeGroup> _toolbar_button_height;
Glib::RefPtr<Gtk::SizeGroup> _toolbar_recarm_width;
Glib::RefPtr<Gtk::SizeGroup> _toolbar_monitoring_width;

View File

@ -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));
}

View File

@ -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',