Oversampling / Pointer to array question


#1

Hi everyone,

I am blocked on a C++ pointer-related question in the context of a VST Effect development, if anyone could point me in the right direction it would be helpful.

In my processor, I am willing to do an oversampling and then process the oversampled array with Vinnie's DSP Filters.

My oversampled data is stored in :

    Array<float> oversampleArray;

But I cannot use the filters like this :

f1->process(buffer.getNumSamples(), oversampleArray);

Because the function requires an array of pointers, like what is returned for instance by :

f1->process(buffer.getNumSamples(), buffer.getArrayOfWritePointers());

I've tried many things, especially by replacing the JUCE Array by a vector<float> and using &myvector[0], but it's not working - I guess my understanding of the pointers is not perfect :D

What would be the best practice in this case ?

Thanks a lot by advance for your help,

A.


#2

Just to add up some information to my question :

I found a possible solution, which is to create a second AudioBuffer, which would be X times the size of the processor's buffer, X being the oversampling factor.

By doing this, I can all AudioBuffer::getArrayOfWritePointers().

Is it the correct way to do ?

On the other hand, I really would prefer using another type, possibly using deque as it would allow me to push values in the front of the array, which can be useful for Catmull Rom interpolation for instance.

So yeah, still the same question in the end : how to get a similar result as getArrayOfWritePointers() with a normal array/vector ?

Thanks a lot

A.


#3

AudioBuffer (Type* const* dataToReferTo,
                 int numChannelsToUse,
                 int numSamples) noexcept

letting dataToReferTo be your oversampleArray ?

 


#4

Thanks a lot oxxyyd for your answer.

I thought about it, but is it common practice with JUCE ?

Because in this case, the memory usage would be higher, because of the existence of both the oversampleArray and the AudioBuffer in whi... ahh no - sorry I am stopping in the middle of my sentence : since it's a pointer I guess the memory required will be very limited, anyhow it's not actually copying the data...

I will give it a try - I realize now it's a much better idea than what I previously thought.

By the way I am not very familiar with const*, does it mean that the dataToReferTo is a pointer that won't change ?

Thanks again for your help, I will post again once I'll have implemented the proposed solution.

All the best,

A.


#5

Just some theory about deque for samples:

a deque (double ended queue) is a list with forward and backward pointers in each elements. That means, that the actual elements are anywhere in the memory. It is the most expensive method to iterate for the processor, as there is no caching available, the compiler can not optimize properly.

So you really should use an array or vector (which is conceptually the same), where the data is aligned in the memory. This speeds things up.

And the other thing to do is to use vectorized operations, like the FloatVectorOperation http://www.juce.com/doc/classFloatVectorOperations whereever you can. Your DSP lib will probably do the same. The FloatVectorOperations are used on most of the AudioBuffer, like on addFrom, copyFrom, applyGain, applyGainRamp etc.

The concept is called SIMD - single instruction multiple data, which is the most possible speedup except maybe for dedicated hardware. And again, this is not possible with lists/deques afaik.

So keep your samples nicely aligned and don't use lists for sample data.


#6

(I removed this post and reposted it below for clarity)


#7

Newbie guide for const pointers and pointers to const here (it's written for C, but for all practical purposes C++ obeys the same rules).


#8

Ah okay, much more clear now. Thank you Timur ! :)


#9

