How to add multiple SamplerSound's for a single note and discriminate by velocity?


#1

Hello,

I am trying to load a piano sample library. As much as I can see by the API docs, there isn't a default way to load all samples for a note and assign velocity limits (different sample to be played for different velocity ranges), is there?

What would be the best/recommended approach here?

 

Thanks in advance for any responses.


#2

Think I found what I was looking for.

I am going to subclass Synthesiser and override noteOn and noteOff. I would also need my own version of SamplerSound, to override appliesToNote.

I will refactor appliesToNote -> appliesTo( note, velocity ) and will call that from noteOn and noteOff in the Synthesiser inheritor.

If this isn't the right direction, I would appreciate any hints. Thanks!

BTW, @jules and everyone else involved into shaping this library - beautiful code!


#3

I've subclassed SamplerSound and Synthesiser. SamplerSoundLayer, has a velocity range field and appliesTo( note, velocityRange ) method.

But overriding noteOn and noteOff in Synthesiser I've hit an issue with private fields, which aren't accessible through public/protected methods.

With noteOn I've used the public getter methods and it's overriden successfully, but noteOff sets the keyIsDown private field of SynthesiserVoice and reads the sustainPedalsDown private field of Synthesiser.

I am not sure what can I do in this case...


#4

If you hit the same problem - here is what I came up with.

Sampler : Synthesiser.

// Sampler.h

#ifndef SAMPLER_H_INCLUDED
#define SAMPLER_H_INCLUDED

#include "../JuceLibraryCode/JuceHeader.h"

class Sampler : public Synthesiser
{
public:
    void noteOn (
        const int midiChannel,
        const int midiNoteNumber,
        const float velocity
    ) override;
};

#endif  // SAMPLER_H_INCLUDED

Implementation.

// Sampler.cpp

#include "Sampler.h"
#include "SamplerSoundLayer.h"

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

    for (int i = sounds.size(); --i >= 0;)
    {
        SynthesiserSound* const soundSource = sounds.getUnchecked(i);
        SamplerSoundLayer* const sound = static_cast<SamplerSoundLayer* const> ( soundSource );

        if ( sound->appliesTo ( midiNoteNumber, velocity )
             && 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 ( int j = voices.size(); --j >= 0; )
            {
                SynthesiserVoice* const voice = voices.getUnchecked (j);
                if ( voice->getCurrentlyPlayingNote() == midiNoteNumber
                     && voice->isPlayingChannel ( midiChannel ) )
                    stopVoice ( voice, 1.0f, true );
            }
            startVoice ( findFreeVoice ( sound, midiChannel, midiNoteNumber, isNoteStealingEnabled() ),
                        sound, midiChannel, midiNoteNumber, velocity );
        }
    }
}

SamplerSoundLayer : SamplerSound.

// SamplerSoundLayer.h

#ifndef SAMPLERSOUNDLAYER_H_INCLUDED
#define SAMPLERSOUNDLAYER_H_INCLUDED

#include "../JuceLibraryCode/JuceHeader.h"

class SamplerSoundLayer : public SamplerSound
{
public:
    SamplerSoundLayer (
        const String& name,
        AudioFormatReader& source,
        const BigInteger& midiNotes,
        int midiNoteForNormalPitch,
        Range<float> velocityRange,
        double attackTimeSecs,
        double releaseTimeSecs,
        double maxSampleLengthSeconds
    );
    ~SamplerSoundLayer();
    bool appliesTo( int midiNoteNumber, float velocity );
private:
    Range<float> velocity;
};

#endif  // SAMPLERSOUNDLAYER_H_INCLUDED

Implementation.

// SamplerSoundLayer.cpp

#include "SamplerSoundLayer.h"

SamplerSoundLayer::SamplerSoundLayer (const String& soundName,
                            AudioFormatReader& source,
                            const BigInteger& notes,
                            const int midiNoteForNormalPitch,
                            Range<float> velocityRange,
                            const double attackTimeSecs,
                            const double releaseTimeSecs,
                            const double maxSampleLengthSeconds)
    :   SamplerSound(
            soundName,
            source,
            notes,
            midiNoteForNormalPitch,
            attackTimeSecs,
            releaseTimeSecs,
            maxSampleLengthSeconds
        ),
        velocity( velocityRange )
{
}

SamplerSoundLayer::~SamplerSoundLayer()
{
}

bool SamplerSoundLayer::appliesTo( int midiNoteNumber, float velocity )
{
    bool appliesToMidiNote = appliesToNote( midiNoteNumber );
    bool isInVelocityRange = this->velocity.contains( velocity );

    return appliesToMidiNote && isInVelocityRange;
}

 

Cheers,

Nikolay Tsenkov