Super minimal code for play audio sample

Tutorials seem cumbersome. Too many classes for classes within classes that take other classes as arguments. I’m confused. I need a simple minimal code to load and play a sound file. No interaction with the interface, no midi, no everything. Something as close as possible to this pseudocode:

//initialization
void NewProjectAudioProcessor::NewProjectAudioProcessor (bla bla arguments)
{
    sample = LoadAudioFile("samplesFolder/1.wav");
}

//update-tick method
void NewProjectAudioProcessor::processBlock (bla bla arguments)
{
	if (sample != nullptr && isPlaying == false)
	{
		isPlaying = true;
		PlaySound(sample);
	}
}

Hi, Juce gives you the tools to do that. You may also have convert the sample rate when it’s loaded in, to match the current rate.

You can then step along the sample yourself, sending chunks to the processing arrays until it’s done.
It should only be a short amount of code, and you’ll learn a lot doing it yourself. Sorry for not being specific with code.
:edit: This might help: JUCE: ResamplingAudioSource Class Reference

i’d like to advice you to check out josh’s juce6 tutorials on youtube (the audio programmer). he has a bunch of videos specifically made to help people get started with simple examples like simple gain-knob-plugins. after you have watched them you know all the basic methods in the pluginEditor (GUI) and the pluginProcessor (AUDIO). after or within these lessons, you can go through the textual tutorials provided by the JUCE website. there are also example codes in juce’ example-folder, but they are more convoluted than they could. probably not your thing. josh also has some videos about loading files btw, so that’s also something to look out for. there are different ways to go about that kinda stuff depending on your goals

this is the problem that I have in each and every one of the new things that I want to learn.

I only need the minimum functional, compact and updated code, and use it as a base from which to expand according to my needs or as I learn, looking at the reference, or doing a specific search. Having control and knowledge of everything I am doing.

The crazy thing is that if I find it, most of the time it is from a newbie’s post, that the code does not work for them, requesting help.

I guess I must be the only one with this problem, otherwise, the internet programming help methodology would be very different. The current one for me is a pain. I just need an example

1 Like

You don’t understand what’s going on in the process block. It is a callback function that is automatically called hundreds of times in a second. The “bla bla arguments” are a chunk of memory in which you can put a part of your sample. If the function is processed, the chunk of memory will be played. And so on. If you only want to play a sample, you don’t need JUCE. For this you can use SDL (for example). Juce gives you the ability to manipulate the audio stream in real time. Like a bucket chain. The bucket comes in. You fill the bucket. The next bucket comes in…

I like minmimal stuff too. So most of time i remove all the rubbish to get the perfect example i would like to have first. That’s a very good way to learn. What could be done is a GitHub place to collect all our resulting newbie codes. Anyway, frankly i don’t think there is a no pain approach to learn C++ with all that DSP machinery.

1 Like

Yeah, we all want simple P5-like media APIs, but sooner or later we need to do things with the samples, and usually ALWAYS with audio DSP. Probably best to learn how processBlock works using Mrugalla + baramgb suggestions above.

1 Like

The main problem seems you landed in a very diverse ecosystem. There are so many questions to be answered before a solution can be proposed, like:

  • is this an Audio plugin, a host or a standalone app?
  • Do you care if the playhead can move? Opens a lot of follow up questions
  • Do you need many samples played at once? Calls for an Instrument like Sampler
  • Do you care about accuracy? So you need a strategy to get start stop events outside the start of processBlock…

But your question can be answered:

// add this member:
std::unique<juce::AudioFormatReaderSource> source;

void NewProjectAudioProcessor::NewProjectAudioProcessor (bla bla arguments)
{
    juce::AudioFormatManager mgr;
    mgr.registerBasicFormats();
    auto file = juce::File::getSpecialLocation (juce::File::userDesktopFolder).getChildFile ("1.wav");
    auto* reader = mgr.createReaderFor (file);
    if (reader)
        source = std::make_unique<AudioFormatReaderSource>(reader, true);
}

void NewProjectAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
{
    auto& mainBus = getBusBuffer (buffer, false, 0);
    if (source)
    {
        juce::AudioChannelSourceInfo info (mainBus);
        source->getNextAudioBlock (info);
    }
    else
    {
        mainBus.clear();
    }
}

