Using multidimensional HeapBlock array

Hello,

this is probably a newbie question but I can’t figure out the best solution for my problem.

I’m working with and old algorithm writen in C and I want to implement this algorithm as an audio plugin. The original code has a few double type C-style multidimensional arrays and I would like to replace these arrays with HeapBlock containers. These containers will not be resized during the audio thread and I would allocate them at once in prepareToPlay(). However, I’m not quite sure if this is the best approach and if the syntax to declare a HeapBlock, for example, with 28 elements, is right:

#define N_LEVEL_BANDS 28

juce::HeapBlock < double> mThirdOctaveLevels[N_LEVEL_BANDS];

void AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
// Use this method as the place to do any pre-playback
// initialisation that you need…

mSampleRate = sampleRate;
mNumSamples = samplesPerBlock;
DecFactorLevel = (int)(sampleRate / SR_LEVEL);
NumSamplesLevel = samplesPerBlock / DecFactorLevel;
for (int i = 1; i < N_LEVEL_BANDS; ++i)
{
    mThirdOctaveLevel[i].calloc(NumSamplesLevel);
}

}

I appreciate any help/suggestion :slight_smile:

Instead of HeapBlock, I’d recommend using an AudioBuffer<double> with N_LEVEL_BANDS channels. This will simplify your code by allowing you to remove the loop where you separately calloc each channel.

1 Like

This seems like an elegant solution. Does the AudioBuffer allocate the data on the heap or on the stack memory? May I ask when a HeapBlock container would be more suitable than the AudioBuffer then? Thank you for your help!

The channel data is stored on the heap.

Rarely! Most of the time, a higher-level type like std::vector, juce::Array, juce::AudioBuffer etc. is a better choice. HeapBlock is very low-level, and you normally shouldn’t need to use it directly.

1 Like

Thank you again. I took a look at the juce::AudioBuffer and juce::Array class and I could not find a simple way to use them for multidimensional arrays. Would there be a way to use juce::Array like std::Array with alias template?

The syntax of the old C-style multidimensional array looks simpler for me, despite the responsibility of avoiding memory leakage. My 3D arrays are big double type arrays but they are global and they will live for all the life of the plugin.

I would recommend to write a wrapper around an one dimensional container:

template<typename T>
struct Cube
{
    void setSize (size_t w, size_t h, size_t d)
    {
        data.resize (w * h * d);
        width = w;
        height = h;
        depth = d;
    }

    T get (size_t x, size_t y, size_t z) const
    {
        return data [x + (y + z * height) * width];
    }

    void set (size_t x, size_t y, size_t z, T value)
    {
        data [x + (y + z * height) * width] = value;
    }

    size_t width = 0;
    size_t height = 0;
    size_t depth = 0;
    std::vector<T> data;
};

This avoids to resize all nested arrays inside.
The drawback of this approach is, if the data is huge, it might swap out. In that case it might make sense to have the planes in individual containers.

1 Like

std::mdspan is a C++23 tool that will be perfect for this (it’s basically a more substantial version of what Daniel posted). Unfortunately it’s only being rolled out now so your compiler might not have it yet.

3 Likes

Hello Daniel, this also seems to be a nice solution. In your example code are the index calculations correct? For example, would the following code work properly?
Cube.setSize(2,2,2); // 3D-Array with dimensions 2x2x2, it would set a single dimension array with length 8
Cube.set(2,2,2, 0); // would this function call try to clear the element at position data[14], out of baoundaries?
By “swap out” you mean that the maximum size of the one dimensional array would not be enough to hold the 3D array elements?
Thank you!

You missed the elements at

cube.set (0, 0, 0, 3.1415f);

With a size of 2 elements, the allowed indices are 0 and 1.

2, 2, 2 is of course out of bounds.

With swapping out, I meant, when the data doesn’t fit in the memory, the OS resorts to slower memory, so called swap space (on the HDD in the worst case, but also the different cache levels).

So if the algorithm works on one plane only, it would only need that one in the memory and can leave the rest in the slower swap space.
With one contiguous block it has to have all in one go.

1 Like

I was asking myself how can it be so complicated to use a big multidimensional array in C++ by the year of 2024, here is the answer :slight_smile: I’m using VisualStudio 2022, probably I will have to update it.

According to this page, mdspan is there on Visual Studio since version 19.39. Make sure you update your project to C++23.

https://en.cppreference.com/w/cpp/compiler_support