rework FFT-graph, add pointer-position annotations
[ardour.git] / gtk2_ardour / fft_graph.cc
index e7a0fd75b6ffadb8904307c04f72f99e32c18b4b..630d822a2ced6b72996afc3749b7b59528bf3dc5 100644 (file)
     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.
-
 */
 
+#ifdef COMPILER_MSVC
+#include <algorithm>
+using std::min; using std::max;
+#endif
+
 #include <iostream>
 
 #include <glibmm.h>
 #include <gtkmm/treemodel.h>
 #include <gtkmm/treepath.h>
 
-#include <pbd/stl_delete.h>
+#include "pbd/stl_delete.h"
 
 #include <math.h>
 
 #include "fft_graph.h"
 #include "analysis_window.h"
+#include "public_editor.h"
+
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace Gtk;
 using namespace Gdk;
 
-FFTGraph::FFTGraph(int windowSize)
+FFTGraph::FFTGraph (int windowSize)
 {
        _logScale = 0;
-       
+
        _in       = 0;
        _out      = 0;
        _hanning  = 0;
        _logScale = 0;
 
+       _surface  = 0;
        _a_window = 0;
 
-       setWindowSize(windowSize);
+       _show_minmax       = false;
+       _show_normalized   = false;
+       _show_proportional = false;
+
+       _ann_x = _ann_y = -1;
+       _yoff = v_margin;
+       _ann_area.width = 0;
+       _ann_area.height = 0;
+
+       setWindowSize (windowSize);
+       set_events (Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK);
 }
 
 void
-FFTGraph::setWindowSize(int windowSize)
+FFTGraph::setWindowSize (int windowSize)
 {
        if (_a_window) {
-               Glib::Mutex::Lock lm  (_a_window->track_list_lock);
-               setWindowSize_internal(windowSize);
+               Glib::Threads::Mutex::Lock lm  (_a_window->track_list_lock);
+               setWindowSize_internal (windowSize);
        } else {
-               setWindowSize_internal(windowSize);
+               setWindowSize_internal (windowSize);
        }
 }
 
 void
