6f626e7010442528d076745ab2e81db7faf2dc46
[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          * the height of the clip-indicator should be at most 7 pixels,
462          * or 5% of the height of the waveview item.
463          */
464
465         const double clip_height = min (7.0, ceil (_height * 0.05));
466
467         set_source_rgba (context, _outline_color);
468                 
469         for (int i = 0; i < n_peaks; ++i) {
470                 context->move_to (i, tips[i].top);
471                         
472                 bool show_top_clip =   _global_show_waveform_clipping && 
473                         ((_shape == WaveView::Rectified && (tips[i].clip_max || tips[i].clip_min)) ||
474                          tips[i].clip_max);
475                         
476                 if (show_top_clip) {
477                         /* clip-indicating upper terminal line */
478                         set_source_rgba (context, _clip_color);
479                         context->rel_line_to (0, clip_height);
480                         context->stroke ();
481                         set_source_rgba (context, _outline_color);
482                 } else {
483                         /* normal upper terminal dot */
484                         context->rel_line_to (0, 1.0);
485                         context->stroke ();
486                 }
487
488
489                 if (_global_show_waveform_clipping && _shape != WaveView::Rectified) {
490                         context->move_to (i, tips[i].bot);
491                         if (tips[i].clip_min) {
492                                 /* clip-indicating lower terminal line */
493                                 set_source_rgba (context, _clip_color);
494                                 context->rel_line_to (0, -clip_height);
495                                 context->stroke ();
496                                 set_source_rgba (context, _outline_color);
497                         } else {
498                                 /* normal lower terminal dot */
499                                 context->rel_line_to (0, -1.0);
500                                 context->stroke ();
501                         }
502                 }
503         }
504
505         if (show_zero_line()) {
506                 set_source_rgba (context, _zero_color);
507                 context->set_line_width (1.0);
508                 context->move_to (0, y_extent (0.0) + 0.5);
509                 context->line_to (n_peaks, y_extent (0.0) + 0.5);
510                 context->stroke ();
511         }
512 }
513
514 void
515 WaveView::get_image (Cairo::RefPtr<Cairo::ImageSurface>& image, framepos_t start, framepos_t end, double& image_offset) const
516 {
517         vector <CacheEntry> caches;
518
519         if (_image_cache.find (_region->audio_source ()) != _image_cache.end ()) {
520                 
521                 caches = _image_cache.find (_region->audio_source ())->second;
522         }
523
524         /* Find a suitable ImageSurface.
525         */
526         for (uint32_t i = 0; i < caches.size (); ++i) {
527
528                 if (_channel != caches[i].channel || _height != caches[i].height || _region_amplitude != caches[i].amplitude) {
529                         continue;
530                 }
531
532                 framepos_t segment_start = caches[i].start;
533                 framepos_t segment_end = caches[i].end;
534
535                 if (end <= segment_end && start >= segment_start) {
536                         image_offset = (segment_start - _region->start()) / _samples_per_pixel;
537                         image = caches[i].image;
538
539                         return;
540                 }
541         }
542
543         consolidate_image_cache ();
544
545         /* sample position is canonical here, and we want to generate
546          * an image that spans about twice the canvas width 
547          */
548         
549         const framepos_t center = start + ((end - start) / 2);
550         const framecnt_t canvas_samples = _canvas->visible_area().width() * _samples_per_pixel; /* one canvas width */
551
552         /* we can request data from anywhere in the Source, between 0 and its length
553          */
554
555         framepos_t sample_start = max ((framepos_t) 0, (center - canvas_samples));
556         framepos_t sample_end = min (center + canvas_samples, _region->source_length (0));
557
558         const int n_peaks = llrintf ((sample_end - sample_start)/ (double) _samples_per_pixel);
559
560         boost::scoped_array<ARDOUR::PeakData> peaks (new PeakData[n_peaks]);
561
562         _region->read_peaks (peaks.get(), n_peaks, 
563                              sample_start, sample_end - sample_start,
564                              _channel, 
565                              _samples_per_pixel);
566
567         image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, ((double)(sample_end - sample_start)) / _samples_per_pixel, _height);
568
569         draw_image (image, peaks.get(), n_peaks);
570
571         _image_cache[_region->audio_source ()].push_back (CacheEntry (_channel, _height, _region_amplitude, sample_start,  sample_end, image));
572
573         image_offset = (sample_start - _region->start()) / _samples_per_pixel;
574
575         //cerr << "_image_cache size is : " << _image_cache.size() << " entries for this audiosource : " << _image_cache.find (_region->audio_source ())->second.size() << endl;
576
577         return;
578 }
579
580 void
581 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
582 {
583         assert (_samples_per_pixel != 0);
584
585         if (!_region) {
586                 return;
587         }
588
589         Rect self = item_to_window (Rect (0.5, 0.0, _region->length() / _samples_per_pixel, _height));
590         boost::optional<Rect> d = self.intersection (area);
591
592         if (!d) {
593                 return;
594         }
595         
596         Rect draw = d.get();
597
598         /* window coordinates - pixels where x=0 is the left edge of the canvas
599          * window. We round down in case we were asked to
600          * draw "between" pixels at the start and/or end.
601          */
602
603         const double draw_start = floor (draw.x0);
604         const double draw_end = floor (draw.x1);
605
606         // cerr << "Need to draw " << draw_start << " .. " << draw_end << endl;
607         
608         /* image coordnates: pixels where x=0 is the start of this waveview,
609          * wherever it may be positioned. thus image_start=N means "an image
610          * that beings N pixels after the start of region that this waveview is
611          * representing. 
612          */
613
614         const framepos_t image_start = window_to_image (self.x0, draw_start);
615         const framepos_t image_end = window_to_image (self.x0, draw_end);
616
617         // cerr << "Image/WV space: " << image_start << " .. " << image_end << endl;
618
619         /* sample coordinates - note, these are not subject to rounding error */
620         framepos_t sample_start = _region_start + (image_start * _samples_per_pixel);
621         framepos_t sample_end   = _region_start + (image_end * _samples_per_pixel);
622
623         // cerr << "Sample space: " << sample_start << " .. " << sample_end << endl;
624
625         Cairo::RefPtr<Cairo::ImageSurface> image;
626         double image_offset = 0;
627
628         get_image (image, sample_start, sample_end, image_offset);
629
630         // cerr << "Offset into image to place at zero: " << image_offset << endl;
631
632         context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
633
634         /* round image origin position to an exact pixel in device space to
635          * avoid blurring
636          */
637
638         double x  = self.x0 + image_offset;
639         double y  = self.y0;
640         context->user_to_device (x, y);
641         x = round (x);
642         y = round (y);
643         context->device_to_user (x, y);
644
645         context->set_source (image, x, y);
646         context->fill ();
647 }
648
649 void
650 WaveView::compute_bounding_box () const
651 {
652         if (_region) {
653                 _bounding_box = Rect (0.0, 0.0, _region->length() / _samples_per_pixel, _height);
654         } else {
655                 _bounding_box = boost::optional<Rect> ();
656         }
657         
658         _bounding_box_dirty = false;
659 }
660         
661 void
662 WaveView::set_height (Distance height)
663 {
664         if (height != _height) {
665                 begin_change ();
666
667                 invalidate_image_cache ();
668                 _height = height;
669
670                 _bounding_box_dirty = true;
671                 end_change ();
672         }
673 }
674
675 void
676 WaveView::set_channel (int channel)
677 {
678         if (channel != _channel) {
679                 begin_change ();
680
681                 invalidate_image_cache ();
682                 _channel = channel;
683
684                 _bounding_box_dirty = true;
685                 end_change ();
686         }
687 }
688
689 void
690 WaveView::set_logscaled (bool yn)
691 {
692         if (_logscaled != yn) {
693                 begin_visual_change ();
694                 invalidate_image_cache ();
695                 _logscaled = yn;
696                 end_visual_change ();
697         }
698 }
699
700 void
701 WaveView::gain_changed ()
702 {
703         begin_visual_change ();
704         invalidate_image_cache ();
705         _region_amplitude = _region->scale_amplitude ();
706         end_visual_change ();
707 }
708
709 void
710 WaveView::set_zero_color (Color c)
711 {
712         if (_zero_color != c) {
713                 begin_visual_change ();
714                 invalidate_image_cache ();
715                 _zero_color = c;
716                 end_visual_change ();
717         }
718 }
719
720 void
721 WaveView::set_clip_color (Color c)
722 {
723         if (_clip_color != c) {
724                 begin_visual_change ();
725                 invalidate_image_cache ();
726                 _clip_color = c;
727                 end_visual_change ();
728         }
729 }
730
731 void
732 WaveView::set_show_zero_line (bool yn)
733 {
734         if (_show_zero != yn) {
735                 begin_visual_change ();
736                 invalidate_image_cache ();
737                 _show_zero = yn;
738                 end_visual_change ();
739         }
740 }
741
742 void
743 WaveView::set_shape (Shape s)
744 {
745         if (_shape != s) {
746                 begin_visual_change ();
747                 invalidate_image_cache ();
748                 _shape = s;
749                 end_visual_change ();
750         }
751 }
752
753 void
754 WaveView::set_amplitude_above_axis (double a)
755 {
756         if (_amplitude_above_axis != a) {
757                 begin_visual_change ();
758                 invalidate_image_cache ();
759                 _amplitude_above_axis = a;
760                 end_visual_change ();
761         }
762 }
763
764 void
765 WaveView::set_global_shape (Shape s)
766 {
767         if (_global_shape != s) {
768                 _global_shape = s;
769                 VisualPropertiesChanged (); /* EMIT SIGNAL */
770         }
771 }
772
773 void
774 WaveView::set_global_logscaled (bool yn)
775 {
776         if (_global_logscaled != yn) {
777                 _global_logscaled = yn;
778                 VisualPropertiesChanged (); /* EMIT SIGNAL */
779         }
780 }
781
782 void
783 WaveView::region_resized ()
784 {
785         if (!_region) {
786                 return;
787         }
788
789         /* special: do not use _region->length() here to compute
790            bounding box because it will already have changed.
791            
792            if we have a bounding box, use it.
793         */
794
795         _pre_change_bounding_box = _bounding_box;
796
797         _bounding_box_dirty = true;
798         compute_bounding_box ();
799
800         end_change ();
801 }
802
803 Coord
804 WaveView::y_extent (double s) const
805 {
806         /* it is important that this returns an integral value, so that we 
807            can ensure correct single pixel behaviour.
808          */
809
810         Coord pos;
811
812         switch (_shape) {
813         case Rectified:
814                 pos = floor (_height - (s * _height));
815                 break;
816         default:
817                 pos = floor ((1.0-s) * (_height / 2.0));
818                 break;
819         }
820
821         return min (_height, (max (0.0, pos)));
822 }
823
824 void
825 WaveView::set_global_gradient_depth (double depth)
826 {
827         if (_global_gradient_depth != depth) {
828                 _global_gradient_depth = depth;
829                 VisualPropertiesChanged (); /* EMIT SIGNAL */
830         }
831 }
832
833 void
834 WaveView::set_global_show_waveform_clipping (bool yn)
835 {
836         if (_global_show_waveform_clipping != yn) {
837                 _global_show_waveform_clipping = yn;
838                 VisualPropertiesChanged (); /* EMIT SIGNAL */
839         }
840 }
841