restore log-scaled waveform functionality
[ardour.git] / libs / canvas / wave_view.cc
index 9ab06c464904081eeb8b17911b61be5536e6ddec..44b93cfd3ffdc84a82aa572feadcd356b8778575 100644 (file)
@@ -18,6 +18,7 @@
 
 */
 
+#include <cmath>
 #include <cairomm/cairomm.h>
 
 #include "gtkmm2ext/utils.h"
 #include "pbd/signals.h"
 
 #include "ardour/types.h"
+#include "ardour/dB.h"
 #include "ardour/audioregion.h"
 
 #include "canvas/wave_view.h"
 #include "canvas/utils.h"
+#include "canvas/canvas.h"
 
 #include <gdkmm/general.h>
 
@@ -37,6 +40,9 @@ using namespace std;
 using namespace ARDOUR;
 using namespace ArdourCanvas;
 
+bool WaveView::_gradient_waveforms = true;
+PBD::Signal0<void> WaveView::InvalidateAllImages;
+
 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
        : Item (parent)
        , Outline (parent)
@@ -46,9 +52,22 @@ WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region
        , _samples_per_pixel (0)
        , _height (64)
        , _wave_color (0xffffffff)
+       , _show_zero (true)
+       , _zero_color (0xff0000ff)
+       , _shape (Normal)
+       , _clip_color (0xff0000ff)
+       , _logscaled (false)
+       , _amplitude (1.0)
        , _region_start (0)
 {
-       
+       InvalidateAllImages.connect_same_thread (invalidation_connection, boost::bind (&WaveView::rebuild, this));
+}
+
+void
+WaveView::rebuild ()
+{
+       invalidate_image_cache ();
+       _canvas->item_visual_property_changed (this);
 }
 
 void
@@ -215,6 +234,65 @@ WaveView::region_resized ()
        _bounding_box_dirty = true;
 }
 
