Search code examples
c++audioportaudio

PortAudio: Play multiple generated sounds by calling function


I've started to play with PortAudio library about week ago. I've checked most of tutorial/test examples but haven't seen solution for what I need. I'm making simple sequencer - I've already done drawing sounds as blocks on the piano roll, but now I need make it sound somehow. I wonder if there is possibility to get it working like this:

  • run a method that will play one sound,
  • give frequency and duration of sound as parameters to that method,
  • play multiple sounds(i.e. three sounds at the same time, bu calling three methods).

In example files it looks more complicated. When I want to play multiple sine waves, so I have to merge all that waves, and then give that data to stream.

Maybe someone got better solutions to resolve this problem?


Solution

  • I'll just give you a simple example (for a sine wave) and then you can create other types of waves you're interested in. The input parameters needed are

    • sampling rate (e.g 8000, 16000 etc)
    • amplitude (the actual values depend on the output format but it's best to have values in the range 0-1 and convert them to whatever format you like/need)
    • frequency (expressed as a fraction of the sampling rate)
    • duration (in seconds)

    The buffer length for tone data (samples) is determined by the tone duration in seconds multiplied by the sampling rate.

    The actual code to create a sine wave may look something like the following

    //global variables
    const float PI = 3.141593;
    const unsigned samplingRate = 8000;
    const float amp = 0.8;
    
    float *GenerateTone(float frequency, unsigned duration, unsigned &bufferLen){
    
         const float freq = frequency/samplingRate; //(e.g 440 / 8000 = 0,055)
    
         bufferLen = samplingRate * duration;
    
         float *buffer = new float[bufferLen]
    
         for(int i = 0; i < bufferLen; i++ ){
    
            buffer[i] = amp * sin(2 * PI*freq  * ((float)i)/samplingRate);
    
            }
        return buffer;
    }   
    

    You can call this function like

    unsigned len;
    float *pTone = GenerateTone(440, 1, len);//len is an out parameter
    ...
    delete [] pTone; //deallocatone memory when you no longer need it
    

    In C++, you can also use std::vector to store the samples. This way you don't have to worry about memory allocation/deallocation.

    std::vector<float> v; //make vector global  
    const float PI = 3.141593;
    const unsigned samplingRate = 8000;
    const float amp = 0.8;
    
    void GenerateTone(float frequency, unsigned duration){
    
      const amp = 0.8f;
    
      const float freq = frequency/samplingRate; 
    
      const unsigned len = samplingRate * duration; 
      for(int i = 0; i < len; i++ )
            v.push_back(amp * sin(2*PI*freq  * ((float)i)/sampleRate));
    
    }
    

    You can also pass amplitude as a paramater but in the examples above the amplitude is hardcoded. Also, see (https://en.wikipedia.org/wiki/Triangle_wave, https://en.wikipedia.org/wiki/Square_wave )