Solved Help improving drawing function of audio peaks in an waveform widget.
-
After many tries and retries I finally finished my waveform widget. While I got my desired results, the time it takes the waveform to fully generate it's too long, since I'm recording peaks from each frames and putting them in a vector.
While reading the samples in the vector it takes around 5 seconds for the waveform to fully generate and show in the waveform.
I'm looking for help to make the drawing time faster.
The snippet below, is the part where I'm taking the samples and drawing it on the widget based on the number of samples divided by the widget's width
i * xScale
:int numberOfSamples = m_samplesL.size(); float xScale = (float)m_width / (numberOfSamples); float center = (float)m_heihgt / 2; QImage waveImage = QImage(m_width, m_heihgt, QImage::Format_RGBA64); waveImage.fill(m_background_color); QPainter painter(&waveImage); painter.setPen(QPen(m_wave_color, 1, Qt::SolidLine, Qt::RoundCap)); for(int i = 0; i < numberOfSamples; i+= (numberOfSamples / m_width)){ if (m_abort) break; if (m_channelCount == 1) painter.drawRect(i * xScale, center - (m_samplesL[i] * center), 2, (m_samplesL[i] * center) * 2); else if (m_channelCount == 2) { painter.drawRect(i * xScale, (center / 2) - (m_samplesL[i] * (center / 2)), 2, (m_samplesL[i] * (center / 2)) * 2); painter.drawRect(i * xScale, ((center / 2) * 3) - (m_samplesR[i] * (center / 2)), 2, (m_samplesR[i] * (center / 2)) * 2); } } emit waveformReady(waveImage);
What I've found it works, reducing the time to generate the waveform in miliseconds it's summing
i
with the number of samples divided by the width. But the issue is that the waveform breaks.for(int i = 0; i < numberOfSamples; i++)
for(int i = 0; i < numberOfSamples; i+= (numberOfSamples / m_width)
Full code is available on my github:
Full code of the snippet sent above
waveformthread.cpp
waveformthread.hCode from where all the data is sent to the waveform drawing
waveformwidget.cpp
waveformwidget.h -
@marcelnitan
There is no point in drawing rectangles that are 1/100 of a pixel wide. There is some benefit in subsample drawing for smoothing, but you can get most of that by simply drawing 2 rectangles per horizontal pixel.It looks a bit like what you tried, but you simply "jump" and skip data. Instead, calculate the maximum/minimum for each half pixel by looping through the samples that make up that half-pixel.
If your data is in a contiguous memory block (like a QVector or std::vector, or even QList from Qt6 on), then processing these min/max values should be very fast. If need be, that calculation could also be massively parallelized, but I'd measure before doing that. -
take a look at an existing program that does graphical audio wave forms. ie audacity.
-
Two things to try:
- First collect all the rectangles and then use QPainter::drawRects instead of drawing each QRect individually. This might be faster.
- Since you are rendering to an image you can have several thread each render part of the waveform to its own image and combine the images in the end. This only helps a little and might not fully achieve the speeds you'd like.
Even better is to use a proper library. If your code in GPL (or you have a commercial Qt license) you could have a look at QCharts. Otherwise there is Qwt which is under LGPL. At least with Qwt you should find an option to have it draw your waveform for you. (Don't know about QCharts.)
-
@marcelnitan
There is no point in drawing rectangles that are 1/100 of a pixel wide. There is some benefit in subsample drawing for smoothing, but you can get most of that by simply drawing 2 rectangles per horizontal pixel.It looks a bit like what you tried, but you simply "jump" and skip data. Instead, calculate the maximum/minimum for each half pixel by looping through the samples that make up that half-pixel.
If your data is in a contiguous memory block (like a QVector or std::vector, or even QList from Qt6 on), then processing these min/max values should be very fast. If need be, that calculation could also be massively parallelized, but I'd measure before doing that. -
Hello, thanks for answering,
Going around the internet I've found a solution to calculate the RMS for each pixel.
First I've calculated the number of samples to fit in one pixel. Then I've taken each sample/pixel (chunk) and calculated the RMS (My waveform vs Audacity faded waveform).
So in the end the result is this: