External Audio Input Output

Hi, I created for my needings a plugin that is able to get audio from another AudioDevice and stream it inside the Daw or that send incoming audio to another AudioDevice.

Those two looks like this:

My Daw crashes if I has more than 2 plugin with same Audio Device streams audio in input (for example in my graph that is streaming audio with AudioDevice “A” all works if I has only 1 instance of “External Audio Inputs” connected to AudioDevice “B”, another connected to device “B” causes crash).

According to you what should be the problem ?
Also sometimes App causes glitches when I interact with another instance plugin or under 128 samples, Need to use some stuffs as AbstractFifo? there’s need to add a queue to buffer to avoid glitches (and in this case how can I do? and what should be the minor BlockSize to add to avoid to many latency?)?

I’m not expert in those stuffs so any help is really appreciated :pray:

Here the code:

.h

/*
class ExternalAudioIO;

/*
  ===================================================
    MARK: ======== WrappedAudioProcessor ========
  ==================================================
*/

const int SAFETY_BLOCK_SIZE = 0;


class WrappedAudioProcessor    :  public AudioProcessor
{
public:
        
    WrappedAudioProcessor(ExternalAudioIO* o, const BusesProperties& ioLayouts) :   AudioProcessor(ioLayouts),
                                                                                    owner(o) {}
    ~WrappedAudioProcessor() override {}

    void prepareToPlay (double sampleRate, int samplesPerBlock) override { numProcessBlock = 0; }
    void reset() override               {}
    void releaseResources() override    {}
    void processBlock (AudioBuffer<float>& audioBuff, MidiBuffer&) override;

    const String getName() const override                                           { return "WrappedAudioProcessor"; }
    double getTailLengthSeconds() const override                                    { return 0.0; }
    bool acceptsMidi() const override                                               { return false; }
    bool producesMidi() const override                                              { return false; }
    bool isMidiEffect() const override                                              { return false; }
    bool hasEditor() const override                                                 { return false; }
    AudioProcessorEditor* createEditor() override                                   { return nullptr; }
    int getNumPrograms() override                                                   { return 1; }
    int getCurrentProgram() override                                                { return 0; }
    void setCurrentProgram (int) override                                           {}
    const String getProgramName (int) override                                      { return {}; }
    void changeProgramName (int, const String&) override                            {}
    void getStateInformation (MemoryBlock&) override                                {}
    void setStateInformation (const void*, int) override                            {}
    
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override { return true; }
    
    bool canAddBus    (bool isInput) const override { return true; }
    bool canRemoveBus (bool isInput) const override { return true; }
    
    int numProcessBlock = 0;
    
private:

    ExternalAudioIO* owner;
        
    juce::CriticalSection buffersLock;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WrappedAudioProcessor)
};


/*
  ===================================================
        MARK: ======== ExternalAudioIOs ========
  ==================================================
*/


class ExternalAudioIO    :  public InternalAudioProcessor, public ChangeListener
{
public:
    
    enum IODeviceType { input, output };
    
    ExternalAudioIO(IODeviceType t);
    ~ExternalAudioIO() override;
    
    void prepareToPlay (double, int) override;
    void reset() override;
    void releaseResources() override;
    void processBuffer (AudioBuffer<float>& audioBuff, MidiBuffer&) override;
    
    static String getIdentifier(IODeviceType deviceType, bool name = false);
    const String getName() const override                                           { return getIdentifier(deviceType, true); }
    double getTailLengthSeconds() const override                                    { return 0.0; }
    bool acceptsMidi() const override                                               { return false; }
    bool producesMidi() const override                                              { return false; }
    bool isMidiEffect() const override                                              { return false; }
    AudioProcessorEditor* createEditor() override;
    bool hasEditor() const override                                                 { return true; }
    int getNumPrograms() override                                                   { return 1; }
    int getCurrentProgram() override                                                { return 0; }
    void setCurrentProgram (int) override                                           {}
    const String getProgramName (int) override                                      { return {}; }
    void changeProgramName (int, const String&) override                            {}
    void getStateInformation (MemoryBlock&) override;
    void setStateInformation (const void*, int) override;
    
    IODeviceType getType() const { return deviceType; }
    
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override { return true; }
    void updateBusLayout();
    void updatePair();
    
    bool canAddBus    (bool isInput) const override { return true; }
    bool canRemoveBus (bool isInput) const override { return true; }
    
    //==============================================================================
        
    void parameterUpdated(const String& parameterID, float newValue) override {}
    
    //==============================================================================
    
    AudioDeviceManager deviceManager {};
    AudioProcessorPlayer processorPlayer {};
    
