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