Roll back to float** in AudioBuffer pls :>

I know this is going to be annoying to whoever made the float* const* breaking change, as that was likely a lot of a rewrite, but I’d still like to make a point. Initially I was not opposed to float* const* because it seemed reasonable to think an AudioBuffer shouldn’t just randomly change whatever their inner buffer points to. Sure. I just took all my self-written DSP libraries and changed the methods from float** to float* const*. That was alright.

However I just ran into a situation where float** would behave better. I’m writing a multiband split and my idea was I want to have all the bands next to each other in memory, so my AudioBuffer is numBands * numChannels. That would mean when actually processing the multiband split you have to sometimes make temporary smaller buffers out of the big one to pass those to the things that do individual things per band. That can look like this:

void splitBands(const float* const* samplesIn, int numChannels, int numSamples) noexcept
        {
            auto ar = bands.getArrayOfWritePointers();

            for (auto c = 0; c < NumCrossOvers; ++c)
            {
                const auto c2 = c + 2;

                float* const bufferLow[2] = { ar[c], ar[c + 1] };
                float* const bufferHigh[2] = { ar[c2], ar[c2 + 1] };
                auto& crossover = crossovers[c];

                crossover.split(samplesIn, bufferLow, bufferHigh, numChannels, numSamples);
            }
        }

But I’m getting a little annoyed at continuously having to getArrayOfWritePointers() and do all that {} stuff in my methods. It has a bad readability. My goal is to have a method that can get a float* const* out of the big buffer that represents just a specific band, so I wrote one:

float* const* getBuffer(int b) noexcept
        {
            b *= 2;
            auto ar = bands.getArrayOfWritePointers();
            float* const buf[] = { ar[b], ar[b +1] };
            return buf;
        }

C++ doesn’t let me do this, because “buf” doesn’t exist anymore when the scope of the method ends, which for some reason is unacceptable even if the object is just a pointer that points to other pointers. It was suggested to me to try this instead:

void getBuffer(float* const* buf, int b) noexcept
        {
            b *= 2;
            auto ar = bands.getArrayOfWritePointers();
            buf[0] = ar[b];
            buf[1] = ar[b + 1];
        }

but this is where the const in float* const* comes into play. you can’t reassign the pointers of a float* const*, so this is impossible.

i argue this is not the only situation where it would be useful to keep this non-const. basically all setups where there’s parallel processing could benefit from this being purely float**, because those are all situations where someone could come to the conclusion that it’s nice to have a big buffer for all the channels.

When you change where the pointers point to, that doesn’t move the memory. The only thing you win is that you can suddenly point to memory that’s not yours, which is UB.

But you can use the juce::dsp::AudioBlock(juce::HeapBlock). This allocates memory and will probably be as close as possible.
I assume the same happens in AudioBuffer, but a) I don’t know and b) you don’t have any control over that anyway.

If you need to have a specific memory layout it seems you will have to roll your own.

yeah i know that. the idea is not to move memory around, but just to have a big block of audio memory all next to each other, but always pick a piece out of that and then work with that. like having a float** that is part of the big float** and doesn’t contain anything out of bounds and pass that partial buffer to other classes or methods for processing.

Just the moment I dropped this comment I had a new idea. Yet to be tested though. Just having a float** as a member of the dsp class that wants to have certain sub-buffers and then constructing into these variables instead of temporary ones. I think it’s a bit of a hack though, because objectively there shouldn’t be a need for member variables for something that doesn’t require to hold its state

Like I wrote above, that’s the idea behind HeapBlock and juce::dsp::AudioBlock can use that.

I risk a guess that except of increasing the chance of bugs, you won’t gain anything effectively by managing the location of samples manually.
But make your own experiences, always good to learn something by experimenting.

it’s not much about managing locations. that’s the easy part, because you just know you have a certain number of parallel buffers and channels and then you pick the one that you want. barely anything can go wrong about this calculation.

this is just about how to make the code look nice, so that it doesn’t feel weird after a few weeks. here’s the current solution. instead of creating buf, i already have it (currentBand, nextBand) and selectBand selects currentBand, so that in the method, where it matters (unifyBands) i can have a clear to read list of instructions. select, copy. then keep on selecting and adding. easy.

if this works, that would be cool, but still: juce::AudioBuffer being float** would enable that upper thing, that looks even nicer.

EDIT: definitely not cool! It does work, but now I have to rewrite my libraries to work with both float* const* and float** cause I got it mixed up now with this. So I still think the best solution would be to roll back to float** because it is simply the thing that allows for most versatile strategies and because of that causes less trouble than float* const*

EDIT 2: currently questioning how much of a problem it is. I thought it would going to be stressful, but then I noticed I could also just put the entire audio buffer into a custom float** right at the start of processBlock and then just not have the problem anymore at all for the whole rest of the code. I think I’ll just do that

So would you share what you did (EDIT 2)
How to cast that thing ?

Allocating memory during the audio thread is not the best practice :thinking: right?

float* const* getBuffer(int b) noexcept
        {
            b *= 2;
            auto ar = bands.getArrayOfWritePointers();
            float* const buf[] = { ar[b], ar[b +1] };
            return buf;
        }

C++ doesn’t let me do this, because “buf” doesn’t exist anymore when the scope of the method ends, which for some reason is unacceptable even if the object is just a pointer that points to other pointers.

I think your problem isn’t so much the data type, but one of “array decay”

See here: https://www.tutorialspoint.com/what-is-array-decay-in-cplusplus

You declare a raw array on the stack (buf) but then only return a pointer to it, whereupon the data it points to goes out of scope.

This wouldn’t be what happens. If you pass a float** to AudioBuffer, it just uses this already allocated memory to manage the audio data.

Absolutely right. I think the return type is just off in general here. Either you want the certainty of two channels being returned, then you could use a tuple or some other type of basic struct, or you return an offset of the current float array with return bands.getArrayOfWritePointers() + b. The thing that is not possible anymore by the const change, is to rewire the returned array to different sources. (i.e. try to point one specific channel to a different source of samples).

But I think that rewiring especially was never the goal of the AudioBuffer and I think putting const there makes sense for the bigger picture.

In my opinion this is the perfectly fine way to go here. Get your wiring in order first, and then wrap it into an AudioBuffer for other parts of the code/juce to work with.

2 Likes