How to play an audio processor without a plugin editor?

All the tutorials and examples of AudioProcessor I find use PluginEditor to launch the AudioProcessor. What if I want to use no gui at all and just play an audio processor?

For example:

HelloSamplerAudioProcessor::HelloSamplerAudioProcessor()
     : AudioProcessor (BusesProperties()
               .withOutput("Output", juce::AudioChannelSet::stereo(), true)

I’m plugigng this to an AudioDeviceManager like this:

auto samplerProcessor = std::make_shared<HelloSamplerAudioProcessor>();
auto samplerProcessorPlayer = std::make_shared<juce::AudioProcessorPlayer>();
samplerProcessorPlayer->setProcessor (samplerProcessor.get());
deviceManager.addAudioCallback (samplerProcessorPlayer.get());

however since it has no inputs, I get

I/JUCE: JUCE Assertion failure in juce_AudioProcessor.cpp:200
I/JUCE: JUCE Assertion failure in juce_AudioProcessor.cpp:350
I/JUCE: JUCE Assertion failure in juce_AudioProcessor.cpp:356
JUCE Assertion failure in juce_AudioProcessor.cpp:360
I/JUCE: JUCE Assertion failure in juce_AudioProcessor.cpp:363

which are from here:

void AudioProcessor::setPlayConfigDetails (int newNumIns, int newNumOuts, double newSampleRate, int newBlockSize)
{
    bool success = true;

    if (getTotalNumInputChannels() != newNumIns)
        success &= setChannelLayoutOfBus (true,  0, AudioChannelSet::canonicalChannelSet (newNumIns));

    // failed to find a compatible input configuration
    jassert (success);

    if (getTotalNumOutputChannels() != newNumOuts)
        success &= setChannelLayoutOfBus (false, 0, AudioChannelSet::canonicalChannelSet (newNumOuts));

    // failed to find a compatible output configuration
    jassert (success);

    // if the user is using this method then they do not want any side-buses or aux outputs
    success &= disableNonMainBuses();
    jassert (success);

    // the processor may not support this arrangement at all
    jassert (success && newNumIns == getTotalNumInputChannels() && newNumOuts == getTotalNumOutputChannels());

    setRateAndBufferSizeDetails (newSampleRate, newBlockSize);
    ignoreUnused (success);
}

Turns out AudioDeviceManager calls setPlayConfigDetails on my processor.

This makes these assertions fail because my processor has no input.

I even tried to give it an input but disable it, but I still get echo.

So, how to I play an audio processor with no input?

Your processor actually owns the editor. If you don’t want an editor, just make has Editor return false.

//==============================================================================
bool YourPluginProcessor::hasEditor() const
{
    return false;
}

But, more importantly, it looks like your problem has nothing to do with the Editor. I can’t really tell what you are trying to accomplish either. For example, why are you using an AudioDeviceManager? More information may help people here to help you.

I don’t know why I’m using AudioDeviceManager. I found somewhere an example that uses it to play an AudioProcessor (that had an input) and I thought it was necessary.

What I want to do is: use this simple sampler in my app GitHub - TheAudioProgrammer/helloSampler: A simple sampler built using the JUCE Framework

I want to basically make this AudioProcessor work: helloSampler/PluginProcessor.cpp at master · TheAudioProgrammer/helloSampler · GitHub. I though I had to call AudioDeviceManager:: addAudioCallback on it.

Given an AudioProcessor , how do I make it process the sounds? If looks like if it uses input, I can pass it to AudioDeviceManager , but what if it has no input, just output?

Ok, your experience level with this or with c++ is not clear to me, so please don’t take what I will say below as any kind of negative.

The example you linked to appears to be a full, but very simple plugin. I don’t know that you can ‘play’ it from another application (except for a host, of course). Do you have experience with hosts such as Apple Logic or Ableton Live?

AudioDeviceManager is intended (as far as I know) to be used in specific cases to handle in/out for midi devices. Is that what you want to do?

You may want to study the code you linked to (or similar - I haven’t read any of what you linked to) to get used to the different components that make up a JUCE plugin. Are you able to use JUCE classes such as AudioProcessor and AudioEditor in other projects?

Also, the tutorials are really good, but I believe they mostly show applications rather than plugins. Which are you trying to do?

I’m trying to, instead of adding this plugin to a program like Ableton, use it in a juce program written by me. I followed this tutorial: JUCE: Tutorial: Cascading plug-in effects and was able to make this plugin work in Android simply creating the AudioProcessor and adding it as a callback to an AudioDeviceManager. Then the effects played well in my Android app.

I’m now trying to do the same for this other AudioProcessor I linked. That is, I want to play sounds from it. Not on Ableton, but on my Android app. I tried creating an instance of HelloSamplerAudioProcessor and passing to my AudioDeviceManager like this:

auto samplerProcessorPlayer = std::make_shared<juce::AudioProcessorPlayer>();
deviceManager.addAudioCallback (samplerProcessorPlayer.get());

but I got those assertion problems.

How do I integrate this plugin into my Juce application? Which part of my application would call this plugin for processing of blocks?

Oh, I understand better now, though I don’t think I can be of much help. I have not done anything with Android, so there may be someone else who is better suited to help.

But, if I were in your position, I would probably not start with the code you linked to above & rather work through the tutorials related to Audio (if you have not already) and then move onto other ideas.

http://docs.juce.com/master/tutorial_audio_device_manager.html
http://docs.juce.com/master/tutorial_playing_sound_files.html
http://docs.juce.com/master/tutorial_looping_audio_sample_buffer.html

Good luck!

actually the Android app is the same as the desktop app. Same C++ code. What would you make to run a AudioProcessor in desktop?

I just need a minimal program that plays an AudioProcessor. Unfortunately these tutorials do not use AudioProcessor.

Have you tried adding an input like this? I don’t know that it will work, but I would try it - it may just need to be stated - given the error you are getting, it seems likely.

.withInput("Input", juce::AudioChannelSet::stereo(), false)

AudioProcessor is a bit big and includes methods similar to what is in the MainContentComponent of the cascading effects tutorial you link to above. But it also includes structs for busses. Have a look at BusesProperties and BusProperties

If that doesn’t help, you may want to tell if you are building an application using an AudioAppComponent or is it something else? Also, given what you state above, I am assuming you are using an AudioProcessorGraph and an AudioProcessorPlayer. Is that correct?

I just looked at Projucer and noticed the it has an AudioPlaybackDemo which may help. You may want to start with that. I would think that doing what you are trying to would only require adding the graph, adding its nodes, and using them as described in the docs.

I’m using an AudioProcessorPlayer to play an AudioProcessor, no AudioProcessorGraph involved in this example.

Adding

.withInput("Input", juce::AudioChannelSet::stereo(), false)

makes the assert errors go away, but then I hear echos, even though the input is setted to false.

This is the processor I’m trying to play: helloSampler/PluginProcessor.cpp at master · TheAudioProgrammer/helloSampler · GitHub

I thought it was just a matter of passing it to AudioProcessorPlayer and then playing the AudioProcessorPlayer by adding it as a callback to AudioDeviceManager, but it won’t work if the processor has no inputs.

All the JUCE tutorials focus on plugins but there’s no tutorial on how to make a standalone app to play a simple AudioProcessor.

If you are forced to use the inputs in an AudioProcessor, but don’t want them incorporated into the processing, you can do a full clear() on the AudioProcessor processBlock buffer before doing any other processing.

Anyway, I think there shouldn’t be a problem using AudioProcessors with no inputs with the AudioDeviceManager/AudioProcessorPlayer, but you would need to set up the AudioDeviceManager so that it doesn’t use the inputs.

Sorry, I know this must be frustrating. Your thought seems logical.

In this position, I would probably start over using the simplest possible project and build it up from information all from the same source to finish my proof of concept. Then I would start over again or really clean up the project to make sure everything that is there is absolutely needed.

I don’t know, but I would guess that the JUCE tutorial about Cascading plug-in effects is a good place for you to start putting together something using a graph. In this tutorial, TutorialProcessor and ProcessorBase are both AudioProcessors. The AudioDeviceManager is named deviceManager.

Classes derive from ProcessorBase which will be added to the graph (AudioGraphIOProcessor). I suspect that the sampler you are trying to build would be one of those nodes.

BTW, are you sure you don’t have the app vs. plugin thing backwards?

It looks like this one shows an application.
https://docs.juce.com/master/tutorial_audio_processor_graph.html

And the other that you are trying to replicate (helloSampler) is a plugin.

Also, this one is an application.
http://docs.juce.com/master/tutorial_looping_audio_sample_buffer.html

The good news is that the audio sample is being played, but even with audio input setted to false and clearing the buffer on processBlock as @xenakios suggested, I still get echo. Here’s the processBlock of the HelloSamplerAudioProcessor:

void HelloSamplerAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    //-------------everything is being cleared, should not have echo even with input false
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());
    
    if (mShouldUpdate)
    {
        updateADSR();
    }

    juce::MidiMessage m;
    juce::MidiBuffer::Iterator it { midiMessages };
    int sample;
    
    while (it.getNextEvent (m, sample))
    {
        if (m.isNoteOn())
            mIsNotePlayed = true;
        else if (m.isNoteOff())
            mIsNotePlayed = false;
    }
    
    mSampleCount = mIsNotePlayed ? mSampleCount += buffer.getNumSamples() : 0;
    std::unique_lock<std::recursive_mutex> lk{samplerMutex};
    sampler.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples());
}

