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. Audio: drum track generation / QTimer reliability

Audio: drum track generation / QTimer reliability

Scheduled Pinned Locked Moved Unsolved General and Desktop
5 Posts 4 Posters 627 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.
  • SeDiS Offline
    SeDiS Offline
    SeDi
    wrote on last edited by SeDi
    #1

    Hi,
    I am trying to play drum groove.s Therefore I created a "PercussionWorker" class object with a m_timer that calls a "timeTickerSlot()" every xx ms (here: 25 ms).
    I've set the timerType to be a Qt::PreciseTimer and moved "PercussionWorker" to a QThread with QThread::HighestPriority.

    Unfortunately, the playback is still uneven, on Android far worse than on Windows (perhaps because Linux/Android doesn't know/respect/use QThread::HighestPriority?) .
    Does anyone see, where my problem can be and if there is a possible fix?

    This is my "PercussionWorker" definition:

    #include <QObject>
    #include <QTimer>
    #include <QSoundEffect>
    
    class PercussionWorker : public QObject
    {
        Q_OBJECT
    public:
        explicit PercussionWorker(QObject *parent = nullptr);
        qreal bpm() const;
        void setBpm(const qreal &bpm);
    
    public slots:
        void timeTickerSlot();
        void startPlaying();
        void stopPlaying() { m_timer->stop(); }
    
    private:
        void setSoundEffect(QSoundEffect &effect, QString fileName);
        QTimer* m_timer = nullptr;
        int m_barUnitsCount = 96; // bar time divided by 96 units. quarte note=24, 8th=12 (binary) / 8th = (8/4) (ternary), 16th=6/(4/2), 32th=3/2 units (binary only)
        qreal m_bpm = 100; // beats per minute
    
        QSoundEffect m_percBassDrum2;
        QSoundEffect m_percSnare;
        QSoundEffect m_percHiHat;
    };
    

    Here's the "PercussionWorker" implementation:

    #include "percussionworker.h"
    #include <math.h>
    #include <QDebug>
    
    PercussionWorker::PercussionWorker(QObject *parent) : QObject(parent)
    {
        setSoundEffect(m_percBassDrum2, "kick2.wav");
        setSoundEffect(m_percSnare, "snare.wav");
        setSoundEffect(m_percHiHat, "hihat.wav");
    }
    
    void PercussionWorker::timeTickerSlot()
    {
        static int fractionsCounter = -1;
        if (fractionsCounter == m_barUnitsCount-1) {
            fractionsCounter = 0;
        } else {
            fractionsCounter++;
        }
        if (fractionsCounter == 90 || fractionsCounter == 0 || fractionsCounter == m_barUnitsCount/2) {
            m_percBassDrum2.play();
        }
        if (fractionsCounter%(m_barUnitsCount/8)==0) {
            m_percHiHat.play();
        }
        if (fractionsCounter == m_barUnitsCount/4 || fractionsCounter == m_barUnitsCount*3 /4) {
            m_percSnare.play();
        }
    }
    
    void PercussionWorker::startPlaying() {
        m_timer = new QTimer();
        m_timer->setTimerType(Qt::PreciseTimer);
        connect(m_timer, &QTimer::timeout, this, &PercussionWorker::timeTickerSlot);
        setBpm(100);
        m_timer->start();
    }
    

    The object is created in my Controller this way:

        PercussionWorker* pw = new PercussionWorker();
        connect(this, &Controller::startPlaying, pw, &PercussionWorker::startPlaying,Qt::QueuedConnection);
        pw->moveToThread(&m_thread);
        m_thread.start();
        m_thread.setPriority(QThread::HighestPriority);
        emit this->startPlaying();
    

    Any ideas welcome. Perhaps I need to take an entirely different approach?

    kshegunovK 1 Reply Last reply
    0
    • SeDiS SeDi

      Hi,
      I am trying to play drum groove.s Therefore I created a "PercussionWorker" class object with a m_timer that calls a "timeTickerSlot()" every xx ms (here: 25 ms).
      I've set the timerType to be a Qt::PreciseTimer and moved "PercussionWorker" to a QThread with QThread::HighestPriority.

      Unfortunately, the playback is still uneven, on Android far worse than on Windows (perhaps because Linux/Android doesn't know/respect/use QThread::HighestPriority?) .
      Does anyone see, where my problem can be and if there is a possible fix?

      This is my "PercussionWorker" definition:

      #include <QObject>
      #include <QTimer>
      #include <QSoundEffect>
      
      class PercussionWorker : public QObject
      {
          Q_OBJECT
      public:
          explicit PercussionWorker(QObject *parent = nullptr);
          qreal bpm() const;
          void setBpm(const qreal &bpm);
      
      public slots:
          void timeTickerSlot();
          void startPlaying();
          void stopPlaying() { m_timer->stop(); }
      
      private:
          void setSoundEffect(QSoundEffect &effect, QString fileName);
          QTimer* m_timer = nullptr;
          int m_barUnitsCount = 96; // bar time divided by 96 units. quarte note=24, 8th=12 (binary) / 8th = (8/4) (ternary), 16th=6/(4/2), 32th=3/2 units (binary only)
          qreal m_bpm = 100; // beats per minute
      
          QSoundEffect m_percBassDrum2;
          QSoundEffect m_percSnare;
          QSoundEffect m_percHiHat;
      };
      

      Here's the "PercussionWorker" implementation:

      #include "percussionworker.h"
      #include <math.h>
      #include <QDebug>
      
      PercussionWorker::PercussionWorker(QObject *parent) : QObject(parent)
      {
          setSoundEffect(m_percBassDrum2, "kick2.wav");
          setSoundEffect(m_percSnare, "snare.wav");
          setSoundEffect(m_percHiHat, "hihat.wav");
      }
      
      void PercussionWorker::timeTickerSlot()
      {
          static int fractionsCounter = -1;
          if (fractionsCounter == m_barUnitsCount-1) {
              fractionsCounter = 0;
          } else {
              fractionsCounter++;
          }
          if (fractionsCounter == 90 || fractionsCounter == 0 || fractionsCounter == m_barUnitsCount/2) {
              m_percBassDrum2.play();
          }
          if (fractionsCounter%(m_barUnitsCount/8)==0) {
              m_percHiHat.play();
          }
          if (fractionsCounter == m_barUnitsCount/4 || fractionsCounter == m_barUnitsCount*3 /4) {
              m_percSnare.play();
          }
      }
      
      void PercussionWorker::startPlaying() {
          m_timer = new QTimer();
          m_timer->setTimerType(Qt::PreciseTimer);
          connect(m_timer, &QTimer::timeout, this, &PercussionWorker::timeTickerSlot);
          setBpm(100);
          m_timer->start();
      }
      

      The object is created in my Controller this way:

          PercussionWorker* pw = new PercussionWorker();
          connect(this, &Controller::startPlaying, pw, &PercussionWorker::startPlaying,Qt::QueuedConnection);
          pw->moveToThread(&m_thread);
          m_thread.start();
          m_thread.setPriority(QThread::HighestPriority);
          emit this->startPlaying();
      

      Any ideas welcome. Perhaps I need to take an entirely different approach?

      kshegunovK Offline
      kshegunovK Offline
      kshegunov
      Moderators
      wrote on last edited by kshegunov
      #2

      Not all platforms support high-precision timers and furthermore there's the timer's jitter you need to worry about. So for this particular purpose, even though it pains me to say it, you need another way to measure the beat/time.

      What they typically do in games (for rendering), for example, is to reverse roles - not the code driving the scene/rendering at specific times, but rather the monitor/vsync/hardware driving the code, so even if frames are dropped (due to high latency) you at least get the timing right.

      PS.
      Perhaps something like: https://doc.qt.io/qt-5/qaudiooutput.html#notify is what could help you.

      Read and abide by the Qt Code of Conduct

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

        In addition to @kshegunov comments, you will never get timing accurate playback by stringing together play() for different effects. IMHO, the only way to make it work is to create your own "work audio stream" of fixed sample rate and format, then insert the effects into your stream at the right intervals, and do a singular play() to send the stream to the backend.

        To merge streams in realtime then you'd need to bypass the Qt audio functions and go right to the soundcard API.

        If you meet the AI on the road, kill it.

        1 Reply Last reply
        2
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          Hi,

          To add to my fellows, you might also want to consider a library like PortAudio which is cross-platform as well.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          SeDiS 1 Reply Last reply
          3
          • SGaistS SGaist

            Hi,

            To add to my fellows, you might also want to consider a library like PortAudio which is cross-platform as well.

            SeDiS Offline
            SeDiS Offline
            SeDi
            wrote on last edited by
            #5

            @kshegunov, @Kent-Dorfman: I see. Bummer. Thanks for the insight!
            @SGaist: Thanks - that looks very promising, I'll definitely give it a try!

            1 Reply Last reply
            0

            • Login

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