    //==============================================================================
    
    bool initialized = false;
    
private:
    
    friend class ExternalAudioIOEditor;
    friend class WrappedAudioProcessor;
    
    IODeviceType deviceType;
    
    bool pair = true; //Not yet Implemented
    
    WrappedAudioProcessor wrappedAudioProcessor;
    std::unique_ptr<juce::XmlElement> savedState;
    
    juce::CriticalSection buffersLock;
    
    //==============================================================================
    
    AudioBuffer<float> audioBuffer;
    
    void changeListenerCallback (ChangeBroadcaster*) override;
    
    //==============================================================================
    
    void deinitAudioDevice();
        
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExternalAudioIO)
};

//MARK: ExternalAudioIOEditor ==============================================================================

class ExternalAudioIOEditor  :  public InternalAudioProcessorEditor
{
public:
    ExternalAudioIOEditor (ExternalAudioIO& processor);
    ~ExternalAudioIOEditor() override;

    void resizedEditor() override;
    void updateEditorCallback(InternalAudioProcessor* processor) override;
    
private:
    
    ExternalAudioIO& audioIOProcessor;
    
    std::unique_ptr<ExternalAudioDeviceSelectorComponent> audioSelectorComponent;
    TextButton pairBtn { "Mono" }; //Not yet Implemented
    
    void update();
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExternalAudioIOEditor)
};

.cpp

//MARK: WrappedAudioProcessor ==============================================================================

void WrappedAudioProcessor::processBlock (AudioBuffer<float>& audioBuff, MidiBuffer&)
{
    juce::ScopedNoDenormals noDenormals;

    auto totalNumInputChannels = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    int numChannels = audioBuff.getNumChannels();
    auto numSamples = audioBuff.getNumSamples();
    
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        audioBuff.clear(i, 0, numSamples);
    
    if (!owner) { return; }
    if (!owner->initialized) { return; }
    
    const ScopedLock lock(buffersLock);
    
    bool isInput = (owner->getType() == ExternalAudioIO::IODeviceType::input);
    
    for (int chan = 0; chan < numChannels; ++chan)
    {

        auto* src  = isInput ? audioBuff.getReadPointer(chan) : owner->audioBuffer.getReadPointer(chan);
        auto* dest = isInput ? owner->audioBuffer.getWritePointer(chan) : audioBuff.getWritePointer(chan);

        for (int i = 0; i < numSamples; ++i)
            dest[i] = src[i];
    }
}

//MARK: ExternalAudioIO ==============================================================================

ExternalAudioIO::ExternalAudioIO(IODeviceType t) :  InternalAudioProcessor ((t == IODeviceType::output) ?
                                                                            BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true) :
                                                                            BusesProperties().withOutput("Output", juce::AudioChannelSet::stereo(), true)),
                                                    deviceType(t),
                                                    wrappedAudioProcessor(this, (BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true)
                                                                                                  .withOutput("Output", juce::AudioChannelSet::stereo(), true)))
{
}

ExternalAudioIO::~ExternalAudioIO()
{
    initialized = false;
    deinitAudioDevice();
}

void ExternalAudioIO::deinitAudioDevice()
{
    deviceManager.closeAudioDevice();
    deviceManager.removeAudioCallback (&processorPlayer);
    processorPlayer.setProcessor (nullptr);
}

String ExternalAudioIO::getIdentifier(IODeviceType deviceType, bool name)
{
    String separator = name ? " " : "_";
    if (deviceType == ExternalAudioIO::IODeviceType::input)
    {
        return "EXTERNAL" + separator + "AUDIO" + separator + "INPUTS";
    } else {
        
        return "EXTERNAL" + separator + "AUDIO" + separator + "OUTPUTS";
    }
}

void ExternalAudioIO::updateBusLayout()
{
}

void ExternalAudioIO::updatePair()
{
    pair = !pair;
    updateEditorAsync();
}

//=======================================================================================

