6a448ac52ca298535a224d38c0e7aae6c73b2acc
[ardour.git] / gtk2_ardour / fft_graph.cc
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 #ifdef COMPILER_MSVC
20 #include <algorithm>
21 using std::min; using std::max;
22 #endif
23
24 #include <iostream>
25
26 #include <glibmm.h>
27 #include <glibmm/refptr.h>
28
29 #include <gdkmm/gc.h>
30
31 #include <gtkmm/widget.h>
32 #include <gtkmm/style.h>
33 #include <gtkmm/treemodel.h>
34 #include <gtkmm/treepath.h>
35
36 #include "pbd/stl_delete.h"
37
38 #include <math.h>
39
40 #include "fft_graph.h"
41 #include "analysis_window.h"
42 #include "public_editor.h"
43
44 #include "pbd/i18n.h"
45
46 using namespace std;
47 using namespace Gtk;
48 using namespace Gdk;
49
50 FFTGraph::FFTGraph (int windowSize)
51 {
52         _logScale = 0;
53
54         _in       = 0;
55         _out      = 0;
56         _hanning  = 0;
57         _logScale = 0;
58
59         _a_window = 0;
60
61         _show_minmax       = false;
62         _show_normalized   = false;
63         _show_proportional = false;
64
65         setWindowSize (windowSize);
66 }
67
68 void
69 FFTGraph::setWindowSize (int windowSize)
70 {
71         if (_a_window) {
72                 Glib::Threads::Mutex::Lock lm  (_a_window->track_list_lock);
73                 setWindowSize_internal (windowSize);
74         } else {
75                 setWindowSize_internal (windowSize);
76         }
77 }
78
79 void
80 FFTGraph::setWindowSize_internal (int windowSize)
81 {
82         // remove old tracklist & graphs
83         if (_a_window) {
84                 _a_window->clear_tracklist ();
85         }
86
87         _windowSize = windowSize;
88         _dataSize = windowSize / 2;
89         if (_in != 0) {
90                 fftwf_destroy_plan (_plan);
91                 free (_in);
92                 _in = 0;
93         }
94
95         if (_out != 0) {
96                 free (_out);
97                 _out = 0;
98         }
99
100         if (_hanning != 0) {
101                 free (_hanning);
102                 _hanning = 0;
103         }
104
105         if (_logScale != 0) {
106                 free (_logScale);
107                 _logScale = 0;
108         }
109
110         // When destroying, window size is set to zero to free up memory
111         if (windowSize == 0) {
112                 return;
113         }
114
115         // FFT input & output buffers
116         _in  = (float *) fftwf_malloc (sizeof (float) * _windowSize);
117         _out = (float *) fftwf_malloc (sizeof (float) * _windowSize);
118
119         // Hanning window
120         _hanning = (float *) malloc (sizeof (float) * _windowSize);
121
122         // normalize the window
123         double sum = 0.0;
124
125         for (unsigned int i = 0; i < _windowSize; ++i) {
126                 _hanning[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(_windowSize)));
127                 sum += _hanning[i];
128         }
129
130         double isum = 2.0 / sum;
131
132         for (unsigned int i = 0; i < _windowSize; i++) {
133                 _hanning[i] *= isum;
134         }
135
136         _logScale = (int *) malloc (sizeof (int) * _dataSize);
137
138         for (unsigned int i = 0; i < _dataSize; i++) {
139                 _logScale[i] = 0;
140         }
141         _plan = fftwf_plan_r2r_1d (_windowSize, _in, _out, FFTW_R2HC, FFTW_MEASURE);
142 }
143
144 FFTGraph::~FFTGraph ()
145 {
146         // This will free everything
147         setWindowSize (0);
148 }
149
150 bool
151 FFTGraph::on_expose_event (GdkEventExpose* /*event*/)
152 {
153         redraw ();
154         return true;
155 }
156
157 FFTResult *
158 FFTGraph::prepareResult (Gdk::Color color, string trackname)
159 {
160         FFTResult *res = new FFTResult (this, color, trackname);
161
162         return res;
163 }
164
165
166 void
167 FFTGraph::set_analysis_window (AnalysisWindow *a_window)
168 {
169         _a_window = a_window;
170 }
171
172 int
173 FFTGraph::draw_scales (Glib::RefPtr<Gdk::Window> window)
174 {
175         int label_height = v_margin;
176
177         Glib::RefPtr<Gtk::Style> style = get_style ();
178         Glib::RefPtr<Gdk::GC> black = style->get_black_gc ();
179         Glib::RefPtr<Gdk::GC> white = style->get_white_gc ();
180
181         window->draw_rectangle (black, true, 0, 0, width, height);
182
183         /**
184          *  4          5
185          *  _          _
186          *   |        |
187          * 1 |        | 2
188          *   |________|
189          *        3
190          **/
191
192         // Line 1
193         window->draw_line (white, hl_margin, v_margin, hl_margin, height - v_margin);
194
195         // Line 2
196         window->draw_line (white, width - hr_margin + 1, v_margin, width - hr_margin + 1, height - v_margin);
197
198         // Line 3
199         window->draw_line (white, hl_margin, height - v_margin, width - hr_margin, height - v_margin);
200
201         // Line 4
202         window->draw_line (white, 3, v_margin, hl_margin, v_margin);
203
204         // Line 5
205         window->draw_line (white, width - hr_margin + 1, v_margin, width - 3, v_margin);
206
207
208         if (graph_gc == 0) {
209                 graph_gc = GC::create (get_window ());
210         }
211
212         Color grey;
213         grey.set_rgb_p (0.2, 0.2, 0.2);
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 x-axis scale 1/3 octaves centered around 1K
222         int overlap = 0;
223
224         // make sure 1K (x=0) is visible
225         for (int x = 0; x < 27; ++x) {
226                 float freq = powf (2.f, x / 3.0) * 1000.f;
227                 if (freq <= _fft_start) { continue; }
228                 if (freq >= _fft_end) { break; }
229
230                 const float pos = currentScaleWidth * logf (freq / _fft_start) / _fft_log_base;
231                 const int coord = floor (hl_margin + pos);
232
233                 if (coord < overlap) {
234                         continue;
235                 }
236
237                 std::stringstream ss;
238                 if (freq >= 10000) {
239                         ss <<  std::setprecision (1) << std::fixed << freq / 1000 << "K";
240                 } else if (freq >= 1000) {
241                         ss <<  std::setprecision (2) << std::fixed << freq / 1000 << "K";
242                 } else {
243                         ss <<  std::setprecision (0) << std::fixed << freq << "Hz";
244                 }
245                 layout->set_text (ss.str ());
246                 int lw, lh;
247                 layout->get_pixel_size (lw, lh);
248                 overlap = coord + lw + 3;
249
250                 if (coord + lw / 2 > width - hr_margin - 2) {
251                         break;
252                 }
253                 if (v_margin / 2 + lh > label_height) {
254                         label_height = v_margin / 2 + lh;
255                 }
256                 window->draw_line (graph_gc, coord, v_margin, coord, height - v_margin - 1);
257                 window->draw_layout (white, coord - lw / 2, v_margin / 2, layout);
258         }
259
260         // now from 1K down to 4Hz
261         for (int x = 0; x > -24; --x) {
262                 float freq = powf (2.f, x / 3.0) * 1000.f;
263                 if (freq >= _fft_end) { continue; }
264                 if (freq <= _fft_start) { break; }
265
266                 const float pos = currentScaleWidth * logf (freq / _fft_start) / _fft_log_base;
267                 const int coord = floor (hl_margin + pos);
268
269                 if (x != 0 && coord > overlap) {
270                         continue;
271                 }
272
273                 std::stringstream ss;
274                 if (freq >= 10000) {
275                         ss <<  std::setprecision (1) << std::fixed << freq / 1000 << "K";
276                 } else if (freq >= 1000) {
277                         ss <<  std::setprecision (2) << std::fixed << freq / 1000 << "K";
278                 } else {
279                         ss <<  std::setprecision (0) << std::fixed << freq << "Hz";
280                 }
281                 layout->set_text (ss.str ());
282                 int lw, lh;
283                 layout->get_pixel_size (lw, lh);
284                 overlap = coord - lw - 3;
285
286                 if (coord - lw / 2 < hl_margin + 2) {
287                         break;
288                 }
289                 if (x == 0) {
290                         // just get overlap position
291                         continue;
292                 }
293                 if (v_margin / 2 + lh > label_height) {
294                         label_height = v_margin / 2 + lh;
295                 }
296                 window->draw_line (graph_gc, coord, v_margin, coord, height - v_margin - 1);
297                 window->draw_layout (white, coord - lw / 2, v_margin / 2, layout);
298         }
299         return label_height;
300 }
301
302 void
303 FFTGraph::redraw ()
304 {
305         Glib::Threads::Mutex::Lock lm  (_a_window->track_list_lock);
306
307         int yoff = draw_scales (get_window ());
308
309         if (_a_window == 0)
310                 return;
311
312         if (!_a_window->track_list_ready)
313                 return;
314
315         float minf;
316         float maxf;
317
318         TreeNodeChildren track_rows = _a_window->track_list.get_model ()->children ();
319
320         if (!_show_normalized) {
321                 maxf =    0.0f;
322                 minf = -108.0f;
323         } else  {
324                 minf =  999.0f;
325                 maxf = -999.0f;
326                 for (TreeIter i = track_rows.begin (); i != track_rows.end (); i++) {
327                         TreeModel::Row row = *i;
328                         FFTResult *res = row[_a_window->tlcols.graph];
329
330                         // disregard fft analysis from empty signals
331                         if (res->minimum (_show_proportional) == res->maximum (_show_proportional)) {
332                                 continue;
333                         }
334                         // don't include invisible graphs
335                         if (!row[_a_window->tlcols.visible]) {
336                                 continue;
337                         }
338
339                         minf = std::min (minf, res->minimum (_show_proportional));
340                         maxf = std::max (maxf, res->maximum (_show_proportional));
341                 }
342         }
343
344         // clamp range, > -200dBFS, at least 24dB (two y-axis labels) range
345         minf = std::max (-200.f, minf);
346         if (maxf <= minf) {
347                 return;
348         }
349
350         if (maxf - minf < 24) {
351                 maxf += 6.f;
352                 minf = maxf - 24.f;
353         }
354
355         cairo_t *cr;
356         cr = gdk_cairo_create (GDK_DRAWABLE (get_window ()->gobj ()));
357         cairo_set_line_width (cr, 1.5);
358         cairo_translate (cr, hl_margin + 1, yoff);
359
360         float fft_pane_size_w = width  - hl_margin - hr_margin;
361         float fft_pane_size_h = height - v_margin - 1 - yoff;
362         double pixels_per_db = (double)fft_pane_size_h / (double)(maxf - minf);
363
364         // draw y-axis dB
365         cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
366
367         int btm_lbl = fft_pane_size_h;
368         {
369                 // y-axis legend
370                 layout->set_text (_("dBFS"));
371                 int lw, lh;
372                 layout->get_pixel_size (lw, lh);
373                 cairo_move_to (cr, -2 - lw, fft_pane_size_h - lh / 2);
374                 pango_cairo_update_layout (cr, layout->gobj ());
375                 pango_cairo_show_layout (cr, layout->gobj ());
376                 btm_lbl = fft_pane_size_h - lh;
377         }
378
379         for (int x = -6; x >= -200; x -= 12) {
380                 float yp = 1.5 + fft_pane_size_h - rint ((x - minf) * pixels_per_db);
381
382                 assert (layout);
383                 std::stringstream ss;
384                 ss << x;
385                 layout->set_text (ss.str ());
386                 int lw, lh;
387                 layout->get_pixel_size (lw, lh);
388
389                 if (yp + 2 + lh / 2 > btm_lbl) {
390                         continue;
391                 }
392                 if (yp < 2 + lh / 2) {
393                         continue;
394                 }
395
396                 cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
397                 cairo_move_to (cr, -2 - lw, yp - lh / 2);
398                 pango_cairo_update_layout (cr, layout->gobj ());
399                 pango_cairo_show_layout (cr, layout->gobj ());
400
401                 cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
402                 cairo_move_to (cr, 0, yp);
403                 cairo_line_to (cr, fft_pane_size_w, yp);
404                 cairo_stroke (cr);
405         }
406
407         cairo_rectangle (cr, 1, 1, fft_pane_size_w, fft_pane_size_h);
408         cairo_clip (cr);
409
410         cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
411         cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
412
413         for (TreeIter i = track_rows.begin (); i != track_rows.end (); i++) {
414                 TreeModel::Row row = *i;
415
416                 // don't show graphs for tracks which are deselected
417                 if (!row[_a_window->tlcols.visible]) {
418                         continue;
419                 }
420
421                 FFTResult *res = row[_a_window->tlcols.graph];
422
423                 // don't show graphs for empty signals
424                 if (res->minimum (_show_proportional) == res->maximum (_show_proportional)) {
425                         continue;
426                 }
427
428                 float mpp;
429                 float X,Y;
430
431                 if (_show_minmax) {
432
433                         X = 0.5f + _logScale[0];
434                         Y = 1.5f + fft_pane_size_h - pixels_per_db * (res->maxAt (0, _show_proportional) - minf);
435                         cairo_move_to (cr, X, Y);
436
437                         // Draw the line of maximum values
438                         mpp = minf;
439                         for (unsigned int x = 1; x < res->length () - 1; ++x) {
440                                 mpp = std::max (mpp, res->maxAt (x, _show_proportional));
441
442                                 if (_logScale[x] == _logScale[x + 1]) {
443                                         continue;
444                                 }
445
446                                 mpp = fmin (mpp, maxf);
447                                 X = 0.5f + _logScale[x];
448                                 Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
449                                 cairo_line_to (cr, X, Y);
450                                 mpp = minf;
451                         }
452
453                         mpp = maxf;
454                         // Draw back to the start using the minimum value
455                         for (int x = res->length () - 1; x >= 0; --x) {
456                                 mpp = std::min (mpp, res->minAt (x, _show_proportional));
457
458                                 if (_logScale[x] == _logScale[x + 1]) {
459                                         continue;
460                                 }
461
462                                 mpp = fmax (mpp, minf);
463                                 X = 0.5f + _logScale[x];
464                                 Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
465                                 cairo_line_to (cr, X, Y);
466                                 mpp = maxf;
467                         }
468
469                         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);
470                         cairo_close_path (cr);
471                         cairo_fill (cr);
472                 }
473
474                 // draw max of averages
475                 X = 0.5f + _logScale[0];
476                 Y = 1.5f + fft_pane_size_h - pixels_per_db * (res->avgAt (0, _show_proportional) - minf);
477                 cairo_move_to (cr, X, Y);
478
479                 mpp = minf;
480                 for (unsigned int x = 0; x < res->length () - 1; x++) {
481                         mpp = std::max (mpp, res->avgAt (x, _show_proportional));
482
483                         if (_logScale[x] == _logScale[x + 1]) {
484                                 continue;
485                         }
486
487                         mpp = fmax (mpp, minf);
488                         mpp = fmin (mpp, maxf);
489
490                         X = 0.5f + _logScale[x];
491                         Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
492                         cairo_line_to (cr, X, Y);
493                         mpp = minf;
494                 }
495
496                 cairo_set_source_rgb (cr, res->get_color ().get_red_p (), res->get_color ().get_green_p (), res->get_color ().get_blue_p ());
497                 cairo_stroke (cr);
498         }
499         cairo_destroy (cr);
500 }
501
502 void
503 FFTGraph::on_size_request (Gtk::Requisition* requisition)
504 {
505         width  = max (requisition->width,  minScaleWidth  + hl_margin + hr_margin);
506         height = max (requisition->height, minScaleHeight + 2 + v_margin * 2);
507
508         update_size ();
509
510         requisition->width  = width;;
511         requisition->height = height;
512 }
513
514 void
515 FFTGraph::on_size_allocate (Gtk::Allocation & alloc)
516 {
517         width = alloc.get_width ();
518         height = alloc.get_height ();
519
520         update_size ();
521
522         DrawingArea::on_size_allocate (alloc);
523 }
524
525 void
526 FFTGraph::update_size ()
527 {
528         framecnt_t SR = PublicEditor::instance ().session ()->nominal_frame_rate ();
529         _fft_start = SR / (double)_dataSize;
530         _fft_end = .5 * SR;
531         _fft_log_base = logf (.5 * _dataSize);
532         currentScaleWidth  = width - hl_margin - hr_margin;
533         _logScale[0] = 0;
534         for (unsigned int i = 1; i < _dataSize; ++i) {
535                 _logScale[i] = floor (currentScaleWidth * logf (.5 * i) / _fft_log_base);
536         }
537 }