Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Help improving drawing function of audio peaks in an waveform widget.
Forum Updated to NodeBB v4.3 + New Features

Help improving drawing function of audio peaks in an waveform widget.

Scheduled Pinned Locked Moved Solved General and Desktop
5 Posts 4 Posters 784 Views 3 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    marcelnitan
    wrote on last edited by
    #1

    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++)

    37e4cd96-ac29-4f7e-be40-5ee4f48e1d3d-image.png

    for(int i = 0; i < numberOfSamples; i+= (numberOfSamples / m_width)

    a02b8310-0ded-4500-971b-c2871ac2d2c3-image.png

    Full code is available on my github:

    Full code of the snippet sent above
    waveformthread.cpp
    waveformthread.h

    Code from where all the data is sent to the waveform drawing
    waveformwidget.cpp
    waveformwidget.h

    A 1 Reply Last reply
    0
    • M marcelnitan

      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++)

      37e4cd96-ac29-4f7e-be40-5ee4f48e1d3d-image.png

      for(int i = 0; i < numberOfSamples; i+= (numberOfSamples / m_width)

      a02b8310-0ded-4500-971b-c2871ac2d2c3-image.png

      Full code is available on my github:

      Full code of the snippet sent above
      waveformthread.cpp
      waveformthread.h

      Code from where all the data is sent to the waveform drawing
      waveformwidget.cpp
      waveformwidget.h

      A Offline
      A Offline
      Asperamanca
      wrote on last edited by Asperamanca
      #4

      @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.

      M 1 Reply Last reply
      1
      • Kent-DorfmanK Offline
        Kent-DorfmanK Offline
        Kent-Dorfman
        wrote on last edited by
        #2

        take a look at an existing program that does graphical audio wave forms. ie audacity.

        1 Reply Last reply
        0
        • S Offline
          S Offline
          SimonSchroeder
          wrote on last edited by
          #3

          Two things to try:

          1. First collect all the rectangles and then use QPainter::drawRects instead of drawing each QRect individually. This might be faster.
          2. 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.)

          1 Reply Last reply
          0
          • M marcelnitan

            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++)

            37e4cd96-ac29-4f7e-be40-5ee4f48e1d3d-image.png

            for(int i = 0; i < numberOfSamples; i+= (numberOfSamples / m_width)

            a02b8310-0ded-4500-971b-c2871ac2d2c3-image.png

            Full code is available on my github:

            Full code of the snippet sent above
            waveformthread.cpp
            waveformthread.h

            Code from where all the data is sent to the waveform drawing
            waveformwidget.cpp
            waveformwidget.h

            A Offline
            A Offline
            Asperamanca
            wrote on last edited by Asperamanca
            #4

            @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.

            M 1 Reply Last reply
            1
            • A Asperamanca

              @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.

              M Offline
              M Offline
              marcelnitan
              wrote on last edited by
              #5

              @Asperamanca

              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:

              fbfd6a11-9744-4b45-b3ab-69400524d60e-image.png

              1 Reply Last reply
              1

              • Login

              • Login or register to search.
              • First post
                Last post
              0
              • Categories
              • Recent
              • Tags
              • Popular
              • Users
              • Groups
              • Search
              • Get Qt Extensions
              • Unsolved