void ExternalAudioIO::prepareToPlay(double sampleRate, int samplesPerBlock)
{
    const ScopedLock lock(buffersLock);
    
    bool isInput = deviceType == IODeviceType::input;
    AudioDeviceManager::AudioDeviceSetup audioDeviceSetup {};
    audioDeviceSetup.bufferSize = samplesPerBlock;
    audioDeviceSetup.sampleRate = sampleRate;
    
    if (isInput)
        audioDeviceSetup.outputDeviceName = String();
    else
        audioDeviceSetup.inputDeviceName  = String();

    deviceManager.initialise(isInput ? 0 : getTotalNumInputChannels(),
                             isInput ? 0 : getTotalNumOutputChannels(),

                             savedState.get() ? savedState.get() : nullptr,
                             false, juce::String(),
                             &audioDeviceSetup);
    
    deviceManager.addAudioCallback (&processorPlayer);
    deviceManager.addChangeListener (this);
    processorPlayer.setProcessor(&wrappedAudioProcessor);
    
    const int numChannels = isInput ? getTotalNumOutputChannels() : getTotalNumInputChannels();
    
    audioBuffer.setSize(numChannels, samplesPerBlock, false, true, false);
    
    wrappedAudioProcessor.prepareToPlay(sampleRate, samplesPerBlock);
    
    initialized = true;
}

void ExternalAudioIO::processBuffer (AudioBuffer<float>& audioBuff, MidiBuffer& midiBuff)
{
    midiBuff.clear();
    
    juce::ScopedNoDenormals noDenormals;

    auto totalNumInputChannels = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
    auto numSamples = audioBuff.getNumSamples();
    int numChannels = audioBuff.getNumChannels();
    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        audioBuff.clear(i, 0, numSamples);
    
    bool isInput = (deviceType == IODeviceType::input);

    const ScopedLock lock(buffersLock);

    for (int chan = 0; chan < numChannels; ++chan)
    {
        auto* src  = isInput ? audioBuffer.getReadPointer(chan) : audioBuff.getReadPointer(chan);
        auto* dest = isInput ? audioBuff.getWritePointer(chan) : audioBuffer.getWritePointer(chan);
        
        for (int i = 0; i < numSamples; ++i)
            dest[i] = src[i];
    }
}

void ExternalAudioIO::releaseResources()
{
    const ScopedLock lock(buffersLock);
    
    wrappedAudioProcessor.releaseResources();
    
    audioBuffer.clear();
    
    initialized = false;
    
    deinitAudioDevice();
}

void ExternalAudioIO::reset()
{
    const ScopedLock lock(buffersLock);
    
    wrappedAudioProcessor.reset();
    
    initialized = false;
}



void ExternalAudioIO::changeListenerCallback (ChangeBroadcaster* source)
{
    if (source == &deviceManager)
    {
        updateBusLayout();
    }
}

//MARK: AudioProcessorEditor =======================================================

AudioProcessorEditor* ExternalAudioIO::createEditor()
{
    InternalAudioProcessorEditor* editor = new ExternalAudioIOEditor(*this);
    updateActiveEditor(editor);
    return editor;
}

void ExternalAudioIO::getStateInformation (juce::MemoryBlock& destData)
{
}

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

//MARK: ExternalAudioIOEditor ==============================================================================

ExternalAudioIOEditor::ExternalAudioIOEditor (ExternalAudioIO& processor) :  InternalAudioProcessorEditor(processor), audioIOProcessor(processor)
{
    addAndMakeVisible(pairBtn);
    pairBtn.onClick = [this]() { audioIOProcessor.updateBusLayout(); };
    
    setSize(400, 200);
    
    update();
}

ExternalAudioIOEditor::~ExternalAudioIOEditor()
{
}

void ExternalAudioIOEditor::resizedEditor()
{
    Rectangle<int> localBounds = getPanelArea();
    Rectangle<int> bottomArea  = localBounds.removeFromBottom(24).reduced(24, 0);

    pairBtn.setBounds(bottomArea.removeFromRight(100));
    localBounds.removeFromBottom(12);
    
    if (!audioSelectorComponent.get()) { return; }
    audioSelectorComponent->setBounds(localBounds);
}

void ExternalAudioIOEditor::update()
{
    if (audioIOProcessor.pair != pairBtn.getToggleState())
    {
        pairBtn.setToggleState(audioIOProcessor.pair, NotificationType::dontSendNotification);
        pairBtn.setButtonText((audioIOProcessor.pair) ? "Stereo" : "Mono");
        
        removeChildComponent(audioSelectorComponent.get());
        audioSelectorComponent.reset(new ExternalAudioDeviceSelectorComponent(audioIOProcessor.deviceManager,
                                                                              0, (audioIOProcessor.deviceType == ExternalAudioIO::IODeviceType::input)  ? 256 : 0,
                                                                              0, (audioIOProcessor.deviceType == ExternalAudioIO::IODeviceType::output) ? 256 : 0,
                                                                              audioIOProcessor.pair));
        
        addAndMakeVisible(audioSelectorComponent.get());
        resized();
    }
}

void ExternalAudioIOEditor::updateEditorCallback(InternalAudioProcessor* processor)
{
    if (processor == &audioIOProcessor)
        update();
}