However since we talk about real time, there are so many caveats that you cannot use that code without modifications.
And there is not one example but many, because each problem requires another solution:

  • you cannot read a file from the audio thread: you need to add a BufferingAudioSource with its own prefetch thread
  • For an instrument you need to keep track of multiple samples, which even start at different times even withing the buffer. So there is juce::Synthesizer and juce::Sampler
  • There can be multiple buses, you might need to convert the format from stereo to mono, vice versa or even to or from 5.1 or other bus formats

That’s why learning is a journey and I am sorry that the examples you found so far were too convoluted for your taste. That’s in the nature of realtime and the diverse field of applications.

Good luck finding your ways, if you have specific questions come back any time.

6 Likes

Thank you very much appreciate your help! This is probably really not a very correct way to extract sound. As far as I can tell, AudioTransportSource can play only 1 sound, even if you create several AudioTransportSource, set its own sample for each and call .start () for each separately. Although I don’t need polyphony, I’ll try to learn the Synthesizer and Sampler. They might be easier to work with as the project scaled up than the AudioTransportSource.

But in general, this works for playing single sounds, maybe it will be useful for someone for training:

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

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

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

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

//==============================================================================
NewProjectAudioProcessor::NewProjectAudioProcessor()
#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 NewProjectAudioProcessor::Init()
{
    formatManager.registerBasicFormats();

    transportSource.addChangeListener(this);
    audioReader = formatManager.createReaderFor(juce::File(juce::String("Samples/test1.wav"))); //in DAW .exe folder or plugin .vst3 folder

    if (audioReader != nullptr)
    {
        juce::AudioFormatReaderSource* newSource = new juce::AudioFormatReaderSource(audioReader, true);
        transportSource.setSource(newSource, 0, nullptr, audioReader->sampleRate);
        transportSource.setGain(1.0f);
    }
}

NewProjectAudioProcessor::~NewProjectAudioProcessor()
{
}

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

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

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

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

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

int NewProjectAudioProcessor::getNumPrograms()
{
    return 1;
}

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

void NewProjectAudioProcessor::setCurrentProgram (int index)
{
}

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

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

//==============================================================================
void NewProjectAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    transportSource.prepareToPlay(samplesPerBlock, sampleRate);
}

void NewProjectAudioProcessor::releaseResources()
{
    transportSource.releaseResources();
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool NewProjectAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::ignoreUnused (layouts);
    return true;
  #else
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void NewProjectAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    transportSource.getNextAudioBlock(juce::AudioSourceChannelInfo(buffer));

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

        if (message.isNoteOn() && audioReader != nullptr)
        {
            PlaySound();
        }
    }
}

void NewProjectAudioProcessor::PlaySound()
{
    transportSource.setPosition(0.0);
    transportSource.start();
}

//==============================================================================
bool NewProjectAudioProcessor::hasEditor() const
{
    return true;
}

juce::AudioProcessorEditor* NewProjectAudioProcessor::createEditor()
{
    return new NewProjectAudioProcessorEditor (*this);
}

//==============================================================================
void NewProjectAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
}

void NewProjectAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
}

void NewProjectAudioProcessor::changeListenerCallback(juce::ChangeBroadcaster* source)
{
}

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

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

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

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

#pragma once

#include <JuceHeader.h>

//==============================================================================
/**
*/
class NewProjectAudioProcessor  :   public juce::AudioProcessor,
                                    public juce::ChangeListener
{
public:
    //==============================================================================
    NewProjectAudioProcessor();
    ~NewProjectAudioProcessor() 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 changeListenerCallback(juce::ChangeBroadcaster* source) override;

    void Init();
    void PlaySound();
    juce::AudioFormatReader* audioReader;
    juce::AudioFormatManager formatManager;
    juce::AudioTransportSource transportSource;
private:
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NewProjectAudioProcessor)
};

1 Like

Yes, that will work and is pretty much what is written in the juce tutorial “playing sounds”.

For an instrument you need to keep track of several voices in parallel, how far they progressed since the note started, so you can copy the correct portion into the playback buffer.

