RecorderUI: initial implementation

This commit is contained in:
Robin Gareus 2020-12-06 22:05:09 +01:00
parent f10d380d9a
commit 582c99a156
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
12 changed files with 3309 additions and 18 deletions

View File

@ -23,9 +23,14 @@
#define __gtk_ardour_group_tabs_h__
#include <gtkmm/menu.h>
#include "editor_component.h"
#include "ardour/session_handle.h"
#include "ardour/types.h"
#include "gtkmm2ext/cairo_widget.h"
#include "editor_component.h"
namespace ARDOUR {
class Session;
class RouteGroup;

View File

@ -0,0 +1,591 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
*
* 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 "ardour/dB.h"
#include "ardour/logmeter.h"
#include "ardour/parameter_descriptor.h"
#include "ardour/port_manager.h"
#include "gtkmm2ext/utils.h"
#include "widgets/fastmeter.h"
#include "widgets/tooltips.h"
#include "input_port_monitor.h"
#include "ui_config.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ArdourWidgets;
#define PX_SCALE(px) std::max ((float)px, rintf ((float)px* UIConfiguration::instance ().get_ui_scale ()))
InputPortMonitor::InputPortMonitor (ARDOUR::DataType dt, samplecnt_t sample_rate, Orientation o)
: _dt (dt)
, _audio_meter (0)
, _audio_scope (0)
, _midi_meter (0)
, _midi_monitor (0)
{
if (o == Vertical) {
_box = new Gtk::HBox;
} else {
_box = new Gtk::VBox;
}
if (_dt == DataType::AUDIO) {
_audio_meter = new FastMeter (
(uint32_t)floor (UIConfiguration::instance ().get_meter_hold ()),
18,
o == Vertical ? FastMeter::Vertical : FastMeter::Horizontal,
PX_SCALE (200),
UIConfiguration::instance ().color ("meter color0"),
UIConfiguration::instance ().color ("meter color1"),
UIConfiguration::instance ().color ("meter color2"),
UIConfiguration::instance ().color ("meter color3"),
UIConfiguration::instance ().color ("meter color4"),
UIConfiguration::instance ().color ("meter color5"),
UIConfiguration::instance ().color ("meter color6"),
UIConfiguration::instance ().color ("meter color7"),
UIConfiguration::instance ().color ("meter color8"),
UIConfiguration::instance ().color ("meter color9"),
UIConfiguration::instance ().color ("meter background bottom"),
UIConfiguration::instance ().color ("meter background top"),
0x991122ff, // red highlight gradient Bot
0x551111ff, // red highlight gradient Top
(115.0 * log_meter0dB (-18)),
89.125, // 115.0 * log_meter0dB(-9);
106.375, // 115.0 * log_meter0dB(-3);
115.0, // 115.0 * log_meter0dB(0);
(UIConfiguration::instance ().get_meter_style_led () ? 3 : 1));
_audio_scope = new InputScope (sample_rate, PX_SCALE (200), 25, o);
_audio_meter->show ();
_audio_scope->show ();
ArdourWidgets::set_tooltip (_audio_scope, _("5 second history waveform"));
_box->pack_start (*_audio_meter, false, false);
_box->pack_start (*_audio_scope, true, true, 1);
} else if (_dt == DataType::MIDI) {
_midi_meter = new EventMeter (o);
_midi_monitor = new EventMonitor (o);
_midi_meter->show ();
_midi_monitor->show ();
ArdourWidgets::set_tooltip (_midi_meter, _("Highlight incoming MIDI data per MIDI channel"));
ArdourWidgets::set_tooltip (_midi_monitor, _("Display most recently received MIDI messages"));
_box->pack_start (*_midi_meter, false, false);
_box->pack_start (*_midi_monitor, true, false, 1);
}
add (*_box);
_box->show ();
}
InputPortMonitor::~InputPortMonitor ()
{
delete _audio_meter;
delete _audio_scope;
delete _midi_meter;
delete _midi_monitor;
delete _box;
}
void
InputPortMonitor::clear ()
{
if (_audio_meter) {
_audio_meter->clear ();
}
if (_audio_scope) {
_audio_scope->clear ();
}
if (_midi_meter) {
_midi_meter->clear ();
}
if (_midi_monitor) {
_midi_monitor->clear ();
}
}
void
InputPortMonitor::update (float l, float p)
{
assert (_dt == DataType::AUDIO && _audio_meter);
_audio_meter->set (log_meter0dB (l), log_meter0dB (p));
}
void
InputPortMonitor::update (ARDOUR::CircularSampleBuffer& csb)
{
assert (_dt == DataType::AUDIO && _audio_scope);
_audio_scope->update (csb);
}
void
InputPortMonitor::update (float const* v)
{
assert (_dt == DataType::MIDI && _midi_meter);
_midi_meter->update (v);
}
void
InputPortMonitor::update (ARDOUR::CircularEventBuffer& ceb)
{
assert (_dt == DataType::MIDI && _midi_monitor);
_midi_monitor->update (ceb);
}
/* ****************************************************************************/
InputPortMonitor::InputScope::InputScope (samplecnt_t rate, int l, int g, Orientation o)
: _pos (0)
, _rate (rate)
, _min_length (l)
, _min_gauge (g)
, _orientation (o)
{
_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, l, g);
use_image_surface (false); /* we already use a surface */
UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &InputScope::parameter_changed));
parameter_changed ("waveform-clip-level");
parameter_changed ("show-waveform-clipping");
parameter_changed ("waveform-scale");
}
void
InputPortMonitor::InputScope::parameter_changed (std::string const& p)
{
if (p == "waveform-clip-level") {
_clip_level = dB_to_coefficient (UIConfiguration::instance ().get_waveform_clip_level ());
} else if (p == "show-waveform-clipping") {
_show_clip = UIConfiguration::instance ().get_show_waveform_clipping ();
} else if (p == "waveform-scale") {
_logscale = UIConfiguration::instance ().get_waveform_scale () == Logarithmic;
}
}
void
InputPortMonitor::InputScope::on_size_request (Gtk::Requisition* req)
{
if (_orientation == Horizontal) {
req->width = 2 + _min_length;
req->height = 2 + _min_gauge;
} else {
req->width = 2 + _min_gauge;
req->height = 2 + _min_length;
}
}
void
InputPortMonitor::InputScope::on_size_allocate (Gtk::Allocation& a)
{
CairoWidget::on_size_allocate (a);
int w, h;
if (_orientation == Horizontal) {
w = 2 + _surface->get_width ();
h = 2 + _surface->get_height ();
} else {
w = 2 + _surface->get_height ();
h = 2 + _surface->get_width ();
}
if (a.get_width () != w || a.get_height () != h) {
_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, a.get_width () - 2, a.get_height () - 2);
}
}
void
InputPortMonitor::InputScope::clear ()
{
int w = _surface->get_width ();
int h = _surface->get_height ();
Cairo::RefPtr<Cairo::Context> cr= Cairo::Context::create (_surface);
cr->rectangle (0, 0, w, h);
cr->set_operator (Cairo::OPERATOR_SOURCE);
cr->set_source_rgba (0, 0, 0, 0);
cr->fill ();
_pos = 0;
set_dirty ();
}
void
InputPortMonitor::InputScope::update (CircularSampleBuffer& csb)
{
int l = _orientation == Horizontal ? _surface->get_width () : _surface->get_height ();
int g = _orientation == Horizontal ? _surface->get_height () : _surface->get_width ();
double g2 = g / 2.0;
int spp = 5.0 /*sec*/ * _rate / l; // samples / pixel
Cairo::RefPtr<Cairo::Context> cr;
bool have_data = false;
float minf, maxf;
while (csb.read (minf, maxf, spp)) {
if (!have_data) {
have_data = true;
cr = Cairo::Context::create (_surface);
}
/* see also ExportReport::draw_waveform */
if (_orientation == Horizontal) {
cr->rectangle (_pos, 0, 1, g);
} else {
if (_pos-- == 0) {
_pos = l - 1;
}
cr->rectangle (0, _pos, g, 1);
}
cr->set_operator (Cairo::OPERATOR_SOURCE);
cr->set_source_rgba (0, 0, 0, 0);
cr->fill ();
cr->set_operator (Cairo::OPERATOR_OVER);
cr->set_line_width (1.0);
if (_show_clip && (maxf >= _clip_level || -minf >= _clip_level)) {
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("clipped waveform"));
} else {
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("waveform fill"));
}
if (_logscale) {
if (maxf > 0) {
maxf = alt_log_meter (fast_coefficient_to_dB (maxf));
} else {
maxf = -alt_log_meter (fast_coefficient_to_dB (-maxf));
}
if (minf > 0) {
minf = alt_log_meter (fast_coefficient_to_dB (minf));
} else {
minf = -alt_log_meter (fast_coefficient_to_dB (-minf));
}
}
if (_orientation == Horizontal) {
cr->move_to (_pos + .5, g2 - g2 * maxf);
cr->line_to (_pos + .5, g2 - g2 * minf);
cr->stroke ();
if (++_pos >= l) {
_pos = 0;
}
} else {
cr->move_to (g2 + g2 * minf, _pos + .5);
cr->line_to (g2 + g2 * maxf, _pos + .5);
cr->stroke ();
}
}
if (have_data) {
_surface->flush ();
set_dirty ();
}
}
void
InputPortMonitor::InputScope::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
{
cr->rectangle (r->x, r->y, r->width, r->height);
cr->clip ();
cr->set_operator (Cairo::OPERATOR_OVER);
int w = _surface->get_width ();
int h = _surface->get_height ();
cr->save ();
cr->translate (1, 1);
cr->rectangle (0, 0, w, h);
cr->clip ();
if (_orientation == Vertical) {
cr->set_source (_surface, 0, 0.0 - _pos);
cr->paint ();
cr->set_source (_surface, 0, h - _pos);
cr->paint ();
double g2 = .5 * w;
cr->move_to (g2, 0);
cr->line_to (g2, h);
} else {
cr->set_source (_surface, 0.0 - _pos, 0);
cr->paint ();
cr->set_source (_surface, w - _pos, 0);
cr->paint ();
double g2 = .5 * h;
cr->move_to (0, g2);
cr->line_to (w, g2);
}
/* zero line */
cr->set_line_width (1.0);
Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("zero line"), .7);
cr->stroke ();
/* black border - compare to FastMeter::horizontal_expose */
cr->set_line_width (2.0);
Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width (), get_height (), boxy_buttons () ? 0 : 2);
cr->set_source_rgb (0, 0, 0); // black
cr->stroke ();
}
/* ****************************************************************************/
InputPortMonitor::EventMeter::EventMeter (Orientation o)
: _orientation (o)
{
_layout = Pango::Layout::create (get_pango_context ());
memset (_chn, 0, sizeof (_chn));
UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &EventMeter::dpi_reset));
dpi_reset ();
}
void
InputPortMonitor::EventMeter::dpi_reset ()
{
_layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ());
_layout->set_text ("Cy5");
_layout->get_pixel_size (_length, _extent);
_extent += 2;
_length += 2;
queue_resize ();
}
void
InputPortMonitor::EventMeter::on_size_request (Gtk::Requisition* req)
{
if (_orientation == Horizontal) {
/* 90 deg CCW */
req->width = _extent * 17 + 4;
req->height = _length + 2;
} else {
req->width = _length + 2;
req->height = _extent * 17 + 4;
}
}
void
InputPortMonitor::EventMeter::clear ()
{
memset (_chn, 0, sizeof (_chn));
set_dirty ();
}
void
InputPortMonitor::EventMeter::update (float const* v)
{
if (memcmp (_chn, v, sizeof (_chn))) {
memcpy (_chn, v, sizeof (_chn));
set_dirty ();
}
}
void
InputPortMonitor::EventMeter::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
{
cr->rectangle (r->x, r->y, r->width, r->height);
cr->clip ();
double bg_r, bg_g, bg_b, bg_a;
double fg_r, fg_g, fg_b, fg_a;
Gtkmm2ext::color_to_rgba (UIConfiguration::instance ().color ("meter bar"), bg_r, bg_g, bg_b, bg_a);
Gtkmm2ext::color_to_rgba (UIConfiguration::instance ().color ("midi meter 56"), fg_r, fg_g, fg_b, fg_a);
fg_r -= bg_r;
fg_g -= bg_g;
fg_b -= bg_b;
cr->set_operator (Cairo::OPERATOR_OVER);
cr->set_line_width (1.0);
for (uint32_t i = 0; i < 17; ++i) {
float off = 1.5 + _extent * i;
if (_orientation == Horizontal) {
Gtkmm2ext::rounded_rectangle (cr, off, 0.5, _extent, _length, boxy_buttons () ? 0 : 2);
} else {
Gtkmm2ext::rounded_rectangle (cr, 0.5, off, _length, _extent, boxy_buttons () ? 0 : 2);
}
cr->set_source_rgba (bg_r + _chn[i] * fg_r, bg_g + _chn[i] * fg_g, bg_b + _chn[i] * fg_b, .9);
cr->fill_preserve ();
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("border color"));
cr->stroke ();
if (i < 16) {
_layout->set_text (PBD::to_string (i + 1));
} else {
_layout->set_text ("SyS");
}
int l, x;
_layout->get_pixel_size (l, x);
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground2"));
if (_orientation == Horizontal) {
cr->save ();
cr->move_to (off + .5 * (_extent - x), .5 + .5 * (_length + l));
cr->rotate (M_PI / -2.0);
_layout->show_in_cairo_context (cr);
cr->restore ();
} else {
cr->move_to (0.5 + .5 * (_length - l), off + .5 * (_extent - x - 2));
_layout->show_in_cairo_context (cr);
}
}
}
/* ****************************************************************************/
InputPortMonitor::EventMonitor::EventMonitor (Orientation o)
: _orientation (o)
{
_layout = Pango::Layout::create (get_pango_context ());
UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &EventMonitor::dpi_reset));
dpi_reset ();
}
void
InputPortMonitor::EventMonitor::dpi_reset ()
{
_layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ());
_layout->set_text ("OffC#-1"); // 7 chars
_layout->get_pixel_size (_width, _height);
_width += 2;
_height += 2;
queue_resize ();
}
void
InputPortMonitor::EventMonitor::on_size_request (Gtk::Requisition* req)
{
if (_orientation == Horizontal) {
req->width = PX_SCALE (200);
req->height = _height;
} else {
req->width = _width;
req->height = 8 * _height;
}
}
void
InputPortMonitor::EventMonitor::clear ()
{
_l.clear ();
set_dirty ();
}
void
InputPortMonitor::EventMonitor::update (CircularEventBuffer& ceb)
{
if (ceb.read (_l)) {
set_dirty ();
}
}
void
InputPortMonitor::EventMonitor::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
{
int ww = get_width () - 12;
int hh = 2;
for (CircularEventBuffer::EventList::const_iterator i = _l.begin (); i != _l.end (); ++i) {
if (i->data[0] == 0) {
break;
}
char tmp[32];
switch (i->data[0] & 0xf0) {
case MIDI_CMD_NOTE_OFF:
sprintf (tmp, "Off%4s", ParameterDescriptor::midi_note_name (i->data[1]).c_str ());
break;
case MIDI_CMD_NOTE_ON:
sprintf (tmp, "On %4s", ParameterDescriptor::midi_note_name (i->data[1]).c_str ());
break;
case MIDI_CMD_NOTE_PRESSURE:
sprintf (tmp, "KP %4s", ParameterDescriptor::midi_note_name (i->data[1]).c_str ());
break;
case MIDI_CMD_CONTROL:
sprintf (tmp, "CC%02x %02x", i->data[1], i->data[2]);
break;
case MIDI_CMD_PGM_CHANGE:
sprintf (tmp, "PC %3d ", i->data[1]);
break;
case MIDI_CMD_CHANNEL_PRESSURE:
sprintf (tmp, "CP %02x ", i->data[1]);
break;
case MIDI_CMD_BENDER:
sprintf (tmp, "PB %04x", i->data[1] | i->data[2] << 7);
break;
case MIDI_CMD_COMMON_SYSEX:
// TODO sub-type ?
sprintf (tmp, " SysEx ");
break;
}
int w, h;
_layout->set_text (tmp);
_layout->get_pixel_size (w, h);
Gtkmm2ext::set_source_rgb_a (cr, UIConfiguration::instance ().color ("widget:bg"), .7);
if (_orientation == Horizontal) {
Gtkmm2ext::rounded_rectangle (cr, ww - w - 1, 1, 2 + w, _height - 3, _height / 4.0);
cr->fill ();
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground2"));
cr->move_to (ww - w, .5 * (_height - h));
_layout->show_in_cairo_context (cr);
ww -= w + 12;
if (ww < w) {
break;
}
} else {
Gtkmm2ext::rounded_rectangle (cr, 1, hh + 1, _width, _height - 3, _height / 4.0);
cr->fill ();
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance ().color ("neutral:foreground2"));
cr->move_to (.5 * (_width - w), hh);
_layout->show_in_cairo_context (cr);
hh += _height;
if (hh + h >= get_height ()) {
break;
}
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2021 Robin Gareus <robin@gareus.org>
*
* 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 __gtk_ardour_input_port_monitor_h__
#define __gtk_ardour_input_port_monitor_h__
#include <gtkmm/box.h>
#include "gtkmm2ext/cairo_widget.h"
#include "ardour/circular_buffer.h"
#include "ardour/session_handle.h"
namespace ArdourWidgets
{
class FastMeter;
}
class InputPortMonitor : public Gtk::EventBox
{
public:
enum Orientation {
Vertical,
Horizontal
};
InputPortMonitor (ARDOUR::DataType, ARDOUR::samplecnt_t, Orientation);
~InputPortMonitor ();
void clear ();
void update (float, float); // FastMeter
void update (float const*); // EventMeter
void update (ARDOUR::CircularSampleBuffer&); // InputScope
void update (ARDOUR::CircularEventBuffer&); // EventMonitor
private:
class InputScope : public CairoWidget
{
public:
InputScope (ARDOUR::samplecnt_t, int length , int gauge, Orientation);
void update (ARDOUR::CircularSampleBuffer&);
void clear ();
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void on_size_request (Gtk::Requisition*);
void on_size_allocate (Gtk::Allocation&);
private:
void parameter_changed (std::string const&);
int _pos;
ARDOUR::samplecnt_t _rate;
int _min_length;
int _min_gauge;
Orientation _orientation;
float _clip_level;
bool _show_clip;
bool _logscale;
Cairo::RefPtr<Cairo::ImageSurface> _surface;
};
class EventMeter : public CairoWidget
{
public:
EventMeter (Orientation);
void update (float const*);
void clear ();
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void on_size_request (Gtk::Requisition*);
private:
void dpi_reset ();
Glib::RefPtr<Pango::Layout> _layout;
float _chn[17];
int _length;
int _extent;
Orientation _orientation;
};
class EventMonitor : public CairoWidget
{
public:
EventMonitor (Orientation);
void update (ARDOUR::CircularEventBuffer&);
void clear ();
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void on_size_request (Gtk::Requisition*);
private:
void dpi_reset ();
ARDOUR::CircularEventBuffer::EventList _l;
Glib::RefPtr<Pango::Layout> _layout;
int _width;
int _height;
Orientation _orientation;
};
Gtk::Box* _box;
ARDOUR::DataType _dt;
ArdourWidgets::FastMeter* _audio_meter;
InputScope* _audio_scope;
EventMeter* _midi_meter;
EventMonitor* _midi_monitor;
};
#endif

View File

@ -3312,6 +3312,21 @@ RCOptionEditor::RCOptionEditor ()
sigc::mem_fun (UIConfiguration::instance(), &UIConfiguration::set_save_export_mixer_screenshot)
));
add_option (S_("Preferences|Metering"), new OptionEditorHeading (_("Input Meter Layout")));
ComboOption<InputMeterLayout>* iml = new ComboOption<InputMeterLayout> (
"input-meter-layout",
_("Input Meter Layout"),
sigc::mem_fun (UIConfiguration::instance(), &UIConfiguration::get_input_meter_layout),
sigc::mem_fun (UIConfiguration::instance(), &UIConfiguration::set_input_meter_layout)
);
iml->add (LayoutAutomatic, _("Automatic"));
iml->add (LayoutHorizontal, _("Horizontal"));
iml->add (LayoutVertical, _("Vertical"));
add_option (S_("Preferences|Metering"), iml);
/* TRANSPORT & Sync */
add_option (_("Transport"), new OptionEditorHeading (_("General")));