((I will repost my message here instead of below Daniel's as it make it a more logical discussion))

Thanks a lot Daniel for this clarification about the deques. For some reason I have so far mostly used vectors when programming in C++, and I had in mind that deques would behave the same, but obviously not.

I am more and more confused, though, about the use of a vector, even coupled with an AudioBuffer, inside my processBlock();

So far I am doing like this :

std::vector<float*> oversampleArray;

Then in the processblock() :

  // For each channel
    for (int channel = 0; channel < getNumInputChannels(); ++channel)
    {
        float* channelData = buffer.getWritePointer(channel);

        // OVERSAMPLING 2X
        for (int i = 0; i < buffer.getNumSamples()*2; i++) {
            if (i % 2 == 0) {
                oversampleArray[i] = new float(channelData[int(i / 2)]);
            } 
            else {
                oversampleArray[i] = new float(0);
            }
        }

// Converting the oversampleArray to an AudioBuffer type
        AudioBuffer<float> oversampleBuffer(oversampleArray.data(), 1, oversampleArray.size());

// Applying the DSPFilter
        f1->process(oversampleArray.size(), oversampleBuffer.getArrayOfWritePointers());

 // DOWNSAMPLING

        for (int i = 0; i < buffer.getNumSamples(); i++) {
            channelData[i] = oversampleBuffer.getSample(1, i);
        }

// DELETE POINTERS
        for (int i = 0; i < oversampleArray.size(); i++)
        {
            delete oversampleArray.at(i);
        }

Needless to say it' s not working at all and only crashing my DAW :) 

I am probably doing many things wrong but after reading the whole forum and searching for examples on github too, I still haven't found any clear information on how to do it. As a result, I am almost completely blocked since I posted the first message :D

My main concern is that AudioBuffer requires a pointer to a vector of pointers to floats. However, when filling my vector, I am calling new float(), which (unless I misunderstand how memory allocation is working) is allocating memory.

So, it would seem to me much more logical to use a simple vector<float> instead of vector<float*>, but I haven't found a way of implementing it.

If you guys can help me a bit on my algorithm, it would be really great :) 

Thanks a lot by advance,

A.


#10

Hi Adrien,

So far I am doing like this : std::vector<float*> oversampleArray;

yes, there are several problems with your code ;-)

First you have to undestand the difference of float and float*. You create a vector of pointers to floats, which doesn't help you much, because the pointers don't point to allocated memory (see Timurs post about that...).

Also you should understand the difference of stack memory vs. heap memory. If you write

float* x = new float();

you have a pointer to one single float value, which is probably not what you wanted. Also if you use new, you need a corresponding delete, otherwise your code is leaking memory (i.e. allocating memory and not freeing will consume the resources until it stops working).

A good read for that is Jules' Coding Standards: http://www.juce.com/learn/coding-standards and see object lifetime and ownership.

I would strongly suggest to use a second AudioBuffer in your AudioProcessor class.

The benefit is, that the AudioBuffer owns the memory block and if the AudioProcessor is deallocated, the memory is freed automatically...

You can see a good example in the plugin demo code:

https://github.com/julianstorer/JUCE/blob/master/examples/audio%20plugin%20demo/Source/PluginProcessor.cpp

And see the delayBuffer there (forget for the moment the template for float and double...)

So you can call in prepareToPlay the setSize method:

class MyProcessor : public AudioProcessor 
{ 
    // [...] 
    void prepareToPlay prepareToPlay (double  sampleRate, int  estimatedSamplesPerBlock) 
    { 
        resampledBuffer.setSize (getTotalNumInputChannels(), estimatedSamplesPerBlock * oversampling); 
        // [...] 
    } 
    void processBlock (AudioBuffer< float > &buffer, MidiBuffer &midiMessages) 
    { 
        for (int i=0; i<buffer.getNumChannels(); ++i) 
        { 
            const float* reader = buffer.getReadPointer (i); 
            float* writer = resampledBuffer.getWritePointer (i); 
            for (int x=0; x<buffer.getNumSamples()-1; ++x) // note -1 otherwise you go out of bounds... 
            { 
                writer[x*oversampling] = reader[x]; 
                writer[x*oversampling+1] = (reader[x] + reader[x+1]) * 0.5; 
            } 
           // do something usefull at the end, as last sample is unknown 
         } 
    } 
private: 
    int oversampling; 
    AudioBuffer<float> resampledBuffer; 
};

This is untested code, but something alike I used in a previous project...

I hope you get the idea...

Good luck, Daniel

 


#11

Oh my god, thank you so much Daniel. It is REALLY helpful. Now it's working perfectly

First you have to undestand the difference of float and float*. You create a vector of pointers to floats, which doesn't help you much, because the pointers don't point to allocated memory

Actually I do understand the concept. For sure I am not yet super-familiar with pointers, but I understand the basis. I think my main problem was linked to the fact that my filter required a Type**, which I was understanding as a "pointer to an array of pointers" whereas it was a "pointer to the buffer which is pointing to an array per channel". Big mistake, which led me to think that I understood nothing about those pointers... that's why I had this horrible std::vector<float> which was incredibly bothering me :D (in particular regarding memory leaks)

Thanks to your clear and detailed explanation, I understood much better the way things were supposed to work !!

I agree with you, now, that using AudioBuffer (as suggested also previously by oxxyyd) is incredibly simplifying the processing.

Just to correct a typo in your code, it should be getNumInputChannels() instead of getTotalNumInputChannels(), but this has prevented me from understanding the code. Just thought I would mention it, would someone else stumbled upon my thread with the same problem.

So yeah, thank you very much - now I can focus on the real DSP problems ;)


#12

glad I could help :-)

For the getTotalNumInputChannels(), due to the new multi bus layout there are several methods to query the number of channels. But you are right, one should chose the right one and stick to it and not mixing them arbitrarily, like I did, sorry for that.

I think for a plugin with only one bus the three methods:

int getTotalNumInputChannels () / int getMainBusNumInputChannels () / getNumInputChannels() return the same number. But getNumInputChannels() is deprecated since 4.0.1, because it does not take the multi bus layout into account. So probably the best choice would be getMainBusNumInputChannels() I think. But that depends on what the DSP code is supposed to do...

Happy hacking :-)


#13

Alright, thank you for the precision :)