3fac6bce7791c99f7edced666c3bc3ead345e3b3
[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 <cairomm/cairomm.h>
22
23 #include "gtkmm2ext/utils.h"
24
25 #include "pbd/compose.h"
26 #include "pbd/signals.h"
27
28 #include "ardour/types.h"
29 #include "ardour/audioregion.h"
30
31 #include "canvas/wave_view.h"
32 #include "canvas/utils.h"
33
34 #include <gdkmm/general.h>
35
36 using namespace std;
37 using namespace ARDOUR;
38 using namespace ArdourCanvas;
39
40 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
41         : Item (parent)
42         , Outline (parent)
43         , Fill (parent)
44         , _region (region)
45         , _channel (0)
46         , _samples_per_pixel (0)
47         , _height (64)
48         , _wave_color (0xffffffff)
49         , _region_start (0)
50 {
51         
52 }
53
54 void
55 WaveView::set_samples_per_pixel (double samples_per_pixel)
56 {
57         begin_change ();
58         
59         _samples_per_pixel = samples_per_pixel;
60
61         _bounding_box_dirty = true;
62         end_change ();
63
64         invalidate_whole_cache ();
65 }
66
67 void
68 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
69 {
70         assert (_samples_per_pixel != 0);
71
72         if (!_region) {
73                 return;
74         }
75
76         /* p, start and end are offsets from the start of the source.
77            area is relative to the position of the region.
78          */
79         
80         int const start = rint (area.x0 + _region_start / _samples_per_pixel);
81         int const end   = rint (area.x1 + _region_start / _samples_per_pixel);
82
83         int p = start;
84         list<CacheEntry*>::iterator cache = _cache.begin ();
85
86         while (p < end) {
87
88                 /* Step through cache entries that end at or before our current position, p */
89                 while (cache != _cache.end() && (*cache)->end() <= p) {
90                         ++cache;
91                 }
92
93                 /* Now either:
94                    1. we have run out of cache entries
95                    2. the one we are looking at finishes after p but also starts after p.
96                    3. the one we are looking at finishes after p and starts before p.
97
98                    Set up a pointer to the cache entry that we will use on this iteration.
99                 */
100
101                 CacheEntry* render = 0;
102
103                 if (cache == _cache.end ()) {
104
105                         /* Case 1: we have run out of cache entries, so make a new one for
106                            the whole required area and put it in the list.
107                         */
108                         
109                         CacheEntry* c = new CacheEntry (this, p, end);
110                         _cache.push_back (c);
111                         render = c;
112
113                 } else if ((*cache)->start() > p) {
114
115                         /* Case 2: we have a cache entry, but it starts after p, so we
116                            need another one for the missing bit.
117                         */
118
119                         CacheEntry* c = new CacheEntry (this, p, (*cache)->start());
120                         cache = _cache.insert (cache, c);
121                         ++cache;
122                         render = c;
123
124                 } else {
125
126                         /* Case 3: we have a cache entry that will do at least some of what
127                            we have left, so render it.
128                         */
129
130                         render = *cache;
131                         ++cache;
132
133                 }
134
135                 int const this_end = min (end, render->end ());
136                 
137                 Coord const left  =        p - _region_start / _samples_per_pixel;
138                 Coord const right = this_end - _region_start / _samples_per_pixel;
139                 
140                 context->save ();
141                 
142                 context->rectangle (left, area.y0, right, area.height());
143                 context->clip ();
144                 
145                 context->translate (left, 0);
146
147                 context->set_source (render->image(), render->start() - p, 0);
148                 context->paint ();
149                 
150                 context->restore ();
151
152                 p = min (end, render->end ());
153         }
154 }
155
156 void
157 WaveView::compute_bounding_box () const
158 {
159         if (_region) {
160                 _bounding_box = Rect (0, 0, _region->length() / _samples_per_pixel, _height);
161         } else {
162                 _bounding_box = boost::optional<Rect> ();
163         }
164         
165         _bounding_box_dirty = false;
166 }
167         
168 XMLNode *
169 WaveView::get_state () const
170 {
171         /* XXX */
172         return new XMLNode ("WaveView");
173 }
174
175 void
176 WaveView::set_state (XMLNode const * /*node*/)
177 {
178         /* XXX */
179 }
180
181 void
182 WaveView::set_height (Distance height)
183 {
184         begin_change ();
185
186         _height = height;
187
188         _bounding_box_dirty = true;
189         end_change ();
190
191         invalidate_image_cache ();
192 }
193
194 void
195 WaveView::set_channel (int channel)
196 {
197         begin_change ();
198         
199         _channel = channel;
200
201         _bounding_box_dirty = true;
202         end_change ();
203
204         invalidate_whole_cache ();
205 }
206
207 void
208 WaveView::invalidate_whole_cache ()
209 {
210         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
211                 delete *i;
212         }
213
214         _cache.clear ();
215 }
216
217 void
218 WaveView::invalidate_image_cache ()
219 {
220         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
221                 (*i)->clear_image ();
222         }
223 }
224
225 void
226 WaveView::region_resized ()
227 {
228         _bounding_box_dirty = true;
229 }
230
231 void
232 WaveView::set_region_start (frameoffset_t start)
233 {
234         _region_start = start;
235         _bounding_box_dirty = true;
236 }
237
238 /** Construct a new CacheEntry with peak data between two offsets
239  *  in the source.
240  */
241 WaveView::CacheEntry::CacheEntry (
242         WaveView const * wave_view,
243         int start,
244         int end
245         )
246         : _wave_view (wave_view)
247         , _start (start)
248         , _end (end)
249 {
250         _n_peaks = _end - _start;
251         _peaks.reset (new PeakData[_n_peaks]);
252
253         _wave_view->_region->read_peaks (
254                 _peaks.get(),
255                 _n_peaks,
256                 _start * _wave_view->_samples_per_pixel,
257                 (_end - _start) * _wave_view->_samples_per_pixel,
258                 _wave_view->_channel,
259                 _wave_view->_samples_per_pixel
260                 );
261 }
262
263 WaveView::CacheEntry::~CacheEntry ()
264 {
265 }
266
267 Cairo::RefPtr<Cairo::ImageSurface>
268 WaveView::CacheEntry::image ()
269 {
270         if (!_image) {
271
272                 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
273                 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
274
275                 _wave_view->setup_outline_context (context);
276                 context->move_to (0.5, position (_peaks[0].min));
277                 for (int i = 1; i < _n_peaks; ++i) {
278                         context->line_to (i + 0.5, position (_peaks[i].max));
279                 }
280                 context->stroke ();
281                 
282                 context->move_to (0.5, position (_peaks[0].min));
283                 for (int i = 1; i < _n_peaks; ++i) {
284                         context->line_to (i + 0.5, position (_peaks[i].min));
285                 }
286                 context->stroke ();
287
288                 set_source_rgba (context, _wave_view->_fill_color);
289                 for (int i = 0; i < _n_peaks; ++i) {
290                         context->move_to (i + 0.5, position (_peaks[i].max) - 1);
291                         context->line_to (i + 0.5, position (_peaks[i].min) + 1);
292                         context->stroke ();
293                 }
294         }
295
296         return _image;
297 }
298
299
300 Coord
301 WaveView::CacheEntry::position (float s) const
302 {
303         return (s + 1) * _wave_view->_height / 2;
304 }
305
306 void
307 WaveView::CacheEntry::clear_image ()
308 {
309         _image.clear ();
310 }
311
312
313