View File

@ -0,0 +1,163 @@
/*
* Copyright (C) 2020 Robin Gareus <robin@gareus.org>
*
* 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.
*/
#include "recorder_group_tabs.h"
#include "recorder_ui.h"
#include "track_record_axis.h"
#include "ui_config.h"
#ifdef WAF_BUILD
#include "gtk2ardour-config.h"
#endif
using namespace ARDOUR;
RecorderGroupTabs::RecorderGroupTabs (RecorderUI* parent)
: _recorder (parent)
{
}
double
RecorderGroupTabs::primary_coordinate (double, double y) const
{
return y;
}
double
RecorderGroupTabs::extent () const
{
return get_height ();
}
std::list<GroupTabs::Tab>
RecorderGroupTabs::compute_tabs () const
{
std::list<Tab> tabs;
Tab tab;
tab.from = 0;
tab.group = 0;
int32_t y = 0;
std::list<TrackRecordAxis*> recorders = _recorder->visible_recorders ();
for (std::list<TrackRecordAxis*>::const_iterator i = recorders.begin (); i != recorders.end (); ++i) {
if ((*i)->route ()->presentation_info ().hidden ()) { // marked_for_display ()
continue;
}
RouteGroup* g = (*i)->route_group ();
if (g != tab.group) {
if (tab.group) {
tab.to = y;
tabs.push_back (tab);
}
tab.from = y;
tab.group = g;
if (g) {
tab.color = group_color (g);
}
}
y += (*i)->get_height ();
}
if (tab.group) {
tab.to = y;
tabs.push_back (tab);
}
return tabs;
}
RouteList
RecorderGroupTabs::routes_for_tab (Tab const* t) const
{
RouteList routes;
int32_t y = 0;
std::list<TrackRecordAxis*> recorders = _recorder->visible_recorders ();
for (std::list<TrackRecordAxis*>::const_iterator i = recorders.begin (); i != recorders.end (); ++i) {
if (y >= t->to) {
/* tab finishes before this track starts */
break;
}
double const h = y + (*i)->get_height () / 2;
if (t->from < h && t->to > h) {
routes.push_back ((*i)->route ());
}
y += (*i)->get_height ();
}
return routes;
}
void
RecorderGroupTabs::draw_tab (cairo_t* cr, Tab const& tab)
{
double const arc_radius = get_width ();
double r, g, b, a;
if (tab.group && tab.group->is_active ()) {
Gtkmm2ext::color_to_rgba (tab.color, r, g, b, a);
} else {
Gtkmm2ext::color_to_rgba (UIConfiguration::instance ().color ("inactive group tab"), r, g, b, a);
}
a = 1.0;
cairo_set_source_rgba (cr, r, g, b, a);
cairo_move_to (cr, 0, tab.from + arc_radius);
cairo_arc (cr, get_width (), tab.from + arc_radius, arc_radius, M_PI, 3 * M_PI / 2);
cairo_line_to (cr, get_width (), tab.to);
cairo_arc (cr, get_width (), tab.to - arc_radius, arc_radius, M_PI / 2, M_PI);
cairo_line_to (cr, 0, tab.from + arc_radius);
cairo_fill (cr);
if (tab.group && (tab.to - tab.from) > arc_radius) {
int text_width, text_height;
Glib::RefPtr<Pango::Layout> layout;
layout = Pango::Layout::create (get_pango_context ());
layout->set_ellipsize (Pango::ELLIPSIZE_MIDDLE);
layout->set_text (tab.group->name ());
layout->set_width ((tab.to - tab.from - arc_radius) * PANGO_SCALE);
layout->get_pixel_size (text_width, text_height);
cairo_move_to (cr, (get_width () - text_height) * .5, (text_width + tab.to + tab.from) * .5);
Gtkmm2ext::Color c = Gtkmm2ext::contrasting_text_color (Gtkmm2ext::rgba_to_color (r, g, b, a));
Gtkmm2ext::color_to_rgba (c, r, g, b, a);
cairo_set_source_rgb (cr, r, g, b);
cairo_save (cr);
cairo_rotate (cr, M_PI * -.5);
pango_cairo_show_layout (cr, layout->gobj ());
cairo_restore (cr);
}
}
RouteList
RecorderGroupTabs::selected_routes () const
{
RouteList rl;
return rl;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Robin Gareus <robin@gareus.org>
*
* 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 __gtk_ardour_recorder_group_tabs_h__
#define __gtk_ardour_recorder_group_tabs_h__
#include "group_tabs.h"
class RecorderUI;
class RecorderGroupTabs : public GroupTabs
{
public:
RecorderGroupTabs (RecorderUI*);
private:
std::list<Tab> compute_tabs () const;
void draw_tab (cairo_t*, Tab const&);
ARDOUR::RouteList routes_for_tab (Tab const*) const;
ARDOUR::RouteList selected_routes () const;
double primary_coordinate (double, double) const;
double extent () const;
RecorderUI* _recorder;
};
#endif /* __gtk_ardour_recorder_ui_h__ */

File diff suppressed because it is too large Load Diff

View File

@ -19,12 +19,36 @@
#ifndef __gtk_ardour_recorder_ui_h__
#define __gtk_ardour_recorder_ui_h__
#include <boost/shared_ptr.hpp>
#include <list>
#include <vector>
#include <gtkmm/alignment.h>
#include <gtkmm/box.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/sizegroup.h>
#include <gtkmm/table.h>
#include "pbd/natsort.h"
#include "ardour/session_handle.h"
#include "ardour/circular_buffer.h"
#include "ardour/types.h"
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/cairo_widget.h"
#include "widgets/ardour_button.h"
#include "widgets/ardour_spacer.h"
#include "widgets/frame.h"
#include "widgets/pane.h"
#include "widgets/tabbable.h"
#include "input_port_monitor.h"
class TrackRecordAxis;
class RecorderGroupTabs;
class RecorderUI : public ArdourWidgets::Tabbable, public ARDOUR::SessionHandlePtr, public PBD::ScopedConnectionList
{
public:
@ -39,16 +63,181 @@ public:
Gtk::Window* use_own_window (bool and_fill_it);
void spill_port (std::string const&);
void add_track (std::string const&);
private:
void load_bindings ();
void register_actions ();
void update_title ();
void session_going_away ();
void parameter_changed (std::string const&);
void presentation_info_changed (PBD::PropertyChange const&);
void gui_extents_changed ();
Gtkmm2ext::Bindings* bindings;
void start_updating ();
void stop_updating ();
bool update_meters ();
void add_or_remove_io (ARDOUR::DataType, std::vector<std::string>, bool);
void update_io_widget_labels ();
Gtk::VBox _content;
void initial_track_display ();
void add_routes (ARDOUR::RouteList&);
void remove_route (TrackRecordAxis*);
void update_rec_table_layout ();
void update_spacer_width (Gtk::Allocation&, TrackRecordAxis*);
void set_connections (std::string const&);
void port_connected_or_disconnected (std::string, std::string);
void port_pretty_name_changed (std::string);
void meter_area_size_allocate (Gtk::Allocation&);
void meter_area_size_request (GtkRequisition*);
void meter_area_layout ();
bool scroller_button_release (GdkEventButton*);
void arm_all ();
void arm_none ();
void peak_reset ();
void update_sensitivity ();
void update_recordstate ();
void new_track_for_port (ARDOUR::DataType, std::string const&);
static int calc_columns (int child_width, int parent_width);
Gtkmm2ext::Bindings* bindings;
Gtk::VBox _content;
Gtk::HBox _toolbar;
Gtk::Table _button_table;
ArdourWidgets::VPane _pane;
Gtk::ScrolledWindow _rec_scroller;
Gtk::VBox _rec_container;
Gtk::HBox _rec_groups;
Gtk::VBox _rec_area;
Gtk::ScrolledWindow _meter_scroller;
Gtk::VBox _meter_area;
Gtk::Table _meter_table;
Gtk::EventBox _scroller_base;
ArdourWidgets::ArdourHSpacer _toolbar_sep;
Gtk::Label _recs_label;
ArdourWidgets::ArdourButton _btn_rec_all;
ArdourWidgets::ArdourButton _btn_rec_none;
ArdourWidgets::ArdourButton _btn_rec_forget;
ArdourWidgets::ArdourButton _btn_peak_reset;
ArdourWidgets::ArdourButton _monitor_in_button;
ArdourWidgets::ArdourButton _monitor_disk_button;
ArdourWidgets::ArdourButton _auto_input_button;
Glib::RefPtr<Gtk::SizeGroup> _toolbar_button_height;
Glib::RefPtr<Gtk::SizeGroup> _toolbar_recarm_width;
Glib::RefPtr<Gtk::SizeGroup> _toolbar_monitoring_width;
int _meter_box_width;
int _meter_area_cols;
bool _vertical;
std::set<std::string> _spill_port_names;
sigc::connection _fast_screen_update_connection;
sigc::connection _ruler_width_update_connection;
PBD::ScopedConnectionList _engine_connections;
class RecRuler : public CairoWidget , public ARDOUR::SessionHandlePtr
{
public:
RecRuler ();
void playhead_position_changed (ARDOUR::samplepos_t);
void set_gui_extents (samplepos_t, samplepos_t);
void set_right_edge (int);
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void on_size_request (Gtk::Requisition*);
bool on_button_press_event (GdkEventButton*);
private:
Glib::RefPtr<Pango::Layout> _layout;
int _time_width;
int _time_height;
int _width;
ARDOUR::samplecnt_t _left;
ARDOUR::samplecnt_t _right;
};
class InputPort : public Gtk::EventBox
{
public:
InputPort (std::string const&, ARDOUR::DataType, RecorderUI*, bool vertical = false);
~InputPort ();
void set_frame_label (std::string const&);
void set_connections (ARDOUR::WeakRouteList);
void setup_name ();
bool spill (bool);
bool spilled () const;
void update_rec_stat ();
ARDOUR::DataType data_type () const;
std::string const& name () const;
void update (float, float); // FastMeter
void update (float const*); // EventMeter
void update (ARDOUR::CircularSampleBuffer&); // InputScope
void update (ARDOUR::CircularEventBuffer&); // EventMonitor
void clear ();
bool operator< (InputPort const& o) const {
if (_dt == o._dt) {
return PBD::naturally_less (_port_name.c_str (), o._port_name.c_str ());
}
return _dt < (uint32_t) o._dt;
}
private:
void rename_port ();
ARDOUR::DataType _dt;
InputPortMonitor _monitor;
Gtk::Alignment _alignment;
ArdourWidgets::Frame _frame;
ArdourWidgets::ArdourButton _spill_button;
ArdourWidgets::ArdourButton _name_button;
Gtk::Label _name_label;
ArdourWidgets::ArdourButton _add_button;
std::string _port_name;
ARDOUR::WeakRouteList _connected_routes;
static bool _size_groups_initialized;
static Glib::RefPtr<Gtk::SizeGroup> _name_size_group;
static Glib::RefPtr<Gtk::SizeGroup> _spill_size_group;
static Glib::RefPtr<Gtk::SizeGroup> _button_size_group;
static Glib::RefPtr<Gtk::SizeGroup> _monitor_size_group;
};
struct InputPortPtrSort {
bool operator() (boost::shared_ptr<InputPort> const& a, boost::shared_ptr<InputPort> const& b) const {
return *a < *b;
}
};
typedef std::map<std::string, boost::shared_ptr<InputPort> > InputPortMap;
RecRuler _ruler;
Gtk::EventBox _space;
Gtk::HBox _ruler_box;
ArdourWidgets::ArdourHSpacer _ruler_sep;
RecorderGroupTabs* _rec_group_tabs;
InputPortMap _input_ports;
std::list<TrackRecordAxis*> _recorders;
std::list<TrackRecordAxis*> _visible_recorders;
public:
/* only for RecorderGroupTab */
std::list<TrackRecordAxis*> visible_recorders () const;
};
#endif /* __gtk_ardour_recorder_ui_h__ */

View File

@ -0,0 +1,703 @@
/*
* Copyright (C) 2020 Robin Gareus <robin@gareus.org>
*
* 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.
*/
#include <list>
#include <sigc++/bind.h>
#include "pbd/unwind.h"
#include "ardour/logmeter.h"
#include "ardour/meter.h"
#include "ardour/playlist.h"
#include "ardour/route.h"
#include "ardour/route_group.h"
#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/track.h"
#include "ardour/audio_track.h"
#include "ardour/midi_track.h"
#include "gtkmm2ext/colors.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/keyboard.h"
#include "gtkmm2ext/rgb_macros.h"
#include "gtkmm2ext/utils.h"
#include "widgets/tooltips.h"
#include "ardour_window.h"
#include "context_menu_helper.h"
#include "editor_cursors.h"
#include "group_tabs.h"
#include "gui_thread.h"
#include "level_meter.h"
#include "meter_patterns.h"
#include "public_editor.h"
#include "route_group_menu.h"
#include "timers.h"
#include "ui_config.h"
#include "utils.h"
#include "track_record_axis.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ArdourMeter;
using namespace ArdourWidgets;
using namespace ARDOUR_UI_UTILS;
using namespace PBD;
using namespace Gtk;
using namespace Gtkmm2ext;
using namespace std;
PBD::Signal1<void, TrackRecordAxis*> TrackRecordAxis::CatchDeletion;
#define PX_SCALE(pxmin, dflt) rint (std::max ((double)pxmin, (double)dflt* UIConfiguration::instance ().get_ui_scale ()))
bool TrackRecordAxis::_size_group_initialized = false;
Glib::RefPtr<Gtk::SizeGroup> TrackRecordAxis::_track_number_size_group;
TrackRecordAxis::TrackRecordAxis (Session* s, boost::shared_ptr<ARDOUR::Route> rt)
: SessionHandlePtr (s)
, RouteUI (s)
, _clear_meters (true)
, _route_ops_menu (0)
, _input_button (true)
, _playlist_button (S_("RTAV|P"))
, _vseparator (1.0)
, _ctrls_button_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
, _monitor_ctrl_size_group (Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH))
, _track_summary (rt)
{
if (!_size_group_initialized) {
_size_group_initialized = true;
_track_number_size_group = Gtk::SizeGroup::create (Gtk::SIZE_GROUP_BOTH);
}
RouteUI::set_route (rt);
_route->DropReferences.connect (_route_connections, invalidator (*this), boost::bind (&TrackRecordAxis::self_delete, this), gui_context ());
UI::instance ()->theme_changed.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().ColorsChanged.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().DPIReset.connect (sigc::mem_fun (*this, &TrackRecordAxis::on_theme_changed));
UIConfiguration::instance ().ParameterChanged.connect (sigc::mem_fun (*this, &TrackRecordAxis::parameter_changed));
Config->ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
s->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
PublicEditor::instance().playhead_cursor()->PositionChanged.connect (*this, invalidator (*this), boost::bind (&TrackSummary::playhead_position_changed, &_track_summary, _1), gui_context());
ResetAllPeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_peak_display));
ResetRoutePeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_route_peak_display));
ResetGroupPeakDisplays.connect (sigc::mem_fun (*this, &TrackRecordAxis::reset_group_peak_display));
_number_label.set_name ("tracknumber label");
_number_label.set_elements ((ArdourButton::Element) (ArdourButton::Edge | ArdourButton::Body | ArdourButton::Text | ArdourButton::Inactive));
_number_label.set_alignment (.5, .5);
_number_label.set_fallthrough_to_parent (true);
_number_label.signal_button_press_event().connect (sigc::mem_fun(*this, &TrackRecordAxis::route_ops_click), false);
PropertyList* plist = new PropertyList();
plist->add (ARDOUR::Properties::group_mute, true);
plist->add (ARDOUR::Properties::group_solo, true);
_playlist_button.set_name ("route button");
_playlist_button.signal_button_press_event().connect (sigc::mem_fun(*this, &TrackRecordAxis::playlist_click), false);
_level_meter = new LevelMeterVBox (s);
_level_meter->set_meter (_route->shared_peak_meter ().get ());
_level_meter->clear_meters ();
_level_meter->setup_meters (120, 12);
name_label.set_name (X_("TrackNameEditor"));
name_label.set_alignment (0.0, 0.5);
name_label.set_width_chars (12);
_input_button.set_sizing_text ("Capture_8888");
_input_button.set_route (rt, this);
parameter_changed ("editor-stereo-only-meters");
parameter_changed ("time-axis-name-ellipsize-mode");
/* force the track header buttons into a boxy grid-shape */
rec_enable_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::TrackHeader | ArdourButton::ForceBoxy));
monitor_disk_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
monitor_input_button->set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
_playlist_button.set_tweaks(ArdourButton::Tweaks(ArdourButton::TrackHeader | ArdourButton::ForceBoxy));
_input_button.set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy));
_number_label.set_tweaks(ArdourButton::Tweaks(ArdourButton::ForceBoxy | ArdourButton::ForceFlat));
_ctrls.attach (*rec_enable_button, 1, 2, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_input_button, 2, 3, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_playlist_button, 3, 4, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*monitor_input_button, 5, 6, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*monitor_disk_button, 6, 7, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (*_level_meter, 7, 8, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
_ctrls.attach (_number_label, 8, 9, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_vseparator, 9, 10, 0, 1, Gtk::SHRINK, Gtk::FILL, 0, 0);
_ctrls.attach (_track_summary, 10, 11, 0, 1, Gtk::EXPAND|FILL, Gtk::FILL, 1, 0);
set_tooltip (*mute_button, _("Mute"));
set_tooltip (*rec_enable_button, _("Record"));
set_tooltip (_playlist_button, _("Playlist")); // playlist_tip ()
set_name_label ();
update_sensitivity ();
_track_number_size_group->add_widget (_number_label);
_ctrls_button_size_group->add_widget (*rec_enable_button);
_ctrls_button_size_group->add_widget (*mute_button);
_ctrls_button_size_group->add_widget (_playlist_button);
_monitor_ctrl_size_group->add_widget (*monitor_input_button);
_monitor_ctrl_size_group->add_widget (*monitor_disk_button);
pack_start (_ctrls, false, false);
rec_enable_button->show ();
monitor_input_button->show ();
monitor_disk_button->show ();
mute_button->show ();
_level_meter->show ();
_playlist_button.show();
_number_label.show ();
name_label.show ();
_input_button.show ();
_track_summary.show ();
_vseparator.show ();
_ctrls.show ();
}
TrackRecordAxis::~TrackRecordAxis ()
{
delete _level_meter;
delete _route_ops_menu;
CatchDeletion (this);
}
void
TrackRecordAxis::self_delete ()
{
delete this;
}
void
TrackRecordAxis::set_session (Session* s)
{
RouteUI::set_session (s);
if (!s) {
return;
}
s->config.ParameterChanged.connect (*this, invalidator (*this), ui_bind (&TrackRecordAxis::parameter_changed, this, _1), gui_context ());
}
void
TrackRecordAxis::blink_rec_display (bool onoff)
{
RouteUI::blink_rec_display (onoff);
}
std::string
TrackRecordAxis::state_id () const
{
if (_route) {
return string_compose ("recctrl %1", _route->id ().to_s ());
} else {
return string ();
}
}
void
TrackRecordAxis::set_button_names ()
{
mute_button->set_text (S_("Mute|M"));
#if 0
monitor_input_button->set_text (S_("MonitorInput|I"));
monitor_disk_button->set_text (S_("MonitorDisk|D"));
#else
monitor_input_button->set_text (_("In"));
monitor_disk_button->set_text (_("Disk"));
#endif
/* Solo/Listen is N/A */
}
void
TrackRecordAxis::route_property_changed (const PropertyChange& what_changed)
{
if (!what_changed.contains (ARDOUR::Properties::name)) {
return;
}
ENSURE_GUI_THREAD (*this, &TrackRecordAxis::route_property_changed, what_changed);
set_name_label ();
set_tooltip (*_level_meter, _route->name ());
}
void
TrackRecordAxis::route_color_changed ()
{
_number_label.set_fixed_colors (gdk_color_to_rgba (color ()), gdk_color_to_rgba (color ()));
}
void
TrackRecordAxis::on_theme_changed ()
{
}
void
TrackRecordAxis::on_size_request (Gtk::Requisition* r)
{
VBox::on_size_request (r);
}
void
TrackRecordAxis::on_size_allocate (Gtk::Allocation& a)
{
VBox::on_size_allocate (a);
}
void
TrackRecordAxis::parameter_changed (std::string const& p)
{
if (p == "editor-stereo-only-meters") {
#if 0
if (UIConfiguration::instance ().get_editor_stereo_only_meters ()) {
_level_meter->set_max_audio_meter_count (2);
} else {
_level_meter->set_max_audio_meter_count (0);
}
#endif
} else if (p == "time-axis-name-ellipsize-mode") {
set_name_ellipsize_mode ();
}
}
string
TrackRecordAxis::name () const
{
return _route->name ();
}
Gdk::Color
TrackRecordAxis::color () const
{
return RouteUI::route_color ();
}
void
TrackRecordAxis::set_name_label ()
{
string x = _route->name ();
if (x != name_label.get_text ()) {
name_label.set_text (x);
}
set_tooltip (name_label, _route->name ());
const int64_t track_number = _route->track_number ();
assert (track_number > 0);
_number_label.set_text (PBD::to_string (track_number));
}
void
TrackRecordAxis::route_active_changed ()
{
RouteUI::route_active_changed ();
update_sensitivity ();
}
void
TrackRecordAxis::map_frozen ()
{
RouteUI::map_frozen ();
switch (track()->freeze_state()) {
case Track::Frozen:
_playlist_button.set_sensitive (false);
break;
default:
_playlist_button.set_sensitive (true);
break;
}
update_sensitivity ();
}
void
TrackRecordAxis::update_sensitivity ()
{
bool en = _route->active ();
monitor_input_button->set_sensitive (en);
monitor_disk_button->set_sensitive (en);
_input_button.set_sensitive (en);
_ctrls.set_sensitive (en);
if (!is_track() || track()->mode() != ARDOUR::Normal) {
_playlist_button.set_sensitive (false);
}
}
void
TrackRecordAxis::set_gui_extents (samplepos_t s, samplepos_t e)
{
_track_summary.set_gui_extents (s, e);
}
bool
TrackRecordAxis::rec_extent (samplepos_t& s, samplepos_t& e) const
{
return _track_summary.rec_extent (s, e);
}
int
TrackRecordAxis::summary_xpos () const
{
return _ctrls.get_width () - _track_summary.get_width ();
}
int
TrackRecordAxis::summary_width () const
{
return _track_summary.get_width ();
}
void
TrackRecordAxis::fast_update ()
{
if (_clear_meters) {
_level_meter->clear_meters ();
_clear_meters = false;
}
_level_meter->update_meters ();
}
void
TrackRecordAxis::reset_route_peak_display (Route* route)
{
if (_route && _route.get () == route) {
reset_peak_display ();
}
}
void
TrackRecordAxis::reset_group_peak_display (RouteGroup* group)
{
if (_route && group == _route->route_group ()) {
reset_peak_display ();
}
}
void
TrackRecordAxis::reset_peak_display ()
{
_route->shared_peak_meter ()->reset_max ();
_clear_meters = true;
}
bool
TrackRecordAxis::playlist_click (GdkEventButton* ev)
{
if (ev->button != 1) {
return true;
}
build_playlist_menu ();
_route->session ().selection().select_stripable_and_maybe_group (_route, false, true, 0);
Gtkmm2ext::anchored_menu_popup (playlist_action_menu, &_playlist_button, "", 1, ev->time);
return true;
}
bool
TrackRecordAxis::route_ops_click (GdkEventButton* ev)
{
if (ev->button != 3 ) {
return false;
}
build_route_ops_menu ();
_route->session ().selection().select_stripable_and_maybe_group (_route, false, true, 0);
Gtkmm2ext::anchored_menu_popup (_route_ops_menu, &_number_label, "", 1, ev->time);
return true;
}
void
TrackRecordAxis::build_route_ops_menu ()
{
using namespace Menu_Helpers;
delete _route_ops_menu;
_route_ops_menu = new Menu;
_route_ops_menu->set_name ("ArdourContextMenu");
MenuList& items = _route_ops_menu->items ();
items.push_back (MenuElem (_("Color..."), sigc::mem_fun (*this, &RouteUI::choose_color)));
items.push_back (MenuElem (_("Comments..."), sigc::mem_fun (*this, &RouteUI::open_comment_editor)));
items.push_back (MenuElem (_("Inputs..."), sigc::mem_fun (*this, &RouteUI::edit_input_configuration)));
items.push_back (MenuElem (_("Outputs..."), sigc::mem_fun (*this, &RouteUI::edit_output_configuration)));
items.push_back (SeparatorElem());
items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteUI::route_rename)));
/* do not allow rename if the track is record-enabled */
items.back().set_sensitive (!is_track() || !track()->rec_enable_control()->get_value());
}
/* ****************************************************************************/
TrackRecordAxis::TrackSummary::TrackSummary (boost::shared_ptr<ARDOUR::Route> r)
: _start (0)
, _end (480000)
, _xscale (1)
, _last_playhead (0)
, _rec_updating (false)
, _rec_active (false)
{
_track = boost::dynamic_pointer_cast<Track> (r);
assert (_track);
_track->PlaylistChanged.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::playlist_changed, this), gui_context ());
_track->playlist()->ContentsChanged.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::playlist_changed, this), gui_context ());
_track->presentation_info().PropertyChanged.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::property_changed, this, _1), gui_context ());
_track->rec_enable_control()->Changed.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().TransportStateChange.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().TransportLooped.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
_track->session().RecordStateChanged.connect (_connections, invalidator (*this), boost::bind (&TrackSummary::maybe_setup_rec_box, this), gui_context());
}
TrackRecordAxis::TrackSummary::~TrackSummary ()
{
_rec_active = false;
if (_rec_updating) {
_screen_update_connection.disconnect();
}
}
void
TrackRecordAxis::TrackSummary::render (Cairo::RefPtr<Cairo::Context> const& cr, cairo_rectangle_t* r)
{
cr->rectangle (r->x, r->y, r->width, r->height);
cr->clip ();
RouteGroup* g = _track->route_group ();
if (g && g->is_color()) {
Gtkmm2ext::set_source_rgba (cr, GroupTabs::group_color (g));
} else {
Gtkmm2ext::set_source_rgba (cr, _track->presentation_info ().color ());
}
double w = get_width();
double h = get_height();
double ht = h - 2;
double yc = 1 + ht / 2.;
cr->set_line_width (ht);
_track->playlist()->foreach_region(sigc::bind (sigc::mem_fun (*this, &TrackSummary::render_region), cr, yc));
/* Record Boxes */
if (_rec_rects.size () > 0) {
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color_mod("recording rect", "recording_rect"));
for (std::vector<RecInfo>::const_iterator i = _rec_rects.begin (); i != _rec_rects.end (); ++i) {
const samplepos_t rs = i->capture_start;
const samplecnt_t re = i->capture_end;
if (re > rs) {
cr->move_to (sample_to_xpos (rs), yc);
cr->line_to (sample_to_xpos (re), yc);
cr->stroke ();
}
}
}
/* top & btm border */
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color ("neutral:backgroundest"));
cr->set_line_width (1.0);
cr->move_to (0, 0.5);
cr->line_to (w, 0.5);
cr->stroke ();
cr->move_to (0, h);
cr->line_to (w, h);
cr->stroke ();
/* Playhead */
Gtkmm2ext::set_source_rgba (cr, UIConfiguration::instance().color ("play head"));
const double phx = sample_to_xpos (PublicEditor::instance().playhead_cursor ()->current_sample());
cr->set_line_width (1.0);
cr->move_to (floor (phx) + .5, 0);
cr->line_to (floor (phx) + .5, h);
cr->stroke ();
_last_playhead = phx;
}
void
TrackRecordAxis::TrackSummary::render_region (boost::shared_ptr<ARDOUR::Region> r, Cairo::RefPtr<Cairo::Context> const& cr, double y)
{
const samplepos_t rp = r->position ();
const samplecnt_t rl = r->length ();
if (rp > _start) {
cr->move_to (sample_to_xpos (rp), y);
} else {
cr->move_to (0, y);
}
if (rp + rl > _start) {
cr->line_to (sample_to_xpos (rp + rl), y);
cr->stroke ();
} else {
cr->begin_new_path ();
}
}
void
TrackRecordAxis::TrackSummary::maybe_setup_rec_box ()
{
if (_track->session ().transport_stopped_or_stopping () || !(_track->session ().transport_rolling () || _track->session ().get_record_enabled ())) {
/* stopped, or not roll/rec */
if (_rec_updating) {
_rec_rects.clear ();
_screen_update_connection.disconnect();
_rec_updating = false;
_rec_active = false;
set_dirty ();
}
return;
}
if (!_track->rec_enable_control()->get_value() || !_track->session ().actively_recording ()) {
/* rolling but not (or no longer) recording [yet] */
_rec_active = false;
return;
}
if (!_rec_active) {
const samplepos_t rs = _track->current_capture_start ();
_rec_rects.push_back (RecInfo (rs, rs));
}
_rec_active = true;
if (!_rec_updating) {
_screen_update_connection.disconnect();
_screen_update_connection = Timers::rapid_connect (sigc::mem_fun(*this, &TrackSummary::update_rec_box));
_rec_updating = true;
}
}
void
TrackRecordAxis::TrackSummary::update_rec_box ()
{
if (_rec_active && _rec_rects.size () > 0) {
RecInfo& rect = _rec_rects.back ();
rect.capture_start = _track->current_capture_start ();
rect.capture_end = _track->current_capture_end ();
set_dirty ();
}
}
void
TrackRecordAxis::TrackSummary::playhead_position_changed (samplepos_t p)
{
int const o = _last_playhead;
int const n = sample_to_xpos (p);
if (o != n) {
int a = max (2, min (o, n));
int b = max (o, n);
cairo_rectangle_t r;
r.x = a - 2;
r.y = 0;
r.width = b - a + 4;
r.height = get_height ();
set_dirty (&r);
}
}
void
TrackRecordAxis::TrackSummary::playlist_changed ()
{
set_dirty ();
}
void
TrackRecordAxis::TrackSummary::property_changed (PropertyChange const& what_changed)
{
if (what_changed.contains (Properties::color)) {
set_dirty ();
}
}
void
TrackRecordAxis::TrackSummary::on_size_request (Gtk::Requisition* req)
{
req->width = 200;
req->height = 16;
}
void
TrackRecordAxis::TrackSummary::on_size_allocate (Gtk::Allocation& a)
{
CairoWidget::on_size_allocate (a);
if (_end > _start) {
_xscale = static_cast<double> (a.get_width ()) / (_end - _start);
}
}
void
TrackRecordAxis::TrackSummary::set_gui_extents (samplepos_t start, samplepos_t end)
{
if (_start == start && _end == end) {
return;
}
_start = start;
_end = end;
_xscale = static_cast<double> (get_width ()) / (_end - _start);
set_dirty ();
}
bool
TrackRecordAxis::TrackSummary::on_button_press_event (GdkEventButton* ev)
{
if (_track->session ().actively_recording ()) {
return false;
}
// use _start + ev->x / _xscale
_track->session ().request_locate (_start + (double) (_end - _start) * ev->x / get_width ());
return true;
}
bool
TrackRecordAxis::TrackSummary::rec_extent (samplepos_t& start, samplepos_t& end) const
{
if (_rec_rects.size () == 0) {
return false;
}
for (std::vector<RecInfo>::const_iterator i = _rec_rects.begin (); i != _rec_rects.end (); ++i) {
start = std::min (start, i->capture_start);
end = std::max (end, i->capture_end);
}
return true;
}

