Can't write SMPL chunk in a wav file if sample is one shot


#1

Hello,
I want to update some values inside the SMPL chunk metadata in a wav file.
I have a wav file that is a one shot sample. It has the SMPL chunk that it is useful for information like “MidiUnityNote” or “MidiPitchFraction”. The value of “NumSampleLoops” is 0 (and it is correct since it is a one shot sample). But when “NumSampleLoops” key is 0 the SMPL chunk is not written into the wave file.
Infact inside juce_WavAudioFormat.cpp the function SMPLChunk::createFrom() skips completely all the chunk so that the resulting file does not have this chunk anymore.

It seems to me that this behaviour is not correct.
The chunk should be saved anywway with “NumSampleLoops” = 0 (as it is in the original file).

Can it be changed?


#2

Ah good catch. It would be useful to have a couple of small examples of valid WAVs with the SMPL chunk and zero loops.


#3

This are two examples…
I don’t know who wrote these two wav files.
I’ll try to provide some examples written by a known source

Since I can’t attach wav files or zip files to the post please downlod them from here.
https://dl.dropboxusercontent.com/u/805324/OneShotSamples.zip


#4

Hmm the Str3_L3_DW_67.WAV one looks corrupted. The smpl chunk length reads as 60 bytes but there are only 36 bytes left in the file… The other one looks OK.


#5

Ah! forget that, this might help…

        uint32 manufacturer;
        uint32 product;
        uint32 samplePeriod;
        uint32 midiUnityNote;
        uint32 midiPitchFraction;
        uint32 smpteFormat;
        uint32 smpteOffset;
        uint32 numSampleLoops;
        uint32 samplerData;

Is 36 bytes. With one loop it would be…

        uint32 manufacturer;
        uint32 product;
        uint32 samplePeriod;
        uint32 midiUnityNote;
        uint32 midiPitchFraction;
        uint32 smpteFormat;
        uint32 smpteOffset;
        uint32 numSampleLoops;
        uint32 samplerData;
            uint32 identifier;
            uint32 type;
            uint32 start;
            uint32 end;
            uint32 fraction;
            uint32 playCount;

Which is 60 bytes. So some implementations look like they include the space for at least one loop even if it’s just zero’d.


#6

This should do it for SMPLChunk::createFrom():

        static MemoryBlock createFrom (const StringPairArray& values)
        {
            MemoryBlock data;
            const int numLoops = jmin (64, values.getValue ("NumSampleLoops", "0").getIntValue());

            data.setSize (roundUpSize (sizeof (SMPLChunk) + (size_t) (jmax (0, numLoops - 1)) * sizeof (SampleLoop)), true);

            SMPLChunk* const s = static_cast<SMPLChunk*> (data.getData());

            s->manufacturer      = getValue (values, "Manufacturer", "0");
            s->product           = getValue (values, "Product", "0");
            s->samplePeriod      = getValue (values, "SamplePeriod", "0");
            s->midiUnityNote     = getValue (values, "MidiUnityNote", "60");
            s->midiPitchFraction = getValue (values, "MidiPitchFraction", "0");
            s->smpteFormat       = getValue (values, "SmpteFormat", "0");
            s->smpteOffset       = getValue (values, "SmpteOffset", "0");
            s->numSampleLoops    = ByteOrder::swapIfBigEndian ((uint32) numLoops);
            s->samplerData       = getValue (values, "SamplerData", "0");

            if (numLoops > 0)
            {
                for (int i = 0; i < numLoops; ++i)
                {
                    SampleLoop& loop = s->loops[i];
                    loop.identifier = getValue (values, i, "Identifier", "0");
                    loop.type       = getValue (values, i, "Type", "0");
                    loop.start      = getValue (values, i, "Start", "0");
                    loop.end        = getValue (values, i, "End", "0");
                    loop.fraction   = getValue (values, i, "Fraction", "0");
                    loop.playCount  = getValue (values, i, "PlayCount", "0");
                }
            }
            else
            {
                zerostruct (s->loops[0]);
            }

            return data;
        }

#7

Thanks for your support. I did a simple test and it’s working correctly now. I’ll do more tests in the next days.
I wrote to the forum because I hope to have this fix released with the next version of Juce. Do you think to commit this modification?


#8

Thanks, looks like a sensible thing to add. (The zeromem call isn’t needed though, the MemoryBlock clears the block itself when allocating it). I’ll add a change to do this…


#9

Ah yes, didn’t spot the true.