-FFTGraph::setWindowSize_internal(int windowSize)
+FFTGraph::setWindowSize_internal (int windowSize)
 {
        // remove old tracklist & graphs
        if (_a_window) {
-               _a_window->clear_tracklist();
+               _a_window->clear_tracklist ();
        }
-       
+
        _windowSize = windowSize;
        _dataSize = windowSize / 2;
        if (_in != 0) {
-               fftwf_destroy_plan(_plan);
-               free(_in);
+               fftwf_destroy_plan (_plan);
+               free (_in);
                _in = 0;
        }
-       
+
        if (_out != 0) {
-               free(_out);
+               free (_out);
                _out = 0;
        }
-       
+
        if (_hanning != 0) {
-               free(_hanning);
+               free (_hanning);
                _hanning = 0;
        }
 
        if (_logScale != 0) {
-               free(_logScale);
+               free (_logScale);
                _logScale = 0;
        }
 
        // When destroying, window size is set to zero to free up memory
-       if (windowSize == 0)
+       if (windowSize == 0) {
                return;
+       }
 
        // FFT input & output buffers
-       _in      = (float *) fftwf_malloc(sizeof(float) * _windowSize);
-       _out     = (float *) fftwf_malloc(sizeof(float) * _windowSize);
+       _in  = (float *) fftwf_malloc (sizeof (float) * _windowSize);
+       _out = (float *) fftwf_malloc (sizeof (float) * _windowSize);
 
        // Hanning window
-       _hanning = (float *) malloc(sizeof(float) * _windowSize);
-
+       _hanning = (float *) malloc (sizeof (float) * _windowSize);
 
        // normalize the window
        double sum = 0.0;
-       
-       for (int i=0; i < _windowSize; i++) {
-               _hanning[i]=0.81f * ( 0.5f - (0.5f * (float) cos(2.0f * M_PI * (float)i / (float)(_windowSize))));
+
+       for (unsigned int i = 0; i < _windowSize; ++i) {
+               _hanning[i] = 0.5f - (0.5f * (float) cos (2.0f * M_PI * (float)i / (float)(_windowSize)));
                sum += _hanning[i];
        }
 
-       double isum = 1.0 / sum;
-       
-       for (int i=0; i < _windowSize; i++) {
+       double isum = 2.0 / sum;
+
+       for (unsigned int i = 0; i < _windowSize; i++) {
                _hanning[i] *= isum;
        }
-       
-       _logScale = (int *) malloc(sizeof(int) * _dataSize);
-       //float count = 0;
-       for (int i = 0; i < _dataSize; i++) {
+
+       _logScale = (int *) malloc (sizeof (int) * _dataSize);
+
+       for (unsigned int i = 0; i < _dataSize; i++) {
                _logScale[i] = 0;
        }
-       _plan = fftwf_plan_r2r_1d(_windowSize, _in, _out, FFTW_R2HC, FFTW_ESTIMATE);
+       _plan = fftwf_plan_r2r_1d (_windowSize, _in, _out, FFTW_R2HC, FFTW_MEASURE);
 }
 
-FFTGraph::~FFTGraph()
+FFTGraph::~FFTGraph ()
 {
        // This will free everything
-       setWindowSize(0);
+       setWindowSize (0);
+
+       if (_surface) {
+               cairo_surface_destroy (_surface);
+       }
 }
 
 bool
 FFTGraph::on_expose_event (GdkEventExpose* event)
 {
-       redraw();
+       cairo_t* cr = gdk_cairo_create (GDK_DRAWABLE (get_window ()->gobj ()));
+       cairo_rectangle (cr, event->area.x, event->area.y, event->area.width, event->area.height);
+       cairo_clip (cr);
+
+       cairo_set_source_surface(cr, _surface, 0, 0);
+       cairo_paint (cr);
+
+
+       if (_ann_x > 0 && _ann_y > 0) {
+               const float x = _ann_x - hl_margin;
+               const float freq = expf(_fft_log_base * x / currentScaleWidth) * _fft_start;
+
+               std::stringstream ss;
+               if (freq >= 10000) {
+                       ss <<  std::setprecision (1) << std::fixed << freq / 1000 << "K";
+               } else if (freq >= 1000) {
+                       ss <<  std::setprecision (2) << std::fixed << freq / 1000 << "K";
+               } else {
+                       ss <<  std::setprecision (0) << std::fixed << freq << "Hz";
+               }
+               layout->set_text (ss.str ());
+               int lw, lh;
+               layout->get_pixel_size (lw, lh);
+               lw|=1; lh|=1;
+
+               const float y0 = _ann_y - lh - 7;
+
+               _ann_area.x = _ann_x - 1 - lw * .5;
+               _ann_area.y =  y0 - 1;
+               _ann_area.width = lw + 3;
+               _ann_area.height = lh + 8;
+
+               cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.7);
+               cairo_rectangle (cr, _ann_x - 1 - lw * .5, y0 - 1, lw + 2, lh + 2);
+               cairo_fill (cr);
+
+               cairo_move_to (cr, _ann_x , _ann_y - 0.5);
+               cairo_rel_line_to (cr, -3.0, -5.5);
+               cairo_rel_line_to (cr, 6, 0);
+               cairo_close_path (cr);
+               cairo_fill (cr);
+
+               cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+               cairo_move_to (cr, _ann_x - lw / 2, y0);
+               pango_cairo_update_layout (cr, layout->gobj ());
+               pango_cairo_show_layout (cr, layout->gobj ());
+
+       }
+
+#ifdef HARLEQUIN_DEBUGGING
+       cairo_rectangle (cr, 0, 0, width, height);
+       cairo_set_source_rgba (cr, (random() % 255) / 255.f, (random() % 255) / 255.f, 0.0, 0.5);
+       cairo_fill (cr);
+#endif
+
+       cairo_destroy (cr);
        return true;
 }
 
-FFTResult *
-FFTGraph::prepareResult(Gdk::Color color, string trackname)
+bool
+FFTGraph::on_motion_notify_event (GdkEventMotion* ev)
 {
-       FFTResult *res = new FFTResult(this, color, trackname);
+       gint x, y;
 
-       return res;
-}
+       x = (int) floor (ev->x);
+       y = (int) floor (ev->y);
 
-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 ];
+       if (x <= hl_margin + 1 || x >= width  - hr_margin) {
+               x = -1;
+       }
+       if (y <= _yoff || y >= height - v_margin - 1) {
+               y = -1;
        }
 
-       fftwf_execute(_plan);
+       if (x == _ann_x && y == _ann_y) {
+               return true;
+       }
+       _ann_x = x;
+       _ann_y = y;
 
-       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]);
+       if (_ann_area.width == 0 || _ann_area.height == 0) {
+               queue_draw ();
+       } else {
+               queue_draw_area (_ann_area.x, _ann_area.y, _ann_area.width, _ann_area.height + 1);
+       }
+
+       if (_ann_x > 0 &&_ann_y > 0) {
+               queue_draw_area (_ann_x - _ann_area.width, _ann_y - _ann_area.height - 1, _ann_area.width * 2, _ann_area.height + 2);
        }