View File

@ -0,0 +1,187 @@
/*
* Copyright (C) 2020 Robin Gareus <robin@gareus.org>
*
* 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 __gtkardour_track_record_axis_h_
#define __gtkardour_track_record_axis_h_
#include <cmath>
#include <vector>
#include <gtkmm/alignment.h>
#include <gtkmm/box.h>
#include <gtkmm/drawingarea.h>
#include <gtkmm/eventbox.h>
#include <gtkmm/separator.h>
#include <gtkmm/sizegroup.h>
#include "pbd/stateful.h"
#include "ardour/ardour.h"
#include "ardour/types.h"
#include "widgets/ardour_button.h"
#include "widgets/ardour_spacer.h"
#include "io_button.h"
#include "level_meter.h"
#include "route_ui.h"
namespace ARDOUR
{
class Region;
class Route;
class RouteGroup;
class Session;
class Track;
}
class LevelMeterVBox;
class RouteGroupMenu;
class TrackRecordAxis : public Gtk::VBox, public AxisView, public RouteUI
{
public:
TrackRecordAxis (ARDOUR::Session*, boost::shared_ptr<ARDOUR::Route>);
~TrackRecordAxis ();
/* AxisView */
std::string name () const;
Gdk::Color color () const;
boost::shared_ptr<ARDOUR::Stripable> stripable() const {
return RouteUI::stripable();
}
void set_session (ARDOUR::Session* s);
void fast_update ();
void set_gui_extents (samplepos_t, samplepos_t);
bool rec_extent (samplepos_t&, samplepos_t&) const;
int summary_xpos () const;
int summary_width () const;
static PBD::Signal1<void, TrackRecordAxis*> CatchDeletion;
protected:
void self_delete ();
void on_size_allocate (Gtk::Allocation&);
void on_size_request (Gtk::Requisition*);
/* AxisView */
std::string state_id () const;
/* route UI */
void set_button_names ();
void blink_rec_display (bool onoff);
void route_active_changed ();
void map_frozen ();
private:
void on_theme_changed ();
void parameter_changed (std::string const& p);
void set_name_label ();
void reset_peak_display ();
void reset_route_peak_display (ARDOUR::Route*);
void reset_group_peak_display (ARDOUR::RouteGroup*);
bool playlist_click (GdkEventButton*);
bool route_ops_click (GdkEventButton*);
void build_route_ops_menu ();
/* RouteUI */
void route_property_changed (const PBD::PropertyChange&);
void route_color_changed ();
void update_sensitivity ();
bool _clear_meters;
Gtk::Table _ctrls;
Gtk::Menu* _route_ops_menu;
LevelMeterVBox* _level_meter;
IOButton _input_button;
ArdourWidgets::ArdourButton _number_label;
ArdourWidgets::ArdourButton _playlist_button;
ArdourWidgets::ArdourVSpacer _vseparator;
Glib::RefPtr<Gtk::SizeGroup> _ctrls_button_size_group;
Glib::RefPtr<Gtk::SizeGroup> _monitor_ctrl_size_group;
static bool _size_group_initialized;
static Glib::RefPtr<Gtk::SizeGroup> _track_number_size_group;
PBD::ScopedConnectionList _route_connections;
struct RecInfo {
RecInfo (samplepos_t s, samplepos_t e)
: capture_start (s)
, capture_end (e)
{}
samplepos_t capture_start;
samplepos_t capture_end;
};
class TrackSummary : public CairoWidget
{
public:
TrackSummary (boost::shared_ptr<ARDOUR::Route>);
~TrackSummary ();
void playhead_position_changed (samplepos_t p);
void set_gui_extents (samplepos_t, samplepos_t);
bool rec_extent (samplepos_t&, samplepos_t&) const;
protected:
void render (Cairo::RefPtr<Cairo::Context> const&, cairo_rectangle_t*);
void on_size_request (Gtk::Requisition*);
void on_size_allocate (Gtk::Allocation&);
bool on_button_press_event (GdkEventButton*);
private:
void render_region (boost::shared_ptr<ARDOUR::Region>, Cairo::RefPtr<Cairo::Context> const&, double);
void playlist_changed ();
void property_changed (PBD::PropertyChange const&);
void maybe_setup_rec_box ();
void update_rec_box ();
double sample_to_xpos (samplepos_t p) const
{
return (p - _start) * _xscale;
}
boost::shared_ptr<ARDOUR::Track> _track;
samplepos_t _start;
samplepos_t _end;
double _xscale;
double _last_playhead;
bool _rec_updating;
bool _rec_active;
std::vector<RecInfo> _rec_rects;
PBD::ScopedConnectionList _connections;
sigc::connection _screen_update_connection;
};
TrackSummary _track_summary;
};
#endif

