-1

When I declare buffer as float buffer[FRAMES_PER_BUFFER][2], I get a nice sound out of PortAudio's Pa_WriteStream. When I declare buffer as a float ** and then dynamically allocate (and zero out) a block of memory for it, I get no sound or popping (be careful if you try this on your computer).

As far as I can tell the structure of block pointed to by buffer in each case is identical, but the memory location is different (stack vs heap). This can be seen by the output of the cout statements at the end of the attached example.

How can I dynamically allocate buffer? My application will not know FRAMES_BER_BUFFER or the constant 2 (could be 3 or 4) at compile time.

I'm running this code on Mac with PortAudio 19.5.0 and compiling with gnu (tried various versions).

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

#define FRAMES_PER_BUFFER 512
#define SAMPLE_RATE 44100
#define TS (1.0/SAMPLE_RATE)

#define CAREFULLY(_call_)                          \
    err = _call_;                                  \
    if ( err != paNoError ) {                      \
        std::cout << Pa_GetErrorText(err) << "\n"; \
        exit(1);                                   \
    }

int main(int argc, char * argv[]) {

    PaStreamParameters outputParameters; 
    PaStream *stream;
    PaError err;

    CAREFULLY(Pa_Initialize());

    outputParameters.device = Pa_GetDefaultOutputDevice();
    outputParameters.channelCount = 2;
    outputParameters.sampleFormat = paFloat32;
    outputParameters.suggestedLatency = 0.00;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    CAREFULLY(Pa_OpenStream(
              &stream,
              NULL,
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,
              NULL,
              NULL)); 

    CAREFULLY(Pa_StartStream(stream));

    // Use this initialization of buffer to make a pretty sound
    float buffer[FRAMES_PER_BUFFER][2];

    // Use this initialization of buffer to get either no sound or popping sounds
    // What should go here instead?
    // float * temp = new float[FRAMES_PER_BUFFER * 2];    
    // float ** buffer = new float*[FRAMES_PER_BUFFER];    
    // for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
    //     buffer[i] = (temp + i * 2);
    //     for ( int j=0; j<2; j++) {
    //         buffer[i][j] = 0.0;
    //     }
    // }

    int frame = 0;
    for ( float t=0; t<1; t += TS ) {
      if ( frame >= FRAMES_PER_BUFFER ) {
        CAREFULLY(Pa_WriteStream(stream, buffer, FRAMES_PER_BUFFER));
        frame = 0;
      }
      buffer[frame][0] = sin(2*M_PI*440*t);
      buffer[frame][1] = sin(2*M_PI*441*t);      
      frame++;     

    }

    // Show addresses and contents 
    for ( int i=0; i<10; i++  ) {
        for ( int j=0; j<2; j++ )
            std::cout << &buffer[i][j] << "\t" << buffer[i][j] << "\t";
        std::cout << "\n";
    }    

    return 0; 

}

Buffer with static allocation

0x7ffee3cb5440  0.556439        0x7ffee3cb5444  0.545642
0x7ffee3cb5448  0.607343        0x7ffee3cb544c  0.597127
0x7ffee3cb5450  0.655866        0x7ffee3cb5454  0.646261
0x7ffee3cb5458  0.701818        0x7ffee3cb545c  0.692851
0x7ffee3cb5460  0.745020        0x7ffee3cb5464  0.736712
0x7ffee3cb5468  0.785301        0x7ffee3cb546c  0.777672
0x7ffee3cb5470  0.822504        0x7ffee3cb5474  0.815571
0x7ffee3cb5478  0.856483        0x7ffee3cb547c  0.850258
0x7ffee3cb5480  0.887105        0x7ffee3cb5484  0.881597
0x7ffee3cb5488  0.914250        0x7ffee3cb548c  0.909465
...

Buffer with dynamic allocation

