Improvements on the FFT analysis tool

- Enable FFT analysis by default
 - FFT graph is now in cairo
 - The window is now a window instead of a dialog
 - Analysis window can be resized
 - The view can be switched between normalized 
   and an absolute value
 - The minimum and maximum values for a graph can 
   be shown



git-svn-id: svn://localhost/ardour2/branches/2.0-ongoing@3135 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Sampo Savolainen 2008-03-04 17:56:17 +00:00
parent 5f5c3fe8d2
commit f45474e688
7 changed files with 302 additions and 111 deletions

View File

@ -38,7 +38,7 @@ opts.AddOptions(
EnumOption('DIST_TARGET', 'Build target for cross compiling packagers', 'auto', allowed_values=('auto', 'i386', 'i686', 'x86_64', 'powerpc', 'tiger', 'panther', 'leopard', 'none' ), ignorecase=2),
BoolOption('DMALLOC', 'Compile and link using the dmalloc library', 0),
BoolOption('EXTRA_WARN', 'Compile with -Wextra, -ansi, and -pedantic. Might break compilation. For pedants', 0),
BoolOption('FFT_ANALYSIS', 'Include FFT analysis window', 0),
BoolOption('FFT_ANALYSIS', 'Include FFT analysis window', 1),
BoolOption('FPU_OPTIMIZATION', 'Build runtime checked assembler code', 1),
BoolOption('LIBLO', 'Compile with support for liblo library', 1),
BoolOption('NLS', 'Set to turn on i18n support', 1),
@ -527,7 +527,7 @@ if env['FFT_ANALYSIS']:
conf = Configure(libraries['fftw3'])
if conf.CheckHeader ('fftw3.h') == False:
print ('FFT Analysis cannot be compiled without the FFTW3 headers, which do not seem to be installed')
print ('Ardour cannot be compiled without the FFTW3 headers, which do not seem to be installed')
sys.exit (1)
conf.Finish()

View File

@ -41,8 +41,10 @@
using namespace ARDOUR;
using namespace PBD;
AnalysisWindow::AnalysisWindow()
: ArdourDialog(_("analysis window")),
AnalysisWindow::AnalysisWindow() :
show_minmax_button (_("Show frequency power range")),
show_normalized_button (_("Normalize values")),
source_selection_label (_("Signal source")),
source_selection_ranges_rb (_("Selected ranges")),
@ -52,8 +54,11 @@ AnalysisWindow::AnalysisWindow()
display_model_composite_separate_rb (_("Composite graphs for each track")),
display_model_composite_all_tracks_rb (_("Composite graph of all tracks")),
fft_graph (2048)
fft_graph (16384)
{
set_name(_("FFT analysis window"));
set_title(_("FFT analysis window"));
track_list_ready = false;
// Left side: track list + controls
@ -124,17 +129,31 @@ AnalysisWindow::AnalysisWindow()
bind ( mem_fun(*this, &AnalysisWindow::display_model_changed), &display_model_composite_all_tracks_rb));
}
vbox.pack_start(hseparator2, false, false);
// Analyze button
refresh_button.set_name("EditorGTKButton");
refresh_button.set_label(_("Analyze data"));
refresh_button.set_label(_("Re-analyze data"));
refresh_button.signal_clicked().connect ( bind ( mem_fun(*this, &AnalysisWindow::analyze_data), &refresh_button));
vbox.pack_start(refresh_button, false, false, 10);
// Feature checkboxes
// minmax
show_minmax_button.signal_toggled().connect( mem_fun(*this, &AnalysisWindow::show_minmax_changed));
vbox.pack_start(show_minmax_button, false, false);
// normalize
show_normalized_button.signal_toggled().connect( mem_fun(*this, &AnalysisWindow::show_normalized_changed));
vbox.pack_start(show_normalized_button, false, false);
hbox.pack_start(vbox);
hbox.pack_start(vbox, Gtk::PACK_SHRINK);
// Analysis window on the right
fft_graph.ensure_style();
@ -144,11 +163,9 @@ AnalysisWindow::AnalysisWindow()
// And last we pack the hbox
get_vbox()->pack_start(hbox);
add(hbox);
show_all();
track_list.show_all();
get_vbox()->show_all();
}
AnalysisWindow::~AnalysisWindow()
@ -156,6 +173,18 @@ AnalysisWindow::~AnalysisWindow()
}
void
AnalysisWindow::show_minmax_changed()
{
fft_graph.set_show_minmax(show_minmax_button.get_active());
}
void
AnalysisWindow::show_normalized_changed()
{
fft_graph.set_show_normalized(show_normalized_button.get_active());
}
void
AnalysisWindow::set_rangemode()
{

View File

@ -31,6 +31,7 @@
#include <gtkmm/label.h>
#include <gtkmm/liststore.h>
#include <gtkmm/separator.h>
#include <gtkmm/window.h>
#include <gtkmm2ext/dndtreeview.h>
@ -42,7 +43,7 @@
#include "fft_result.h"
class AnalysisWindow : public ArdourDialog
class AnalysisWindow : public Gtk::Window
{
public:
AnalysisWindow ();
@ -55,12 +56,18 @@ class AnalysisWindow : public ArdourDialog
void analyze ();
const void set_session(ARDOUR::Session *session) { _session = session; };
private:
ARDOUR::Session *_session;
void clear_tracklist();
void source_selection_changed (Gtk::RadioButton *);
void display_model_changed (Gtk::RadioButton *);
void show_minmax_changed ();
void show_normalized_changed ();
void analyze_data (Gtk::Button *);
@ -88,7 +95,8 @@ class AnalysisWindow : public ArdourDialog
Gtk::TreeView track_list;
Gtk::Label source_selection_label;
Gtk::RadioButton source_selection_ranges_rb;
Gtk::RadioButton source_selection_regions_rb;
@ -98,9 +106,13 @@ class AnalysisWindow : public ArdourDialog
Gtk::RadioButton display_model_composite_separate_rb;
Gtk::RadioButton display_model_composite_all_tracks_rb;
Gtk::HSeparator hseparator2;
Gtk::Button refresh_button;
Gtk::CheckButton show_minmax_button;
Gtk::CheckButton show_normalized_button;
// The graph
FFTGraph fft_graph;

View File

@ -51,6 +51,9 @@ FFTGraph::FFTGraph(int windowSize)
_a_window = 0;
_show_minmax = false;
_show_normalized = false;
setWindowSize(windowSize);
}
@ -151,23 +154,6 @@ FFTGraph::prepareResult(Gdk::Color color, string trackname)
return res;
}
void
FFTGraph::analyze(float *window, float *composite)
{
int i;
// Copy the data and apply the hanning window
for (i = 0; i < _windowSize; i++) {
_in[i] = window[ i ] * _hanning[ i ];
}
fftwf_execute(_plan);
composite[0] += (_out[0] * _out[0]);
for (i=1; i < _dataSize - 1; i++) { // TODO: check with Jesse whether this is really correct
composite[i] += (_out[i] * _out[i]) + (_out[_windowSize-i] * _out[_windowSize-i]);
}
}
void
FFTGraph::set_analysis_window(AnalysisWindow *a_window)
@ -198,17 +184,18 @@ FFTGraph::draw_scales(Glib::RefPtr<Gdk::Window> window)
window->draw_line(white, h_margin, v_margin, h_margin, height - v_margin );
// Line 2
window->draw_line(white, width - h_margin, v_margin, width - h_margin, height - v_margin );
window->draw_line(white, width - h_margin + 1, v_margin, width - h_margin + 1, height - v_margin );
// Line 3
window->draw_line(white, h_margin, height - v_margin, width - h_margin, height - v_margin );
#define DB_METRIC_LENGTH 8
// Line 5
// Line 4
window->draw_line(white, h_margin - DB_METRIC_LENGTH, v_margin, h_margin, v_margin );
// Line 6
window->draw_line(white, width - h_margin, v_margin, width - h_margin + DB_METRIC_LENGTH, v_margin );
// Line 5
window->draw_line(white, width - h_margin + 1, v_margin, width - h_margin + DB_METRIC_LENGTH, v_margin );
if (graph_gc == 0) {
@ -229,8 +216,24 @@ FFTGraph::draw_scales(Glib::RefPtr<Gdk::Window> window)
// Draw logscale
int logscale_pos = 0;
int position_on_scale;
/* TODO, write better scales and change the log function so that octaves are of equal pixel length
float scale_points[10] = { 55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0, 14080.0, 28160.0 };
for (int x = 0; x < 10; x++) {
// i = 0.. _dataSize-1
float freq_at_bin = (SR/2.0) * ((double)i / (double)_dataSize);
freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)(currentScaleWidth - 1) );
}
*/
for (int x = 1; x < 8; x++) {
position_on_scale = (int)floor( (double)scaleWidth*(double)x/8.0);
position_on_scale = (int)floor( (double)currentScaleWidth*(double)x/8.0);
while (_logScale[logscale_pos] < position_on_scale)
logscale_pos++;
@ -251,7 +254,7 @@ FFTGraph::draw_scales(Glib::RefPtr<Gdk::Window> window)
layout->set_text(label);
window->draw_line(graph_gc, coord, v_margin, coord, height - v_margin);
window->draw_line(graph_gc, coord, v_margin, coord, height - v_margin - 1);
int width, height;
layout->get_pixel_size (width, height);
@ -268,12 +271,19 @@ FFTGraph::redraw()
Glib::Mutex::Lock lm (_a_window->track_list_lock);
draw_scales(get_window());
if (_a_window == 0)
return;
if (!_a_window->track_list_ready)
return;
cairo_t *cr;
cr = gdk_cairo_create(GDK_DRAWABLE(get_window()->gobj()));
cairo_set_line_width(cr, 1.5);
cairo_translate(cr, (float)v_margin + 1.0, (float)h_margin);
// Find "session wide" min & max
@ -300,17 +310,24 @@ FFTGraph::redraw()
max = res->maximum();
}
}
int graph_height = height - 2 * h_margin;
if (graph_gc == 0) {
graph_gc = GC::create( get_window() );
if (!_show_normalized) {
min = -150.0f;
max = 0.0f;
}
double pixels_per_db = (double)graph_height / (double)(max - min);
//int graph_height = height - 2 * h_margin;
float fft_pane_size_w = (float)(width - 2*v_margin) - 1.0;
float fft_pane_size_h = (float)(height - 2*h_margin);
double pixels_per_db = (double)fft_pane_size_h / (double)(max - min);
cairo_rectangle(cr, 0.0, 0.0, fft_pane_size_w, fft_pane_size_h);
cairo_clip(cr);
for (TreeIter i = track_rows.begin(); i != track_rows.end(); i++) {
TreeModel::Row row = *i;
@ -326,72 +343,104 @@ FFTGraph::redraw()
if (res->minimum() == res->maximum()) {
continue;
}
float mpp;
std::string name = row[_a_window->tlcols.trackname];
if (_show_minmax) {
mpp = -1000000.0;
cairo_set_source_rgba(cr, res->get_color().get_red_p(), res->get_color().get_green_p(), res->get_color().get_blue_p(), 0.30);
cairo_move_to(cr, 0.5f + (float)_logScale[0], 0.5f + (float)( fft_pane_size_h - (int)floor( (res->maxAt(0) - min) * pixels_per_db) ));
// Draw the line of maximum values
for (int x = 1; x < res->length(); x++) {
if (res->maxAt(x) > mpp)
mpp = res->maxAt(x);
mpp = fmax(mpp, min);
mpp = fmin(mpp, max);
// If the next point on the log scale is at the same location,
// don't draw yet
if (x + 1 < res->length() && _logScale[x] == _logScale[x + 1]) {
continue;
}
float X = 0.5f + (float)_logScale[x];
float Y = 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - min) * pixels_per_db) );
cairo_line_to(cr, X, Y);
mpp = -1000000.0;
}
mpp = +10000000.0;
// Draw back to the start using the minimum value
for (int x = res->length()-1; x >= 0; x--) {
if (res->minAt(x) < mpp)
mpp = res->minAt(x);
mpp = fmax(mpp, min);
mpp = fmin(mpp, max);
// If the next point on the log scale is at the same location,
// don't draw yet
if (x - 1 > 0 && _logScale[x] == _logScale[x - 1]) {
continue;
}
float X = 0.5f + (float)_logScale[x];
float Y = 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - min) * pixels_per_db) );
cairo_line_to(cr, X, Y );
mpp = +10000000.0;
}
cairo_close_path(cr);
cairo_fill(cr);
}
// Set color from track
graph_gc->set_rgb_fg_color( res->get_color() );
cairo_set_source_rgb(cr, res->get_color().get_red_p(), res->get_color().get_green_p(), res->get_color().get_blue_p());
float mpp = -1000000.0;
int prevx = 0;
float prevSample = min;
for (int x = 0; x < res->length() - 1; x++) {
mpp = -1000000.0;
cairo_move_to(cr, 0.5, fft_pane_size_h-0.5);
for (int x = 0; x < res->length(); x++) {
if (res->sampleAt(x) > mpp)
mpp = res->sampleAt(x);
if (res->avgAt(x) > mpp)
mpp = res->avgAt(x);
mpp = fmax(mpp, min);
mpp = fmin(mpp, max);
// If the next point on the log scale is at the same location,
// don't draw yet
if (x + 1 < res->length() &&
_logScale[x] == _logScale[x + 1]) {
if (x + 1 < res->length() && _logScale[x] == _logScale[x + 1]) {
continue;
}
get_window()->draw_line(
graph_gc,
v_margin + 1 + prevx,
graph_height - (int)floor( (prevSample - min) * pixels_per_db) + h_margin - 1,
v_margin + 1 + _logScale[x],
graph_height - (int)floor( (mpp - min) * pixels_per_db) + h_margin - 1);
prevx = _logScale[x];
prevSample = mpp;
cairo_line_to(cr, 0.5f + (float)_logScale[x], 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - min) * pixels_per_db) ));
mpp = -1000000.0;
}
cairo_stroke(cr);
}
cairo_destroy(cr);
}
void
FFTGraph::on_size_request(Gtk::Requisition* requisition)
{
width = scaleWidth + h_margin * 2;
height = scaleHeight + 2 + v_margin * 2;
width = max(requisition->width, minScaleWidth + h_margin * 2);
height = max(requisition->height, minScaleHeight + 2 + v_margin * 2);
if (_logScale != 0) {
free(_logScale);
}
_logScale = (int *) malloc(sizeof(int) * _dataSize);
float SR = 44100;
float FFT_START = SR/(double)_dataSize;
float FFT_END = SR/2.0;
float FFT_RANGE = log( FFT_END / FFT_START);
float pixel = 0;
for (int i = 0; i < _dataSize; i++) {
float freq_at_bin = (SR/2.0) * ((double)i / (double)_dataSize);
float freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)scaleWidth );
while (freq_at_bin > freq_at_pixel) {
pixel++;
freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)scaleWidth );
}
_logScale[i] = (int)floor(pixel);
//printf("logscale at %d = %3.3f, freq_at_pixel %3.3f, freq_at_bin %3.3f, scaleWidth %d\n", i, pixel, freq_at_pixel, freq_at_bin, scaleWidth);
}
update_size();
requisition->width = width;;
requisition->height = height;
@ -403,7 +452,32 @@ FFTGraph::on_size_allocate(Gtk::Allocation & alloc)
width = alloc.get_width();
height = alloc.get_height();
DrawingArea::on_size_allocate (alloc);
update_size();
DrawingArea::on_size_allocate (alloc);
}
void
FFTGraph::update_size()
{
currentScaleWidth = width - h_margin*2;
currentScaleHeight = height - 2 - v_margin*2;
float SR = 44100;
float FFT_START = SR/(double)_dataSize;
float FFT_END = SR/2.0;
float FFT_RANGE = log( FFT_END / FFT_START);
float pixel = 0;
for (int i = 0; i < _dataSize; i++) {
float freq_at_bin = (SR/2.0) * ((double)i / (double)_dataSize);
float freq_at_pixel;
pixel--;
do {
pixel++;
freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)(currentScaleWidth - 1) );
} while (freq_at_bin > freq_at_pixel);
_logScale[i] = (int)floor(pixel);
}
}