+
+       return true;
+}
+
+bool
+FFTGraph::on_leave_notify_event (GdkEventCrossing *)
+{
+       if (_ann_x == -1 && _ann_y == -1) {
+               return true;
+       }
+       _ann_x = _ann_y = -1;
+       if (_ann_area.width == 0 || _ann_area.height == 0) {
+               queue_draw ();
+       } else {
+               queue_draw_area (_ann_area.x, _ann_area.y, _ann_area.width, _ann_area.height + 1);
+       }
+       _ann_area.width = _ann_area.height = 0;
+       return false;
+}
+
+FFTResult *
+FFTGraph::prepareResult (Gdk::Color color, string trackname)
+{
+       FFTResult *res = new FFTResult (this, color, trackname);
+
+       return res;
 }
 
 void
-FFTGraph::set_analysis_window(AnalysisWindow *a_window)
+FFTGraph::set_analysis_window (AnalysisWindow *a_window)
 {
        _a_window = a_window;
 }
 
-void
-FFTGraph::draw_scales(Glib::RefPtr<Gdk::Window> window)
+int
+FFTGraph::draw_scales (cairo_t* cr)
 {
-       
-       Glib::RefPtr<Gtk::Style> style = get_style();
-       Glib::RefPtr<Gdk::GC> black = style->get_black_gc();
-       Glib::RefPtr<Gdk::GC> white = style->get_white_gc();
-       
-       window->draw_rectangle(black, true, 0, 0, width, height);
-       
-       /**
-        *  4          5
+       int label_height = v_margin;
+
+       cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+       cairo_rectangle (cr, 0, 0, width, height);
+       cairo_fill (cr);
+
+       /*
+        *  1          5
         *  _          _
         *   |        |
-        * 1 |        | 2
+        * 2 |        | 4
         *   |________|
         *        3
-        **/
+        */
+
+       cairo_set_line_width (cr, 1.0);
+       cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+       cairo_move_to (cr, 3                      , .5 + v_margin);
+       cairo_line_to (cr, .5 + hl_margin         , .5 + v_margin);  // 1
+       cairo_line_to (cr, .5 + hl_margin         , .5 + height - v_margin); // 2
+       cairo_line_to (cr, 1.5 + width - hr_margin, .5 + height - v_margin); // 3
+       cairo_line_to (cr, 1.5 + width - hr_margin, .5 + v_margin); // 4
+       cairo_line_to (cr, width - 3              , .5 + v_margin); // 5
+       cairo_stroke (cr);
 
-       // Line 1
-       window->draw_line(white, h_margin, v_margin, h_margin, height - v_margin );
+       if (layout == 0) {
+               layout = create_pango_layout ("");
+               layout->set_font_description (get_style ()->get_font ());
+       }
 
-       // Line 2
-       window->draw_line(white, width - h_margin, v_margin, width - h_margin, height - v_margin );
+       // Draw x-axis scale 1/3 octaves centered around 1K
+       int overlap = 0;
+
+       // make sure 1K (x=0) is visible
+       for (int x = 0; x < 27; ++x) {
+               float freq = powf (2.f, x / 3.0) * 1000.f;
+               if (freq <= _fft_start) { continue; }
+               if (freq >= _fft_end) { break; }
+
+               const float pos = currentScaleWidth * logf (freq / _fft_start) / _fft_log_base;
+               const int coord = floor (hl_margin + pos);
+
+               if (coord < overlap) {
+                       continue;
+               }
 
-       // Line 3
-       window->draw_line(white, h_margin, height - v_margin, width - h_margin, height - v_margin );
+               std::stringstream ss;
+               if (freq >= 10000) {
+                       ss <<  std::setprecision (1) << std::fixed << freq / 1000 << "K";
+               } else if (freq >= 1000) {
+                       ss <<  std::setprecision (2) << std::fixed << freq / 1000 << "K";
+               } else {
+                       ss <<  std::setprecision (0) << std::fixed << freq << "Hz";
+               }
+               layout->set_text (ss.str ());
+               int lw, lh;
+               layout->get_pixel_size (lw, lh);
+               overlap = coord + lw + 3;
 
-#define DB_METRIC_LENGTH 8
-       // Line 5
-       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 );
+               if (coord + lw / 2 > width - hr_margin - 2) {
+                       break;
+               }
+               if (v_margin / 2 + lh > label_height) {
+                       label_height = v_margin / 2 + lh;
+               }
 
+               cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1.0);
+               cairo_move_to (cr, coord, v_margin);
+               cairo_line_to (cr, coord, height - v_margin - 1);
+               cairo_stroke (cr);
 
