A better AudioThumbnail?

While experimenting with minibleps I noticed that when zooming in on an audiothumbnail to much the waveform turns into a connect-the-dots puzzle, see fig 1. I understand this is because the thumbnail for efficiency reasons is drawn entirely with recatngles. But if you switch to paths when zooming in on a sub sample level, you won’t have to sacrifice on efficiency and still have the sub sample resolution, see pic 2. So what about updating AudioThumbnail::drawCahnnel like this?

void drawChannel (Graphics& g, const Rectangle& area,
const double startTime, const double endTime,
const int channelNum, const float verticalZoomFactor,
const double rate, const int numChans, const int sampsPerThumbSample,
LevelDataSource* levelData, const OwnedArray& chans)
{
if (refillCache (area.getWidth(), startTime, endTime, rate,
numChans, sampsPerThumbSample, levelData, chans)
&& isPositiveAndBelow (channelNum, numChannelsCached))
{
const Rectangle clip (g.getClipBounds().getIntersection (area.withWidth (jmin (numSamplesCached, area.getWidth()))));

        if (! clip.isEmpty())
        {
            const float topY = (float) area.getY();
            const float bottomY = (float) area.getBottom();
            const float midY = (topY + bottomY) * 0.5f;
            const float vscale = verticalZoomFactor * (bottomY - topY) / 256.0f;
            const MinMaxValue* cacheData = getData (channelNum, clip.getX() - area.getX());
            RectangleList<float> waveform;
            waveform.ensureStorageAllocated (clip.getWidth() + 1);	//1 added. don't remember exactly why...
  			 double x = (double)clip.getX();	//need double here to get enough resolution to do x++ in big zoom in
  			 double pixelsPerSamples = g.getClipBounds().getWidth() / (endTime - startTime) / rate;
  			 const bool usePath = pixelsPerSamples > 1.0;
  			 bool havePoints = false;
  			 Path path;
            for (int w = clip.getWidth(); --w >= 0;)
            {
                if (cacheData->isNonZero())
                {
                    const float top    = jmax (midY - cacheData->getMaxValue() * vscale - 0.3f, topY);
                    const float bottom = jmin (midY - cacheData->getMinValue() * vscale + 0.3f, bottomY);
  						if (usePath)
  							if (havePoints)
  								path.lineTo((float)x, top);
  							else
  							{
  								path.startNewSubPath((float)x, top);
  								havePoints = true;
  							}
  						else
  							waveform.addWithoutMerging(Rectangle<float>((float)x, top, 1.0f, bottom - top));
                }
                x += 1.0f;
                ++cacheData;
            }
  			 if (!path.isEmpty())
  			 {
  				 PathStrokeType pst(1.0f, PathStrokeType::JointStyle::curved, PathStrokeType::EndCapStyle::butt);
  				 g.strokePath(path, pst);
  			 }
  			 else
  				 g.fillRectList(waveform);
  		}
    }
}
8 Likes

Man, why did this not get noticed by the Juce devs?

1 Like

Going to try using this with the audio thumbnail today.