0x7f816e00de00  0.556439        0x7f816e00de04  0.545642
0x7f816e00de08  0.607343        0x7f816e00de0c  0.597127
0x7f816e00de10  0.655866        0x7f816e00de14  0.646261
0x7f816e00de18  0.701818        0x7f816e00de1c  0.692851
0x7f816e00de20  0.745020        0x7f816e00de24  0.736712
0x7f816e00de28  0.785301        0x7f816e00de2c  0.777672
0x7f816e00de30  0.822504        0x7f816e00de34  0.815571
0x7f816e00de38  0.856483        0x7f816e00de3c  0.850258
0x7f816e00de40  0.887105        0x7f816e00de44  0.881597
0x7f816e00de48  0.914250        0x7f816e00de4c  0.909465
...
Eric
  • 73
  • 10
  • 1
    An array of arrays is **not** the same as an array of pointers. Your two versions of `buffer` are not the same. Should `buffer[frame]` be a `float[2]` or a `float*`? – Drew Dormann Feb 15 '22 at 19:10
  • This a quite standard way of allocating a 2D array in C++. Either way, buffer is a pointer to a array of arrays of floats. – Eric Feb 15 '22 at 19:13
  • I've also used code like ```code int **ary = new int*[sizeY]; for(int i = 0; i < sizeY; ++i) { ary[i] = new int[sizeX]; } ``` Generally looking at options from this post: https://stackoverflow.com/questions/936687/how-do-i-declare-a-2d-array-in-c-using-new – Eric Feb 15 '22 at 19:14
  • _"Either way, buffer is a pointer to a array of arrays"_ That is not correct. `float ** buffer` is radically different from `float buffer[FRAMES_PER_BUFFER][2]`. The later stores no pointers. – Drew Dormann Feb 15 '22 at 19:19
  • std::cout << buffer << "\n" gives me 0x7ffee3cb5440 and 0x7f816e00de00 for the two examples respectively. – Eric Feb 15 '22 at 19:22
  • The other observation is that the signature of the PaWriteStream method calls for a void * pointer. So all I'm doing is pointing to a memory location where a bunch of 32 bit floats start: `PaError WriteStream( PaStream* stream, const void *buffer, unsigned long framesRequested )` – Eric Feb 15 '22 at 19:23
  • 1
    How do you dynamically allocate a buffer for use with Pa_WriteStream => Usually, audio libs expect a single contiguous buffer of interleaved data, so the correct way should be `new float[nFrames*nChannels]` here with nChannels=2 for stereo – QuentinC Feb 15 '22 at 19:43
  • 1
    I'm not certain, but I suspect @EricK is confusing arrays and pointers. Perhaps thinking that they are the same thing. That's not an uncommon mistake. – Drew Dormann Feb 15 '22 at 19:44
  • @QuentinC But if you look at the output at the end, it *is* a contiguous buffer of interleaved data in both cases. Also, look at the definition of `temp` in the code. It's as you suggest. – Eric Feb 15 '22 at 19:47
  • my first hit on google for Pa_WriteStream shows a nice example of malloc https://cpp.hotexamples.com/examples/-/-/Pa_WriteStream/cpp-pa_writestream-function-examples.html – KamilCuk Feb 15 '22 at 19:53
  • @KamilCuk Ah, I think I saw that. The call to calloc is pretty similar to the call to new for temp in this case. Both allocate the same size buffer. Perhaps my problem is how I assign the data to this buffer (e.g. `buffer[i][0] = sin(...)`. The example you link doesn't show how the buffer is filled, but I'll try hunting that down. – Eric Feb 15 '22 at 20:01
  • 2
    The commented code isn't correct. You aren't creating a single buffer, but two separate unrelated buffers. Then follows that you don't fill the correct region, and so hear random sound – QuentinC Feb 15 '22 at 20:01

1 Answers1

1

How do you dynamically allocate a buffer for PortAudio Pa_WriteStream in C++?

float buffer[FRAMES_PER_BUFFER][2]

Just:

float *buffer = malloc(sizeof(*buffer) * FRAMES_PER_BUFFER * 2);
// or in c++:     
float *buffer = new float[FRAMES_PER_BUFFER * 2];

for (...) {
  Pa_WriteStream(..., buffer, FRAMES_PER_BUFFER);
  buffer[frame * 2 + 0] = ...;
  buffer[frame * 2 + 1] = ...;
}

If you want to have an array of arrays of 2 floats, you could do:

float (*buffer)[2] = malloc(sizeof(*buffer) * FRAMES_PER_BUFFER);
// or in c++:
float (*buffer)[2] = new float[FRAMES_PER_BUFFER][2];

for (...) {
  Pa_WriteStream(..., buffer, FRAMES_PER_BUFFER);
  buffer[frame][0] = ...;
  buffer[frame][1] = ...;
}

This is not and is unrelated to float **. In your code, you are writing into Pa_WriteStream(..., buffer, ...), which writes float values into a region allocated with new float*[FRAMES_PER_BUFFER]; This overwrites some values written before with assignment buffer[i] = with floating point values written by Pa_WriteStream. This results in invalid pointer values beeing stored in the region allocated for buffer, which makes the subsequent access buffer[frame][0] = invalid - the memory region pointed to by buffer stores float values, not valid float * values.

If you want, you can pass the region allocated with temp (or buffer[0], as they point to the same region) to Pa_WriteStream and then access them using pointers allocated in buffer. This is unnecessary indirection and waste of memory - just access the memory you allocated directly, there is no reason to use an array of pointers to the region.

// with the original code uncommented with 
float * temp = new float[FRAMES_PER_BUFFER * 2];    
float ** buffer = new float*[FRAMES_PER_BUFFER];    
for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
     // every element in buffer _points_ to an element in temp
     buffer[i] = temp + i * 2;
     // I would prefer buffer[i] = &temp[i + 2];
}

for (...) {
  Pa_WriteStream(..., temp, FRAMES_PER_BUFFER);
  // because buffer[0] == temp , the above does exactly the same as:
  Pa_WriteStream(..., buffer[0], FRAMES_PER_BUFFER);


  buffer[frame][0] = ...;
  buffer[frame][1] = ...;
  // because buffer[i] == temp + i * 2 , the above does the same as:
  temp[frame * 2 + 0] = ...;
  temp[frame * 2 + 0] = ...;
  // so buffer can be removed and is unnecessary
}
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • So this is what I do in my example code, but it doesn't work: float * temp = new float[FRAMES_PER_BUFFER * 2]; (it compiles, but PortAudio doesn't seem to like it. – Eric Feb 15 '22 at 20:08
  • I do not understand - `temp` is not `buffer`. With that you have to do `temp[frame * 2 + 1]` instead of `buffer[frame][1]`. – KamilCuk Feb 15 '22 at 20:12
  • buffer gets assigned to temp in the for loop. See this stackoverflow conversation: stackoverflow.com/questions/936687/… – Eric Feb 15 '22 at 20:15
  • Yay! That's what I needed. Sending buffer[0] makes it work. And I uderstand that's not needed, so I'll probably do as you with frame*2+1, etc. Thank you soooo much. – Eric Feb 15 '22 at 21:08