-       if (graph_gc == 0) {
-               graph_gc = GC::create( get_window() );
+               cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+               cairo_move_to (cr, coord - lw / 2, v_margin / 2);
+               pango_cairo_update_layout (cr, layout->gobj ());
+               pango_cairo_show_layout (cr, layout->gobj ());
        }
 
-       Color grey;
+       // now from 1K down to 4Hz
+       for (int x = 0; x > -24; --x) {
+               float freq = powf (2.f, x / 3.0) * 1000.f;
+               if (freq >= _fft_end) { continue; }
+               if (freq <= _fft_start) { break; }
 
-       grey.set_rgb_p(0.2, 0.2, 0.2);
-       
-       graph_gc->set_rgb_fg_color( grey );
+               const float pos = currentScaleWidth * logf (freq / _fft_start) / _fft_log_base;
+               const int coord = floor (hl_margin + pos);
 
-       if (layout == 0) {
-               layout = create_pango_layout ("");
-               layout->set_font_description (get_style()->get_font());
-       }
-
-       // Draw logscale
-       int logscale_pos = 0;
-       int position_on_scale;
-       for (int x = 1; x < 8; x++) {
-               position_on_scale = (int)floor( (double)scaleWidth*(double)x/8.0);
-
-               while (_logScale[logscale_pos] < position_on_scale)
-                       logscale_pos++;
-               
-               int coord = (int)(v_margin + 1.0 + position_on_scale);
-               
-               int SR = 44100;
-
-               int rate_at_pos = (int)((double)(SR/2) * (double)logscale_pos / (double)_dataSize);
-               
-               char buf[32];
-               if (rate_at_pos < 1000)
-                       snprintf(buf,32,"%dHz",rate_at_pos);
-               else
-                       snprintf(buf,32,"%dk",(int)floor( (float)rate_at_pos/(float)1000) );
-
-               std::string label = buf;
-               
-               layout->set_text(label);
-               
-               window->draw_line(graph_gc, coord, v_margin, coord, height - v_margin);
-
-               int width, height;
-               layout->get_pixel_size (width, height);
-               
-               window->draw_layout(white, coord - width / 2, v_margin / 2, layout);
-               
+               if (x != 0 && coord > overlap) {
+                       continue;
+               }
+
+               std::stringstream ss;
+               if (freq >= 10000) {
+                       ss <<  std::setprecision (1) << std::fixed << freq / 1000 << "K";
+               } else if (freq >= 1000) {
+                       ss <<  std::setprecision (2) << std::fixed << freq / 1000 << "K";
+               } else {
+                       ss <<  std::setprecision (0) << std::fixed << freq << "Hz";
+               }
+               layout->set_text (ss.str ());
+               int lw, lh;
+               layout->get_pixel_size (lw, lh);
+
+               overlap = coord - lw - 3;
+
+               if (coord - lw / 2 < hl_margin + 2) {
+                       break;
+               }
+               if (x == 0) {
+                       // just get overlap position
+                       continue;
+               }
+               if (v_margin / 2 + lh > label_height) {
+                       label_height = v_margin / 2 + lh;
+               }
+
+
+               cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1.0);
+               cairo_move_to (cr, coord, v_margin);
+               cairo_line_to (cr, coord, height - v_margin - 1);
+               cairo_stroke (cr);
+
+               cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
+               cairo_move_to (cr, coord - lw / 2, v_margin / 2);
+               pango_cairo_update_layout (cr, layout->gobj ());
+               pango_cairo_show_layout (cr, layout->gobj ());
        }
 
