changeset 365:26ce2fb7bcbf

* Ensure waveforms are strictly correct even when using a non-power-of-two non-power-of-sqrt-two block size with cacheing off and painting only small areas at a time
author Chris Cannam
date Thu, 07 Feb 2008 15:25:05 +0000
parents f1e6204c1f17
children a55affd7ab6c
files layer/WaveformLayer.cpp layer/WaveformLayer.h
diffstat 2 files changed, 133 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/layer/WaveformLayer.cpp	Thu Feb 07 12:36:18 2008 +0000
+++ b/layer/WaveformLayer.cpp	Thu Feb 07 15:25:05 2008 +0000
@@ -396,6 +396,73 @@
 static float meterdbs[] = { -40, -30, -20, -15, -10,
                             -5, -3, -2, -1, -0.5, 0 };
 
+bool
+WaveformLayer::getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
+                                   size_t &f0, size_t &f1) const
+{
+    long viewFrame = v->getFrameForX(x);
+    if (viewFrame < 0) {
+        f0 = 0;
+        f1 = 0;
+        return false;
+    }
+
+    f0 = viewFrame;
+    
+    f0 = f0 / modelZoomLevel;
+    f0 = f0 * modelZoomLevel;
+
+    viewFrame = v->getFrameForX(x + 1);
+    
+    f1 = viewFrame;
+    f1 = f1 / modelZoomLevel;
+    f1 = f1 * modelZoomLevel;
+    
+    return (f0 < m_model->getEndFrame());
+}
+
+float
+WaveformLayer::getNormalizeGain(View *v, int channel) const
+{
+    long startFrame = v->getStartFrame();
+    long endFrame = v->getEndFrame();
+
+    // Although a long for purposes of comparison against the view
+    // start and end frames, these are known to be non-negative
+    long modelStart = long(m_model->getStartFrame());
+    long modelEnd = long(m_model->getEndFrame());
+    
+    size_t rangeStart, rangeEnd;
+            
+    if (startFrame < modelStart) rangeStart = modelStart;
+    else rangeStart = startFrame;
+
+    if (endFrame < 0) rangeEnd = 0;
+    else if (endFrame > modelEnd) rangeEnd = modelEnd;
+    else rangeEnd = endFrame;
+
+    if (rangeEnd < rangeStart) rangeEnd = rangeStart;
+
+    RangeSummarisableTimeValueModel::Range range =
+        m_model->getSummary(channel, rangeStart, rangeEnd - rangeStart);
+
+    size_t minChannel = 0, maxChannel = 0;
+    bool mergingChannels = false, mixingChannels = false;
+
+    getChannelArrangement(minChannel, maxChannel,
+                          mergingChannels, mixingChannels);
+
+    if (mergingChannels || mixingChannels) {
+        RangeSummarisableTimeValueModel::Range otherRange =
+            m_model->getSummary(1, rangeStart, rangeEnd - rangeStart);
+        range.max = std::max(range.max, otherRange.max);
+        range.min = std::min(range.min, otherRange.min);
+        range.absmean = std::min(range.absmean, otherRange.absmean);
+    }
+
+    return 1.0 / std::max(fabsf(range.max), fabsf(range.min));
+}
+
 void
 WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const
 {
@@ -403,8 +470,6 @@
 	return;
     }
   
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
     int zoomLevel = v->getZoomLevel();
 
 #ifdef DEBUG_WAVEFORM_PAINT
@@ -478,11 +543,26 @@
     if (x0 > 0) --x0;
     if (x1 < v->width()) ++x1;
 
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1 + 1);
+    // Our zoom level may differ from that at which the underlying
+    // model has its blocks.
 
+    // Each pixel within our visible range must always draw from
+    // exactly the same set of underlying audio frames, no matter what
+    // the range being drawn is.  And that set of underlying frames
+    // must remain the same when we scroll one or more pixels left or
+    // right.
+            
+    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+
+    size_t frame0;
+    size_t frame1;
+    size_t spare;
+
+    getSourceFramesForX(v, x0, modelZoomLevel, frame0, spare);
+    getSourceFramesForX(v, x1, modelZoomLevel, spare, frame1);
+    
 #ifdef DEBUG_WAVEFORM_PAINT
-    std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" <<  std::endl;
+    std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << " and model zoom " << modelZoomLevel << ")" <<  std::endl;
 #endif
 
     RangeSummarisableTimeValueModel::RangeBlock *ranges = 
@@ -507,45 +587,15 @@
         m_effectiveGains.push_back(m_gain);
     }
 