View File

@ -84,6 +84,7 @@ UI_CONFIG_VARIABLE (float, meter_hold, "meter-hold", 100.0f)
UI_CONFIG_VARIABLE (ARDOUR::VUMeterStandard, meter_vu_standard, "meter-vu-standard", ARDOUR::MeteringVUstandard)
UI_CONFIG_VARIABLE (ARDOUR::MeterLineUp, meter_line_up_level, "meter-line-up-level", ARDOUR::MeteringLineUp18)
UI_CONFIG_VARIABLE (ARDOUR::MeterLineUp, meter_line_up_din, "meter-line-up-din", ARDOUR::MeteringLineUp15)
UI_CONFIG_VARIABLE (ARDOUR::InputMeterLayout, input_meter_layout, "input-meter-layout", ARDOUR::LayoutAutomatic)
UI_CONFIG_VARIABLE (float, meter_peak, "meter-peak", 0.0f)
UI_CONFIG_VARIABLE (bool, meter_style_led, "meter-style-led", false)
UI_CONFIG_VARIABLE (bool, show_editor_meter, "show-editor-meter", true)

View File

@ -127,6 +127,7 @@ gtk2_ardour_sources = [
'group_tabs.cc',
'gui_object.cc',
'idleometer.cc',
'input_port_monitor.cc',
'insert_remove_time_dialog.cc',
'instrument_selector.cc',
'interthread_progress_window.cc',
@ -225,6 +226,7 @@ gtk2_ardour_sources = [
'public_editor.cc',
'quantize_dialog.cc',
'rc_option_editor.cc',
'recorder_group_tabs.cc',
'recorder_ui.cc',
'region_editor.cc',
'region_gain_line.cc',
@ -279,6 +281,7 @@ gtk2_ardour_sources = [
'time_info_box.cc',
'time_selection.cc',
'timers.cc',
'track_record_axis.cc',
'track_selection.cc',
'track_view_list.cc',
'transform_dialog.cc',