Drum sampler implementation

Hey guys!

I am curious how drum samplers are usually structured, think Battery by Native Instruments.

I am looking to improve my drum sampler which currently uses an instance of juce::Synthesiser, and modified versions of the JUCE SamplerSound and SamplerVoice classes, with each drum sample using a SamplerSound mapped to one midi note.

As I add more features to the plug-in I begin to feel I’ve structured it the wrong way by only using one instance of juce::Synthesiser . If I am modeling the behavior of a product like Battery by NI or Drum Rack in Ableton, would it make more sense to use a separate instance of juce::Synthesiser for each drum sample? For instance:

OwnedArray<juce::Synthesiser> samplerEngine;

Super curious how this is commonly handled.

Thanks a bunch!

-Cameron

What issue are you having with single sound per note model?

While there are some hiccups using the JUCE synth architecture, the pros hugely outweigh the cons of doing a custom engine from scratch – Using an array of synths only sounds more like a move in the wrong direction imo

I agree that making it from scratch is not the direction to go.

An issue I am running into now is per drum sample effects.

Like in Battery, I want to have filters and other FXs for each drum sample. Because the rendering is done in SamplerVoice I have to add my dsp FX processors as a member of the voice class.

What bothers me about this setup is that there is no particular instance of the SynthVoice that will be called when you trigger a drum sample, the Synthesiser class just finds the first available voice. This means if I set my filter settings for one drum sample, trigger it, let the voice stop, and then trigger another sample with different settings, they both will end up using the same voice, and FX instance. It seems like it would make much more sense that when I trigger a sample, it always is processed through the same FX instance to prevent the internal DSP values from jumping all over the place when voices are used and reused.

Hope that was clear!
Thanks

Yeah that totally makes sense – I think at first glance reaching for a synth per note might be an intuitive pull – but a suggestion – you should really be resetting everything on every note on regardless? So does it matter if it’s the same instance or not? even if there was a difference where a certain voice had a different chain than another voice type – the sounds methods are there to separate that logic.

I would extend the external model – not only should a sound be tied to a specific note – but also a struct of all of that notes “per note settings” is what I refer to them as.

When a note triggers in a voice - it has a pointer to the global structure of state – then when it starts playback:

1. Pull & assign the sample pointer
2. Reset All DSP
3. Update the DSP chain via the per note settings for that note
4. Let is rip

I imagine you have something like:

std::map<int, sample*> // midi to sample

would be more like:

std::map<int, NoteSettings>


In either case you’ll find you’ll want to flush the internal state of the DSP regardless, there’s little overhead in these steps above – and unfortunately having the DSP internal the voice object is unavoidable unless you wanted to modify the synth to be multi output – but after experimenting with a lot of architectures dealing with per voice modulation – packing the voice layer effects into the voice is the cleanest – but you shouldn’t shy away from further breaking down the “voice” into it’s own own sort of “AudioProcessor” type class, and have it call numerous other class processor from inside it’s processing loop.

1 Like

There are many drawbacks having the effects inside the voice.

  • the voice is not called continuously, expect discontinuities
  • hard to have effects with latency and tail

I would rather work with effect busses:

  • create an internal AudioBuffer with as many channels as you need effects
  • when the note is triggered, decide which bus to render the note to
  • after the sampler.renderNextBlock() apply the effects
  • mix the channels down to the output buffer

The advantages:

  • minimises the number of effect instances/CPU
  • the effects run continuously, no discontinuities
  • tails are rendered without hogging a voice for longer than needed

Just an idea, maybe that fits your use case

2 Likes

Nice I like that – it definitely depends on the effect - voice level filters need to be in the voice if they’re connected to any voice level modulation / envelopes – that’s pretty much all I stick in the voice level

Hey guys, thanks so much for your input. I ended up using per voice effects in the manner @jakemumu suggested for basic things like filters and a ADSR when I can reset the processors on every startNote()

I was overthinking the multiple voices thing a bit :slight_smile:

For more complex effects like reverb, the solution @daniel presented is great, super clean, and will help with CPU load!

Thanks a bunch.

1 Like