Hi Daniel,
It’s hard to know what’s going wrong without seeing the implementation of AudioSourceProcessor, so please excuse me if I’m babbling about something you probably already know:
The revised multibus API was written in a way so that it remains backward compatible to pre 4.1 JUCE projects, i.e. just pretend that the deprecated multibus API was never there
.
In fact when testing the new multibus API, I tried compiling the 4.0 version of the plug-in host with the new latest JUCE on develop (keeping the plug-in host source code at version 4.0) and tested that everything worked. I also did the same thing but reverse: I compiled the 4.0 version of the audio plugin demo with the new JUCE multibus API and checked that it works both in the old host and new host.
Pre 4.1, the default number of input and output channels of an AudioProcessor was always zero (see code here). Also the AudioProcessorGraph would never change the number of channels. This means that when using the AudioProcessorGraph in pre JUCE 4.1 code, you would need to call setPlayConfigDetails on each node with the desired number of channels before adding them. The audio plugin host does this indirectly as most nodes are AudioPluginInstances and the setPlayConfigDetails will be called by the wrapper when the AudioPluginInstance is created.
As was the case with pre 4.1 JUCE, the revised multibus API once again requires you to tell the graph how many channels a Node should have by calling setPlayConfigDetails. However, with the new multibus API, it is also possible to tell the graph how many channels the node should have by calling setBusesLayout instead - or by having the AudioProcessor create a default layout by using the new non-default AudioProcessor constructors.
See some code below that uses the latter technique. You can just copy it into the Main.cpp of a new GUI application - you also need to add the juce_audio_utils module.
Hope this helps!
Fabian
#include "../JuceLibraryCode/JuceHeader.h"
//==============================================================================
class MultiToneAudioProcessor : public AudioProcessor
{
public:
static constexpr int numOutStems = 2;
static constexpr int channelsPerStem = 2;
MultiToneAudioProcessor ()
: AudioProcessor (getBusProperties())
{
for (int i = 0; i < numOutStems; ++i)
tones.add (new ToneGeneratorAudioSource);
}
//==============================================================================
const String getName() const override { return "MultiToneAudioProcessor"; }
double getTailLengthSeconds() const override { return 0.0; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
AudioProcessorEditor* createEditor() override { return nullptr; }
bool hasEditor() const override { return false; }
int getNumPrograms() override { return 0; }
int getCurrentProgram() override { return -1; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return String(); }
void changeProgramName (int, const String&) override {}
void getStateInformation (MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
//==============================================================================
void prepareToPlay (double sampleRate, int maximumExpectedSamplesPerBlock) override
{
jassert (getBusCount (false) == numOutStems);
for (int i = 0; i < numOutStems; ++i)
{
if (ToneGeneratorAudioSource* source = tones[i])
{
source->setAmplitude (0.5f);
source->setFrequency (440.0f + (static_cast<float> (i) * 110.f));
source->prepareToPlay (maximumExpectedSamplesPerBlock, sampleRate);
}
}
}
void releaseResources() override
{
for (int i = 0; i < numOutStems; ++i)
if (ToneGeneratorAudioSource* source = tones[i])
source->releaseResources();
}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
jassert (getBusCount (false) == numOutStems);
for (int busIdx = 0; busIdx < numOutStems; ++busIdx)
{
AudioBuffer<float> busBuffer = getBusBuffer (buffer, false, busIdx);
busBuffer.clear();
AudioSourceChannelInfo bufferToFill (&busBuffer, 0, busBuffer.getNumSamples());
if (ToneGeneratorAudioSource* source = tones[busIdx])
source->getNextAudioBlock (bufferToFill);
}
}
private:
static BusesProperties getBusProperties()
{
BusesProperties retval;
for (int i = 0; i < numOutStems; ++i)
retval.addBus (false, "Output #" + String (i + 1), AudioChannelSet::canonicalChannelSet (channelsPerStem));
return retval;
}
OwnedArray<ToneGeneratorAudioSource> tones;
};
//==============================================================================
class GraphMixerApplication : public JUCEApplication
{
public:
//==============================================================================
GraphMixerApplication()
{
graph.setPlayConfigDetails (0, 2, 44100., 512);
AudioProcessorGraph::Node* outNode =
graph.addNode (new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode));
AudioProcessorGraph::Node* sourceNode =
graph.addNode (new MultiToneAudioProcessor());
for (int busIdx = 0; busIdx < MultiToneAudioProcessor::numOutStems; ++busIdx)
for (int channelIdx = 0; channelIdx < MultiToneAudioProcessor::channelsPerStem; ++channelIdx)
graph.addConnection (sourceNode->nodeId, (busIdx * MultiToneAudioProcessor::channelsPerStem) + channelIdx,
outNode->nodeId, channelIdx);
player.setProcessor (&graph);
dm.initialiseWithDefaultDevices (0, 2);
dm.addAudioCallback (&player);
}
const String getApplicationName() override { return ProjectInfo::projectName; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return true; }
void initialise (const String& commandLine) override { mainWindow = new MainWindow (getApplicationName()); }
void shutdown() override { mainWindow = nullptr; }
void systemRequestedQuit() override { quit(); }
void anotherInstanceStarted (const String& /*commandLine*/) override {}
//==============================================================================
class MainWindow : public DocumentWindow
{
private:
class MainContentComponent : public Component
{
public:
MainContentComponent()
: helloWorld ("helloWorld", "Hello World!")
{
addAndMakeVisible (helloWorld);
helloWorld.setJustificationType (Justification::centred);
setOpaque(true);
setSize (320, 240);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::lightgrey);
}
void resized() override
{
helloWorld.setBounds (getLocalBounds());
}
private:
Label helloWorld;
};
public:
MainWindow (String name) : DocumentWindow (name,
Colours::lightgrey,
DocumentWindow::allButtons)
{
setUsingNativeTitleBar (true);
setContentOwned (new MainContentComponent(), true);
centreWithSize (getWidth(), getHeight());
setVisible (true);
}
void closeButtonPressed() override { JUCEApplication::getInstance()->systemRequestedQuit(); }
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
private:
ScopedPointer<MainWindow> mainWindow;
AudioProcessorGraph graph;
AudioProcessorPlayer player;
AudioDeviceManager dm;
};
//==============================================================================
START_JUCE_APPLICATION (GraphMixerApplication)