Tutorial: Looping Audio - Exercise suggestions?


#1

Hi,
I’ve been reading up on Tutorial: Looping audio using the AudioSampleBuffer class (advanced), but it’s a lot to wrap my head around. I kind of feel like I’m just accepting things as a blackbox and moving on. Could you suggest some exercises that I could try to help me familiarize myself with it? I’m afraid any exercises I try may be way ambitious and will steer me away before I’ve even learned anything. :slight_smile:


#2

Well it look likes like pretty important stuff, just having a read. Which bits are the most inexplicably challenging? There’s a lot in there…and that ReferenceCountedObject stuff is dirty :wink:


#3

The ReferenceCountedBuffer class and the getNextAudioBlock method seem to be the most confusing for me.
So it seems ReferenceCountedBuffer::Ptr is good for having pointers that can handle data between threads. That’s pretty interesting

   void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
    {
		// currentBuffer gets its info from the openButtonClicked() method
        ReferenceCountedBuffer::Ptr retainedCurrentBuffer (currentBuffer);

		 // If retainedCurrentBuffer is empty, clear the bufferToFill
         // So, ReferenceCountedBuffer is litterally just a referece?
        if (retainedCurrentBuffer == nullptr)
        {
            bufferToFill.clearActiveBufferRegion();
            return;
        }
		//**************************************************************

		//currentAudioSampleBuffer is geting its buffer and position info from the instantiated
		//ReferenceCountedBuffer object, retainedCurrentBuffer? 
        AudioSampleBuffer* currentAudioSampleBuffer (retainedCurrentBuffer->getAudioSampleBuffer());
        int position = retainedCurrentBuffer->position;

		//Not sure why 2 different objects are used for I/O
        const int numInputChannels = currentAudioSampleBuffer->getNumChannels();
        const int numOutputChannels = bufferToFill.buffer->getNumChannels();

        int outputSamplesRemaining = bufferToFill.numSamples;
        int outputSamplesOffset = 0;

        while (outputSamplesRemaining > 0)
        {
            int bufferSamplesRemaining = currentAudioSampleBuffer->getNumSamples() - position;
            int samplesThisTime = jmin (outputSamplesRemaining, bufferSamplesRemaining);

            for (int channel = 0; channel < numOutputChannels; ++channel)
            {
				//The actual point where the buffer gets filled?
                bufferToFill.buffer->copyFrom (channel,
                                               bufferToFill.startSample + outputSamplesOffset,
                                               *currentAudioSampleBuffer,
                                               channel % numInputChannels,
                                               position,
                                               samplesThisTime);
            }

            outputSamplesRemaining -= samplesThisTime;
			//don't outputSamplesOffest and position do the same thing?
            outputSamplesOffset += samplesThisTime;
            position += samplesThisTime;

            if (position == currentAudioSampleBuffer->getNumSamples())
                position = 0;
        }

        retainedCurrentBuffer->position = position;
    }

#4

I think the point is that:

  • Creating (new) and releasing objects (delete) on the audio thread is bad practice. It may involve a call to the operating systems memory manager and may incur some unknown time-delay which could bring the audio to a halt.

  • Reference counting lets you have an object owned by multiple objects.

  • Some fancy shit goes on here checking that the reference count hasn’t reached 1 or 2 and then freeing the object on the correct (i.e. not the audio) thread… However if you’ve not used reference counting before you might want to understand this before you start trying to decode this slightly fancier use of it.

Also - worth noting you have THREE threads here.

    • Main aka message aka UI thread.
    • The “Background Thread” which is managing the loading of files and creating/deleting of these large audio objects.
    • The audio thread which is repeated calling getNextAudioBlock().

This is a good read: http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing

There are lots of ways of solving this problem though. Reference counting is only one solution.


#5

That was an inspiring read. I’m wanting to dig a bit deeper into it all because of it. Just dug up a book I bought back in 2011, “The Audio Programming Book”, but I don’t want to wander too far off-course either. Ideally, I’d like to apply what I’ve read to this tutorial (especially since I’ve been having problems with playback of .wav files. They only seem to work sometimes, and I’m wondering if it’s a threading issue, but I haven’t found a way of figuring out what the problem is).
Thanks for the good read. I’d like to find some more audio playback examples with JUCE, so I can broaden my perspective as much I can.


#6

Haven’t been able to find anything, but I really am suspicious about threading. It must be why my application isn’t responding. “Getting Started with JUCE” doesn’t talk much about threading either.


#7

Hello,

Yes there wasn’t space to open that can of worms in the book!

This tutorial implements something similar to Timur’s example from his 2015 CppCon talk “C++ in the Audio Industry” from about 44:25:

But implements it using JUCE containers rather than the standard library.

BTW The reason for waiting for a count of 2 in checkForBuffersToFree() was to avoid accessing the raw pointer (for example using ReferenceCountedArray::getObjectPointerUnchecked()). The checkForBuffersToFree() function could be rewritten using ReferenceCountedArray::getObjectPointerUnchecked()

void checkForBuffersToFree()
{
    for (int i = buffers.size(); --i >= 0;)                            
    {
        ReferenceCountedBuffer* buffer (buffers.getObjectPointerUnchecked (i)); 
        
        if (buffer->getReferenceCount() == 1)         // check for count of 1                 
            buffers.remove (i);
    }
}

#8

Hi,
I did end up watching that, along with his related ADC talk. Was interesting. I wonder how I’m looking for a tutorial to go about learning to use it. None of the audio programming books I have even mention threading(??).

When you say avoid accessing the raw pointer, you mean, the original one and not the copy?


#9

Yes, so if you access the raw pointer and pass that value around then it loses its reference-counting ability. This is fine if you’re careful and you know exactly what’s going on. But you risk holding on to a pointer that might have been deleted if you store the the raw pointer anywhere.


#10

I see. Very cool.
Is there a way for me to visualize this in…“Visual” Studio? :slight_smile:


#11

Found this playlist. Going to start with this and see if it gives me the courage to explore more.