Extracting loop start/end cues from .wav and .aiff files

Is this in AudioFormatReader::metadataValues? Anyone have a quick how too?

Thanks
JS

No, the metadata for a wav comes from its bwav chunk, and I don’t think that contains loop points… There must be a way of getting those values, but it’d need to be added to the code…

that’s tricky loop points can be in two different chunks as far as i remeber CUE and SMPL chunks http://www.sonicspot.com/guide/wavefiles.html#smpl on windows you get MME api to get chunks on other platforms i don’t know.

I started to play with getting loop points in the AiffAudioFormatReader:

http://164.11.131.73/svn/CEMS/mt/other/misc/juce_AiffAudioFormat.cpp

It’s a mess, but I haven’t had a real need to work on it. NB this was a while ago so the I haven’t checked it against the current juce_AiffAudioFormat.cpp file…

I modified WavAudioFormatReader to parse the smpl chunk and add those information to the metadataValues.
If anyone interested I can send the code.

Stefan

I’d be interested, - I could revisit the Aiff code to do similarly. Actually I remember Jules saying he’d be interested in including extensions like this.

I seem to remember looking at that code some time ago and I couldn’t see a neat way of either
[list]
[]doing this stuff without modifying the Juce source; or[/]
[]copying a lot of the juce classes (the format classes etc)[/][/list]

…probably one of the main reasons I didn’t pursue it further. Can’t remember the details though.

Of course, modifying Juce itself is evil, but sometimes I admit I just cannot resist. So here the code snippets, maybe we can convince Jules to add these too:

This vomes right after declaration of BWAVChunk:

#if 1 // Stenz

typedef struct {
uint32 identifier;
uint32 type;
uint32 start;
uint32 end;
uint32 fraction;
uint32 playCount;
} SampleLoop;

struct SMPLChunk
{
uint32 manufacturer; // 0x0100003E for Waldorf
uint32 product; // 0x00000013 for Blofeld
uint32 samplePeriod;
uint32 midiUnityNote; // should use this
uint32 midiPitchFraction; // should use that too
uint32 smpteFormat;
uint32 smpteOffset;
uint32 nSampleLoops; // looped or not
uint32 samplerData;
// struct SampleLoop Loops[];
SampleLoop loop[1];

void copyTo (StringPairArray& values) const
{
    uint32 l;

    l =  swapIfBigEndian(manufacturer);
    values.set (T("Manufacturer"),String(l));
    
    l =  swapIfBigEndian(product);
    values.set (T("Product"),String(l));
    
    l =  swapIfBigEndian(samplePeriod);
    values.set (T("SamplePeriod"),String(l));
    
    l =  swapIfBigEndian(midiUnityNote);
    values.set (T("MidiUnityNote"),String(l));
    
    l =  swapIfBigEndian(midiPitchFraction);
    values.set (T("MidiPitchFraction"),String(l));
    
    l =  swapIfBigEndian(smpteFormat);
    values.set (T("SmpteFormat"),String(l));

     l =  swapIfBigEndian(smpteOffset);
    values.set (T("SmpteOffset"),String(l));
     
     l =  swapIfBigEndian(nSampleLoops);
    values.set (T("NSampleLoops"),String(l));

     l =  swapIfBigEndian(samplerData);
    values.set (T("SamplerData"),String(l));

     for(uint32 i = 0; i<nSampleLoops; i++)
     {
        String lb; 
        
        l =  swapIfBigEndian(loop[i].identifier);
        lb.printf(T("Loop%dIdentifier"),i);
        values.set (lb,String(l));

        l =  swapIfBigEndian(loop[i].type);
        lb.printf(T("Loop%dType"),i);
        values.set (lb,String(l));

        l =  swapIfBigEndian(loop[i].start);
        lb.printf(T("Loop%dStart"),i);
        values.set (lb,String(l));

        l =  swapIfBigEndian(loop[i].end);
        lb.printf(T("Loop%dEnd"),i);
        values.set (lb,String(l));

        l =  swapIfBigEndian(loop[i].fraction);
        lb.printf(T("Loop%dFraction"),i);
        values.set (lb,String(l));

        l =  swapIfBigEndian(loop[i].playCount);
        lb.printf(T("Loop%dIplayCount"),i);
        values.set (lb,String(l));
     }
}

/*
static MemoryBlock createFrom (const StringPairArray& values)
{}
*/

} PACKED;

#endif

And this here in the chunk parsing code:

#if 1
else if (chunkType == chunkName (“smpl”)) // Stenz: sampler chunk
{
SMPLChunk* const smpl = (SMPLChunk*) juce_calloc (length);

                    if (smpl != 0)
                    {
                        input->read (smpl,length);
                        smpl->copyTo (metadataValues);
                        juce_free (smpl);
                    }
                }

#endif

Nice one, thanks for posting that, I’m sure I can add something along those lines for you!

Jules, if you add this you might consider changing the condition here as follows, because the smpl chunk might come after the data chunk:

        // bug: this prevents chunks coming after data to be read    
        // else if ((hasGotType && hasGotData) || chunkEnd <= input->getPosition())
        else if (chunkEnd <= input->getPosition())      // better
        {
           break;
        }

Note that I also learned how to post code properly.

Stefan

Yes, that makes sense… but I wonder why I put that condition in there - I’ve a nagging doubt that there might have been a good reason…

So the SMPL chunk is there in WavAudioFormat, but to actually use the information I’d have to write my own AudioFormatReaderSource (that would loop e.g. a certain section of a wav file with two embedded loop points)? Or am I overlooking some already present functionality?