13
0
livetrax/gtk2_ardour/plugin_eq_gui.cc
David Robillard e0aaed6d65 *** NEW CODING POLICY ***
All #include statements that include a header that is a part of a library
bundled with ardour MUST use quotes, not angle brackets.

Do this:

#include "ardour/types.h"

NOT this:

#include <ardour/types.h>

Rationale:

This is best practice in general, to ensure we include the local version
and not the system version.  That quotes mean "local" (in some sense)
and angle brackets mean "system" (in some sense) is a ubiquitous
convention and IIRC right in the C spec somewhere.

More pragmatically, this is required by (my) waf (stuff) for dependencies
to work correctly.  That is:

!!! FAILURE TO DO THIS CAN RESULT IN BROKEN BUILDS !!!

Failure to comply is punishable by death by torture. :)

P.S. It's not that dramatic in all cases, but this (in combination with some
GCC flags specific to the include type) is the best way I have found to be
absolutely 100% positive the local ones are being used (and we definitely
want to be absolutely 100% positive on that one).


git-svn-id: svn://localhost/ardour2/branches/3.0@4655 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-02-25 18:26:51 +00:00

725 lines
17 KiB
C++

/*
Copyright (C) 2008 Paul Davis
Author: Sampo Savolainen
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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "plugin_eq_gui.h"
#include "fft.h"
#include "ardour_ui.h"
#include "gui_thread.h"
#include "ardour/audio_buffer.h"
#include "ardour/data_type.h"
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include <iostream>
#include <cmath>
PluginEqGui::PluginEqGui(boost::shared_ptr<ARDOUR::PluginInsert> pluginInsert)
: _min_dB(-12.0),
_max_dB(+12.0),
_step_dB(3.0),
_impulse_fft(0),
_signal_input_fft(0),
_signal_output_fft(0),
_plugin_insert(pluginInsert)
{
_signal_analysis_running = false;
_samplerate = ARDOUR_UI::instance()->the_session()->frame_rate();
_plugin = _plugin_insert->get_impulse_analysis_plugin();
_plugin->activate();
set_buffer_size(4096, 16384);
//set_buffer_size(4096, 4096);
_log_coeff = (1.0 - 2.0 * (1000.0/(_samplerate/2.0))) / powf(1000.0/(_samplerate/2.0), 2.0);
_log_max = log10f(1 + _log_coeff);
// Setup analysis drawing area
_analysis_scale_surface = 0;
_analysis_area = new Gtk::DrawingArea();
_analysis_width = 500.0;
_analysis_height = 500.0;
_analysis_area->set_size_request(_analysis_width, _analysis_height);
_analysis_area->signal_expose_event().connect( sigc::mem_fun (*this, &PluginEqGui::expose_analysis_area));
_analysis_area->signal_size_allocate().connect( sigc::mem_fun (*this, &PluginEqGui::resize_analysis_area));
// dB selection
dBScaleModel = Gtk::ListStore::create(dBColumns);
dBScaleCombo = new Gtk::ComboBox(dBScaleModel);
dBScaleCombo -> set_title("dB scale");
#define ADD_DB_ROW(MIN,MAX,STEP,NAME) \
{ \
Gtk::TreeModel::Row row = *(dBScaleModel->append()); \
row[dBColumns.dBMin] = (MIN); \
row[dBColumns.dBMax] = (MAX); \
row[dBColumns.dBStep] = (STEP); \
row[dBColumns.name] = NAME; \
}
ADD_DB_ROW( -6, +6, 1, "-6dB .. +6dB");
ADD_DB_ROW(-12, +12, 3, "-12dB .. +12dB");
ADD_DB_ROW(-24, +24, 5, "-24dB .. +24dB");
ADD_DB_ROW(-36, +36, 6, "-36dB .. +36dB");
ADD_DB_ROW(-64, +64,12, "-64dB .. +64dB");
#undef ADD_DB_ROW
dBScaleCombo -> pack_start(dBColumns.name);
dBScaleCombo -> set_active(1);
dBScaleCombo -> signal_changed().connect( sigc::mem_fun(*this, &PluginEqGui::change_dB_scale) );
Gtk::Label *dBComboLabel = new Gtk::Label("dB scale");
Gtk::HBox *dBSelectBin = new Gtk::HBox(false, 5);
dBSelectBin->add( *manage(dBComboLabel));
dBSelectBin->add( *manage(dBScaleCombo));
// Phase checkbutton
_phase_button = new Gtk::CheckButton("Show phase");
_phase_button->set_active(true);
_phase_button->signal_toggled().connect( sigc::mem_fun(*this, &PluginEqGui::redraw_scales));
// populate table
attach( *manage(_analysis_area), 1, 3, 1, 2);
attach( *manage(dBSelectBin), 1, 2, 2, 3, Gtk::SHRINK, Gtk::SHRINK);
attach( *manage(_phase_button), 2, 3, 2, 3, Gtk::SHRINK, Gtk::SHRINK);
// Connect the realtime signal collection callback
_plugin_insert->AnalysisDataGathered.connect( sigc::mem_fun(*this, &PluginEqGui::signal_collect_callback ));
}
PluginEqGui::~PluginEqGui()
{
if (_analysis_scale_surface) {
cairo_surface_destroy (_analysis_scale_surface);
}
delete _impulse_fft;
delete _signal_input_fft;
delete _signal_output_fft;
_plugin->deactivate();
// all gui objects are *manage'd by the inherited Table object
}
void
PluginEqGui::on_hide()
{
stop_updating();
Gtk::Table::on_hide();
}
void
PluginEqGui::stop_updating()
{
if (_update_connection.connected()) {
_update_connection.disconnect();
}
}
void
PluginEqGui::start_updating()
{
if (!_update_connection.connected() && is_visible()) {
_update_connection = Glib::signal_timeout().connect( sigc::mem_fun(this, &PluginEqGui::timeout_callback), 250);
}
}
void
PluginEqGui::on_show()
{
Gtk::Table::on_show();
start_updating();
Gtk::Widget *toplevel = get_toplevel();
if (!toplevel) {
std::cerr << "No toplevel widget for PluginEqGui?!?!" << std::endl;
}
if (!_window_unmap_connection.connected()) {
_window_unmap_connection = toplevel->signal_unmap().connect( sigc::mem_fun(this, &PluginEqGui::stop_updating));
}
if (!_window_map_connection.connected()) {
_window_map_connection = toplevel->signal_map().connect( sigc::mem_fun(this, &PluginEqGui::start_updating));
}
}
void
PluginEqGui::change_dB_scale()
{
Gtk::TreeModel::iterator iter = dBScaleCombo -> get_active();
Gtk::TreeModel::Row row;
if(iter && (row = *iter)) {
_min_dB = row[dBColumns.dBMin];
_max_dB = row[dBColumns.dBMax];
_step_dB = row[dBColumns.dBStep];
redraw_scales();
}
}
void
PluginEqGui::redraw_scales()
{
if (_analysis_scale_surface) {
cairo_surface_destroy (_analysis_scale_surface);
_analysis_scale_surface = 0;
}
_analysis_area->queue_draw();
// TODO: Add graph legend!
}
void
PluginEqGui::set_buffer_size(uint32_t size, uint32_t signal_size)
{
if (_buffer_size == size && _signal_buffer_size == signal_size)
return;
FFT *tmp1 = _impulse_fft;
FFT *tmp2 = _signal_input_fft;
FFT *tmp3 = _signal_output_fft;
try {
_impulse_fft = new FFT(size);
_signal_input_fft = new FFT(signal_size);
_signal_output_fft = new FFT(signal_size);
} catch( ... ) {
// Don't care about lost memory, we're screwed anyhow
_impulse_fft = tmp1;
_signal_input_fft = tmp2;
_signal_output_fft = tmp3;
throw;
}
delete tmp1;
delete tmp2;
delete tmp3;
_buffer_size = size;
_signal_buffer_size = signal_size;
// These are for impulse analysis only, the signal analysis uses the actual
// number of I/O's for the plugininsert
uint32_t inputs = _plugin->get_info()->n_inputs.n_audio();
uint32_t outputs = _plugin->get_info()->n_outputs.n_audio();
// buffers for the signal analysis are ensured inside PluginInsert
uint32_t n_chans = std::max(inputs, outputs);
_bufferset.ensure_buffers(ARDOUR::DataType::AUDIO, n_chans, _buffer_size);
ARDOUR::ChanCount chanCount(ARDOUR::DataType::AUDIO, n_chans);
_bufferset.set_count(chanCount);
}
void
PluginEqGui::resize_analysis_area(Gtk::Allocation& size)
{
_analysis_width = (float)size.get_width();
_analysis_height = (float)size.get_height();
if (_analysis_scale_surface) {
cairo_surface_destroy (_analysis_scale_surface);
_analysis_scale_surface = 0;
}
}
bool
PluginEqGui::timeout_callback()
{
if (!_signal_analysis_running) {
_signal_analysis_running = true;
_plugin_insert -> collect_signal_for_analysis(_signal_buffer_size);
}
run_impulse_analysis();
return true;
}
void
PluginEqGui::signal_collect_callback(ARDOUR::BufferSet *in, ARDOUR::BufferSet *out)
{
ENSURE_GUI_THREAD(bind (mem_fun (*this, &PluginEqGui::signal_collect_callback), in, out));
_signal_input_fft ->reset();
_signal_output_fft->reset();
for (uint32_t i = 0; i < _plugin_insert->input_streams().n_audio(); ++i) {
_signal_input_fft ->analyze(in ->get_audio(i).data(_signal_buffer_size, 0), FFT::HANN);
}
for (uint32_t i = 0; i < _plugin_insert->output_streams().n_audio(); ++i) {
_signal_output_fft->analyze(out->get_audio(i).data(_signal_buffer_size, 0), FFT::HANN);
}
_signal_input_fft ->calculate();
_signal_output_fft->calculate();
_signal_analysis_running = false;
// This signals calls expose_analysis_area()
_analysis_area->queue_draw();
}
void
PluginEqGui::run_impulse_analysis()
{
uint32_t inputs = _plugin->get_info()->n_inputs.n_audio();
uint32_t outputs = _plugin->get_info()->n_outputs.n_audio();
// Create the impulse, can't use silence() because consecutive calls won't work
for (uint32_t i = 0; i < inputs; ++i) {
ARDOUR::AudioBuffer &buf = _bufferset.get_audio(i);
ARDOUR::Sample *d = buf.data(_buffer_size, 0);
memset(d, 0, sizeof(ARDOUR::Sample)*_buffer_size);
*d = 1.0;
}
uint32_t x,y;
x=y=0;
_plugin->connect_and_run(_bufferset, x, y, _buffer_size, (nframes_t)0);
// Analyze all output buffers
_impulse_fft->reset();
for (uint32_t i = 0; i < outputs; ++i) {
_impulse_fft->analyze(_bufferset.get_audio(i).data(_buffer_size, 0));
}
// normalize the output
_impulse_fft->calculate();
// This signals calls expose_analysis_area()
_analysis_area->queue_draw();
}
bool
PluginEqGui::expose_analysis_area(GdkEventExpose *evt)
{
redraw_analysis_area();
return false;
}
void
PluginEqGui::draw_analysis_scales(cairo_t *ref_cr)
{
// TODO: check whether we need rounding
_analysis_scale_surface = cairo_surface_create_similar(cairo_get_target(ref_cr),
CAIRO_CONTENT_COLOR,
_analysis_width,
_analysis_height);
cairo_t *cr = cairo_create (_analysis_scale_surface);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_rectangle(cr, 0.0, 0.0, _analysis_width, _analysis_height);
cairo_fill(cr);
draw_scales_power(_analysis_area, cr);
if (_phase_button->get_active()) {
draw_scales_phase(_analysis_area, cr);
}
cairo_destroy(cr);
}
void
PluginEqGui::redraw_analysis_area()
{
cairo_t *cr;
cr = gdk_cairo_create(GDK_DRAWABLE(_analysis_area->get_window()->gobj()));
if (_analysis_scale_surface == 0) {
draw_analysis_scales(cr);
}
cairo_copy_page(cr);
cairo_set_source_surface(cr, _analysis_scale_surface, 0.0, 0.0);
cairo_paint(cr);
if (_phase_button->get_active()) {
plot_impulse_phase(_analysis_area, cr);
}
plot_impulse_amplitude(_analysis_area, cr);
// TODO: make this optional
plot_signal_amplitude_difference(_analysis_area, cr);
cairo_destroy(cr);
}
#define PHASE_PROPORTION 0.5
void
PluginEqGui::draw_scales_phase(Gtk::Widget *w, cairo_t *cr)
{
float y;
cairo_font_extents_t extents;
cairo_font_extents(cr, &extents);
char buf[256];
cairo_text_extents_t t_ext;
for (uint32_t i = 0; i < 3; i++) {
y = _analysis_height/2.0 - (float)i*(_analysis_height/8.0)*PHASE_PROPORTION;
cairo_set_source_rgb(cr, .8, .9, 0.2);
if (i == 0) {
snprintf(buf,256, "0\u00b0");
} else {
snprintf(buf,256, "%d\u00b0", (i * 45));
}
cairo_text_extents(cr, buf, &t_ext);
cairo_move_to(cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent);
cairo_show_text(cr, buf);
if (i == 0)
continue;
cairo_set_source_rgba(cr, .8, .9, 0.2, 0.6/(float)i);
cairo_move_to(cr, 0.0, y);
cairo_line_to(cr, _analysis_width, y);
y = _analysis_height/2.0 + (float)i*(_analysis_height/8.0)*PHASE_PROPORTION;
// label
snprintf(buf,256, "-%d\u00b0", (i * 45));
cairo_set_source_rgb(cr, .8, .9, 0.2);
cairo_text_extents(cr, buf, &t_ext);
cairo_move_to(cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent);
cairo_show_text(cr, buf);
// line
cairo_set_source_rgba(cr, .8, .9, 0.2, 0.6/(float)i);
cairo_move_to(cr, 0.0, y);
cairo_line_to(cr, _analysis_width, y);
cairo_set_line_width (cr, 0.25 + 1.0/(float)(i+1));
cairo_stroke(cr);
}
}
void
PluginEqGui::plot_impulse_phase(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
cairo_set_source_rgba(cr, 0.95, 0.3, 0.2, 1.0);
for (uint32_t i = 0; i < _impulse_fft->bins()-1; i++) {
// x coordinate of bin i
x = log10f(1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
x *= _analysis_width;
y = _analysis_height/2.0 - (_impulse_fft->phase_at_bin(i)/M_PI)*(_analysis_height/2.0)*PHASE_PROPORTION;
if ( i == 0 ) {
cairo_move_to(cr, x, y);
avgY = 0;
avgNum = 0;
} else if (rint(x) > prevX || i == _impulse_fft->bins()-1 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
//cairo_line_to(cr, prevX, avgY/(float)avgNum);
avgY = 0;
avgNum = 0;
}
prevX = rint(x);
avgY += y;
avgNum++;
}
cairo_set_line_width (cr, 2.0);
cairo_stroke(cr);
}
void
PluginEqGui::draw_scales_power(Gtk::Widget *w, cairo_t *cr)
{
static float scales[] = { 30.0, 70.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 15000.0, 20000.0, -1.0 };
float divisor = _samplerate / 2.0 / _impulse_fft->bins();
float x;
cairo_set_line_width (cr, 1.5);
cairo_set_font_size(cr, 9);
cairo_font_extents_t extents;
cairo_font_extents(cr, &extents);
// float fontXOffset = extents.descent + 1.0;
char buf[256];
for (uint32_t i = 0; scales[i] != -1.0; ++i) {
float bin = scales[i] / divisor;
x = log10f(1.0 + bin / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
x *= _analysis_width;
if (scales[i] < 1000.0) {
snprintf(buf, 256, "%0.0f", scales[i]);
} else {
snprintf(buf, 256, "%0.0fk", scales[i]/1000.0);
}
cairo_set_source_rgb(cr, 0.4, 0.4, 0.4);
//cairo_move_to(cr, x + fontXOffset, 3.0);
cairo_move_to(cr, x - extents.height, 3.0);
cairo_rotate(cr, M_PI / 2.0);
cairo_show_text(cr, buf);
cairo_rotate(cr, -M_PI / 2.0);
cairo_stroke(cr);
cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
cairo_move_to(cr, x, _analysis_height);
cairo_line_to(cr, x, 0.0);
cairo_stroke(cr);
}
float y;
//double dashes[] = { 1.0, 3.0, 4.5, 3.0 };
double dashes[] = { 3.0, 5.0 };
for (float dB = 0.0; dB < _max_dB; dB += _step_dB ) {
snprintf(buf, 256, "+%0.0f", dB );
y = ( _max_dB - dB) / ( _max_dB - _min_dB );
//std::cerr << " y = " << y << std::endl;
y *= _analysis_height;
if (dB != 0.0) {
cairo_set_source_rgb(cr, 0.4, 0.4, 0.4);
cairo_move_to(cr, 1.0, y + extents.height + 1.0);
cairo_show_text(cr, buf);
cairo_stroke(cr);
}
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0, y);
cairo_line_to(cr, _analysis_width, y);
cairo_stroke(cr);
if (dB == 0.0) {
cairo_set_dash(cr, dashes, 2, 0.0);
}
}
for (float dB = - _step_dB; dB > _min_dB; dB -= _step_dB ) {
snprintf(buf, 256, "%0.0f", dB );
y = ( _max_dB - dB) / ( _max_dB - _min_dB );
y *= _analysis_height;
cairo_set_source_rgb(cr, 0.4, 0.4, 0.4);
cairo_move_to(cr, 1.0, y - extents.descent - 1.0);
cairo_show_text(cr, buf);
cairo_stroke(cr);
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
cairo_move_to(cr, 0, y);
cairo_line_to(cr, _analysis_width, y);
cairo_stroke(cr);
}
cairo_set_dash(cr, 0, 0, 0.0);
}
inline float
power_to_dB(float a)
{
return 10.0 * log10f(a);
}
void
PluginEqGui::plot_impulse_amplitude(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_set_line_width (cr, 2.5);
for (uint32_t i = 0; i < _impulse_fft->bins()-1; i++) {
// x coordinate of bin i
x = log10f(1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max;
x *= _analysis_width;
float yCoeff = ( power_to_dB(_impulse_fft->power_at_bin(i)) - _min_dB) / (_max_dB - _min_dB);
y = _analysis_height - _analysis_height*yCoeff;
if ( i == 0 ) {
cairo_move_to(cr, x, y);
avgY = 0;
avgNum = 0;
} else if (rint(x) > prevX || i == _impulse_fft->bins()-1 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
//cairo_line_to(cr, prevX, avgY/(float)avgNum);
avgY = 0;
avgNum = 0;
}
prevX = rint(x);
avgY += y;
avgNum++;
}
cairo_stroke(cr);
}
void
PluginEqGui::plot_signal_amplitude_difference(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
cairo_set_line_width (cr, 2.5);
for (uint32_t i = 0; i < _signal_input_fft->bins()-1; i++) {
// x coordinate of bin i
x = log10f(1.0 + (float)i / (float)_signal_input_fft->bins() * _log_coeff) / _log_max;
x *= _analysis_width;
float power_out = power_to_dB(_signal_output_fft->power_at_bin(i));
float power_in = power_to_dB(_signal_input_fft ->power_at_bin(i));
float power = power_out - power_in;
// for SaBer
/*
double p = 10.0 * log10( 1.0 + (double)_signal_output_fft->power_at_bin(i) - (double)
- _signal_input_fft ->power_at_bin(i));
//p *= 1000000.0;
float power = (float)p;
if ( (i % 1000) == 0) {
std::cerr << i << ": " << power << std::endl;
}
*/
if (std::isinf(power)) {
if (power < 0) {
power = _min_dB - 1.0;
} else {
power = _max_dB - 1.0;
}
} else if (std::isnan(power)) {
power = _min_dB - 1.0;
}
float yCoeff = ( power - _min_dB) / (_max_dB - _min_dB);
y = _analysis_height - _analysis_height*yCoeff;
if ( i == 0 ) {
cairo_move_to(cr, x, y);
avgY = 0;
avgNum = 0;
} else if (rint(x) > prevX || i == _impulse_fft->bins()-1 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
avgY = 0;
avgNum = 0;
}
prevX = rint(x);
avgY += y;
avgNum++;
}
cairo_stroke(cr);
}