+void
+WaveView::set_logscaled (bool yn)
+{
+       if (_logscaled != yn) {
+               _logscaled = yn;
+               invalidate_whole_cache ();
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
+void
+WaveView::set_amplitude (double a)
+{
+       if (_amplitude != a) {
+               _amplitude = a;
+               invalidate_whole_cache ();
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
+void
+WaveView::set_zero_color (Color c)
+{
+       if (_zero_color != c) {
+               _zero_color = c;
+               invalidate_whole_cache ();
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
+void
+WaveView::set_clip_color (Color c)
+{
+       if (_clip_color != c) {
+               _clip_color = c;
+               invalidate_whole_cache ();
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
+void
+WaveView::set_show_zero_line (bool yn)
+{
+       if (_show_zero != yn) {
+               _show_zero = yn;
+               invalidate_whole_cache ();
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
+void
+WaveView::set_shape (Shape s)
+{
+       if (_shape != s) {
+               _shape = s;
+               _canvas->item_visual_property_changed (this);
+       }
+}
+
 void
 WaveView::set_region_start (frameoffset_t start)
 {
@@ -251,6 +329,18 @@ WaveView::CacheEntry::~CacheEntry ()
 {
 }
 
+static inline float
+_log_meter (float power, double lower_db, double upper_db, double non_linearity)
+{
+       return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
+}
+
+static inline float
+alt_log_meter (float power)
+{
+       return _log_meter (power, -192.0, 0.0, 8.0);
+}
+
 Cairo::RefPtr<Cairo::ImageSurface>
 WaveView::CacheEntry::image ()
 {
@@ -259,23 +349,90 @@ WaveView::CacheEntry::image ()
                _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
                Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
 
-               _wave_view->setup_outline_context (context);
-               context->move_to (0.5, position (_peaks[0].min));
-               for (int i = 1; i < _n_peaks; ++i) {
-                       context->line_to (i + 0.5, position (_peaks[i].max));
+               /* Draw the edge of the waveform, top half first, the loop back
+                * for the bottom half to create a clockwise path
+                */
+
+               context->begin_new_path();
+
+               if (_wave_view->_logscaled) {
+                       for (int i = 0; i < _n_peaks; ++i) {
+                               Coord y = _peaks[i].max;
+                               if (y > 0.0) {
+                                       context->line_to (i + 0.5, position (_wave_view->amplitude() * alt_log_meter (fast_coefficient_to_dB (y))));
+                               } else if (y < 0.0) {
+                                       context->line_to (i + 0.5, position (_wave_view->amplitude() * -alt_log_meter (fast_coefficient_to_dB (-y))));
+                               } else {
+                                       context->line_to (i + 0.5, position (0.0));
+                               }
+                       } 
+               } else {
+                       for (int i = 0; i < _n_peaks; ++i) {
+                               context->line_to (i + 0.5, position (_wave_view->amplitude() * _peaks[i].max));
+                       }
                }
-               context->stroke ();
+
+               /* from final top point, move out of the clip zone */
+
+               context->line_to (_n_peaks + 10, position (0.0));
                
-               context->move_to (0.5, position (_peaks[0].min));
-               for (int i = 1; i < _n_peaks; ++i) {
-                       context->line_to (i + 0.5, position (_peaks[i].min));
+               /* bottom half, in reverse */
+
+               if (_wave_view->_logscaled) {
+                       for (int i = _n_peaks-1; i >= 0; --i) {
+                               Coord y = _peaks[i].min;
+                               if (y > 0.0) {
+                                       context->line_to (i + 0.5, position (_wave_view->amplitude() * alt_log_meter (fast_coefficient_to_dB (y))));
+                               } else if (y < 0.0) {
+                                       context->line_to (i + 0.5, position (_wave_view->amplitude() * -alt_log_meter (fast_coefficient_to_dB (-y))));
+                               } else {
+                                       context->line_to (i + 0.5, position (0.0));
+                               }
+                       } 
+               } else {
+                       for (int i = _n_peaks-1; i >= 0; --i) {
+                               context->line_to (i + 0.5, position (_wave_view->amplitude() * _peaks[i].min));
+                       }
                }
+               
+               /* from final bottom point, move out of the clip zone */
+
+               context->line_to (-10.0, position (0.0));
+
+               context->close_path ();
+
+               if (WaveView::gradient_waveforms()) {
+
+                       Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _wave_view->_height));
+                       
+                       double r, g, b, a;
+                       
+                       color_to_rgba (_wave_view->_fill_color, r, g, b, a);
+                       gradient->add_color_stop_rgba (0.1, r, g, b, a);
+                       gradient->add_color_stop_rgba (0.9, r, g, b, a);
+                       
+                       /* generate a new color for the middle of the gradient */
+                       double h, s, v;
+                       color_to_hsv (_wave_view->_fill_color, h, s, v);
+                       /* tone down the saturation */
+                       s *= 0.75;
+                       Color center = hsv_to_color (h, s, v, a);
+                       color_to_rgba (center, r, g, b, a);
+                       gradient->add_color_stop_rgba (0.5, r, g, b, a);
+                       
+                       context->set_source (gradient);
+               } else {
+                       set_source_rgba (context, _wave_view->_fill_color);
+               }
+
+               context->fill_preserve ();
+               _wave_view->setup_outline_context (context);
                context->stroke ();
 
-               set_source_rgba (context, _wave_view->_fill_color);
-               for (int i = 0; i < _n_peaks; ++i) {
-                       context->move_to (i + 0.5, position (_peaks[i].max) - 1);
-                       context->line_to (i + 0.5, position (_peaks[i].min) + 1);
+               if (_wave_view->show_zero_line()) {
+                       set_source_rgba (context, _wave_view->_zero_color);
+                       context->move_to (0, position (0.0));
+                       context->line_to (_n_peaks, position (0.0));
                        context->stroke ();
                }
        }
@@ -285,9 +442,15 @@ WaveView::CacheEntry::image ()
 
 
 Coord
-WaveView::CacheEntry::position (float s) const
+WaveView::CacheEntry::position (double s) const
 {
-       return (s + 1) * _wave_view->_height / 2;
+       switch (_wave_view->_shape) {
+       case Rectified:
+               return _wave_view->_height - (s * _wave_view->_height);
+       default:
+               break;
+       }
+       return (s+1.0) * (_wave_view->_height / 2.0);
 }
 
 void
@@ -295,6 +458,12 @@ WaveView::CacheEntry::clear_image ()
 {
        _image.clear ();
 }
-
-
-               
+           
+void
+WaveView::set_gradient_waveforms (bool yn)
+{
+       if (_gradient_waveforms != yn) {
+               _gradient_waveforms = yn;
+               InvalidateAllImages (); /* EMIT SIGNAL */
+       }
+}