Simplest possible sampler not making any sound

i’ve followed both the audio programmers videos on this topic, as well as scoured this forum. I’ve attempted to follow the tutorials on this website but I think they must be written for an older version of juce because they don’t make any sense.
I just want to load a sound and play it. How could it be this difficult?

here is my code, please enlighten me. I must be missing something obvious.

pluginprocessor.cpp


#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
MinimalsamplerAudioProcessor::MinimalsamplerAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
    init();
}

void MinimalsamplerAudioProcessor::init() {
    formatmanager.registerBasicFormats();

    juce::AudioFormatReader* reader = formatmanager.createReaderFor(juce::File(juce::String("audio.wav"))); //tried loading "audio.wav" in the same folder as the DAW .exe, as well as using a universal path, neither work :(

    if (reader != nullptr) {
        synth.clearSounds();

        for (auto i = 0; i < 4; ++i) {
            synth.addVoice(new juce::SamplerVoice());
        }

        juce::BigInteger range;
        range.setRange(0, 1, true);
        synth.addSound(new juce::SamplerSound("sample", *reader, range, 0, 0.0f, 0.0f, 999.0));
    }
}

MinimalsamplerAudioProcessor::~MinimalsamplerAudioProcessor()
{
}

//==============================================================================
const juce::String MinimalsamplerAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

bool MinimalsamplerAudioProcessor::acceptsMidi() const
{
   #if JucePlugin_WantsMidiInput
    return true;
   #else
    return false;
   #endif
}

bool MinimalsamplerAudioProcessor::producesMidi() const
{
   #if JucePlugin_ProducesMidiOutput
    return true;
   #else
    return false;
   #endif
}

bool MinimalsamplerAudioProcessor::isMidiEffect() const
{
   #if JucePlugin_IsMidiEffect
    return true;
   #else
    return false;
   #endif
}

double MinimalsamplerAudioProcessor::getTailLengthSeconds() const
{
    return 0.0;
}

int MinimalsamplerAudioProcessor::getNumPrograms()
{
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
                // so this should be at least 1, even if you're not really implementing programs.
}

int MinimalsamplerAudioProcessor::getCurrentProgram()
{
    return 0;
}

void MinimalsamplerAudioProcessor::setCurrentProgram (int index)
{
}

const juce::String MinimalsamplerAudioProcessor::getProgramName (int index)
{
    return {};
}

void MinimalsamplerAudioProcessor::changeProgramName (int index, const juce::String& newName)
{
}

//==============================================================================
void MinimalsamplerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    synth.setCurrentPlaybackSampleRate(sampleRate);
}

void MinimalsamplerAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool MinimalsamplerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::ignoreUnused (layouts);
    return true;
  #else
    // This is the place where you check if the layout is supported.
    // In this template code we only support mono or stereo.
    // Some plugin hosts, such as certain GarageBand versions, will only
    // load plugins that support stereo bus layouts.
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

    // This checks if the input layout matches the output layout
   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void MinimalsamplerAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());

    for (const auto metadata : midiMessages)
    {
        auto message = metadata.getMessage();
        if (message.isNoteOn())
        {
            DBG("Note number " << message.getNoteNumber());
        }
    }

}

//==============================================================================
bool MinimalsamplerAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

juce::AudioProcessorEditor* MinimalsamplerAudioProcessor::createEditor()
{
    return new MinimalsamplerAudioProcessorEditor (*this);
}

//==============================================================================
void MinimalsamplerAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void MinimalsamplerAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

//==============================================================================
// This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new MinimalsamplerAudioProcessor();
}

pluginprocessor.h

/*
  ==============================================================================

    This file contains the basic framework code for a JUCE plugin processor.

  ==============================================================================
*/

#pragma once

#include <JuceHeader.h>

