1 #include <cairomm/cairomm.h>
3 #include "gtkmm2ext/utils.h"
5 #include "pbd/compose.h"
6 #include "pbd/signals.h"
8 #include "ardour/types.h"
9 #include "ardour/audioregion.h"
11 #include "canvas/wave_view.h"
12 #include "canvas/utils.h"
14 #include <gdkmm/general.h>
17 using namespace ARDOUR;
18 using namespace ArdourCanvas;
20 WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region)
26 , _frames_per_pixel (0)
28 , _wave_color (0xffffffff)
35 WaveView::set_frames_per_pixel (double frames_per_pixel)
39 _frames_per_pixel = frames_per_pixel;
41 _bounding_box_dirty = true;
44 invalidate_whole_cache ();
48 WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
50 assert (_frames_per_pixel != 0);
56 /* p, start and end are offsets from the start of the source.
57 area is relative to the position of the region.
60 int const start = rint (area.x0 + _region_start / _frames_per_pixel);
61 int const end = rint (area.x1 + _region_start / _frames_per_pixel);
64 list<CacheEntry*>::iterator cache = _cache.begin ();
68 /* Step through cache entries that end at or before our current position, p */
69 while (cache != _cache.end() && (*cache)->end() <= p) {
74 1. we have run out of cache entries
75 2. the one we are looking at finishes after p but also starts after p.
76 3. the one we are looking at finishes after p and starts before p.
78 Set up a pointer to the cache entry that we will use on this iteration.
81 CacheEntry* render = 0;
83 if (cache == _cache.end ()) {
85 /* Case 1: we have run out of cache entries, so make a new one for
86 the whole required area and put it in the list.
89 CacheEntry* c = new CacheEntry (this, p, end);
93 } else if ((*cache)->start() > p) {
95 /* Case 2: we have a cache entry, but it starts after p, so we
96 need another one for the missing bit.
99 CacheEntry* c = new CacheEntry (this, p, (*cache)->start());
100 cache = _cache.insert (cache, c);
106 /* Case 3: we have a cache entry that will do at least some of what
107 we have left, so render it.
115 int const this_end = min (end, render->end ());
117 Coord const left = p - _region_start / _frames_per_pixel;
118 Coord const right = this_end - _region_start / _frames_per_pixel;
122 context->rectangle (left, area.y0, right, area.height());
125 context->translate (left, 0);
127 Gdk::Cairo::set_source_pixbuf (context, render->pixbuf (), render->start() - p, 0);
132 p = min (end, render->end ());
137 WaveView::compute_bounding_box () const
140 _bounding_box = Rect (0, 0, _region->length() / _frames_per_pixel, _height);
142 _bounding_box = boost::optional<Rect> ();
145 _bounding_box_dirty = false;
149 WaveView::get_state () const
152 return new XMLNode ("WaveView");
156 WaveView::set_state (XMLNode const * /*node*/)
162 WaveView::set_height (Distance height)
168 _bounding_box_dirty = true;
171 invalidate_pixbuf_cache ();
175 WaveView::set_channel (int channel)
181 _bounding_box_dirty = true;
184 invalidate_whole_cache ();
188 WaveView::invalidate_whole_cache ()
190 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
198 WaveView::invalidate_pixbuf_cache ()
200 for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
201 (*i)->clear_pixbuf ();
206 WaveView::region_resized ()
208 _bounding_box_dirty = true;
212 WaveView::set_region_start (frameoffset_t start)
214 _region_start = start;
215 _bounding_box_dirty = true;
218 /** Construct a new CacheEntry with peak data between two offsets
221 WaveView::CacheEntry::CacheEntry (
222 WaveView const * wave_view,
226 : _wave_view (wave_view)
230 _n_peaks = _end - _start;
231 _peaks = new PeakData[_n_peaks];
233 _wave_view->_region->read_peaks (
236 _start * _wave_view->_frames_per_pixel,
237 (_end - _start) * _wave_view->_frames_per_pixel,
238 _wave_view->_channel,
239 _wave_view->_frames_per_pixel
243 WaveView::CacheEntry::~CacheEntry ()
248 Glib::RefPtr<Gdk::Pixbuf>
249 WaveView::CacheEntry::pixbuf ()
252 _pixbuf = Gdk::Pixbuf::create (Gdk::COLORSPACE_RGB, true, 8, _n_peaks, _wave_view->_height);
253 Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, _n_peaks, _wave_view->_height);
254 Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
256 _wave_view->setup_outline_context (context);
257 context->move_to (0.5, position (_peaks[0].min));
258 for (int i = 1; i < _n_peaks; ++i) {
259 context->line_to (i + 0.5, position (_peaks[i].max));
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].min));
269 set_source_rgba (context, _wave_view->_fill_color);
270 for (int i = 0; i < _n_peaks; ++i) {
271 context->move_to (i + 0.5, position (_peaks[i].max) - 1);
272 context->line_to (i + 0.5, position (_peaks[i].min) + 1);
276 Gtkmm2ext::convert_bgra_to_rgba (surface->get_data(), _pixbuf->get_pixels(), _n_peaks, _wave_view->_height);
284 WaveView::CacheEntry::position (float s) const
286 return (s + 1) * _wave_view->_height / 2;
290 WaveView::CacheEntry::clear_pixbuf ()