Multichannel AudioFormatReader


#1

I need to read multichannel WAVs and AIFFs. It seemed all the buffering and source classes were ready but the second read member in juce_AudioFormatReader.cpp needed to be extended.

I used a similar approach seen here http://www.juce.com/forum/topic/audiosamplebufferreadfromaudioreader-multichannel-support and did it this way:


void AudioFormatReader::read (AudioSampleBuffer* buffer,
                              int startSample,
                              int numSamples,
                              int64 readerStartSample,
                              bool useReaderLeftChan,
                              bool useReaderRightChan)
{
    jassert (buffer != nullptr);
    jassert (startSample >= 0 && startSample + numSamples <= buffer->getNumSamples());
    if (numSamples > 0)
    {
        const int numTargetChannels = buffer->getNumChannels();
        HeapBlock<int*> chans(jmax (3, numTargetChannels + 1));
        if (numTargetChannels < 3)
        {
            if (useReaderLeftChan == useReaderRightChan)
            {
                chans[0] = reinterpret_cast<int*> (buffer->getSampleData (0, startSample));
                chans[1] = (numChannels > 1 && numTargetChannels > 1) ? reinterpret_cast<int*> (buffer->getSampleData (1, startSample)) : nullptr;
            }
            else if (useReaderLeftChan || (numChannels == 1))
            {
                chans[0] = reinterpret_cast<int*> (buffer->getSampleData (0, startSample));
                chans[1] = nullptr;
            }
            else if (useReaderRightChan)
            {
                chans[0] = nullptr;
                chans[1] = reinterpret_cast<int*> (buffer->getSampleData (0, startSample));
            }
            chans[2] = nullptr;
            read (chans, 2, readerStartSample, numSamples, true);
        }
        else
        {
            for (int j = 0; j < numTargetChannels; ++j)
            {
                chans[j] = reinterpret_cast<int*> (buffer->getSampleData (j, startSample)); 
            }
            
            chans[numTargetChannels] = nullptr;
            read (chans, numTargetChannels, readerStartSample, numSamples, true);
        }
        if (! usesFloatingPointData)
        {
            for (int j = 0; j < numTargetChannels; ++j)
            {
                if (float* const d = reinterpret_cast <float*> (chans[j]))
                {
                    const float multiplier = 1.0f / 0x7fffffff;
                    for (int i = 0; i < numSamples; ++i)
                        d[i] = *reinterpret_cast<int*> (d + i) * multiplier;
                }
            }
        }
        if (numTargetChannels == 2 && (chans[0] == nullptr || chans[1] == nullptr))
        {
            // if this is a stereo buffer and the source was mono, dupe the first channel..
            memcpy (buffer->getSampleData (1, startSample),
                    buffer->getSampleData (0, startSample),
                    sizeof (float) * (size_t) numSamples);
        }
    }
}

 

I've attached a diff against yesterday's tip if this is suitable for inclusion.

I'm not sure if the cost of making a HeapBlock on every call is too high. Perhaps an Array of int* as a private member of the class, grown if getNumChannels() changes?

 


#2

Or, in the interests of not penalizing the most common mono and stereo cases, just use temporary stack space, changing:

        HeapBlock<int*> chans(jmax (3, numTargetChannels + 1));

to:

        jassert (numTargetChannels < 64);       
        int* chans[64];

 


#3

I see the update, thanks!

Tidier too, nicely done:)

---------

One thing I hadn't considered, which might be better for general usage, is that you might want to set fillLeftoverChannelsWithCopies false for multichannel buffers.

It makes sense to dupe when filling a stereo buffer from a mono source but if the buffer is multichannel, whether the application is working with surround formats or is using a custom file format with effects or alternate takes in the extra channels, it's probably better to fill with silence.


#4

To be honest that entire method is a bit nasty - I've mostly just left it in there for backwards-compatibility, so don't really want to mess with its behaviour too much. I'd recommend just using the other more direct read methods if you can.


#5

Now I'm confused and possibly missing a fundamental concept.

AudioTransportSource and the related classes eventually call the read() member of the source and because they all deal with AudioSampleBuffers in float format, use the void AudioFormatReader::read ( AudioSampleBuffer *  buffer... signature. I assumed no-one directly calls the bool AudioFormatReader::read ( int *const *  destSamples... method.

Now that I pay attention, I see that method isn't declared static. Is there another whole class hierarchy I should be using for accessing files that uses that method and was already working with multichannel files?