1edd483dab4f5148ba5d4b106cfbbaac1fbc86b4
[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
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
77 FFTGraph::setWindowSize_internal(int windowSize)
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
141 FFTGraph::~FFTGraph()
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
164 FFTGraph::set_analysis_window(AnalysisWindow *a_window)
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
179         /**
180          *  4          5
181          *  _          _
182          *   |        |
183          * 1 |        | 2
184          *   |________|
185          *        3
186          **/
187
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
274 FFTGraph::redraw()
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
466 FFTGraph::update_size()
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