Here’s how I’m making the input false:

HelloSamplerAudioProcessor::HelloSamplerAudioProcessor()
     : AudioProcessor (BusesProperties()
               .withInput("Input", juce::AudioChannelSet::stereo(), false)
               .withOutput("Output", juce::AudioChannelSet::stereo(), true)

I followed the AudioProcessorGraph tutorial and could make it work on my Android app. It works very well because it uses input and output. In fact that’s where I took the idea that every AudioProcessor needs an AudioProcessorPlayer and every AudioProcessorPlayer needs to be called by an AudioDeviceManager.

Unfortunately the Looping Audio Sample Buffer tutorial is for n AudioAppComponent, not an AudioProcessor and also the initialization of the things is hidden by the window. I don’t use window, I’m trying to do a bare minimum C++ only app that plays an AudioProcessor

What could possibly be doing that echo? And do I really need an input? I think something as simple as a sampler shouldn’t need :frowning:

Your code is not clearing the channels completely. The for loop at the beginning clears channels that are above the inputs in the buffer. (And the code might not potentially even do anything if you have for example 2 inputs and 2 outputs, since the channels would be shared.) You should just do buffer.clear() and leave out the for loop.

I also noted that

class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback,
public MidiInputCallback
{
public:

AudioProcessorPlayer (bool doDoublePrecisionProcessing = false);

/** Destructor. */
~AudioProcessorPlayer() override;

AudioProcessorPlayer shouldn’t even be created like I do:

auto samplerProcessor = std::make_shared<HelloSamplerAudioProcessor>();
auto samplerProcessorPlayer = std::make_shared<juce::AudioProcessorPlayer>();
samplerProcessorPlayer->setProcessor (samplerProcessor.get());
deviceManager.addAudioCallback (samplerProcessorPlayer.get());

because it is abstract (the destructor is virtual and not defined). This makes my code crash when I close the android app, with

Abort message: 'Pure virtual function called!'

so now I’m even more confused, I shouldn’t even create std::make_shared<juce::AudioProcessorPlayer>();. I should make my own class?

indeed, buffer.clear() eliminated the echo. But would be nicer to not have the input :smile:

How could this code even work at all? :thinking: Where are you putting all that code? (And no, you don’t need to subclass AudioProcessorPlayer further, it’s not an abstract class.)

I don’t know where I found that passing an AudioProcessor to an AudioProcessorPlayer and then this AudioProcessorPlayer to an AudioDeviceManager is the right way but it worked for both the sampler example and the graph processor tutorial. I scanned lots of questions on forums to discover this since I could not find examples on how to do a standalone app without GUI.

But I think that AudioDeviceManager calls all the AudioProcessorPlayers with the audio input, which forwards to the AudioProcessor.

If you know a better or the right way I’d be so thankful!

Also, the fact that AudioProcessorPlayer has a not defined destructor makes the phone crash when it is destructed (that is, when I close the app). I get this android crash:

Abort message: 'Pure virtual function called!'

so I’m confused about why it has a pure virtual destructor if it’s intended to be instantiated, not subclassed.

I think he was asking where you put that code, not where you got it from.

The AudioProcessorPlayer does have a destructor:

AudioProcessorPlayer::~AudioProcessorPlayer ()
{
    setProcessor (nullptr);
}

What makes you think that that is where the problem happened?

void shutdown() override
{
    ALOGV(TAG, "shutting down");
    ALOGV(TAG, "audioProcessors.clear()");
    audioProcessors.clear();
    ALOGV(TAG, "aaudioProcessorPlayers.clear()");
    audioProcessorPlayers.clear();
    ALOGV(TAG, "gonna closeAudioDevice");
    deviceManager.closeAudioDevice();
    ALOGV(TAG, "shutdown success");
}

This is the log on android:

V/main.cpp( 4479): shutting down
V/main.cpp( 4479): audioProcessors.clear()
V/main.cpp( 4479): aaudioProcessorPlayers.clear()
E/libc++abi( 4479): Pure virtual function called!
F/libc    ( 4479): Fatal signal 6 (SIGABRT), code -6 in tid 4479 (pai.flutter_app)
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'motorola/griffin/griffin:8.0.0/OPL27.76-71-2-3/3:user/release-keys'
Revision: 'p3b0'
ABI: 'arm64'
pid: 4479, tid: 4479, name: pai.flutter_app  >>> com.pai.flutter_app <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'Pure virtual function called!'
    x0   0000000000000000  x1   000000000000117f  x2   0000000000000006  x3   0000000000000008

the error happens on the audioProcessorPlayers.clear(), which is destructing the players. But you’re right, the destructor is not virtual, I just looked at the .h and jumped to conclusions.

I don’t know which pure virtual function is being called though

Well, what does your audioProcessorPlayers.clear() do? That’s where I’d look next.

(And you still haven’t answered the question of where you put all that code that xenakios asked about.)