Working with an OwnedArray of an OwnedArray? (Two-dimensional OwnedArray)

I have put together a modal synth I am very happy with. Currently each voice has an OwnedArray of “ModalUnit” objects. Each modal unit synthesizes for one mode of the voice, either with a sine wave or resonant bandpass. The array of 50-100+ modal units (each which has its own frequency/decay settings, etc) combines to create a final voice.

I am managing this with the creation of:

OwnedArray<ModalUnit> mModalUnits;

The modal array is initialized like this in the voice on startup:

for (int i = 0; i < numPartials; i++) {

	ModalUnit* unit = new ModalUnit(i + 1);

	unit->setSampleRate(mSampleRate);
	unit->setResoQSmoothingTime(*parametersPointer->getRawParameterValue(id_QSmoothSec));
        ...//other basic setup functions every unit must go through

	mModalUnits.add(unit);
}

Then when a parameter is changed in the voice, it is updated into the units like this:

for (int i = 0; i < mModalUnits.size(); i++) {
	ModalUnit* unit = mModalUnits.getUnchecked(i);
	unit->setVelocity(velocityVar);
	unit->setFrequencyBaseRaw(frequencyFinal); 
	...//other per sample or note or knob change or get output functions as needed
}

This is all working rather well. However, I would like to adapt this to make it a bit more complicated. In physical modeling synthesis, for example, you might have an array to simulate horizontal vibrations of a string and an array to simulate vertical vibrations.

I would like to set up an array of arrays of modal units, so I might have:

"STRING" ARRAY POSITION ZERO -> contains 50 modalUnits
"STRING" ARRAY POSITION ONE -> contains another 50 modalUnits

This way I could iterate through the array of all 100 units when wanted, or I could specify if I want to iterate through the first 50 modalUnits or second 50 modalUnits separately (ie. for group specific changes).

I think I need to declare this as:

OwnedArray<OwnedArray<ModalUnit>> arrayOfStrings;

Would I then initialize this like so:

numStrings = 2;
numPartials = 50;
for (int i = 0; i < numStrings; i++) {

	OwnedArray* string = new OwnedArray;
		
	for (int j = 0; j < numPartials; j++) {

		ModalUnit* unit = new ModalUnit(j + 1);

		unit->setSampleRate(mSampleRate);
		unit->setResoQSmoothingTime(*parametersPointer->getRawParameterValue(id_QSmoothSec));

		string.add(unit);
	}
 	arrayOfStrings.add(string);
}

Then to iterate through all the strings and units, it would be like this:

for (int i = 0; i < arrayOfStrings.size(); i++) {
	OwnedArray* string = arrayOfStrings.getUnchecked(i);

	for (int j = 0; j < string.size(); j++) {
		ModalUnit* unit = string.getUnchecked(j);
		unit->setVelocity(velocityVar);
		unit->setFrequencyBaseRaw(frequencyFinal); 
		...//other per sample or note or knob change or get output functions as needed
	}
}

Or if I wanted to just iterate through the second “string”:

for (int i = 1; i < 2; i++) {
	OwnedArray* string = arrayOfStrings.getUnchecked(i);

	for (int j = 0; j < string.size(); j++) {
		ModalUnit* unit = string.getUnchecked(j);
		unit->setVelocity(velocityVar);
		unit->setFrequencyBaseRaw(frequencyFinal); 
		...//other per sample or note or knob change or get output functions as needed
	}
}

Does this look correct or like it should work?

I can’t think of a foolproof way of testing it except to really dismantle my synth and put it back together this way or start doing some tricky debugging games which could take a while. I’m not that good with this sort of thing, so I thought it might be quicker just to ask if I’m on the right track before I dig into all that and maybe make a mess.

Any help is very appreciated as always. Thanks.

It’s not an answer to your Q, but you should use std::vector< std::unique_ptr<T> > because that is inspectable in Xcode and Visual Studio’s debugger. OwnedArray<T> is not inspectable, which is super annoying when you’re trying to look at the elements inside the containers. same goes for juce::Array<T>

Well I got it working with this syntax. As far as I can tell. I can get the units to all debug out their string number and partial numbers and they’re all accounted for.

To create the array of arrays (OwnedArray<OwnedArray<ModalUnit>> arrayOfStrings;):

		int numStringsTest = 2;
		int numPartialsTest = 50;
		for (int i = 0; i < numStringsTest; i++) {
			
			OwnedArray<ModalUnit>* newString = new OwnedArray<ModalUnit>;
			
			for (int j = 0; j < numPartialsTest; j++) {

				ModalUnit* unit = new ModalUnit(j + 1, i);

				unit->setSampleRate(mSampleRate);
				unit->setResoQSmoothingTime(*parametersPointer->getRawParameterValue(id_QSmoothSec));

				newString->add(unit);
			}
			arrayOfStrings.add(newString);
		}

And to iterate through them:

for (int i = 0; i < numStringsTest; i++) {
			OwnedArray<ModalUnit>* newString = arrayOfStrings.getUnchecked(i);

			for (int j = 0; j < newString->size(); j++) {
				ModalUnit* unit = newString->getUnchecked(j);
				unit->debugString();

			}
		}

Now time to rebuild the synth accordingly. Shouldn’t take much time. :slight_smile:

Now if I go

	arrayOfStrings.clear();

I am clearing the whole array or arrays, right? I mean everything will get deleted/cleared? It seems so but I’m not sure.

Everything works fine now one the “arrayOfStrings” is set up. But I am getting intermittent crashing on clearing and reconstructing it.

The error is juce::ArrayBase<ElementType, TypeofCriticalSectionToUse>

inline ElementType& operator[] (const int index) const noexcept
{
    jassert (elements != nullptr);
    jassert (isPositiveAndBelow (index, numUsed)); // <<==== EXCEPTION THROWN HERE
    return elements[index];
}

The isPositiveAndBelow function is:

bool isPositiveAndBelow (int valueToTest, Type upperLimit) noexcept
{
    jassert (upperLimit >= 0); // makes no sense to call this if the upper limit is itself below zero..
    return static_cast<unsigned int> (valueToTest) < static_cast<unsigned int> (upperLimit);
}

I have absolutely no idea what that means or why this breakpoint is intermittently triggering.

Here’s what my construction section looks like that is triggering the crash (in shortened form):

void constructModalUnits()
	{
	const ScopedLock constructLock(lock);
	numPartials = *parametersPointer->getRawParameterValue(id_NumPartials);
	numStrings = *parametersPointer->getRawParameterValue(id_NumStrings);
		
	for (int i = 0; i < arrayOfStrings.size(); i++) {
		OwnedArray<ModalUnit>* newString = arrayOfStrings.getUnchecked(i);
		newString->clear(); 
	}
	arrayOfStrings.clear(); 

	for (int i = 0; i < numStrings; i++) {
		OwnedArray<ModalUnit>* newString = new OwnedArray<ModalUnit>;
		for (int j = 0; j < numPartials; j++) {
			ModalUnit* unit = new ModalUnit(j + 1, i);
			unit->setSampleRate(mSampleRate);
			unit->setEventSampleRate(eventSampleRate);
			newString->add(unit);
		}
		arrayOfStrings.add(newString);
}

I presume it has something to do with the clearing function but I don’t understand what. I tried clearing the inner arrays first then the outer array as shown but that didn’t help.

I switched it to using clearQuick(true) but it is still crashing.

What does this breakpoint mean? Any idea how to fix it?

It’s testing that the index is greater than zero, and less than the number of items in the array.
So accessing index -1 of the array would trigger the assert, accessing index 10 of a 9 element array would also trigger the assert.

Thanks richie! That was very helpful. I did some more debugging and I’ve narrowed down that it is not actually a problem with the clearing method or how the arrays are being rebuilt. Rather, it is a problem with the voices being active while this is happening. ie. If the voice is running a note while the construction command happens, it will try to access an array position that doesn’t exist (as the arrays are being changed or cannot be accessed) and the synth will crash.

I have established this because if I start the synth with no MIDI input (don’t play any notes), it can rebuild and clear and rebuild infinitely with no problems. I can do it over and over and it won’t crash. But as soon as I am playing a note, or have played a note, it becomes vulnerable to the crashing behavior.

I note this crash can happen at multiple levels of the construction command, which is in my MPESynthesiserVoiceInherited class as:

void constructModalUnits() 
	{
		DBG("STARTING CONSTRUCTION COMMAND, ABOUT TO LOCK");
		const ScopedLock constructLock(lock);

		DBG("STARTING CONSTRUCTION COMMAND, LOCKED");
		numPartials = *parametersPointer->getRawParameterValue(id_NumPartials);
		numStrings = *parametersPointer->getRawParameterValue(id_NumStrings);

		DBG("ABOUT TO CLEAR, ARRAY SIZE: " << arrayOfStrings.size());
		for (int i = 0; i < arrayOfStrings.size(); i++) {
			OwnedArray<ModalUnit>* newString = arrayOfStrings.getUnchecked(i);
			newString->clearQuick(true); //or try quickclear
		}
		arrayOfStrings.clearQuick(true);

		DBG("CLEAR COMPLETE, STARTING REBUILD NEXT");
		for (int i = 0; i < numStrings; i++) {
			OwnedArray<ModalUnit>* newString = new OwnedArray<ModalUnit>;
			for (int j = 0; j < numPartials; j++) {
				ModalUnit* unit = new ModalUnit(j + 1, i);
				unit->setSampleRate(mSampleRate);
				unit->setEventSampleRate(eventSampleRate);
				newString->add(unit);
			}
			arrayOfStrings.add(newString);
		}
		DBG("REBUILD DONE");
}

For example, it crashed once after debugging out “STARTING CONSTRUCTION COMMAND, ABOUT TO LOCK” (ie. it broke during the lock command). Another time it crashed after debugging out “CLEAR COMPLETE, STARTING REBUILD NEXT”. So it doesn’t seem to matter where it is in the command.

I can see that if there are no active notes (ie. the initialization state of the synthesiser before you play any notes) it works perfectly. So it is definitely a locking related issue of some kind.

Isn’t the whole point of the scopedLock to protect the section so nothing else should be happening at the same time? Or do other sections of the voice/synth still continue to try to access the array while that section is locked?

If there’s no way to properly lock and protect the section (ie. STOP EVERYTHING ELSE IN THE SYNTH UNTIL THIS IS DONE), then the only other solution I can see is to wipe the voices out from the PluginProcessor.cpp every time there’s an array change and rebuild them. ie. I am currently doing voice changes like this in PluginProcessor.cpp:

void AudioPlugInAudioProcessor::updateNumVoices()
{
	const ScopedLock voiceLock(lock);
	const int targetNumVoices = (int)*parameters.getRawParameterValue(id_NumVoices);
	const int currentNumVoices = mMpeSynth.getNumVoices();

	if (targetNumVoices < currentNumVoices) {
		mMpeSynth.reduceNumVoices(targetNumVoices);
	}

	else if (targetNumVoices > currentNumVoices) {
		for (int i = 0; i < (targetNumVoices - currentNumVoices); i++) {
			mMpeSynth.addVoice(new MPESynthesiserVoiceInherited(&parameters));
		}
	}
}

It doesn’t seem to trigger crashes even when I have active notes and I’m going up or down on voices. So that seems to be a safe way to control and rebuild things. Worse case scenario, I could make any partial # or string # changes trigger a full rebuild of all the strings like:

void AudioPlugInAudioProcessor::updateNumVoices()
{
	const ScopedLock voiceLock(lock);
	const int targetNumVoices = (int)*parameters.getRawParameterValue(id_NumVoices);
	const int currentNumVoices = mMpeSynth.getNumVoices();

	mMpeSynth.reduceNumVoices(0); //reduce number of voices to zero (ie. clear them all)

	for (int i = 0; i < targetNumVoices; i++) { //rebuild all voices from scratch
		mMpeSynth.addVoice(new MPESynthesiserVoiceInherited(&parameters));
	}
	
}

I’m pretty confident that will work but it’s kind of like going nuclear on the problem because then every time I change the number of strings or partials, I have to recreate all the entire voices.

Is there any way to adequately protect the construction section in the voices from everything else in the synth (ie. stop everything until it is done)? Why isn’t the scopedLock working to do this now? Otherwise any other ideas besides just wiping the voices and starting over each time?

Well that worked. I am just wiping out all the voices and letting them reinitialize with the new construction every time. It’s actually I think faster somehow. I think trying to change the array in the voice while an active note was playing was a recipe for disaster somehow.

I am still curious why the scopedLock didn’t work if anyone has read all this and has any thoughts. Thanks. Either way problem seems solved.