0

I am struggling with outputting user selected audio tones with portaudio. I have based a function on the paex_sine.cpp and modified it somewhat based on this post multi-audio-tones-to-sound-card-using-portaudio

I am able to generate a sine wave on the fly by placing the sin(n * FREQ * 2 * PI / SAMPLE_RATE) calculation in paCallBackMethod() but the frequency isn't correct, and doesn't change based on user input. Here is my function code.

void CDeepWaveDlg::generateSound(float freq)
{

pitch = tone;
offset = freq;

class Sine
{
public:
    Sine() : stream(0), left_phase(0), right_phase(0)
    {


    }

    bool open(PaDeviceIndex index)
    {
        PaStreamParameters outputParameters;

        outputParameters.device = index;
        if (outputParameters.device == paNoDevice) {
            return false;
        }

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

        PaError err = Pa_OpenStream(
            &stream,
            NULL, /* no input */
            &outputParameters,
            SAMPLE_RATE,
            FRAMES_PER_BUFFER,
            paClipOff,      /* we won't output out of range samples so don't bother clipping them */
            &Sine::paCallback,
            this            /* Using 'this' for userData so we can cast to Sine* in paCallback method */
        );

        if (err != paNoError)
        {
            /* Failed to open stream to device !!! */
            return false;
        }

        err = Pa_SetStreamFinishedCallback(stream, &Sine::paStreamFinished);

        if (err != paNoError)
        {
            Pa_CloseStream(stream);
            stream = 0;

            return false;
        }

        return true;
    }

    bool close()
    {
        if (stream == 0)
            return false;

        PaError err = Pa_CloseStream(stream);
        stream = 0;

        return (err == paNoError);
    }


    bool start()
    {
        if (stream == 0)
            return false;

        PaError err = Pa_StartStream(stream);

        return (err == paNoError);
    }

    bool stop()
    {
        if (stream == 0)
            return false;

        PaError err = Pa_StopStream(stream);

        return (err == paNoError);
    }

private:
    /* The instance callback, where we have access to every method/variable in object of class Sine */
    int paCallbackMethod(const void *inputBuffer, void *outputBuffer,
        unsigned long framesPerBuffer,
        const PaStreamCallbackTimeInfo* timeInfo,
        PaStreamCallbackFlags statusFlags)
    {
        float *out = (float*)outputBuffer;
        unsigned long i;

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

        for (i = 0; i<framesPerBuffer; i++)
        {
            float v = sin(i * pitch * 2.0 * M_PI /(float)SAMPLE_RATE);
            float v2 = sin(i * (pitch + (float)offset) * 2.0 * M_PI /(float)SAMPLE_RATE);
            *out++ = v; 
            *out++ = v2;
        }

        return paContinue;

    }

    /* This routine will be called by the PortAudio engine when audio is needed.
    ** It may called at interrupt level on some machines so don't do anything
    ** that could mess up the system like calling malloc() or free().
    */
    static int paCallback(const void *inputBuffer, void *outputBuffer,
        unsigned long framesPerBuffer,
        const PaStreamCallbackTimeInfo* timeInfo,
        PaStreamCallbackFlags statusFlags,
        void *userData)
    {
        /* Here we cast userData to Sine* type so we can call the instance method paCallbackMethod, we can do that since
        we called Pa_OpenStream with 'this' for userData */
        return ((Sine*)userData)->paCallbackMethod(inputBuffer, outputBuffer,
            framesPerBuffer,
            timeInfo,
            statusFlags);
    }


    void paStreamFinishedMethod()
    {
        printf("Stream Completed: %s\n", message);
    }

    /*
    * This routine is called by portaudio when playback is done.
    */
    static void paStreamFinished(void* userData)
    {
        return ((Sine*)userData)->paStreamFinishedMethod();
    }

    PaStream *stream;
    float sine[TABLE_SIZE];
    int left_phase;
    int right_phase;
    char message[20];
};



PaError err;
Sine sine;


err = Pa_Initialize();
if (err != paNoError) goto error;

if (sine.open(Pa_GetDefaultOutputDevice()))
{
    if (sine.start())
    {

        Pa_Sleep(NUM_SECONDS * 1000);

        sine.stop();
    }

    sine.close();
}

Pa_Terminate();

The only way I am able to get close to the desired pitch is to increase frames per buffer, which is currently 64, but I still run into the problem of not changing frequency based on user selection. This problem is definitely beyond my skill level, but I am hoping someone can help me understand what's going on here.Thanks in advance for any help.

Community
  • 1
  • 1
  • Based on http://stackoverflow.com/questions/14847612/generate-sine-wave-to-play-middle-c-using-portaudio?rq=1 I'm guessing it's because each frame, `i` starts again from 0. Try using a `static` variable like in this other question so that each frame the value increases from the previous one. – Jonathan Potter Jul 17 '16 at 19:31
  • @JonathanPotter is right, there needs to be some sort of global variable tracking the overall phase of the sine wave to be continuous. But what happens when you print out `pitch`? That seems to be the problem. And it seems kind of redundant to have a callback method when you could do everything inside the main `paCallback` function – yun Jul 18 '16 at 12:44
  • Thank-you @Jonathan Potter. I had tried that once before when filling a table with sine values and it wasn't working but for filling the buffer on the fly it works great. User selected pitch is working now, but there is high pitched feedback coming from somewhere and some tones aren't playing through speakers but show up through headphones. yun.cloud I left the paCallbackMethod, just so I wouldn't break the example code I had. I agree it's probably a good idea to move it to the main callback function and will work on that when I clean up my very messy project. – Norton Deckert Jul 19 '16 at 00:41

0 Answers0