How to apply effects to only one AudioTransportSource in a multi-transport app?

Hi all,

The Tutorials and the Forum has been really helpful as a new JUCE and C++ programmer – thanks heaps.

So I have a standalone audioapp inheriting from AudioAppComponent with two AudioTransportSources – one for vocals and one for music, and a mixer to play them in sync.

std::unique_ptr<AudioFormatReaderSource> mVocalSource;
std::unique_ptr<AudioFormatReaderSource> mMusicSource;
AudioTransportSource mTransportSourceVocals;
AudioTransportSource mTransportSourceMusic;
MixerAudioSource mAudioMixer;
//Load file into transport
    auto* musicFileReader = mFormatManager.createReaderFor(File(filePath));
    if (musicFileReader != nullptr)
    {
        std::unique_ptr<AudioFormatReaderSource> newMusicSource(new AudioFormatReaderSource(musicFileReader, true));
        mTransportSourceMusic.setSource(newMusicSource.get(), 0, nullptr, musicFileReader->sampleRate);
        mAudioMixer.addInputSource(&mTransportSourceMusic, false);
        mMusicSource.reset(newMusicSource.release());
    }

    //Load vocals
    AudioFormatReader* vocalFileReader = nullptr;
        auto* vocalFileReader = mFormatManager.createReaderFor(File("C:/Users/aman_/Music/Beats/Beat01.wav"));
    }
    if (vocalFileReader != nullptr)
    {
        std::unique_ptr<AudioFormatReaderSource> newVocalSource(new AudioFormatReaderSource(vocalFileReader, true));
        mTransportSourceVocals.setSource(newVocalSource.get(), 0, nullptr, vocalFileReader->sampleRate);
        mAudioMixer.addInputSource(&mTransportSourceVocals, false);
        mVocalSource.reset(newVocalSource.release());
    }
    //Rewind both tracks to the start and play
    mTransportSourceVocals.setPosition(0.0);
    mTransportSourceMusic.setPosition(0.0);
    mMusicSource->setLooping(true);

    mTransportSourceVocals.start();
    mTransportSourceMusic.start();

Then in the getNextAudioBlock, I play the mixer:

void SelectTrackComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate)
{
    mSamplesPerBlockExpected = samplesPerBlockExpected;
    mSampleRate = sampleRate;

    // This function will be called when the audio device is started, or when its settings (i.e. sample rate, block size, etc) are changed.
    //mAudioMixer.prepareToPlay(samplesPerBlockExpected, int(mSampleRate / mSampleRateXRatio
    mAudioMixer.prepareToPlay(samplesPerBlockExpected, int(mSampleRate / mSampleRateXRatio));
}

void SelectTrackComponent::getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill)
{
    // Your audio-processing code goes here!
    mAudioMixer.getNextAudioBlock(bufferToFill);
    mAudioVisualiser.pushBuffer(bufferToFill);
}

This all works. Except I now want to add effects to ONLY ONE AudioTransportSource / track / audiosource (mTransportSourceVocals). All the tutorials and forum entries I’ve seen only seem to cover manipulating the entire buffer (see https://docs.juce.com/master/tutorial_processing_audio_input.html).

How do I apply effects / do signal processing for only 1 AudioTransportSource?

Hey man,

Got your ping in the other thread :wink: It’s been a while since I’ve tinkered with JUCE now, but if I understand your problem correctly, the first half, presented by Fabian Renn-Giles solves your issue: https://www.youtube.com/watch?v=dl-2hhNUvZQ

Hi Arif,

Thanks for your response. The link you sent certainly pointed me in the right direction. It got me to look more closely at the AudioMixerSource Class.

I have A solution but am not confident its THE solution.

I took the idea of having a temp buffer from the AudioMixerSource class and have solved the problem.

First, I declared an AudioBuffer tempBuffer; in my header file;

AudioBuffer<float> tempBuffer;

Then, I replaced the above getNextAudioBlock() code with the below:

void SelectTrackComponent::getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill)
{
    //mAudioMixer.getNextAudioBlock(bufferToFill);
    
    //Setup the temporary buffer with same number of channels and num of samples as the main buffer (bufferToFill)
    tempBuffer.setSize(jmax(1, bufferToFill.buffer->getNumChannels()), bufferToFill.buffer->getNumSamples());
    AudioSourceChannelInfo tempInfo(&tempBuffer, 0, bufferToFill.numSamples);

    //Populate that buffer with audio from AudioSource 1  
    mTransportSourceVocals.getNextAudioBlock(tempInfo);

    //Add the temp buffer to the main buffer using buffer->addFrom()
    for (int chan = 0; chan < bufferToFill.buffer->getNumChannels(); ++chan)
    {
        bufferToFill.buffer->addFrom(chan, bufferToFill.startSample, tempBuffer, chan, 0, bufferToFill.numSamples);
    }

    //At this point, the main buffer only has the samples from the first AudioTransportSource (mTransportSourceVocals)
    //Apply your effects / DSP - I've just tested this with a simple volume change of the vocals
    for (auto channel = 0; channel < bufferToFill.buffer->getNumChannels(); ++channel)
    {
       auto* inBuffer = bufferToFill.buffer->getReadPointer(channel, bufferToFill.startSample);
       auto* outBuffer = bufferToFill.buffer->getWritePointer(channel, bufferToFill.startSample);

       for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
       {
           outBuffer[sample] = inBuffer[sample] * mVocalVolSlider.getValue();
       }
    }

    //Repeat the process for each additional audio input source but skip the adding effects
    mTransportSourceMusic.getNextAudioBlock(tempInfo);
    for (int chan = 0; chan < bufferToFill.buffer->getNumChannels(); ++chan)
    {
        bufferToFill.buffer->addFrom(chan, bufferToFill.startSample, tempBuffer, chan, 0, bufferToFill.numSamples);
    }

    mAudioVisualiser.pushBuffer(bufferToFill);
}

Good news is that it works. Bad news is that now am bypassing built the MixerAudioSource. Ideas for improvements:

  1. Create a custom MixerAudioSource class object that inherits from MixerAudioSource, and override its getNextAudioBlock() function
  2. Create a custom AudioTransportSource class object that inherits from AudioTransportSource and override its getNextAudioBlock() function

Anyone have any thoughts on improvements? Please do share.

The problem with AudioTransportSource is, it is designed to allow asunchronous play() and stop() commands from the UI thread. That means they arrive “as soon as possible”, but not at a well defined point in time. And they can only occur at full buffer limits (only a problem for instruments, not in your case).

Even when calling start() on both sources right after another, there is a chance that they start a buffer (up to 20ms) apart.

You should never have more than one AudioTransportSource, and only for user interaction, when the user should be able to start or pause the whole audio pipeline all together.

What you can do is simply move the AudioTransportSource behind the MixerAudioSource, so it will play and pause all mixed sources in sync.

The drawback is, that you have to implement your looping part yourself, but that is actually an advantage, because that way you can make sure, it loops sample accurate at the same point and both loops have the same length or stay in sync, depending on how you design it.

Hope that helps

Hi Daniel, that is really good feedback and I will refactor my code to either not use AudioTransportSource or put it behind the mixer :slight_smile: