Waveform rendering is sub-optimal because it is implemented as a kludgey
adaptation of thumbnail rendering. I talked with the Ardour developers, and now
I have some ideas on how we can do better:
There are two main problems:
1) Waveforms take too long to render
2) Caching and drawing waveform bitmaps is sub-optimal
1) Speed up waveform rendering by drawing the waveform as a series of vertical
2) Draw waveforms directly during expose events, or at least rescale them at the
current zoom ratio.
Currently, portions of the waveform are rendered as bitmaps and stored in a
cache indexed by (timestamp, duration) pairs. The bitmap rendering code is
shared between thumbnails and waveforms, and it currently assumes that
thumbnails are always the same width regardless of zoom ratio. The big problem
with this approach that a constant width means the duration of the bitmap varies
with zoom ratio. The previewer would have to scale the bitmaps in order to reuse
them at different zoom ratios (constant duration), which it currently doesn't
do. This translates into a lot of cache misses, and subsequent re-rendering,
when the zoom ratio changes.
The reason for caching waveforms as bitmaps in the first place is that it can
take more than a second to render them, depending on the zoom ratio, which is
far too slow to be done directly during an expose event. There are a number of
reasons for this, but the biggest is the method used to draw the waveform. The
waveform is rendered as single cairo path with up to hundreds of points.
Stroking this complicated path is the biggest performance bottleneck next to
fetching the samples off the disk. Iterating over the samples takes far less
time, even in python.
Ardour's method of rendering of waveforms is fast enough that a bitmap cache is
unnecessary, and in Ardour waveforms are drawn directly in response to expose
events. It's based on the observation that it's much more efficient to draw
vertical lines than diagonal ones. Here's a simplified outline of Ardour's
* calculate the samples-per-pixel ratio for the current zoom level
* divide the file into blocks of samples-per-pixel samples
* find the min and max value (summary) for each block
* for each block x from 0 to n
* get min, max sample values
* draw a vertical line from (x, min * height) to (x, max * height)
To reduce disk and CPU usage, Ardour pre-computes min and max values for blocks
N samples, where N is an arbitrary value (currently 1024) and stores them in a
"summary file". This summary file data is cached and fetched off the disk as
required. The sample cache is resampled when samples-per-pixel is > N (when
samples-per-pixel is < N, the waveform code looks directly at the sample data).
There are a couple of caveats:
* Ardour's UI is implemented in C
* Ardour doesn't use cairo to draw waveforms
* The developers point out that several summary files are needed, for various
values of N. Ardour currently doesn't do this, and it can impact performance
when the samples-per-pixel ratio is either much greater than N (too much data
must be read from the disk), or less than N (too much processing done while
I did a test to see whether pycairo is fast enough for this, and achieved > 100
fps drawing a waveform from a randomly-generated array (not doing any min/max
computations), which is encouraging. Even if python turns out to be too slow to
do this, we could still implement direct waveform rendering in a C extension.
Alternatively, we could use the vertical-line method by itself to speed up
rendering of waveform bitmaps, and rescale waveform bitmaps at the current zoom
ratio so we don't need to render nearly as many bitmaps.