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

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.

1 Like

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!

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...

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

5 Likes

Sampler.cpp fails with:

Fixed it with:

SynthesiserSound* const soundSource = sounds.getUnchecked(i);

to

SynthesiserSound* const soundSource = sounds.getUnchecked(i).get();

I tried to use the Code but it seems not to work. I can not set the Velocity. Each Velocity play the Sound, which it should not.

BigInteger rangeMidiNotes;
rangeMidiNotes.setRange(0, 128, true);
Range<float> rangeVelocity;
rangeVelocity.setStart(55);
rangeVelocity.setEnd(56);
mSampler.addSound(new SamplerSoundLayer(
   "Sample",   // String& soundName
   *formatReader,  // AudioFormatReader& source
   rangeMidiNotes,  // BigInteger& notes
   60,  // midiNoteForNormalPitch
   rangeVelocity,  // Range<float> velocityRange
   0.0,  // double attackTimeSecs
   0.001,  // double releaseTimeSecs
   10.0  // double maxSampleLengthSeconds
));

Edit

Didn’t use the new Implementation correctly:

PluginProcessor.h

private:
    Synthesiser mSampler;
    const int mNumVoices { 16 };

to

private:
    Sampler mSampler;
    const int mNumVoices { 16 };

Also the Velocity is float from 0.0f to 1.0f - not pretty sure how to scale 128 Velocity Values here, maybe just math.

BigInteger rangeMidiNotes;
rangeMidiNotes.setRange(60, 5, true);  // Middle C + 4 following Notes
Range<float> rangeVelocity;
rangeVelocity.setStart(1.f/127*0);  // From Velocity 0
rangeVelocity.setEnd(1.f/127*38);  // To Velocity 38
mSampler.addSound(new SamplerSoundLayer(
   "Sample",   // String& soundName
   *formatReader,  // AudioFormatReader& source
   rangeMidiNotes,  // BigInteger& notes
   60,  // midiNoteForNormalPitch
   rangeVelocity,  // Range<float> velocityRange
   0.0,  // double attackTimeSecs
   0.001,  // double releaseTimeSecs
   10.0  // double maxSampleLengthSeconds
));

Someone knows why Velocity is float ?