View File

@ -54,27 +54,34 @@ class FFTGraph : public Gtk::DrawingArea
void on_size_allocate(Gtk::Allocation & alloc);
FFTResult *prepareResult(Gdk::Color color, std::string trackname);
const void set_show_minmax (bool v) { _show_minmax = v; redraw(); };
const void set_show_normalized (bool v) { _show_normalized = v; redraw(); };
private:
void update_size();
void setWindowSize_internal(int windowSize);
void draw_scales(Glib::RefPtr<Gdk::Window> window);
static const int scaleWidth = 512;
static const int scaleHeight = 420;
static const int minScaleWidth = 512;
static const int minScaleHeight = 420;
int currentScaleWidth;
int currentScaleHeight;
static const int h_margin = 20;
static const int v_margin = 20;
Glib::RefPtr<Gdk::GC> graph_gc;
int width;
int height;
void analyze(float *window, float *composite);
int _windowSize;
int _dataSize;
Glib::RefPtr<Pango::Layout> layout;
Glib::RefPtr<Gdk::GC> graph_gc;
AnalysisWindow *_a_window;
fftwf_plan _plan;
@ -84,6 +91,9 @@ class FFTGraph : public Gtk::DrawingArea
float *_hanning;
int *_logScale;
bool _show_minmax;
bool _show_normalized;
friend class FFTResult;
};

