mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-23 15:16:25 +01:00
new image cache design for waveviews, with various fixes.
Rather than maintain a set of images in a cache, when we no longer have the required waveform data, create a new image that is appropriately centered and extends to roughly twice the screen width (or the limits of the region's source file(s), as necessary)
This commit is contained in:
parent
3604bf12a2
commit
27c943f1dd
3 changed files with 84 additions and 165 deletions
|
|
@ -342,14 +342,14 @@ AudioSource::read_peaks_with_fpp (PeakData *peaks, framecnt_t npeaks, framepos_t
|
||||||
/* fix for near-end-of-file conditions */
|
/* fix for near-end-of-file conditions */
|
||||||
|
|
||||||
if (cnt > _length - start) {
|
if (cnt > _length - start) {
|
||||||
// cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << endl;
|
cerr << "too close to end @ " << _length << " given " << start << " + " << cnt << " (" << _length - start << ")" << endl;
|
||||||
cnt = _length - start;
|
cnt = _length - start;
|
||||||
framecnt_t old = npeaks;
|
framecnt_t old = npeaks;
|
||||||
npeaks = min ((framecnt_t) floor (cnt / samples_per_visual_peak), npeaks);
|
npeaks = min ((framecnt_t) floor (cnt / samples_per_visual_peak), npeaks);
|
||||||
zero_fill = old - npeaks;
|
zero_fill = old - npeaks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl;
|
cerr << "actual npeaks = " << npeaks << " zf = " << zero_fill << endl;
|
||||||
|
|
||||||
if (npeaks == cnt) {
|
if (npeaks == cnt) {
|
||||||
|
|
||||||
|
|
@ -527,6 +527,7 @@ AudioSource::read_peaks_with_fpp (PeakData *peaks, framecnt_t npeaks, framepos_t
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zero_fill) {
|
if (zero_fill) {
|
||||||
|
cerr << "Zero fill end of peaks (@ " << npeaks << " with " << zero_fill << endl;
|
||||||
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
|
memset (&peaks[npeaks], 0, sizeof (PeakData) * zero_fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
WaveView (Group *, boost::shared_ptr<ARDOUR::AudioRegion>);
|
WaveView (Group *, boost::shared_ptr<ARDOUR::AudioRegion>);
|
||||||
|
~WaveView ();
|
||||||
|
|
||||||
void render (Rect const & area, Cairo::RefPtr<Cairo::Context>) const;
|
void render (Rect const & area, Cairo::RefPtr<Cairo::Context>) const;
|
||||||
void compute_bounding_box () const;
|
void compute_bounding_box () const;
|
||||||
|
|
@ -141,7 +142,7 @@ private:
|
||||||
class CacheEntry
|
class CacheEntry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CacheEntry (WaveView const *, double, double);
|
CacheEntry (WaveView const *, double, double, ARDOUR::framepos_t, ARDOUR::framepos_t);
|
||||||
~CacheEntry ();
|
~CacheEntry ();
|
||||||
|
|
||||||
double pixel_start () const {
|
double pixel_start () const {
|
||||||
|
|
@ -209,7 +210,7 @@ private:
|
||||||
*/
|
*/
|
||||||
ARDOUR::frameoffset_t _region_start;
|
ARDOUR::frameoffset_t _region_start;
|
||||||
|
|
||||||
mutable std::list<CacheEntry*> _cache;
|
mutable CacheEntry* _cache;
|
||||||
|
|
||||||
PBD::ScopedConnection invalidation_connection;
|
PBD::ScopedConnection invalidation_connection;
|
||||||
|
|
||||||
|
|
@ -220,6 +221,8 @@ private:
|
||||||
static PBD::Signal0<void> VisualPropertiesChanged;
|
static PBD::Signal0<void> VisualPropertiesChanged;
|
||||||
|
|
||||||
void handle_visual_property_change ();
|
void handle_visual_property_change ();
|
||||||
|
void ensure_cache (ARDOUR::framecnt_t pixel_start, ARDOUR::framecnt_t pixel_end,
|
||||||
|
ARDOUR::framepos_t sample_start, ARDOUR::framepos_t sample_end) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,17 @@ WaveView::WaveView (Group* parent, boost::shared_ptr<ARDOUR::AudioRegion> region
|
||||||
, _gradient_depth_independent (false)
|
, _gradient_depth_independent (false)
|
||||||
, _amplitude_above_axis (1.0)
|
, _amplitude_above_axis (1.0)
|
||||||
, _region_start (region->start())
|
, _region_start (region->start())
|
||||||
|
, _cache (0)
|
||||||
{
|
{
|
||||||
VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
|
VisualPropertiesChanged.connect_same_thread (invalidation_connection, boost::bind (&WaveView::handle_visual_property_change, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaveView::~WaveView ()
|
||||||
|
{
|
||||||
|
delete _cache;
|
||||||
|
_cache = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
WaveView::handle_visual_property_change ()
|
WaveView::handle_visual_property_change ()
|
||||||
{
|
{
|
||||||
|
|
@ -130,18 +137,6 @@ WaveView::set_samples_per_pixel (double samples_per_pixel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline double
|
|
||||||
to_src_sample_offset (frameoffset_t src_sample_start, double pixel_offset, double spp)
|
|
||||||
{
|
|
||||||
return llrintf (src_sample_start + (pixel_offset * spp));
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline double
|
|
||||||
to_pixel_offset (frameoffset_t src_sample_start, double sample_offset, double spp)
|
|
||||||
{
|
|
||||||
return llrintf ((sample_offset - src_sample_start) / spp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline double
|
static inline double
|
||||||
image_to_window (double wave_origin, double image_start)
|
image_to_window (double wave_origin, double image_start)
|
||||||
{
|
{
|
||||||
|
|
@ -154,6 +149,53 @@ window_to_image (double wave_origin, double image_start)
|
||||||
return image_start - wave_origin;
|
return image_start - wave_origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
WaveView::ensure_cache (framecnt_t start, framecnt_t end,
|
||||||
|
framepos_t sample_start, framepos_t sample_end) const
|
||||||
|
{
|
||||||
|
if (_cache && _cache->sample_start() <= sample_start && _cache->sample_end() >= sample_end) {
|
||||||
|
/* cache already covers required range, do nothing */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cache) {
|
||||||
|
delete _cache;
|
||||||
|
_cache = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sample position is canonical here, and we want to generate
|
||||||
|
* an image that spans about twice the canvas width
|
||||||
|
*/
|
||||||
|
|
||||||
|
const framepos_t center = sample_start + ((sample_end - sample_start) / 2);
|
||||||
|
const framecnt_t canvas_samples = 2 * (_canvas->visible_area().width() * _samples_per_pixel);
|
||||||
|
|
||||||
|
/* we can request data from anywhere in the Source, between 0 and its length
|
||||||
|
*/
|
||||||
|
|
||||||
|
sample_start = max ((framepos_t) 0, (center - canvas_samples));
|
||||||
|
sample_end = min (center + canvas_samples, _region->source_length (0));
|
||||||
|
|
||||||
|
if (sample_end <= sample_start) {
|
||||||
|
cerr << "sample start = " << sample_start << endl;
|
||||||
|
cerr << "center+ = " << center<< endl;
|
||||||
|
cerr << "CS = " << canvas_samples << endl;
|
||||||
|
cerr << "pui = " << center + canvas_samples << endl;
|
||||||
|
cerr << "sl = " << _region->source_length (0) << endl;
|
||||||
|
cerr << "st = " << _region->start () << endl;
|
||||||
|
cerr << "END: " << sample_end << endl;
|
||||||
|
assert (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = floor (sample_start / (double) _samples_per_pixel);
|
||||||
|
end = ceil (sample_end / (double) _samples_per_pixel);
|
||||||
|
|
||||||
|
assert (end > start);
|
||||||
|
|
||||||
|
cerr << name << " cache miss - new CE, span " << start << " .. " << end << " (" << sample_start << " .. " << sample_end << ")\n";
|
||||||
|
_cache = new CacheEntry (this, start, end, sample_start, sample_end);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
|
WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
|
||||||
{
|
{
|
||||||
|
|
@ -170,139 +212,23 @@ WaveView::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) cons
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we have a set of cached images that have precise pixel positions
|
|
||||||
* whose origin is 0,0 within our own rect. To convert these pixel
|
|
||||||
* positions so that they are useful when rendering, they need to
|
|
||||||
* be offset by the window position of our own origin. This is given
|
|
||||||
* by self.x0
|
|
||||||
*/
|
|
||||||
|
|
||||||
Rect draw = d.get();
|
Rect draw = d.get();
|
||||||
|
|
||||||
|
/* pixel coordinates - we round up and down in case we were asked to
|
||||||
|
* draw "between" pixels at the start and/or end
|
||||||
|
*/
|
||||||
|
double draw_start = floor (draw.x0);
|
||||||
|
double draw_end = ceil (draw.x1);
|
||||||
|
|
||||||
/* pixel coordinates */
|
/* sample coordinates - note, these are not subject to rounding error */
|
||||||
double start = floor (draw.x0);
|
framepos_t sample_start = _region_start + (draw_start * _samples_per_pixel);
|
||||||
double const end = ceil (draw.x1);
|
framepos_t sample_end = _region_start + (draw_end * _samples_per_pixel);
|
||||||
|
|
||||||
list<CacheEntry*>::iterator cache;
|
ensure_cache (draw_start, draw_end, sample_start, sample_end);
|
||||||
|
|
||||||
cache = _cache.begin ();
|
context->rectangle (draw_start, draw.y0, draw_end - draw_start, draw.height());
|
||||||
|
context->set_source (_cache->image(), self.x0 + _cache->pixel_start(), self.y0);
|
||||||
while (end > start) {
|
context->fill ();
|
||||||
|
|
||||||
/* Step through cache entries that end at or before our current position */
|
|
||||||
|
|
||||||
for (; cache != _cache.end(); ++cache) {
|
|
||||||
if (image_to_window (self.x0, (*cache)->pixel_start()) <= start) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now either:
|
|
||||||
|
|
||||||
1. we have run out of cache entries
|
|
||||||
|
|
||||||
2. we have found a cache entry that starts after start
|
|
||||||
create a new cache entry to "fill in" before the one we have found.
|
|
||||||
|
|
||||||
3. we have found a cache entry that starts at or before
|
|
||||||
start, but finishes before end: create a new cache entry
|
|
||||||
to extend the cache further along the timeline.
|
|
||||||
|
|
||||||
Set up a pointer to the cache entry that we will use on this iteration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
CacheEntry* image = 0;
|
|
||||||
|
|
||||||
/* Cairo limit, caused by its use of 16.16 fixed point */
|
|
||||||
const double BIG_IMAGE_SIZE = 32767.0;
|
|
||||||
|
|
||||||
if (cache == _cache.end ()) {
|
|
||||||
|
|
||||||
/* Case 1: we have run out of cache entries, so make a new one for
|
|
||||||
the whole required area and put it in the list.
|
|
||||||
|
|
||||||
We would like to avoid lots of little images in the
|
|
||||||
cache, so when we create a new one, make it as wide
|
|
||||||
as possible, within the limits inherent in Cairo.
|
|
||||||
|
|
||||||
However, we don't want to try to make it larger than
|
|
||||||
the source could allow, so clamp with that too.
|
|
||||||
*/
|
|
||||||
|
|
||||||
double const region_end_pixel = image_to_window (self.x0, floor (_region->latest_possible_frame() / _samples_per_pixel));
|
|
||||||
double const end_pixel = min (region_end_pixel, start + BIG_IMAGE_SIZE);
|
|
||||||
|
|
||||||
if (end_pixel <= start) {
|
|
||||||
/* nothing more to draw */
|
|
||||||
image = 0;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
CacheEntry* c = new CacheEntry (this, window_to_image (self.x0, start), window_to_image (self.x0, end_pixel));
|
|
||||||
|
|
||||||
_cache.push_back (c);
|
|
||||||
image = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (image_to_window (self.x0, (*cache)->pixel_start()) > start) {
|
|
||||||
|
|
||||||
/* Case 2: we have a cache entry, but it begins after
|
|
||||||
* start, so we need another one for the missing section.
|
|
||||||
*
|
|
||||||
* Create a new cached image that extends as far as the
|
|
||||||
* next cached image's start, or the end of the region,
|
|
||||||
* or the end of a BIG_IMAGE, whichever comes first.
|
|
||||||
*/
|
|
||||||
|
|
||||||
double end_pixel;
|
|
||||||
|
|
||||||
if (end > image_to_window (self.x0, (*cache)->pixel_start())) {
|
|
||||||
double const region_end_pixel = image_to_window (self.x0, floor (_region->length() / _samples_per_pixel));
|
|
||||||
end_pixel = min (region_end_pixel, max (image_to_window (self.x0, (*cache)->pixel_start()), start + BIG_IMAGE_SIZE));
|
|
||||||
} else {
|
|
||||||
end_pixel = image_to_window (self.x0, (*cache)->pixel_start());
|
|
||||||
}
|
|
||||||
|
|
||||||
CacheEntry* c = new CacheEntry (this, window_to_image (self.x0, start), window_to_image (self.x0, end_pixel));
|
|
||||||
|
|
||||||
cache = _cache.insert (cache, c);
|
|
||||||
++cache;
|
|
||||||
image = c;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
/* Case 3: we have a cache entry that covers some of what
|
|
||||||
we have left to render
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
image = *cache;
|
|
||||||
++cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
double this_end = min (end, image_to_window (self.x0, image->pixel_end ()));
|
|
||||||
#if 0
|
|
||||||
cerr << "\t\tDraw image between "
|
|
||||||
<< start << " .. " << this_end
|
|
||||||
<< " using image spanning "
|
|
||||||
<< image->pixel_start() << " (" << image_to_window (self.x0, image->pixel_start()) << ")"
|
|
||||||
<< " .. "
|
|
||||||
<< image->pixel_end () << " (" << image_to_window (self.x0, image->pixel_end()) << ")"
|
|
||||||
<< " offset into image = " << image_to_window (self.x0, image->pixel_start()) - start
|
|
||||||
<< endl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
context->rectangle (start, draw.y0, this_end - start, draw.height());
|
|
||||||
context->set_source (image->image(), self.x0 + (image_to_window (self.x0, image->pixel_start()) - start), self.y0);
|
|
||||||
context->fill ();
|
|
||||||
|
|
||||||
start = this_end;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -351,26 +277,15 @@ void
|
||||||
WaveView::invalidate_whole_cache ()
|
WaveView::invalidate_whole_cache ()
|
||||||
{
|
{
|
||||||
begin_visual_change ();
|
begin_visual_change ();
|
||||||
|
delete _cache;
|
||||||
for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
|
_cache = 0;
|
||||||
delete *i;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cache.clear ();
|
|
||||||
|
|
||||||
end_visual_change ();
|
end_visual_change ();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
WaveView::invalidate_image_cache ()
|
WaveView::invalidate_image_cache ()
|
||||||
{
|
{
|
||||||
begin_visual_change ();
|
invalidate_whole_cache ();
|
||||||
|
|
||||||
for (list<CacheEntry*>::iterator i = _cache.begin(); i != _cache.end(); ++i) {
|
|
||||||
(*i)->clear_image ();
|
|
||||||
}
|
|
||||||
|
|
||||||
end_visual_change ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -485,19 +400,19 @@ WaveView::region_resized ()
|
||||||
end_change ();
|
end_change ();
|
||||||
}
|
}
|
||||||
|
|
||||||
WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end)
|
WaveView::CacheEntry::CacheEntry (WaveView const * wave_view, double pixel_start, double pixel_end,
|
||||||
|
framepos_t sample_start,framepos_t sample_end)
|
||||||
: _wave_view (wave_view)
|
: _wave_view (wave_view)
|
||||||
, _pixel_start (pixel_start)
|
, _pixel_start (pixel_start)
|
||||||
, _pixel_end (pixel_end)
|
, _pixel_end (pixel_end)
|
||||||
|
, _sample_start (sample_start)
|
||||||
|
, _sample_end (sample_end)
|
||||||
, _n_peaks (_pixel_end - _pixel_start)
|
, _n_peaks (_pixel_end - _pixel_start)
|
||||||
{
|
{
|
||||||
_peaks.reset (new PeakData[_n_peaks]);
|
_peaks.reset (new PeakData[_n_peaks]);
|
||||||
|
|
||||||
_sample_start = _wave_view->_region_start + pixel_start * _wave_view->_samples_per_pixel;
|
|
||||||
_sample_end = _wave_view->_region_start + pixel_end * _wave_view->_samples_per_pixel;
|
|
||||||
|
|
||||||
_wave_view->_region->read_peaks (_peaks.get(), _n_peaks,
|
_wave_view->_region->read_peaks (_peaks.get(), _n_peaks,
|
||||||
_sample_start, _sample_end,
|
_sample_start, _sample_end - _sample_start,
|
||||||
_wave_view->_channel,
|
_wave_view->_channel,
|
||||||
_wave_view->_samples_per_pixel);
|
_wave_view->_samples_per_pixel);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue