Question: removeSound does not seem to effectively remove sounds

I’ve created child classes for a sampler plugin where positions corresponding to midiNotes are populated by sounds according to category from a sound bank. On first run if there are currently no sounds fitted to the specified category, it properly just does not play the sound when the corresponding note is hit, however, if its changed to an empty category, then the program crashes when sounds are reloaded, generally throwing LittleEndian AudioBuffer errors.

I have attempted multiple approaches to removing sounds, but none seem to work. when running:

    if(getNumSounds() != 0)
    {
        for(int i = 0; i < getNumSounds(); i++)
        {
            
            removeSound(i);
        }
        DBG(std::to_string(getNumSounds()) << " Sounds");
        DBG(std::to_string(slots.size()) << " Slots");
    }

the number of sounds gradually increases to encompass the entire sound bank. If a particular slot becomes empty, it still crashes. I have attempted clearing the soundBuffer, regenerating items, setting the format reader and/or sound[index] to a nullptr, etc… I’ve considered just adding a constraint to the category ComboBox, but that would not help in the instance a different sound bank is chosen where some sound categories might not be available. I’ve been banging my head on this for a few days, and its driving me nuts since I know there should be a way to handle cases where a specific sampler item becomes empty.

Any advice would be much appreciated, thanks!

I think you are suffering from the common issue of removing things from the beginning, which will result in the number of sounds decreasing by 1… so, if you had 3 items, and you remove the first one, now you have 2, and then you remove the second one (which used to be the third one), and now you have one, and now you remove the third one, which is out of bounds, because you only have one! :slight_smile: Solution, remove them in reverse order and the problem goes away. :slight_smile:

Thank you, that helped me to reduce the number of sounds Array back to consistent lengths, but whenever I attempt to remove and reset Voices, I get the error thrown from the SamplerSound class:

    juce::AudioData::LittleEndian::copyFrom<juce::AudioData::Pointer<juce::AudioData::Int16, juce::AudioData::LittleEndian, juce::AudioData::Interleaved, juce::AudioData::Const>, juce::AudioData::Int32>(juce::AudioData::Int32&, juce::AudioData::Pointer<juce::AudioData::Int16, juce::AudioData::LittleEndian, juce::AudioData::Interleaved, juce::AudioData::Const>&) + 0 (juce_AudioDataConverters.h:87)
1  0x000000010b7473c5 void juce::AudioData::Pointer<juce::AudioData::Int32, juce::AudioData::NativeEndian, juce::AudioData::NonInterleaved, juce::AudioData::NonConst>::convertSamples<juce::AudioData::Pointer<juce::AudioData::Int16, juce::AudioData::LittleEndian, juce::AudioData::Interleaved, juce::AudioData::Const> >(juce::AudioData::Pointer<juce::AudioData::Int16, juce::AudioData::LittleEndian, juce::AudioData::Interleaved, juce::AudioData::Const>, int) const + 149 (juce_AudioDataConverters.h:471)
2  0x000000010b746798 void juce::AudioFormatReader::ReadHelper<juce::AudioData::Int32, juce::AudioData::Int16, juce::AudioData::LittleEndian>::read<int>(int* const*, int, int, void const*, int, int) + 184 (juce_AudioFormatReader.h:294)
3  0x000000010b7745e7 juce::WavAudioFormatReader::copySampleData(unsigned int, bool, int* const*, int, int, void const*, int, int) + 151 (juce_WavAudioFormat.cpp:1236)
4   0x000000010b773782 juce::WavAudioFormatReader::readSamples(int**, int, int, long long, int) + 578 (juce_WavAudioFormat.cpp:1222)
5. 0x000000010b6fbbcf juce::AudioFormatReader::read(int* const*, int, long long, int, bool) + 351 (juce_AudioFormatReader.cpp:89)
6. 0x000000010b6fc074 juce::AudioFormatReader::read(juce::AudioBuffer<float>*, int, int, long long, bool, bool) + 676 (juce_AudioFormatReader.cpp:178)
7   0x000000010b700a42 juce::SamplerSound::SamplerSound(juce::String const&, juce::AudioFormatReader&, juce::BigInteger const&, int, double, double, double) + 498 (juce_Sampler.cpp:48)

Edit
I get the above if attempting to removeVoice(), below is what pops up when a sound slot that is null gets a file, or vice-versa. Sorry to be confusing
/ Edit

this is accompanied by: libc++abi.dylib: Pure virtual function called!

This happens if a previously unused slot is populated, or a previously used slot is returned with no sound. It otherwise works and generates sound.

The Sound is a Ptr (i.e. reference counted object). So it seems safe to be removed at any point.

Unfortunately that isn’t the case, because when you look at the startVoice command, it takes a raw pointer, so there is no guarantee this is not still in use when you called removeSound.

You need to somehow make sure the sound is really unused when you remove it.

A proper design would use the Ptr for the startNote as well and use an auto release pool to be realtime safe. But the juce Synthesiser is not designed that way.

I might be wrong and there is such safety measure, but at first glance it doesn’t seem to be the case.

The odd thing is that it happens too if a slot is empty, and is then populated. I’m wondering if this has something to do with indexing with regards to the sampler’s sounds container?

I am sorry, I don’t know about slots in that context. Are you talking of a specific UI?
I think the index of a sound will not be constant. It is not an ID, but rather to iterate through the sounds.

Ah sorry, I should have been a bit more clear. Slot is basically the subclass of SamplerVoice I’m implementing. Right now I’m trying to figure out if there is a way to invoke SynthesizerVoice::clearCurrentNote() from the main sampler function via getVoice() on resetting the voices, but that is a protected member of SynthesizerVoice. It seems like it might do the trick?

And an empty slot means, you added nullptrs to the sounds array?

Hmmm, what I am doing is invoking clearSounds() or removeSound(index) (I’ve been attempting these approaches separately, but clearSounds() previous to iteration seems to be equally effective and cleaner in terms of code redundancy) when iterating through samples. I’m basically trying to reset to an initial state on each load without deleting/re-initializing the entire sampler and everything else, which seems like it would be a more poor approach.

Well, clearing and then recreating the sounds could be potentially wasteful. But that is up to you.

That’s a totally fair point.

Also, in another turn of weirdness, clearVoices() causes a program crash (I had noticed this previously), even used in tandem with clearSounds(), and I’ve used these in reverse orders…

Any attempt to remove a voice results in a program crash, whereas removing sound objects do not…

I guess at the very least you would hve to stop calling renderNextBlock() from processBlock() before you try that.

From within the main processor object? How exactly do you do that? I can’t find a method in AudioProcessor for accomplishing this, the closest is reset, but I still get the error when removing voices. The only way, in fact, to keep distortion out is to set voices using something like voices.set(index, SamplerVoice/Slot), because without being able to clear voices the number of voices will just double on every load :upside_down_face: lol

Oh sweet Jesus, lol, I figured out a workaround, though I’m sure that this is likely a bad practice but otherwise works---- on instances where the slot is empty, voices.set(index, new juce::SamplerVoice() ) as an empty item instead of using either the empty slot (the custom class) or a nullptr…

Edit
I mean empty item as in using the basic SamplerVoice as a sort of placeholder element.

Thanks @daniel and @cpr2323 for responding, I really appreciate your help since this little quirk has been driving me mad for a few days now, lol.

1 Like