592 lines
16 KiB
C++
592 lines
16 KiB
C++
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|