ardour
fft_graph.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 Paul Davis
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #ifdef COMPILER_MSVC
21 #include <algorithm>
22 using std::min; using std::max;
23 #endif
24 
25 #include <iostream>
26 
27 #include <glibmm.h>
28 #include <glibmm/refptr.h>
29 
30 #include <gdkmm/gc.h>
31 
32 #include <gtkmm/widget.h>
33 #include <gtkmm/style.h>
34 #include <gtkmm/treemodel.h>
35 #include <gtkmm/treepath.h>
36 
37 #include "pbd/stl_delete.h"
38 
39 #include <math.h>
40 
41 #include "fft_graph.h"
42 #include "analysis_window.h"
43 
44 using namespace std;
45 using namespace Gtk;
46 using namespace Gdk;
47 
48 FFTGraph::FFTGraph(int windowSize)
49 {
50  _logScale = 0;
51 
52  _in = 0;
53  _out = 0;
54  _hanning = 0;
55  _logScale = 0;
56 
57  _a_window = 0;
58 
59  _show_minmax = false;
60  _show_normalized = false;
61 
62  setWindowSize(windowSize);
63 }
64 
65 void
66 FFTGraph::setWindowSize(int windowSize)
67 {
68  if (_a_window) {
69  Glib::Threads::Mutex::Lock lm (_a_window->track_list_lock);
70  setWindowSize_internal(windowSize);
71  } else {
72  setWindowSize_internal(windowSize);
73  }
74 }
75 
76 void
78 {
79  // remove old tracklist & graphs
80  if (_a_window) {
81  _a_window->clear_tracklist();
82  }
83 
84  _windowSize = windowSize;
85  _dataSize = windowSize / 2;
86  if (_in != 0) {
87  fftwf_destroy_plan(_plan);
88  free(_in);
89  _in = 0;
90  }
91 
92  if (_out != 0) {
93  free(_out);
94  _out = 0;
95  }
96 
97  if (_hanning != 0) {
98  free(_hanning);
99  _hanning = 0;
100  }
101 
102  if (_logScale != 0) {
103  free(_logScale);
104  _logScale = 0;
105  }
106 
107  // When destroying, window size is set to zero to free up memory
108  if (windowSize == 0)
109  return;
110 
111  // FFT input & output buffers
112  _in = (float *) fftwf_malloc(sizeof(float) * _windowSize);
113  _out = (float *) fftwf_malloc(sizeof(float) * _windowSize);
114 
115  // Hanning window
116  _hanning = (float *) malloc(sizeof(float) * _windowSize);
117 
118 
119  // normalize the window
120  double sum = 0.0;
121 
122  for (int i=0; i < _windowSize; i++) {
123  _hanning[i]=0.81f * ( 0.5f - (0.5f * (float) cos(2.0f * M_PI * (float)i / (float)(_windowSize))));
124  sum += _hanning[i];
125  }
126 
127  double isum = 1.0 / sum;
128 
129  for (int i=0; i < _windowSize; i++) {
130  _hanning[i] *= isum;
131  }
132 
133  _logScale = (int *) malloc(sizeof(int) * _dataSize);
134  //float count = 0;
135  for (int i = 0; i < _dataSize; i++) {
136  _logScale[i] = 0;
137  }
138  _plan = fftwf_plan_r2r_1d(_windowSize, _in, _out, FFTW_R2HC, FFTW_ESTIMATE);
139 }
140 
142 {
143  // This will free everything
144  setWindowSize(0);
145 }
146 
147 bool
148 FFTGraph::on_expose_event (GdkEventExpose* /*event*/)
149 {
150  redraw();
151  return true;
152 }
153 
154 FFTResult *
155 FFTGraph::prepareResult(Gdk::Color color, string trackname)
156 {
157  FFTResult *res = new FFTResult(this, color, trackname);
158 
159  return res;
160 }
161 
162 
163 void
165 {
166  _a_window = a_window;
167 }
168 
169 void
170 FFTGraph::draw_scales(Glib::RefPtr<Gdk::Window> window)
171 {
172 
173  Glib::RefPtr<Gtk::Style> style = get_style();
174  Glib::RefPtr<Gdk::GC> black = style->get_black_gc();
175  Glib::RefPtr<Gdk::GC> white = style->get_white_gc();
176 
177  window->draw_rectangle(black, true, 0, 0, width, height);
178 
188  // Line 1
189  window->draw_line(white, h_margin, v_margin, h_margin, height - v_margin );
190 
191  // Line 2
192  window->draw_line(white, width - h_margin + 1, v_margin, width - h_margin + 1, height - v_margin );
193 
194  // Line 3
195  window->draw_line(white, h_margin, height - v_margin, width - h_margin, height - v_margin );
196 
197 #define DB_METRIC_LENGTH 8
198  // Line 4
199  window->draw_line(white, h_margin - DB_METRIC_LENGTH, v_margin, h_margin, v_margin );
200 
201  // Line 5
202  window->draw_line(white, width - h_margin + 1, v_margin, width - h_margin + DB_METRIC_LENGTH, v_margin );
203 
204 
205 
206  if (graph_gc == 0) {
207  graph_gc = GC::create( get_window() );
208  }
209 
210  Color grey;
211 
212  grey.set_rgb_p(0.2, 0.2, 0.2);
213 
214  graph_gc->set_rgb_fg_color( grey );
215 
216  if (layout == 0) {
217  layout = create_pango_layout ("");
218  layout->set_font_description (get_style()->get_font());
219  }
220 
221  // Draw logscale
222  int logscale_pos = 0;
223  int position_on_scale;
224 
225 
226 /* TODO, write better scales and change the log function so that octaves are of equal pixel length
227  float scale_points[10] = { 55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0, 14080.0, 28160.0 };
228 
229  for (int x = 0; x < 10; x++) {
230 
231  // i = 0.. _dataSize-1
232  float freq_at_bin = (SR/2.0) * ((double)i / (double)_dataSize);
233 
234 
235 
236  freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)(currentScaleWidth - 1) );
237  }
238  */
239 
240  for (int x = 1; x < 8; x++) {
241  position_on_scale = (int)floor( (double)currentScaleWidth*(double)x/8.0);
242 
243  while (_logScale[logscale_pos] < position_on_scale)
244  logscale_pos++;
245 
246  int coord = (int)(v_margin + 1.0 + position_on_scale);
247 
248  int SR = 44100;
249 
250  int rate_at_pos = (int)((double)(SR/2) * (double)logscale_pos / (double)_dataSize);
251 
252  char buf[32];
253  if (rate_at_pos < 1000)
254  snprintf(buf,32,"%dHz",rate_at_pos);
255  else
256  snprintf(buf,32,"%dk",(int)floor( (float)rate_at_pos/(float)1000) );
257 
258  std::string label = buf;
259 
260  layout->set_text(label);
261 
262  window->draw_line(graph_gc, coord, v_margin, coord, height - v_margin - 1);
263 
264  int width, height;
265  layout->get_pixel_size (width, height);
266 
267  window->draw_layout(white, coord - width / 2, v_margin / 2, layout);
268 
269  }
270 
271 }
272 
273 void
275 {
276  Glib::Threads::Mutex::Lock lm (_a_window->track_list_lock);
277 
278  draw_scales(get_window());
279 
280 
281  if (_a_window == 0)
282  return;
283 
284  if (!_a_window->track_list_ready)
285  return;
286 
287  cairo_t *cr;
288  cr = gdk_cairo_create(GDK_DRAWABLE(get_window()->gobj()));
289  cairo_set_line_width(cr, 1.5);
290  cairo_translate(cr, (float)v_margin + 1.0, (float)h_margin);
291 
292 
293 
294  // Find "session wide" min & max
295  float minf = 1000000000000.0;
296  float maxf = -1000000000000.0;
297 
298  TreeNodeChildren track_rows = _a_window->track_list.get_model()->children();
299 
300  for (TreeIter i = track_rows.begin(); i != track_rows.end(); i++) {
301 
302  TreeModel::Row row = *i;
303  FFTResult *res = row[_a_window->tlcols.graph];
304 
305  // disregard fft analysis from empty signals
306  if (res->minimum() == res->maximum()) {
307  continue;
308  }
309 
310  if ( res->minimum() < minf) {
311  minf = res->minimum();
312  }
313 
314  if ( res->maximum() > maxf) {
315  maxf = res->maximum();
316  }
317  }
318 
319  if (!_show_normalized) {
320  minf = -150.0f;
321  maxf = 0.0f;
322  }
323 
324  //int graph_height = height - 2 * h_margin;
325 
326 
327 
328  float fft_pane_size_w = (float)(width - 2*v_margin) - 1.0;
329  float fft_pane_size_h = (float)(height - 2*h_margin);
330 
331  double pixels_per_db = (double)fft_pane_size_h / (double)(maxf - minf);
332 
333  cairo_rectangle(cr, 0.0, 0.0, fft_pane_size_w, fft_pane_size_h);
334  cairo_clip(cr);
335 
336  for (TreeIter i = track_rows.begin(); i != track_rows.end(); i++) {
337 
338  TreeModel::Row row = *i;
339 
340  // don't show graphs for tracks which are deselected
341  if (!row[_a_window->tlcols.visible]) {
342  continue;
343  }
344 
345  FFTResult *res = row[_a_window->tlcols.graph];
346 
347  // don't show graphs for empty signals
348  if (res->minimum() == res->maximum()) {
349  continue;
350  }
351 
352  float mpp;
353 
354  if (_show_minmax) {
355  mpp = -1000000.0;
356 
357  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);
358  cairo_move_to(cr, 0.5f + (float)_logScale[0], 0.5f + (float)( fft_pane_size_h - (int)floor( (res->maxAt(0) - minf) * pixels_per_db) ));
359 
360  // Draw the line of maximum values
361  for (int x = 1; x < res->length(); x++) {
362  if (res->maxAt(x) > mpp)
363  mpp = res->maxAt(x);
364  mpp = fmax(mpp, minf);
365  mpp = fmin(mpp, maxf);
366 
367  // If the next point on the log scale is at the same location,
368  // don't draw yet
369  if (x + 1 < res->length() && _logScale[x] == _logScale[x + 1]) {
370  continue;
371  }
372 
373  float X = 0.5f + (float)_logScale[x];
374  float Y = 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - minf) * pixels_per_db) );
375 
376  cairo_line_to(cr, X, Y);
377 
378  mpp = -1000000.0;
379  }
380 
381  mpp = +10000000.0;
382  // Draw back to the start using the minimum value
383  for (int x = res->length()-1; x >= 0; x--) {
384  if (res->minAt(x) < mpp)
385  mpp = res->minAt(x);
386  mpp = fmax(mpp, minf);
387  mpp = fmin(mpp, maxf);
388 
389  // If the next point on the log scale is at the same location,
390  // don't draw yet
391  if (x - 1 > 0 && _logScale[x] == _logScale[x - 1]) {
392  continue;
393  }
394 
395  float X = 0.5f + (float)_logScale[x];
396  float Y = 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - minf) * pixels_per_db) );
397 
398  cairo_line_to(cr, X, Y );
399 
400  mpp = +10000000.0;
401  }
402 
403  cairo_close_path(cr);
404 
405  cairo_fill(cr);
406  }
407 
408 
409 
410  // Set color from track
411  cairo_set_source_rgb(cr, res->get_color().get_red_p(), res->get_color().get_green_p(), res->get_color().get_blue_p());
412 
413  mpp = -1000000.0;
414 
415  cairo_move_to(cr, 0.5, fft_pane_size_h-0.5);
416 
417  for (int x = 0; x < res->length(); x++) {
418 
419 
420  if (res->avgAt(x) > mpp)
421  mpp = res->avgAt(x);
422  mpp = fmax(mpp, minf);
423  mpp = fmin(mpp, maxf);
424 
425  // If the next point on the log scale is at the same location,
426  // don't draw yet
427  if (x + 1 < res->length() && _logScale[x] == _logScale[x + 1]) {
428  continue;
429  }
430 
431  cairo_line_to(cr, 0.5f + (float)_logScale[x], 0.5f + (float)( fft_pane_size_h - (int)floor( (mpp - minf) * pixels_per_db) ));
432 
433  mpp = -1000000.0;
434  }
435 
436  cairo_stroke(cr);
437  }
438 
439  cairo_destroy(cr);
440 }
441 
442 void
443 FFTGraph::on_size_request(Gtk::Requisition* requisition)
444 {
445  width = max(requisition->width, minScaleWidth + h_margin * 2);
446  height = max(requisition->height, minScaleHeight + 2 + v_margin * 2);
447 
448  update_size();
449 
450  requisition->width = width;;
451  requisition->height = height;
452 }
453 
454 void
455 FFTGraph::on_size_allocate(Gtk::Allocation & alloc)
456 {
457  width = alloc.get_width();
458  height = alloc.get_height();
459 
460  update_size();
461 
462  DrawingArea::on_size_allocate (alloc);
463 }
464 
465 void
467 {
468  currentScaleWidth = width - h_margin*2;
469  currentScaleHeight = height - 2 - v_margin*2;
470 
471  float SR = 44100;
472  float FFT_START = SR/(double)_dataSize;
473  float FFT_END = SR/2.0;
474  float FFT_RANGE = log( FFT_END / FFT_START);
475  float pixel = 0;
476  for (int i = 0; i < _dataSize; i++) {
477  float freq_at_bin = (SR/2.0) * ((double)i / (double)_dataSize);
478  float freq_at_pixel;
479  pixel--;
480  do {
481  pixel++;
482  freq_at_pixel = FFT_START * exp( FFT_RANGE * pixel / (double)(currentScaleWidth - 1) );
483  } while (freq_at_bin > freq_at_pixel);
484 
485  _logScale[i] = (int)floor(pixel);
486  }
487 }
488 
int length() const
Definition: fft_result.h:41
float maxAt(int x)
Definition: fft_result.cc:152
Definition: ardour_ui.h:130
void draw_scales(Glib::RefPtr< Gdk::Window > window)
Definition: fft_graph.cc:170
float minimum() const
Definition: fft_result.h:47
tuple f
Definition: signals.py:35
Definition: Beats.hpp:239
float avgAt(int x)
Definition: fft_result.cc:134
Gdk::Color get_color() const
Definition: fft_result.h:50
#define DB_METRIC_LENGTH
FFTGraph(int windowSize)
Definition: fft_graph.cc:48
float maximum() const
Definition: fft_result.h:48
void on_size_request(Gtk::Requisition *requisition)
Definition: fft_graph.cc:443
void set_analysis_window(AnalysisWindow *a_window)
Definition: fft_graph.cc:164
void setWindowSize_internal(int windowSize)
Definition: fft_graph.cc:77
void update_size()
Definition: fft_graph.cc:466
FFTResult * prepareResult(Gdk::Color color, std::string trackname)
Definition: fft_graph.cc:155
void redraw()
Definition: fft_graph.cc:274
bool on_expose_event(GdkEventExpose *event)
Definition: fft_graph.cc:148
float minAt(int x)
Definition: fft_result.cc:143
void on_size_allocate(Gtk::Allocation &alloc)
Definition: fft_graph.cc:455
void setWindowSize(int windowSize)
Definition: fft_graph.cc:66
LIBARDOUR_API PBD::PropertyDescriptor< bool > color
Definition: route_group.cc:50