View File

@ -37,8 +37,16 @@ FFTResult::FFTResult(FFTGraph *graph, Gdk::Color color, string trackname)
_averages = 0;
_data = (float *) malloc(sizeof(float) * _dataSize);
memset(_data,0,sizeof(float) * _dataSize);
_data_avg = (float *) malloc(sizeof(float) * _dataSize);
memset(_data_avg,0,sizeof(float) * _dataSize);
_data_min = (float *) malloc(sizeof(float) * _dataSize);
_data_max = (float *) malloc(sizeof(float) * _dataSize);
for (int i = 0; i < _dataSize; i++) {
_data_min[i] = FLT_MAX;
_data_max[i] = FLT_MIN;
}
_color = color;
_trackname = trackname;
@ -47,7 +55,34 @@ FFTResult::FFTResult(FFTGraph *graph, Gdk::Color color, string trackname)
void
FFTResult::analyzeWindow(float *window)
{
_graph->analyze(window, _data);
float *_hanning = _graph->_hanning;
float *_in = _graph->_in;
float *_out = _graph->_out;
int i;
// Copy the data and apply the hanning window
for (i = 0; i < _windowSize; i++) {
_in[i] = window[ i ] * _hanning[ i ];
}
fftwf_execute(_graph->_plan);
float b = _out[0] * _out[0];
_data_avg[0] += b;
if (b < _data_min[0]) _data_min[0] = b;
if (b > _data_max[0]) _data_max[0] = b;
for (i=1; i < _dataSize - 1; i++) { // TODO: check with Jesse whether this is really correct
b = (_out[i] * _out[i]);
_data_avg[i] += b; // + (_out[_windowSize-i] * _out[_windowSize-i]);, TODO: thanks to Stefan Kost
if (_data_min[i] > b) _data_min[i] = b;
if (_data_max[i] < b ) _data_max[i] = b;
}
_averages++;
}
@ -59,21 +94,27 @@ FFTResult::finalize()
_maximum = 0.0;
return;
}
// Average & scale
for (int i = 0; i < _dataSize; i++) {
_data[i] /= _averages;
_data[i] = 10.0f * log10f(_data[i]);
_data_avg[i] /= _averages;
_data_avg[i] = 10.0f * log10f(_data_avg[i]);
_data_min[i] = 10.0f * log10f(_data_min[i]);
if (_data_min[i] < -10000.0f) {
_data_min[i] = -10000.0f;
}
_data_max[i] = 10.0f * log10f(_data_max[i]);
}
// find min & max
_minimum = _maximum = _data[0];
_minimum = _maximum = _data_avg[0];
for (int i = 1; i < _dataSize; i++) {
if (_data[i] < _minimum && !isinf(_data[i])) {
_minimum = _data[i];
} else if (_data[i] > _maximum && !isinf(_data[i])) {
_maximum = _data[i];
if (_data_avg[i] < _minimum && !isinf(_data_avg[i])) {
_minimum = _data_avg[i];
} else if (_data_avg[i] > _maximum && !isinf(_data_avg[i])) {
_maximum = _data_avg[i];
}
}
@ -82,16 +123,36 @@ FFTResult::finalize()
FFTResult::~FFTResult()
{
free(_data);
free(_data_avg);
free(_data_min);
free(_data_max);
}
float
FFTResult::sampleAt(int x)
FFTResult::avgAt(int x)
{
if (x < 0 || x>= _dataSize)
return 0.0f;
return _data[x];
return _data_avg[x];
}
float
FFTResult::minAt(int x)
{
if (x < 0 || x>= _dataSize)
return 0.0f;
return _data_min[x];
}
float
FFTResult::maxAt(int x)
{
if (x < 0 || x>= _dataSize)
return 0.0f;
return _data_max[x];
}

View File

@ -41,7 +41,9 @@ class FFTResult
const int length() { return _dataSize; }
float sampleAt(int x);
float avgAt(int x);
float maxAt(int x);
float minAt(int x);
const float minimum() { return _minimum; }
const float maximum() { return _maximum; }
@ -53,10 +55,13 @@ class FFTResult
int _averages;
float* _data;
float* _data_avg;
float* _data_max;
float* _data_min;
float* _work;
int _windowSize;
int _windowSize;
int _dataSize;
float _minimum;