How to add multiple SamplerSound’s for a single note

I’m trying to create a simple sampler using the Synthesiser, SamplerVoice, and SamplerSound classes. When I hit a note, I want multiple samples to play back for the same notes. The classes seem set up to do this, but whenever I actually hit notes on my MIDI controller, only the second SamplerSound I added to the Synthesier plays. I can’t figure out why this is. When I tried debugging it.

To test this, I created a completely vanilla Audio Plug-In, and plopped all my Synthesizer configuration right in the processor constructor, as follows:

    for (int i = 0; i < 16; i++) {
        synth.addVoice(new SamplerVoice());
    }
    audioFormatManager.registerBasicFormats();
    BigInteger allNotes;
    allNotes.setRange(0, 128, true);
    
    // Layer 1
    File* file = new File("Samples/LPV_SUS_72_1.wav");
    std::unique_ptr<AudioFormatReader> reader1 (audioFormatManager.createReaderFor (*file));
    SamplerSound *sound1 = new SamplerSound("LPV_SUS_72_1.wav", *reader1, allNotes, 72, 0, 1, 100);
    synth.addSound(sound1);
    
    // Layer 2
    File* file2 = new File("Samples/68_G#3_1.wav");
    std::unique_ptr<AudioFormatReader> reader2 (audioFormatManager.createReaderFor (*file2));
    SamplerSound *sound2 = new SamplerSound("68_G#3_1.wav", *reader2, allNotes, 68, 0, 1, 100.0);
    synth.addSound(sound2);

Anyone have any ideas why both sounds aren’t getting played back?

1 Like

It’s not supported by the Juce Synthesiser class.

Ah, really? So how does one use the Synthesiser class to create a sampler with multiple layers (i.e. several sounds playing simultaneously)? Does one have to subclass Synthesiser? Or does one have to subclass SampleVoice? Or does one have to subclass SamplerSound so that these each somehow contain multiple samples? Or is the better route to simply not use Synthesiser at all?

1 Like

I’ve come up with a solution. It seems that the only reason multiple sounds don’t trigger is because of this code in noteOn() function:

// If hitting a note that's still ringing, stop it first (it could be
// still playing because of the sustain or sostenuto pedal).
for (auto* voice : voices)
    if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
        stopVoice (voice, 1.0f, true);

Basically, each time noteOn is being triggered for a specific voice, it checks to make sure the note isn’t already playing, then kills it. This is a good idea when you have one voice being used per keypress, but it’s terrible when you are using multiple voices. The solution is to subclass Synthesiser and modify that chunk of code so that it only stops the voice if the sounds match as well:

void MSTSynthesiser::noteOn (const int midiChannel,
                          const int midiNoteNumber,
                          const float velocity)
{
    const ScopedLock sl (lock);

    for (auto* sound : sounds)
    {
        if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel))
        {
            // If hitting a note that's still ringing, stop it first (it could be
            // still playing because of the sustain or sostenuto pedal).
            for (auto* voice : voices) {
                if ((voice->getCurrentlyPlayingNote() == midiNoteNumber) &&
                    voice->isPlayingChannel (midiChannel) &&
                    (voice->getCurrentlyPlayingSound() == sound)
                    ) {
                    stopVoice (voice, 1.0f, true);
                }
            }

            startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, isNoteStealingEnabled()),
                        sound, midiChannel, midiNoteNumber, velocity);
        }
    }
}
6 Likes