How to play sounds with a delay in between?



  • I am very new to Qt and C++, but I want to play a series of sounds (directly out of raw data stored in QVectors) with a delay between adjacent sounds. For example, after the first sound has finished playing, there shall be a delay (e.g, a 500 millisecond delay) before the second sound can start playing. Similarly, after the second sound has finished playing, there should be a delay (e.g., the same 500 millisecond delay) before the third sound can start playing, and so on.

    With the help from a previous discussion thread in Qt Forum, I managed to write some Qt codes that allowed me play one sound directly from a raw data vector. That discussion thread can be found through the link https://forum.qt.io/topic/86218/trouble-playing-sound-directly-out-of-raw-data-vectors

    During the past one week, I tried very hard, but was unable to make the program to play more than one sound. For some reason, the sound only got played one time. All subsequent sounds were not played.

    If anyone can help me with this, I would greatly appreciate it.

    P.S. I use Windows 10, Qt 5.10.0, Qt Creator 4.5.0. For illustration purposes, I created an empty "Qt Widget Applications" program, with an empty form. I then added one and only one pushbutton on this form.

    Here is the code/snippet when this pushbutton is clicked.

    
    void MainWindow::on_pushButton_clicked()
    {
    
        qreal sampleRate = 40000;   // sample rate
        qreal duration = 1.000;     // duration in seconds
        qreal frequency = 1000;     // frequency
        const quint32 n = static_cast<quint32>(duration * sampleRate);   // number of data samples
    
        // --- transfer QVector data to QByteBuffer
        QByteArray *byteArray = new QByteArray();  // create a new instance of QByteArray class (in the heap, dynamically arranged in memory), and set its pointer to byteArray
        byteArray->resize(sizeof(float) * n);  // resize byteArray to the total number of bytes that will be needed to accommodate all the n data samples that are of type float
    
        for (quint32 i = 0; i < n; i++)
        {
            qreal sinVal = (qreal)qSin(2.0 * M_PI * frequency * i / sampleRate);  // create sine wave data samples, one at a time
    
            // break down one float into four bytes
            float sample = (float)sinVal;  // save one data sample in a local variable, so I can break it down into four bytes
            char *ptr = (char*)(&sample);  // assign a char* pointer to the address of this data sample
            char byte00 = *ptr;         // 1st byte
            char byte01 = *(ptr + 1);   // 2nd byte
            char byte02 = *(ptr + 2);   // 3rd byte
            char byte03 = *(ptr + 3);   // 4th byte
    
            // put byte data into QByteArray, one byte at a time
            (*byteArray)[4 * i] = byte00;       // put 1st byte into QByteArray
            (*byteArray)[4 * i + 1] = byte01;   // put 2nd byte into QByteArray
            (*byteArray)[4 * i + 2] = byte02;   // put 3rd byte into QByteArray
            (*byteArray)[4 * i + 3] = byte03;   // put 4th byte into QByteArray
        }
    
        // create and setup a QAudioFormat object
        QAudioFormat audioFormat;
        audioFormat.setSampleRate(static_cast<int>(sampleRate));
        audioFormat.setChannelCount(1);
        audioFormat.setSampleSize(32);   // set the sample size in bits. We set it to 32 bis, because we set SampleType to float (one float has 4 bytes ==> 32 bits)
        audioFormat.setCodec("audio/pcm");
        audioFormat.setByteOrder(QAudioFormat::LittleEndian);
        audioFormat.setSampleType(QAudioFormat::Float);   // use Float, to have a better resolution than SignedInt or UnSignedInt
    
        // create a QAudioDeviceInfo object, to make sure that our audioFormat is supported by the device
        QAudioDeviceInfo deviceInfo(QAudioDeviceInfo::defaultOutputDevice());
        if(!deviceInfo.isFormatSupported(audioFormat))
        {
            qWarning() << "Raw audio format not supported by backend, cannot play audio.";
            return;
        }
    
        // Make a QBuffer with our QByteArray
        QBuffer* input = new QBuffer(byteArray);
        input->open(QIODevice::ReadOnly);   // set the QIODevice to read-only
    
        // Create an audio output with our QAudioFormat
        QAudioOutput* audio = new QAudioOutput(audioFormat, this);
    
        quint32 nSounds = 4;   // number of sounds to be played
    
        for (quint32 i = 0; i < nSounds; i++)
        {
            // start the audio (i.e., play sound from the QAudioOutput object that we just created)
            audio->start(input);
    
            QThread::msleep(500);  // sleep for 500 milliseconds
    
        }
    
    
    }
    
    

  • Lifetime Qt Champion

    Hi,

    You can use a single shot QTimer that will call the slot where you play your data.



  • @SGaist

    That is a wonderful suggestion. Thanks a lot!

    I tried your suggestion and was able to play sounds repetitively or as a singleShot.

    After a few trials and errors, I was even able to play the sound for n times (and n times only), with a delay specified in QTimer.

    I don't think that I can accomplish this without your guidance.

    Thank you SO MUCH!!!



  • I know I may be too picky if I ask the question below.

    In some experiments, I do need to write a program that can play two sounds (let's say they are sound 1 and sound 2) for a total of say 10 times. Sound 1 and sound 2 will be presented in a random order. Let's say that I have pre-determined a random order, as shown below, for sound presentation.

    sound 1
    sound 1
    sound 1
    sound 2
    sound 1
    sound 1
    sound 2
    sound 1
    sound 1
    sound 1

    In this situation, I tried to create a QTimer object and play sound repetitively, but I did not know how to change it to play sound 2 for certain repetitions. I also tried to use the QTimer::singleShot() function and repeat it 10 time, but I could not make it to work neither.

    What would be the best approach to accomplish this?

    Thanks a lot in advance for your time and guidance!


  • Lifetime Qt Champion

    Create a list (QVector) that will contain the order of the sound you want to play and when the timer fires, pop the ID of the sound you should play and play it.



  • @SGaist

    WOW! You are a genius. I tried your method and it worked!!! Thanks a lot!

    However, when testing my program, I realized that the last sound was not played properly.

    Please allow me to explain my situation first. Let's say that my QTimer will wait for say 1000 ms, and then play the first sound. At the end of the first 1000 ms, my QTimer will trigger another timeout() signal, wait for a 2nd 1000 ms, and after that, execute my slot function the 2nd time and to play the 2nd sound. At the end of the 2nd 1000 ms, my QTimer will trigger a 3rd timeout() signal, wait for a 3rd 1000 ms, and then execute my slot function the 3rd time to play the 3rd sound, and so on.

    A problem occurs when the last sound is played. Say my QTimer will play a total of 10 sounds. At the end of the 9th timeout() signal, my QTimer will triger a 10th timeout() signal, the program will then wait for a 10th 1000 ms, and after that, execute my slot function to play the 10th sound.

    The problem is that, the timer->start(1000) command is not the last line of command in my program. Let's say that, after the 10th sound has finished playing, I want to make two pushbuttons visible, so that the user can click on one of the two pushbuttons as their response to the 10 sounds. My problem is that, immediately after the 10th timeout() signal, these two pushbutton become visible. That is, these two pushbuttons become visible, when the 10th sound is still playing. This is not what I want. I need the 10th sound to finish playing first, and after that, these pushbuttons visible for the user to click on. In other words, these two pushbuttons shall not be visible to the user when the 10th sound is still playing.

    With an attempt to solve this problem, I have thought about making my QTimer to play 11 sounds (instead of 10 sounds) and make the 11th sound very short in duration and inaudible in amplitude. Although this method may seem to work, it creates an additional 1000 ms (i.e., the 11th 1000-ms) timeout waiting. This extra 1000 ms after the 10th sound is not desirable, neither.

    So, I am running out of ideas...

    Can you give me some suggestions and directions to follow?

    Thanks again for your time and guidance!


  • Moderators

    @beginner123 You can use http://doc.qt.io/qt-5/qaudiooutput.html#stateChanged signal. In the slot you connect to it check whether the state is Idle and whether you were playing last audio file - if both conditions are true then enable the buttons.



  • Wonderful! I took me a while, but I finally figured out a way to use the state change of a QAudio object to signal a slot function.

    It worked!!!

    Thank you ALL SO MUCH for your guidance! Greatly Appreciated!!!!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.