-    // Although a long for purposes of comparison against the view
-    // start and end frames, these are known to be non-negative
-    long modelStart = long(m_model->getStartFrame());
-    long modelEnd = long(m_model->getEndFrame());
-
-#ifdef DEBUG_WAVEFORM_PAINT
-    std::cerr << "Model start = " << modelStart << ", end = " << modelEnd << std::endl;
-#endif
-
     for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
 
 	int prevRangeBottom = -1, prevRangeTop = -1;
 	QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour;
-        size_t rangeStart, rangeEnd;
 
         m_effectiveGains[ch] = m_gain;
 
         if (m_autoNormalize) {
-
-            if (startFrame < modelStart) rangeStart = modelStart;
-            else rangeStart = startFrame;
-
-            if (endFrame < 0) rangeEnd = 0;
-            else if (endFrame > modelEnd) rangeEnd = modelEnd;
-            else rangeEnd = endFrame;
-
-            if (rangeEnd < rangeStart) rangeEnd = rangeStart;
-
-            RangeSummarisableTimeValueModel::Range range =
-                m_model->getSummary(ch, rangeStart, rangeEnd - rangeStart);
-            if (mergingChannels || mixingChannels) {
-                RangeSummarisableTimeValueModel::Range otherRange =
-                    m_model->getSummary(1, rangeStart, rangeEnd - rangeStart);
-                range.max = std::max(range.max, otherRange.max);
-                range.min = std::min(range.min, otherRange.min);
-                range.absmean = std::min(range.absmean, otherRange.absmean);
-            }
-            m_effectiveGains[ch] = 1.0 / std::max(fabsf(range.max),
-                                                  fabsf(range.min));
+            m_effectiveGains[ch] = getNormalizeGain(v, ch);
         }
 
         float gain = m_effectiveGains[ch];
@@ -613,25 +663,14 @@
                 }
             }
         }
+  
+        m_model->getSummaries(ch, frame0, frame1 - frame0,
+                              *ranges, modelZoomLevel);
 
-	if (frame1 < modelStart) continue;
+#ifdef DEBUG_WAVEFORM_PAINT
+        std::cerr << ranges->size() << " ranges from " << frame0 << " to " << frame1 << std::endl;
+#endif
 