+       return label_height;
 }
 
 void
-FFTGraph::redraw()
-{      
-       Glib::Mutex::Lock lm  (_a_window->track_list_lock);
+FFTGraph::redraw ()
+{
+       Glib::Threads::Mutex::Lock lm  (_a_window->track_list_lock);
+
+       assert (_surface);
+       cairo_t* cr = cairo_create (_surface);
+
+       _yoff = draw_scales (cr);
 
-       draw_scales(get_window());
-       
-       if (_a_window == 0)
+       if (_a_window == 0) {
+               cairo_destroy (cr);
+               queue_draw ();
                return;
+       }
 
-       if (!_a_window->track_list_ready)
+       if (!_a_window->track_list_ready) {
+               cairo_destroy (cr);
+               queue_draw ();
                return;
-       
-       
-       // Find "session wide" min & max
-       float min =  1000000000000.0;
-       float max = -1000000000000.0;
-       
-       TreeNodeChildren track_rows = _a_window->track_list.get_model()->children();
-       
-       for (TreeIter i = track_rows.begin(); i != track_rows.end(); i++) {
-               
-               TreeModel::Row row = *i;
-               FFTResult *res = row[_a_window->tlcols.graph];
+       }
+
+       float minf;
+       float maxf;
+
+       TreeNodeChildren track_rows = _a_window->track_list.get_model ()->children ();
 
-               // disregard fft analysis from empty signals
-               if (res->minimum() == res->maximum()) {
+       if (!_show_normalized) {
+               maxf =    0.0f;
+               minf = -108.0f;
+       } else  {
+               minf =  999.0f;
+               maxf = -999.0f;
+               for (TreeIter i = track_rows.begin (); i != track_rows.end (); i++) {
+                       TreeModel::Row row = *i;
+                       FFTResult *res = row[_a_window->tlcols.graph];
+
+                       // disregard fft analysis from empty signals
+                       if (res->minimum (_show_proportional) == res->maximum (_show_proportional)) {
+                               continue;
+                       }
+                       // don't include invisible graphs
+                       if (!row[_a_window->tlcols.visible]) {
+                               continue;
+                       }
+
+                       minf = std::min (minf, res->minimum (_show_proportional));
+                       maxf = std::max (maxf, res->maximum (_show_proportional));
+               }
+       }
+
+       // clamp range, > -200dBFS, at least 24dB (two y-axis labels) range
+       minf = std::max (-200.f, minf);
+       if (maxf <= minf) {
+               cairo_destroy (cr);
+               queue_draw ();
+               return;
+       }
+
+       if (maxf - minf < 24) {
+               maxf += 6.f;
+               minf = maxf - 24.f;
+       }
+
+       cairo_set_line_width (cr, 1.5);
+       cairo_translate (cr, hl_margin + 1, _yoff);
+
+       float fft_pane_size_w = width  - hl_margin - hr_margin;
+       float fft_pane_size_h = height - v_margin - 1 - _yoff;
+       double pixels_per_db = (double)fft_pane_size_h / (double)(maxf - minf);
+
+       // draw y-axis dB
+       cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
+
+       int btm_lbl = fft_pane_size_h;
+       {
+               // y-axis legend
+               layout->set_text (_("dBFS"));
+               int lw, lh;
+               layout->get_pixel_size (lw, lh);
+               cairo_move_to (cr, -2 - lw, fft_pane_size_h - lh / 2);
+               pango_cairo_update_layout (cr, layout->gobj ());
+               pango_cairo_show_layout (cr, layout->gobj ());
+               btm_lbl = fft_pane_size_h - lh;
+       }
+
+       for (int x = -6; x >= -200; x -= 12) {
+               float yp = 1.5 + fft_pane_size_h - rint ((x - minf) * pixels_per_db);
+
+               assert (layout);
+               std::stringstream ss;
+               ss << x;
+               layout->set_text (ss.str ());
+               int lw, lh;
+               layout->get_pixel_size (lw, lh);
+
+               if (yp + 2 + lh / 2 > btm_lbl) {
                        continue;
                }
-               
-               if ( res->minimum() < min) {
-                       min = res->minimum();
+               if (yp < 2 + lh / 2) {
+                       continue;
                }
 
-               if ( res->maximum() > max) {
-                       max = res->maximum();
-               }
-       }
-       
-       int graph_height = height - 2 * h_margin;
+               cairo_set_source_rgba (cr, .8, .8, .8, 1.0);
+               cairo_move_to (cr, -2 - lw, yp - lh / 2);
+               pango_cairo_update_layout (cr, layout->gobj ());
+               pango_cairo_show_layout (cr, layout->gobj ());
 
-       if (graph_gc == 0) {
-               graph_gc = GC::create( get_window() );
+               cairo_set_source_rgba (cr, .2, .2, .2, 1.0);
+               cairo_move_to (cr, 0, yp);
+               cairo_line_to (cr, fft_pane_size_w, yp);
+               cairo_stroke (cr);
        }
-       
-       
-       double pixels_per_db = (double)graph_height / (double)(max - min);
-       
-       
-       for (TreeIter i = track_rows.begin(); i != track_rows.end(); i++) {
-               
+
+       cairo_rectangle (cr, 1, 1, fft_pane_size_w, fft_pane_size_h);
+       cairo_clip (cr);
+
+       cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+       cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+
+       for (TreeIter i = track_rows.begin (); i != track_rows.end (); i++) {
                TreeModel::Row row = *i;
 
                // don't show graphs for tracks which are deselected
                if (!row[_a_window->tlcols.visible]) {
                        continue;
                }
-               
+
                FFTResult *res = row[_a_window->tlcols.graph];
 
                // don't show graphs for empty signals
-               if (res->minimum() == res->maximum()) {
+               if (res->minimum (_show_proportional) == res->maximum (_show_proportional)) {
                        continue;
                }
-               
-               std::string name = row[_a_window->tlcols.trackname];
-
-               // Set color from track
-               graph_gc->set_rgb_fg_color( res->get_color() );
-
-               float mpp = -1000000.0;
-               int prevx = 0;
-               float prevSample = min;
-               
-               for (int x = 0; x < res->length() - 1; x++) {
-                       
-                       if (res->sampleAt(x) > mpp)
-                               mpp = res->sampleAt(x);
-                       
-                       // 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]) {
+
+               float mpp;
+               float X,Y;
+
+               if (_show_minmax) {
+
+                       X = 0.5f + _logScale[0];
+                       Y = 1.5f + fft_pane_size_h - pixels_per_db * (res->maxAt (0, _show_proportional) - minf);
+                       cairo_move_to (cr, X, Y);
+
+                       // Draw the line of maximum values
+                       mpp = minf;
+                       for (unsigned int x = 1; x < res->length () - 1; ++x) {
+                               mpp = std::max (mpp, res->maxAt (x, _show_proportional));
+
+                               if (_logScale[x] == _logScale[x + 1]) {
+                                       continue;
+                               }
+
+                               mpp = fmin (mpp, maxf);
+                               X = 0.5f + _logScale[x];
+                               Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
+                               cairo_line_to (cr, X, Y);
+                               mpp = minf;
+                       }
+
+                       mpp = maxf;
+                       // Draw back to the start using the minimum value
+                       for (int x = res->length () - 1; x >= 0; --x) {
+                               mpp = std::min (mpp, res->minAt (x, _show_proportional));
+
+                               if (_logScale[x] == _logScale[x + 1]) {
+                                       continue;
+                               }
+
+                               mpp = fmax (mpp, minf);
+                               X = 0.5f + _logScale[x];
+                               Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
+                               cairo_line_to (cr, X, Y);
+                               mpp = maxf;
+                       }
+
+                       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_close_path (cr);
+                       cairo_fill (cr);
+               }
+
+               // draw max of averages
+               X = 0.5f + _logScale[0];
+               Y = 1.5f + fft_pane_size_h - pixels_per_db * (res->avgAt (0, _show_proportional) - minf);
+               cairo_move_to (cr, X, Y);
+
+               mpp = minf;
+               for (unsigned int x = 0; x < res->length () - 1; x++) {
+                       mpp = std::max (mpp, res->avgAt (x, _show_proportional));
+
+                       if (_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;
-                       
-
-                       mpp = -1000000.0;
-                       
+                       mpp = fmax (mpp, minf);
+                       mpp = fmin (mpp, maxf);
+
+                       X = 0.5f + _logScale[x];
+                       Y = 1.5f + fft_pane_size_h - pixels_per_db * (mpp - minf);
+                       cairo_line_to (cr, X, Y);
+                       mpp = minf;
                }
-       }
 
+               cairo_set_source_rgb (cr, res->get_color ().get_red_p (), res->get_color ().get_green_p (), res->get_color ().get_blue_p ());
+               cairo_stroke (cr);
+       }
+       cairo_destroy (cr);
+       queue_draw ();
 }
 
 void
-FFTGraph::on_size_request(Gtk::Requisition* requisition)
+FFTGraph::on_size_request (Gtk::Requisition* requisition)
 {
-       width  = scaleWidth  + h_margin * 2;
-       height = scaleHeight + 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);
-       }
+       width  = max (requisition->width,  minScaleWidth  + hl_margin + hr_margin);
+       height = max (requisition->height, minScaleHeight + 2 + v_margin * 2);
 
        requisition->width  = width;;
        requisition->height = height;
 }
 
 void
-FFTGraph::on_size_allocate(Gtk::Allocation & alloc)
+FFTGraph::on_size_allocate (Gtk::Allocation & alloc)
 {
-       width = alloc.get_width();
-       height = alloc.get_height();
-       
-       DrawingArea::on_size_allocate (alloc);
+       width = alloc.get_width ();
+       height = alloc.get_height ();
+
+       update_size ();
 
+       DrawingArea::on_size_allocate (alloc);
 }
 
+void
+FFTGraph::update_size ()
+{
+       framecnt_t SR = PublicEditor::instance ().session ()->nominal_frame_rate ();
+       _fft_start = SR / (double)_dataSize;
+       _fft_end = .5 * SR;
+       _fft_log_base = logf (.5 * _dataSize);
+       currentScaleWidth  = width - hl_margin - hr_margin;
+       _logScale[0] = 0;
+       for (unsigned int i = 1; i < _dataSize; ++i) {
+               _logScale[i] = floor (currentScaleWidth * logf (.5 * i) / _fft_log_base);
+       }
+       if (_surface) {
+               cairo_surface_destroy (_surface);
+       }
+       _surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+       redraw ();
+}