Merge branch 'waveview_caching_for_upstream' of https://github.com/nmains/ardour...
[ardour.git] / libs / canvas / wave_view.cc
1 /*
2     Copyright (C) 2011-2013 Paul Davis
3     Author: Carl Hetherington <cth@carlh.net>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <cmath>
22 #include <cairomm/cairomm.h>
23
24 #include "gtkmm2ext/utils.h"
25
26 #include "pbd/compose.h"
27 #include "pbd/signals.h"
28 #include "pbd/stacktrace.h"
29
30 #include "ardour/types.h"
31 #include "ardour/dB.h"
32 #include "ardour/audioregion.h"
33
34 #include "canvas/wave_view.h"
35 #include "canvas/utils.h"
36 #include "canvas/canvas.h"
37
38 #include <gdkmm/general.h>
39
40 using namespace std;
41 using namespace ARDOUR;
42 using namespace ArdourCanvas;
43
44 #define CACHE_HIGH_WATER (2)
45
46 std::map <boost::shared_ptr<AudioSource>, std::vector<WaveView::CacheEntry> >  WaveView::_image_cache;
47 double WaveView::_global_gradient_depth = 0.6;
48 bool WaveView::_global_logscaled = false;
49 WaveView::Shape WaveView::_global_shape = WaveView::Normal;
50 bool WaveView::_global_show_waveform_clipping = true;
51 double WaveView::_clip_level = 0.98853;
52
53 PBD::Signal0<void> WaveView::VisualPropertiesChanged;
54 PBD::Signal0<void> WaveView::ClipLevelChanged;
55
56 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
57         : Item (parent)
58         , Outline (parent)
59         , Fill (parent)
60         , _region (region)
61         , _channel (0)
62         , _samples_per_pixel (0)
63         , _height (64)
64         , _show_zero (false)
65         , _zero_color (0xff0000ff)
66         , _clip_color (0xff0000ff)
67         , _logscaled (_global_logscaled)
68         , _shape (_global_shape)
69         , _gradient_depth (_global_gradient_depth)
70         , _shape_independent (false)
71         , _logscaled_independent (false)
72         , _gradient_depth_independent (false)
73         , _amplitude_above_axis (1.0)
74         , _region_amplitude (_region->scale_amplitude ())
75         , _region_start (region->start())
76 {
77         VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
78         ClipLevelChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_clip_level_change, this));
79 }
80
81 WaveView::~WaveView ()
82 {
83         invalidate_image_cache ();
84 }
85
86 void
87 WaveView::handle_visual_property_change ()
88 {
89         bool changed = false;
90
91         if (!_shape_independent && (_shape != global_shape())) {
92                 _shape = global_shape();
93                 changed = true;
94         }
95
96         if (!_logscaled_independent && (_logscaled != global_logscaled())) {
97                 _logscaled = global_logscaled();
98                 changed = true;
99         }
100
101         if (!_gradient_depth_independent && (_gradient_depth != global_gradient_depth())) {
102                 _gradient_depth = global_gradient_depth();
103                 changed = true;
104         }
105         
106         if (changed) {
107                 begin_visual_change ();
108                 invalidate_image_cache ();
109                 end_visual_change ();
110         }
111 }
112
113 void
114 WaveView::handle_clip_level_change ()
115 {
116         begin_visual_change ();
117         invalidate_image_cache ();
118         end_visual_change ();
119 }
120
121 void
122 WaveView::set_fill_color (Color c)
123 {
124         if (c != _fill_color) {
125                 begin_visual_change ();
126                 invalidate_image_cache ();
127                 Fill::set_fill_color (c);
128                 end_visual_change ();
129         }
130 }
131
132 void
133 WaveView::set_outline_color (Color c)
134 {
135         if (c != _outline_color) {
136                 begin_visual_change ();
137                 invalidate_image_cache ();
138                 Outline::set_outline_color (c);
139                 end_visual_change ();
140         }
141 }
142
143 void
144 WaveView::set_samples_per_pixel (double samples_per_pixel)
145 {
146         if (samples_per_pixel != _samples_per_pixel) {
147                 begin_change ();
148
149                 invalidate_image_cache ();
150                 _samples_per_pixel = samples_per_pixel;
151                 _bounding_box_dirty = true;
152                 
153                 end_change ();
154         }
155 }
156
157 static inline double
158 image_to_window (double wave_origin, double image_start)
159 {
160         return wave_origin + image_start;
161 }
162
163 static inline double
164 window_to_image (double wave_origin, double image_start)
165 {
166         return image_start - wave_origin;
167 }
168
169 static inline float
170 _log_meter (float power, double lower_db, double upper_db, double non_linearity)
171 {
172         return (power < lower_db ? 0.0 : pow((power-lower_db)/(upper_db-lower_db), non_linearity));
173 }
174
175 static inline float
176 alt_log_meter (float power)
177 {
178         return _log_meter (power, -192.0, 0.0, 8.0);
179 }
180
181 void
182 WaveView::set_clip_level (double dB)
183 {
184         const double clip_level = dB_to_coefficient (dB);
185         if (clip_level != _clip_level) {
186                 _clip_level = clip_level;
187                 ClipLevelChanged ();
188         }
189 }
190
191 void
192 WaveView::invalidate_image_cache ()
193 {
194         vector <uint32_t> deletion_list;
195         vector <CacheEntry> caches;
196
197         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
198                 caches = _image_cache.find (_region->audio_source ())->second;
199         } else {
200                 return;
201         }
202
203         for (uint32_t i = 0; i < caches.size (); ++i) {
204
205                 if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude) {
206                         continue;
207                 }
208
209                 deletion_list.push_back (i);
210                 
211         }
212
213         while (deletion_list.size() > 0) {
214                 caches[deletion_list.back ()].image.clear ();
215                 caches.erase (caches.begin() + deletion_list.back());
216                 deletion_list.pop_back();
217         }
218
219         if (caches.size () == 0) {
220                 _image_cache.erase(_region->audio_source ());
221         } else {
222                 _image_cache[_region->audio_source ()] = caches;
223         }
224
225 }
226
227 void
228 WaveView::consolidate_image_cache () const
229 {
230         list <uint32_t> deletion_list;
231         vector <CacheEntry> caches;
232         uint32_t other_entries = 0;
233
234         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
235                 caches  = _image_cache.find (_region->audio_source ())->second;
236         }
237
238         for (uint32_t i = 0; i < caches.size (); ++i) {
239
240                 if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude) {
241                         other_entries++;
242                         continue;
243                 }
244
245                 framepos_t segment_start = caches[i].start;
246                 framepos_t segment_end = caches[i].end;
247
248                 for (uint32_t j = i; j < caches.size (); ++j) {
249
250                         if (i == j || _channel != caches[j].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude) {
251                                 continue;
252                         }
253
254                         if (caches[j].start >= segment_start && caches[j].end <= segment_end) {
255
256                                 deletion_list.push_back (j);
257                         }
258                 }
259         }
260
261         deletion_list.sort ();
262         deletion_list.unique ();
263
264         while (deletion_list.size() > 0) {
265                 caches[deletion_list.back ()].image.clear ();
266                 caches.erase (caches.begin() + deletion_list.back ());
267                 deletion_list.pop_back();
268         }
269
270         /* We don't care if this channel/height/amplitude has anything in the cache - just drop the Last Added entries 
271            until we reach a size where there is a maximum of CACHE_HIGH_WATER + other entries.
272         */
273
274         while (caches.size() > CACHE_HIGH_WATER + other_entries) {
275                 caches.front ().image.clear ();
276                 caches.erase(caches.begin ());
277         }
278
279         if (caches.size () == 0) {
280                 _image_cache.erase (_region->audio_source ());
281         } else {
282                 _image_cache[_region->audio_source ()] = caches;
283         }
284 }
285
286 struct LineTips {
287         double top;
288         double bot;
289         bool clip_max;
290         bool clip_min;
291         
292         LineTips() : top (0.0), bot (0.0), clip_max (false), clip_min (false) {}
293 };
294
295 void
296 WaveView::draw_image (Cairo::RefPtr<Cairo::ImageSurface>& image, PeakData* _peaks, int n_peaks) const
297 {
298         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (image);
299         boost::scoped_array<LineTips> tips (new LineTips[n_peaks]);
300
301         /* Clip level nominally set to -0.9dBFS to account for inter-sample
302            interpolation possibly clipping (value may be too low).
303
304            We adjust by the region's own gain (but note: not by any gain
305            automation or its gain envelope) so that clip indicators are closer
306            to providing data about on-disk data. This multiplication is
307            needed because the data we get from AudioRegion::read_peaks()
308            has been scaled by scale_amplitude() already.
309         */
310
311         const double clip_level = _clip_level * _region_amplitude;
312
313         if (_shape == WaveView::Rectified) {
314
315                 /* each peak is a line from the bottom of the waveview
316                  * to a point determined by max (_peaks[i].max,
317                  * _peaks[i].min)
318                  */
319
320                 if (_logscaled) {
321                         for (int i = 0; i < n_peaks; ++i) {
322                                 tips[i].bot = height();
323                                 tips[i].top = y_extent (alt_log_meter (fast_coefficient_to_dB (max (fabs (_peaks[i].max), fabs (_peaks[i].min)))));
324
325                                 if (fabs (_peaks[i].max) >= clip_level) {
326                                         tips[i].clip_max = true;
327                                 }
328
329                                 if (fabs (_peaks[i].min) >= clip_level) {
330                                         tips[i].clip_min = true;
331                                 }
332                         }
333                 } else {for (int i = 0; i < n_peaks; ++i) {
334                                 tips[i].bot = height();
335                                 tips[i].top = y_extent (max (fabs (_peaks[i].max), fabs (_peaks[i].min)));
336
337                                 if (fabs (_peaks[i].max) >= clip_level) {
338                                         tips[i].clip_max = true;
339                                 }
340
341                                 if (fabs (_peaks[i].min) >= clip_level) {
342                                         tips[i].clip_min = true;
343                                 }
344                         }
345                 }
346
347         } else {
348
349                 if (_logscaled) {
350                         for (int i = 0; i < n_peaks; ++i) {
351                                 Coord top = _peaks[i].min;
352                                 Coord bot = _peaks[i].max;
353
354                                 if (fabs (top) >= clip_level) {
355                                         tips[i].clip_max = true;
356                                 }
357
358                                 if (fabs (bot) >= clip_level) {
359                                         tips[i].clip_min = true;
360                                 }
361
362                                 if (top > 0.0) {
363                                         top = y_extent (alt_log_meter (fast_coefficient_to_dB (top)));
364                                 } else if (top < 0.0) {
365                                         top = y_extent (-alt_log_meter (fast_coefficient_to_dB (-top)));
366                                 } else {
367                                         top = y_extent (0.0);
368                                 }
369
370                                 if (bot > 0.0) {
371                                         bot = y_extent (alt_log_meter (fast_coefficient_to_dB (bot)));
372                                 } else if (bot < 0.0) {
373                                         bot = y_extent (-alt_log_meter (fast_coefficient_to_dB (-bot)));
374                                 } else {
375                                         bot = y_extent (0.0);
376                                 }
377
378                                 tips[i].top = top;
379                                 tips[i].bot = bot;
380                         } 
381
382                 } else {
383                         for (int i = 0; i < n_peaks; ++i) {
384
385                                 if (fabs (_peaks[i].max) >= clip_level) {
386                                         tips[i].clip_max = true;
387                                 }
388
389                                 if (fabs (_peaks[i].min) >= clip_level) {
390                                         tips[i].clip_min = true;
391                                 }
392
393                                 tips[i].top = y_extent (_peaks[i].min);
394                                 tips[i].bot = y_extent (_peaks[i].max);
395
396
397                         }
398                 }
399         }
400
401         if (gradient_depth() != 0.0) {
402                         
403                 Cairo::RefPtr<Cairo::LinearGradient> gradient (Cairo::LinearGradient::create (0, 0, 0, _height));
404                         
405                 double stops[3];
406                         
407                 double r, g, b, a;
408
409                 if (_shape == Rectified) {
410                         stops[0] = 0.1;
411                         stops[0] = 0.3;
412                         stops[0] = 0.9;
413                 } else {
414                         stops[0] = 0.1;
415                         stops[1] = 0.5;
416                         stops[2] = 0.9;
417                 }
418
419                 color_to_rgba (_fill_color, r, g, b, a);
420                 gradient->add_color_stop_rgba (stops[0], r, g, b, a);
421                 gradient->add_color_stop_rgba (stops[2], r, g, b, a);
422
423                 /* generate a new color for the middle of the gradient */
424                 double h, s, v;
425                 color_to_hsv (_fill_color, h, s, v);
426                 /* change v towards white */
427                 v *= 1.0 - gradient_depth();
428                 Color center = hsv_to_color (h, s, v, a);
429                 color_to_rgba (center, r, g, b, a);
430                 gradient->add_color_stop_rgba (stops[1], r, g, b, a);
431                         
432                 context->set_source (gradient);
433         } else {
434                 set_source_rgba (context, _fill_color);
435         }
436
437         /* ensure single-pixel lines */
438                 
439         context->set_line_width (0.5);
440         context->translate (0.5, 0.0);
441
442         /* draw the lines */
443
444         if (_shape == WaveView::Rectified) {
445                 for (int i = 0; i < n_peaks; ++i) {
446                         context->move_to (i, tips[i].top); /* down 1 pixel */
447                         context->line_to (i, tips[i].bot);
448                 }
449         } else {
450                 for (int i = 0; i < n_peaks; ++i) {
451                         context->move_to (i, tips[i].top);
452                         context->line_to (i, tips[i].bot);
453                 }
454         }
455
456         context->stroke ();
457
458         /* now add dots to the top and bottom of each line (this is
459          * modelled on pyramix, except that we add clipping indicators.
460          */
461
462         if (_global_show_waveform_clipping) {
463                 set_source_rgba (context, _clip_color);
464
465                 /* the height of the clip-indicator should be at most 7 pixels,
466                    or 5% of the height of the waveview item.
467                 */
468                 const double clip_height = min (7.0, ceil (_height * 0.05));
469                 
470                 for (int i = 0; i < n_peaks; ++i) {
471                         context->move_to (i, tips[i].top);
472                         
473                         bool show_top_clip = (_shape == WaveView::Rectified && (tips[i].clip_max || tips[i].clip_min)) ||
474                                 tips[i].clip_max;
475                         
476                         if (show_top_clip) {
477                                 context->rel_line_to (0, clip_height);
478                         }
479
480                         if (_shape != WaveView::Rectified) {
481                                 context->move_to (i, tips[i].bot);
482                                 if (tips[i].clip_min) {
483                                         context->rel_line_to (0, -clip_height);
484                                 }
485                         }
486                 }
487                 context->stroke ();
488         }
489
490         if (show_zero_line()) {
491
492                 set_source_rgba (context, _zero_color);
493                 context->set_line_width (1.0);
494                 context->move_to (0, y_extent (0.0) + 0.5);
495                 context->line_to (n_peaks, y_extent (0.0) + 0.5);
496                 context->stroke ();
497         }
498 }
499
500 void
501 WaveView::get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const
502 {
503         vector <CacheEntry> caches;
504
505         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
506                 
507                 caches = _image_cache.find (_region->audio_source ())->second;
508         }
509
510         /* Find a suitable ImageSurface.
511         */
512         for (uint32_t i = 0; i < caches.size (); ++i) {
513
514                 if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude) {
515                         continue;
516                 }
517
518                 framepos_t segment_start = caches[i].start;
519                 framepos_t segment_end = caches[i].end;
520
521                 if (end <= segment_end && start >= segment_start) {
522                         image_offset = (segment_start - _region->start()) / _samples_per_pixel;
523                         image = caches[i].image;
524
525                         return;
526                 }
527         }
528
529         consolidate_image_cache ();
530
531         /* sample position is canonical here, and we want to generate
532          * an image that spans about twice the canvas width 
533          */
534         
535         const framepos_t center = start + ((end - start) / 2);
536         const framecnt_t canvas_samples = _canvas->visible_area().width() * _samples_per_pixel; /* one canvas width */
537
538         /* we can request data from anywhere in the Source, between 0 and its length
539          */
540
541         framepos_t sample_start = max ((framepos_t) 0, (center - canvas_samples));
542         framepos_t sample_end = min (center + canvas_samples, _region->source_length (0));
543
544         const int n_peaks = llrintf ((sample_end - sample_start)/ (double) _samples_per_pixel);
545
546         boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
547
548         _region->read_peaks (peaks.get(), n_peaks, 
549                              sample_start, sample_end - sample_start,
550                              _channel, 
551                              _samples_per_pixel);
552
553         image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, ((double)(sample_end - sample_start)) / _samples_per_pixel, _height);
554
555         draw_image (image, peaks.get(), n_peaks);
556
557         _image_cache[_region->audio_source ()].push_back (CacheEntry (_channel, _height, _region_amplitude, sample_start,  sample_end, image));
558
559         image_offset = (sample_start - _region->start()) / _samples_per_pixel;
560
561         //cerr << "_image_cache size is : " << _image_cache.size() << " entries for this audiosource : " << _image_cache.find (_region->audio_source ())->second.size() << endl;
562
563         return;
564 }
565
566 void
567 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
568 {
569         assert (_samples_per_pixel != 0);
570
571         if (!_region) {
572                 return;
573         }
574
575         Rect self = item_to_window (Rect (0.5, 0.0, _region->length() / _samples_per_pixel, _height));
576         boost::optional<Rect> d = self.intersection (area);
577
578         if (!d) {
579                 return;
580         }
581         
582         Rect draw = d.get();
583
584         /* window coordinates - pixels where x=0 is the left edge of the canvas
585          * window. We round down in case we were asked to
586          * draw "between" pixels at the start and/or end.
587          */
588
589         const double draw_start = floor (draw.x0);
590         const double draw_end = floor (draw.x1);
591
592         // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
593         
594         /* image coordnates: pixels where x=0 is the start of this waveview,
595          * wherever it may be positioned. thus image_start=N means "an image
596          * that beings N pixels after the start of region that this waveview is
597          * representing. 
598          */
599
600         const framepos_t image_start = window_to_image (self.x0, draw_start);
601         const framepos_t image_end = window_to_image (self.x0, draw_end);
602
603         // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
604
605         /* sample coordinates - note, these are not subject to rounding error */
606         framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
607         framepos_t sample_end   = _region_start + (image_end * _samples_per_pixel);
608
609         // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
610
611         Cairo::RefPtr<Cairo::ImageSurface> image;
612         double image_offset = 0;
613
614         get_image (image, sample_start, sample_end, image_offset);
615
616         // cerr << "Offset into image to place at zero: " << image_offset << endl;
617
618         context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
619
620         /* round image origin position to an exact pixel in device space to
621          * avoid blurring
622          */
623
624         double x  = self.x0 + image_offset;
625         double y  = self.y0;
626         context->user_to_device (x, y);
627         x = round (x);
628         y = round (y);
629         context->device_to_user (x, y);
630
631         context->set_source (image, x, y);
632         context->fill ();
633 }
634
635 void
636 WaveView::compute_bounding_box () const
637 {
638         if (_region) {
639                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
640         } else {
641                 _bounding_box = boost::optional<Rect> ();
642         }
643         
644         _bounding_box_dirty = false;
645 }
646         
647 void
648 WaveView::set_height (Distance height)
649 {
650         if (height != _height) {
651                 begin_change ();
652
653                 invalidate_image_cache ();
654                 _height = height;
655
656                 _bounding_box_dirty = true;
657                 end_change ();
658         }
659 }
660
661 void
662 WaveView::set_channel (int channel)
663 {
664         if (channel != _channel) {
665                 begin_change ();
666
667                 invalidate_image_cache ();
668                 _channel = channel;
669
670                 _bounding_box_dirty = true;
671                 end_change ();
672         }
673 }
674
675 void
676 WaveView::set_logscaled (bool yn)
677 {
678         if (_logscaled != yn) {
679                 begin_visual_change ();
680                 invalidate_image_cache ();
681                 _logscaled = yn;
682                 end_visual_change ();
683         }
684 }
685
686 void
687 WaveView::gain_changed ()
688 {
689         begin_visual_change ();
690         invalidate_image_cache ();
691         _region_amplitude = _region->scale_amplitude ();
692         end_visual_change ();
693 }
694
695 void
696 WaveView::set_zero_color (Color c)
697 {
698         if (_zero_color != c) {
699                 begin_visual_change ();
700                 invalidate_image_cache ();
701                 _zero_color = c;
702                 end_visual_change ();
703         }
704 }
705
706 void
707 WaveView::set_clip_color (Color c)
708 {
709         if (_clip_color != c) {
710                 begin_visual_change ();
711                 invalidate_image_cache ();
712                 _clip_color = c;
713                 end_visual_change ();
714         }
715 }
716
717 void
718 WaveView::set_show_zero_line (bool yn)
719 {
720         if (_show_zero != yn) {
721                 begin_visual_change ();
722                 invalidate_image_cache ();
723                 _show_zero = yn;
724                 end_visual_change ();
725         }
726 }
727
728 void
729 WaveView::set_shape (Shape s)
730 {
731         if (_shape != s) {
732                 begin_visual_change ();
733                 invalidate_image_cache ();
734                 _shape = s;
735                 end_visual_change ();
736         }
737 }
738
739 void
740 WaveView::set_amplitude_above_axis (double a)
741 {
742         if (_amplitude_above_axis != a) {
743                 begin_visual_change ();
744                 invalidate_image_cache ();
745                 _amplitude_above_axis = a;
746                 end_visual_change ();
747         }
748 }
749
750 void
751 WaveView::set_global_shape (Shape s)
752 {
753         if (_global_shape != s) {
754                 _global_shape = s;
755                 VisualPropertiesChanged (); /* EMIT SIGNAL */
756         }
757 }
758
759 void
760 WaveView::set_global_logscaled (bool yn)
761 {
762         if (_global_logscaled != yn) {
763                 _global_logscaled = yn;
764                 VisualPropertiesChanged (); /* EMIT SIGNAL */
765         }
766 }
767
768 void
769 WaveView::region_resized ()
770 {
771         if (!_region) {
772                 return;
773         }
774
775         /* special: do not use _region->length() here to compute
776            bounding box because it will already have changed.
777            
778            if we have a bounding box, use it.
779         */
780
781         _pre_change_bounding_box = _bounding_box;
782
783         _bounding_box_dirty = true;
784         compute_bounding_box ();
785
786         end_change ();
787 }
788
789 Coord
790 WaveView::y_extent (double s) const
791 {
792         /* it is important that this returns an integral value, so that we 
793            can ensure correct single pixel behaviour.
794          */
795
796         Coord pos;
797
798         switch (_shape) {
799         case Rectified:
800                 pos = floor (_height - (s * _height));
801                 break;
802         default:
803                 pos = floor ((1.0-s) * (_height / 2.0));
804                 break;
805         }
806
807         return min (_height, (max (0.0, pos)));
808 }
809
810 void
811 WaveView::set_global_gradient_depth (double depth)
812 {
813         if (_global_gradient_depth != depth) {
814                 _global_gradient_depth = depth;
815                 VisualPropertiesChanged (); /* EMIT SIGNAL */
816         }
817 }
818
819 void
820 WaveView::set_global_show_waveform_clipping (bool yn)
821 {
822         if (_global_show_waveform_clipping != yn) {
823                 _global_show_waveform_clipping = yn;
824                 VisualPropertiesChanged (); /* EMIT SIGNAL */
825         }
826 }
827