remove all xml++.h inclusion by canvas implementations
[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 void
169 WaveView::set_height (Distance height)
170 {
171         begin_change ();
172
173         _height = height;
174
175         _bounding_box_dirty = true;
176         end_change ();
177
178         invalidate_image_cache ();
179 }
180
181 void
182 WaveView::set_channel (int channel)
183 {
184         begin_change ();
185         
186         _channel = channel;
187
188         _bounding_box_dirty = true;
189         end_change ();
190
191         invalidate_whole_cache ();
192 }
193
194 void
195 WaveView::invalidate_whole_cache ()
196 {
197         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
198                 delete *i;
199         }
200
201         _cache.clear ();
202 }
203
204 void
205 WaveView::invalidate_image_cache ()
206 {
207         for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
208                 (*i)->clear_image ();
209         }
210 }
211
212 void
213 WaveView::region_resized ()
214 {
215         _bounding_box_dirty = true;
216 }
217
218 void
219 WaveView::set_region_start (frameoffset_t start)
220 {
221         _region_start = start;
222         _bounding_box_dirty = true;
223 }
224
225 /** Construct a new CacheEntry with peak data between two offsets
226  *  in the source.
227  */
228 WaveView::CacheEntry::CacheEntry (
229         WaveView const * wave_view,
230         int start,
231         int end
232         )
233         : _wave_view (wave_view)
234         , _start (start)
235         , _end (end)
236 {
237         _n_peaks = _end - _start;
238         _peaks.reset (new PeakData[_n_peaks]);
239
240         _wave_view->_region->read_peaks (
241                 _peaks.get(),
242                 _n_peaks,
243                 _start * _wave_view->_samples_per_pixel,
244                 (_end - _start) * _wave_view->_samples_per_pixel,
245                 _wave_view->_channel,
246                 _wave_view->_samples_per_pixel
247                 );
248 }
249
250 WaveView::CacheEntry::~CacheEntry ()
251 {
252 }
253
254 Cairo::RefPtr<Cairo::ImageSurface>
255 WaveView::CacheEntry::image ()
256 {
257         if (!_image) {
258
259                 _image = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
260                 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (_image);
261
262                 _wave_view->setup_outline_context (context);
263                 context->move_to (0.5, position (_peaks[0].min));
264                 for (int i = 1; i < _n_peaks; ++i) {
265                         context->line_to (i + 0.5, position (_peaks[i].max));
266                 }
267                 context->stroke ();
268                 
269                 context->move_to (0.5, position (_peaks[0].min));
270                 for (int i = 1; i < _n_peaks; ++i) {
271                         context->line_to (i + 0.5, position (_peaks[i].min));
272                 }
273                 context->stroke ();
274
275                 set_source_rgba (context, _wave_view->_fill_color);
276                 for (int i = 0; i < _n_peaks; ++i) {
277                         context->move_to (i + 0.5, position (_peaks[i].max) - 1);
278                         context->line_to (i + 0.5, position (_peaks[i].min) + 1);
279                         context->stroke ();
280                 }
281         }
282
283         return _image;
284 }
285
286
287 Coord
288 WaveView::CacheEntry::position (float s) const
289 {
290         return (s + 1) * _wave_view->_height / 2;
291 }
292
293 void
294 WaveView::CacheEntry::clear_image ()
295 {
296         _image.clear ();
297 }
298
299
300