//==============================================================================
/**
*/
class MinimalsamplerAudioProcessor  : public juce::AudioProcessor
                            #if JucePlugin_Enable_ARA
                             , public juce::AudioProcessorARAExtension
                            #endif
{
public:
    //==============================================================================
    MinimalsamplerAudioProcessor();
    ~MinimalsamplerAudioProcessor() override;

    //==============================================================================
    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void releaseResources() override;

   #ifndef JucePlugin_PreferredChannelConfigurations
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
   #endif

    void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;

    //==============================================================================
    juce::AudioProcessorEditor* createEditor() override;
    bool hasEditor() const override;

    //==============================================================================
    const juce::String getName() const override;

    bool acceptsMidi() const override;
    bool producesMidi() const override;
    bool isMidiEffect() const override;
    double getTailLengthSeconds() const override;

    //==============================================================================
    int getNumPrograms() override;
    int getCurrentProgram() override;
    void setCurrentProgram (int index) override;
    const juce::String getProgramName (int index) override;
    void changeProgramName (int index, const juce::String& newName) override;

    //==============================================================================
    void getStateInformation (juce::MemoryBlock& destData) override;
    void setStateInformation (const void* data, int sizeInBytes) override;

    void init();
    juce::AudioFormatManager formatmanager;
    juce::Synthesiser synth;

private:
    

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MinimalsamplerAudioProcessor)
};

You are passing a range from 0 to 1, so C-2 to C#-2. you probably want 0 to 127

thats not it. tried changing that and it still produces no sound

something else i’ve noticed, the sampler demo that comes with juce also does not work. It just buzzes. Is it a problem with the audio plug in host or the demo sampler itself?
Is it related to the issue of my plugin not making any noise? I don’t know.

First thing to check is if you get any reader in the debugger, or print to the console if there is a reader returned.

My strongest guess is, that the file is not found. Don’t try relative locations, they usually end up as undefined.
Always use juce::File::getSpecialLocation() and getChildFile.
In your case:

// the desktop is ideal for testing
auto file = juce::File::getSpecialLocation (juce::File::userDesktopDirectory).getChildFile("audio.wav"); 
jassert (file.existsAsFile()); // break if the file is not there

auto* reader = formatmanager.createReaderFor (file);
jassert (reader); // break if no reader was created

Those jassert calls are debugging helpers, it stops the debugger and allows you to inspect the variables and fix the code that led to the condition not being satisfied.

Good luck

Got it! It was a combination of two things im guessing. I was loading the file wrong. Using juce::File::getSpecialLocation (juce::File::userDesktopDirectory).getChildFile("audio.wav");
fixed that.
As well as I wasn’t clearing the buffer at the beginning of the process block. I have seen some tutorials say this is necessary, while other ones don’t bother, so I’m not sure why it works for some but not others.
In any case, here is my working pluginprocessor.cpp for future generations. will definitely be making a video tutorial on this as it is ridiculous how little reliable info there is on this pretty basic task.


#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
MinsampAudioProcessor::MinsampAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  juce::AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
{
    init();
}

void MinsampAudioProcessor::init() {
    formatmanager.registerBasicFormats();

    //juce::AudioFormatReader* reader = formatmanager.createReaderFor(juce::File(juce::String("C://Users//sharo//Music//joydrums OLD//audiohack break2 at 137 bpm.wav"))); //tried loading "audio.wav" in the same folder as the DAW .exe, as well as using a universal path, neither work :(

    auto file = juce::File::getSpecialLocation(juce::File::userDesktopDirectory).getChildFile("audio.wav");
    jassert(file.existsAsFile());
    DBG("its working");



    auto* reader = formatmanager.createReaderFor(file);
    jassert(reader);

    if (reader != nullptr) {
        synth.clearSounds();

        for (auto i = 0; i < 4; ++i) {
            synth.addVoice(new juce::SamplerVoice());
        }

        juce::BigInteger range;
        range.setRange(0, 127, true);
        synth.addSound(new juce::SamplerSound("sample", *reader, range, 60, 0, 10, 10.0));

        DBG("its really working this time");
    }
}