You can certainly do that by hand as an exercise, but I would recommend to look into the juce::Synthesiser class or directly into juce::SamplerSound.

You can write a sampler in the Synthesizer class, I think I would even prefer that.

There are also several tutorials on theaudioprogrammers youtube (disclaimer: I didn’t watch them but know people who did.

Managed to get Synthesiser to work. Several notes can be played at the same time.
I am publishing it in the hope that it can help someone figure it out. To test, I put the sound on the first and two keys on the keyboard in Init() method:

void NewProjectAudioProcessor::Init()
{
    formatManager.registerBasicFormats();
    AudioFormatReader* audioReader = formatManager.createReaderFor(juce::File(juce::String("Samples/test1.wav"))); //in DAW .exe folder or plugin .vst3 folder
    AudioFormatReader* audioReader2 = formatManager.createReaderFor(juce::File(juce::String("Samples/test2.wav"))); //in DAW .exe folder or plugin .vst3 folder

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

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

        BigInteger usedNotes;
        usedNotes.setRange(0, 1, true);
        synth.addSound(new SamplerSound("demo sound", *audioReader, usedNotes, 0, 0.0f, 0.0f, 999.0));

        BigInteger usedNotes2;
        usedNotes2.setRange(1, 1, true);
        synth.addSound(new SamplerSound("demo sound", *audioReader2, usedNotes2, 1, 0.0f, 0.0f, 999.0));
    }
}

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

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

void NewProjectAudioProcessor::Init()
{
    formatManager.registerBasicFormats();
    AudioFormatReader* audioReader = formatManager.createReaderFor(juce::File(juce::String("Samples/test1.wav"))); //in DAW .exe folder or plugin .vst3 folder
    AudioFormatReader* audioReader2 = formatManager.createReaderFor(juce::File(juce::String("Samples/test2.wav"))); //in DAW .exe folder or plugin .vst3 folder

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

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

        BigInteger usedNotes;
        usedNotes.setRange(0, 1, true);
        synth.addSound(new SamplerSound("demo sound", *audioReader, usedNotes, 0, 0.0f, 0.0f, 999.0));

        BigInteger usedNotes2;
        usedNotes2.setRange(1, 1, true);
        synth.addSound(new SamplerSound("demo sound", *audioReader2, usedNotes2, 1, 0.0f, 0.0f, 999.0));
    }
}

NewProjectAudioProcessor::~NewProjectAudioProcessor()
{
}

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

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

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

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

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

int NewProjectAudioProcessor::getNumPrograms()
{
    return 1;
}

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

void NewProjectAudioProcessor::setCurrentProgram (int index)
{
}

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

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

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

void NewProjectAudioProcessor::releaseResources()
{
}

#ifndef JucePlugin_PreferredChannelConfigurations
bool NewProjectAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
  #if JucePlugin_IsMidiEffect
    juce::ignoreUnused (layouts);
    return true;
  #else
    if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
     && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
        return false;

   #if ! JucePlugin_IsSynth
    if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
        return false;
   #endif

    return true;
  #endif
}
#endif

void NewProjectAudioProcessor::processBlock (AudioBuffer<float>& buffer, 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());
        }
    }
}

void NewProjectAudioProcessor::PlaySound()
{
}

//==============================================================================
bool NewProjectAudioProcessor::hasEditor() const
{
    return true;
}

AudioProcessorEditor* NewProjectAudioProcessor::createEditor()
{
    return new NewProjectAudioProcessorEditor (*this);
}

//==============================================================================
void NewProjectAudioProcessor::getStateInformation (MemoryBlock& destData)
{
}

void NewProjectAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
}

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


#pragma once

#include <JuceHeader.h>
using namespace juce;

//==============================================================================
/**
*/
class NewProjectAudioProcessor  : public AudioProcessor
{
public:
    //==============================================================================
    NewProjectAudioProcessor();
    ~NewProjectAudioProcessor() override;

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

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

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

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

    //==============================================================================
    const 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 String getProgramName (int index) override;
    void changeProgramName (int index, const String& newName) override;

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

    void Init();
    void PlaySound();

    AudioFormatManager formatManager;
    Synthesiser synth;

private:
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NewProjectAudioProcessor)
};