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


#1

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

Thanks
JS


#2

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…


#3

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.


#4

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…


#5

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

Stefan


#6

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.


#7

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


#8

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


#9

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


#10

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…


#11

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?