c++qtaudioportaudio

PortAudio: short noise at start and end of sound


I have piano-like application, and when I'm pressing piano key, it plays a sound. In my app I wrote my own sine wave generator. App is wrote in Qt. I think that problem is with portAudio, but I can't find any solution for this.

I've recorded for you how my problem sounds: https://vocaroo.com/i/s1yiWjaJffTU

And here is my generator class:

soundEngine.h

#ifndef SOUNDENGINE_H
#define SOUNDENGINE_H

#include <QThread>
#include <math.h>
#include "portaudio.h"

#define SAMPLE_RATE   (44100)
#define FRAMES_PER_BUFFER  (64)
#define FREQUENCY 220

#ifndef M_PI
#define M_PI  (3.14159265)
#endif

#define TABLE_SIZE   (200)

typedef struct
{
    float sine[TABLE_SIZE];
    int phase;
}
paTestData;

class SoundEngine : public QThread
{
    Q_OBJECT
public:
    bool turnOFF;
    void run();
    static int patestCallback( const void *inputBuffer, void *outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo* timeInfo,PaStreamCallbackFlags statusFlags,void *userData );
    void generateSine();
    void removeSine();
private:
    paTestData data;
    PaStream *stream;
    PaError err;
    bool isPressed;
};

#endif // SOUNDENGINE_H

soundEngine.cpp

#include "soundengine.h"
#include <QDebug>

void SoundEngine::run()
{
    PaStreamParameters outputParameters;
    int i;
    double t;
    turnOFF = false;
    isPressed = false;

    static unsigned long n=0;
    for( i=0; i<TABLE_SIZE; i++, n++ )
    {
        t = (double)i/(double)SAMPLE_RATE;
        data.sine[i] = 0;
        //data.sine[i] = 0.3*sin(2 * M_PI * FREQUENCY * t);
        /*data.sine[i] *= 1.0/2;
        data.sine[i] += 0.5*sin(2 * M_PI * (FREQUENCY+110) * t);
        data.sine[i] *= 2.0/3;
        data.sine[i] += (1.0/3)*sin(2 * M_PI * (FREQUENCY+60) * t);
        data.sine[i] *= 3.0/4;
        data.sine[i] += (1.0/4)*sin(2 * M_PI * (FREQUENCY+160) * t);*/
    }
    data.phase = 0;

    err = Pa_Initialize();
    if(err != paNoError) qDebug()<<"Błąd przy inicjalizacji strumienia:"<<Pa_GetErrorText(err);

    outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
    if (outputParameters.device == paNoDevice) qDebug()<<"Błąd: Brak domyślnego urządzenia wyjścia!";

    outputParameters.channelCount = 2;       /* stereo output */
    outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */
    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    err = Pa_OpenStream(
              &stream,
              NULL, /* no input */
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,      /*paNoFlag we won't output out of range samples so don't bother clipping them */
              patestCallback,
              &data );
    if(err != paNoError) qDebug()<<"Błąd przy otwieraniu strumienia:"<<Pa_GetErrorText(err);
    //err = Pa_StartStream( stream );
    if(err != paNoError) qDebug()<<"Błąd przy starcie strumienia:"<<Pa_GetErrorText(err);

    while (turnOFF == false) {
        Pa_Sleep(500);
    }

    //err = Pa_StopStream( stream );
    if(err != paNoError) qDebug()<<"Błąd przy zatrzymywaniu strumienia:"<<Pa_GetErrorText(err);
    err = Pa_CloseStream( stream );
    if(err != paNoError) qDebug()<<"Błąd przy zamykaniu strumienia:"<<Pa_GetErrorText(err);
    Pa_Terminate();
}

int SoundEngine::patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
    paTestData *callData = (paTestData*)userData;
    float *out = (float*)outputBuffer;
    float sample;
    unsigned long i;

    (void) timeInfo; /* Prevent unused variable warnings. */
    (void) statusFlags;
    (void) inputBuffer;

    for( i=0; i<framesPerBuffer; i++ )
    {
        sample = callData->sine[callData->phase++];
        *out++ = sample;  /* left */
        *out++ = sample;  /* right */
        if( callData->phase >= TABLE_SIZE ) callData->phase -= TABLE_SIZE;
    }

    return paContinue;
}

void SoundEngine::generateSine()
{
    if(isPressed == false)
    {
        for(int i=0; i<TABLE_SIZE; i++)
        {
            data.sine[i] += 0.3*sin(2 * M_PI * 440 * ((double)i/(double)SAMPLE_RATE));
        }
        isPressed = true;
        err = Pa_StartStream( stream );
    }
}

void SoundEngine::removeSine()
{
    err = Pa_StopStream( stream );
    for(int i=0; i<TABLE_SIZE; i++)
    {
        data.sine[i] -= 0.3*sin(2 * M_PI * 440 * ((double)i/(double)SAMPLE_RATE));
    }
    isPressed = false;

}

When I'm pressing button, function

void SoundEngine::generateSine()

is running - it generates sound. When I release the button, method

void SoundEngine::removeSine()

removes the sound.


Solution

  • There's nothing wrong with either your sound or PortAudio. The sound you're hearing at the end is just the result of the audio being abruptly stopped. Take a look at the following image of a sound that has a constant amplitude through the entire duration. This sound will have an audible pop at the end.

    Constant amplitude throughout the sound file

    Conversely, if we attenuate the amplitude by modifying the waveform's envelope (the same sound as in image #1) so that it resembles the sound in image #2, we won't hear any abrupt change(s) in the sound at the end.

    enter image description here

    In conclusion, if your goal is to completely eliminate the pops that you're hearing, fade out (or fade in) your sound(s).