MinsampAudioProcessor::~MinsampAudioProcessor()
{
}

//==============================================================================
const juce::String MinsampAudioProcessor::getName() const
{
    return JucePlugin_Name;
}

bool MinsampAudioProcessor::acceptsMidi() const
{
   #if JucePlugin_WantsMidiInput
    return true;
   #else
    return false;
   #endif
}

bool MinsampAudioProcessor::producesMidi() const
{
   #if JucePlugin_ProducesMidiOutput
    return true;
   #else
    return false;
   #endif
}

bool MinsampAudioProcessor::isMidiEffect() const
{
   #if JucePlugin_IsMidiEffect
    return true;
   #else
    return false;
   #endif
}

double MinsampAudioProcessor::getTailLengthSeconds() const
{
    return 0.0;
}

int MinsampAudioProcessor::getNumPrograms()
{
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
                // so this should be at least 1, even if you're not really implementing programs.
}

int MinsampAudioProcessor::getCurrentProgram()
{
    return 0;
}

void MinsampAudioProcessor::setCurrentProgram (int index)
{
}

const juce::String MinsampAudioProcessor::getProgramName (int index)
{
    return {};
}

void MinsampAudioProcessor::changeProgramName (int index, const juce::String& newName)
{
}

//==============================================================================
void MinsampAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    synth.setCurrentPlaybackSampleRate(sampleRate);
}

void MinsampAudioProcessor::releaseResources()
{
    // When playback stops, you can use this as an opportunity to free up any
    // spare memory, etc.
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool MinsampAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::ignoreUnused (layouts);
    return true;
  #else
    // This is the place where you check if the layout is supported.
    // In this template code we only support mono or stereo.
    // Some plugin hosts, such as certain GarageBand versions, will only
    // load plugins that support stereo bus layouts.
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

    // This checks if the input layout matches the output layout
   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void MinsampAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear(i, 0, buffer.getNumSamples());

    synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples());

    for (const auto metadata : midiMessages)
    {
        auto message = metadata.getMessage();
        if (message.isNoteOn())
        {
            DBG("Note number " << message.getNoteNumber());
        }
    }
}

//==============================================================================
bool MinsampAudioProcessor::hasEditor() const
{
    return true; // (change this to false if you choose to not supply an editor)
}

juce::AudioProcessorEditor* MinsampAudioProcessor::createEditor()
{
    return new MinsampAudioProcessorEditor (*this);
}

//==============================================================================
void MinsampAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
    // You should use this method to store your parameters in the memory block.
    // You could do that either as raw data, or use the XML or ValueTree classes
    // as intermediaries to make it easy to save and load complex data.
}

void MinsampAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    // You should use this method to restore your parameters from this memory block,
    // whose contents will have been created by the getStateInformation() call.
}

//==============================================================================
// This creates new instances of the plugin..
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new MinsampAudioProcessor();
}

note, i havent changed my pluginprocessor.h so you can copy it from the first post in this thread.

About clearing the AudioBuffer: that depends on what you are doing.

The Synthesizer and the Sampler (which inherits Synthesizer) work by adding to the existing buffer. That way each voice keeps the existing signal.

But on the other hand it means, if the buffer was dirty before you called renderNextAudioBlock(), then this noise will persist. In some cases it is the desired effect, that’s why that clear is not the default.

I see. So if for whatever reason I didn’t want to clear the buffer every processblock, i would still need to do it at least once somewhere like preparetoplay() right?

Also while tying up loose ends, I still have no explanation for the sampler demo not working. It just creates a buzzing that sounds like a stuck buffer or something. I’m on juce 7.0.6

No you don’t. If your plugin specifies it has an audio input, there might be audio you want to keep on the buffer.
But for an actual synth or sampler (read instrument, not effect plugin) the buffer will contain garbage and you should clear it beforehand.

I haven’t used the sampler class myself. I think you need to debug into the source.