-	size_t modelZoomLevel = zoomLevel;
-
-        if (frame0 < modelStart) rangeStart = modelStart;
-        else rangeStart = frame0;
-
-        if (frame1 < 0) rangeEnd = 0;
-        else if (frame1 > modelEnd) rangeEnd = modelEnd;
-        else rangeEnd = frame1;
-        
-        if (rangeEnd < rangeStart) rangeEnd = rangeStart;
-
-	m_model->getSummaries
-	    (ch, rangeStart, rangeEnd - rangeStart, *ranges, modelZoomLevel);
-
-//        std::cerr << ranges->size() << " ranges" << std::endl;
-        
 	if (mergingChannels || mixingChannels) {
             if (m_model->getChannelCount() > 1) {
                 if (!otherChannelRanges) {
@@ -639,7 +678,7 @@
                         new RangeSummarisableTimeValueModel::RangeBlock;
                 }
                 m_model->getSummaries
-                    (1, rangeStart, rangeEnd - rangeStart, *otherChannelRanges,
+                    (1, frame0, frame1 - frame0, *otherChannelRanges,
                      modelZoomLevel);
             } else {
                 if (otherChannelRanges != ranges) delete otherChannelRanges;
@@ -650,49 +689,35 @@
 	for (int x = x0; x <= x1; ++x) {
 
 	    range = RangeSummarisableTimeValueModel::Range();
-	    size_t index = x - x0;
-	    size_t maxIndex = index;
 
-            if (frame0 < modelStart) {
-                if (index < size_t((modelStart - frame0) / zoomLevel)) {
-                    continue;
-                } else {
-                    index -= ((modelStart - frame0) / zoomLevel);
-                    maxIndex = index;
-                }
+            size_t f0, f1;
+            if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) continue;
+            f1 = f1 - 1;
+
+            if (f0 < frame0) {
+                std::cerr << "ERROR: WaveformLayer::paint: pixel " << x << " has f0 = " << f0 << " which is less than range frame0 " << frame0 << " for x0 = " << x0 << std::endl;
+                continue;
             }
-            
-	    if (int(modelZoomLevel) != zoomLevel) {
 
-                std::cerr << "WaveformLayer::paint: zoom level " << zoomLevel << " differs from model zoom level " << modelZoomLevel << std::endl;
-                std::cerr << "index from " << index;
+            size_t i0 = (f0 - frame0) / modelZoomLevel;
+            size_t i1 = (f1 - frame0) / modelZoomLevel;
 
-		index = size_t((double(index) * zoomLevel) / modelZoomLevel);
+#ifdef DEBUG_WAVEFORM_PAINT
+            std::cerr << "WaveformLayer::paint: pixel " << x << ": i0 " << i0 << " (f " << f0 << "), i1 " << i1 << " (f " << f1 << ")" << std::endl;
+#endif
 
-                std::cerr << " to " << index << std::endl;
+            if (i1 > i0 + 1) {
+                std::cerr << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << zoomLevel << ", model zoom = " << modelZoomLevel << ")" << std::endl;
+            }
 
-		if (int(modelZoomLevel) < zoomLevel) {
-		    // Peaks may be missed!  The model should avoid
-		    // this by rounding zoom levels up rather than
-		    // down, but we'd better cope in case it doesn't
-		    maxIndex = index;
-		} else {
-		    maxIndex = size_t((double(index + 1) * zoomLevel)
-				      / modelZoomLevel) - 1;
-		}
+	    if (ranges && i0 < ranges->size()) {
 
-                std::cerr << "maxIndex = " << maxIndex << std::endl;
-	    }
+		range = (*ranges)[i0];
 
-	    if (ranges && index < ranges->size()) {
-
-		range = (*ranges)[index];
-
-		if (maxIndex > index && maxIndex < ranges->size()) {
-		    range.max = std::max(range.max, (*ranges)[maxIndex].max);
-		    range.min = std::min(range.min, (*ranges)[maxIndex].min);
-		    range.absmean = (range.absmean +
-				     (*ranges)[maxIndex].absmean) / 2;
+		if (i1 > i0 && i1 < ranges->size()) {
+		    range.max = std::max(range.max, (*ranges)[i1].max);
+		    range.min = std::min(range.min, (*ranges)[i1].min);
+		    range.absmean = (range.absmean + (*ranges)[i1].absmean) / 2;
 		}
 
 	    } else {
@@ -703,28 +728,28 @@
 
 	    if (mergingChannels) {
 
-		if (otherChannelRanges && index < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
 
 		    range.max = fabsf(range.max);
-		    range.min = -fabsf((*otherChannelRanges)[index].max);
+		    range.min = -fabsf((*otherChannelRanges)[i0].max);
 		    range.absmean = (range.absmean +
-				     (*otherChannelRanges)[index].absmean) / 2;
+				     (*otherChannelRanges)[i0].absmean) / 2;
 
-		    if (maxIndex > index && maxIndex < otherChannelRanges->size()) {
+		    if (i1 > i0 && i1 < otherChannelRanges->size()) {
 			// let's not concern ourselves about the mean
 			range.min = std::min
 			    (range.min,
-			     -fabsf((*otherChannelRanges)[maxIndex].max));
+			     -fabsf((*otherChannelRanges)[i1].max));
 		    }
 		}
 
 	    } else if (mixingChannels) {
 
-		if (otherChannelRanges && index < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
 
-                    range.max = (range.max + (*otherChannelRanges)[index].max) / 2;
-                    range.min = (range.min + (*otherChannelRanges)[index].min) / 2;
-                    range.absmean = (range.absmean + (*otherChannelRanges)[index].absmean) / 2;
+                    range.max = (range.max + (*otherChannelRanges)[i0].max) / 2;
+                    range.min = (range.min + (*otherChannelRanges)[i0].min) / 2;
+                    range.absmean = (range.absmean + (*otherChannelRanges)[i0].absmean) / 2;
                 }
             }
 
@@ -897,11 +922,12 @@
 
     if (!m_model || !m_model->isOK()) return "";
 
-    long f0 = v->getFrameForX(x);
-    long f1 = v->getFrameForX(x + 1);
+    int zoomLevel = v->getZoomLevel();
 
-    if (f0 < 0) f0 = 0;
-    if (f1 <= f0) return "";
+    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+
+    size_t f0, f1;
+    if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) return "";
     
     QString text;
 
--- a/layer/WaveformLayer.h	Thu Feb 07 12:36:18 2008 +0000
+++ b/layer/WaveformLayer.h	Thu Feb 07 15:25:05 2008 +0000
@@ -202,6 +202,11 @@
 
     float getValueForY(const View *v, int y, size_t &channel) const;
 
+    bool getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
+                             size_t &f0, size_t &f1) const;
+
+    float getNormalizeGain(View *v, int channel) const;
+
     virtual void flagBaseColourChanged() { m_cacheValid = false; }
 
     float        m_gain;