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