AudioSampleBuffer::writeToAudioWriter clipping


#1

Hi Jules,

I just encountered that AudioSampleBuffer::writeToAudioWriter() clips values above 1 to -1. I used the following code:

AudioSampleBuffer* buffer = createBuffer();
File f("filename.wav");
f.deleteFile();
WavAudioFormat wavFormat;
ScopedPointer<AudioFormatWriter> writer(wavFormat.createWriterFor(f.createOutputStream(), sampleRate, 1, 24, StringPairArray(), 0));
buffer->writeToAudioWriter(writer, 0, buffer->getNumSamples());

createBuffer() creates a filled AudioSampleBuffer and returns a pointer.
Juce is the latest tip, Windows, Visual Studio 2008 EE.

Chris


#2

hmm. It looks like the float->int conversion might be wrapping around rather than clipping properly - thanks, I’ll take a look at that…


#3

I still can reproduce this problem with today’s git version. If I write a buffer alternating between -1.0f and 1.0f to a 16-bit wav file, all samples end up as -32778.

This still occurs in PPMulator, and here’s the code I used to double-check it:

{
            AudioSampleBuffer alternatingBuffer(1, 100);
            for (int i=0; i<alternatingBuffer.getNumSamples(); i++)
            {
                float* sample = alternatingBuffer.getSampleData(0, i);
                *sample = (i%2) ? 1.0f : -1.0f;
            }

            File appFolder = File::getSpecialLocation(File::currentApplicationFile).getParentDirectory();
            File wavFile = appFolder.getChildFile("test.wav");
            wavFile.deleteFile();

            FileOutputStream* fos = new FileOutputStream(wavFile);
            WavAudioFormat wavFormat;
            ScopedPointer<AudioFormatWriter> afw 
                    (wavFormat.createWriterFor(fos, 48000.0, alternatingBuffer.getNumChannels(), 16, StringPairArray(), 0));

            afw->writeFromAudioSampleBuffer(alternatingBuffer, 0, alternatingBuffer.getNumSamples());
}

We had a “great time” today with a similar issue when checking the reference files created with ppmulator. At some levels (for example -20dBFS / 0.1) the peaks in the wav file are not symmetric (min=-3277 and max=3276). ToneGeneratorAudioSource doesn’t look responsible for that, so it’s probably a similar rounding/conversion issue in the juce_AudioDataConverter.h. This may sound nitpicky, but for sine reference signals it’s really odd to have an dc offset.

It would be great if you could have another look at both the clipping and rounding issue. We tried to debug this on our own, but couldn’t follow all the casting and rounding involved there. What’s happeding in AudioData::Float32::getAsInt32LE() seemed odd to us (as data seems to hold a valid int32 value, so why interpret is as float?), but probably we just didn’t understand the context right.


#4

Thanks for the sample code, that was really helpful. What was happening was that the 32-bit floats used in conversion were overflowing, so I’ve changed it to use doubles and all seems good now - it’s checked-in if you want to have a go.


#5

Thanks, the wrap-around issue is indeed fixed now. But did you check if rounding works as intended?

If I write a signal with a second channel being a exact inversion of the first, I’d expect the data in the wav file to be exatcly inverted as well.

AudioSampleBuffer alternatingBuffer(2, 100);
for (int i=0; i<alternatingBuffer.getNumSamples(); i++)
{
	float* leftSample = alternatingBuffer.getSampleData(0, i);
	*leftSample = (i+1.f) / alternatingBuffer.getNumSamples();

	float* rightSample = alternatingBuffer.getSampleData(1, i);
	*rightSample = - *leftSample;
}

What I find in the 16-bit wav file are these sample values, where most samples (except 0.25, 0.5, 0.75, 1.0) are rounded differently on the inverted channel.

    327   -328
    655   -656
    983   -984
   1310  -1311
   1638  -1639
   1966  -1967
   2293  -2294
   2621  -2622
   2949  -2950
   3276  -3277
   3604  -3605
   3932  -3933
   4259  -4260
   4587  -4588
   4915  -4916
   5242  -5243
   5570  -5571
   5898  -5899
   6225  -6226
   6553  -6554
   6881  -6882
   7208  -7209
   7536  -7537
   7864  -7865
   8192  -8192
   8519  -8520
   8847  -8848
   9175  -9176
   9502  -9503
   9830  -9831
  10158 -10159
  10485 -10486
  10813 -10814
  11141 -11142
  11468 -11469
  11796 -11797
  12124 -12125
  12451 -12452
  12779 -12780
  13107 -13108
  13434 -13435
  13762 -13763
  14090 -14091
  14417 -14418
  14745 -14746
  15073 -15074
  15400 -15401
  15728 -15729
  16056 -16057
  16384 -16384
  16711 -16712
  17039 -17040
  17367 -17368
  17694 -17695
  18022 -18023
  18350 -18351
  18677 -18678
  19005 -19006
  19333 -19334
  19660 -19661
  19988 -19989
  20316 -20317
  20643 -20644
  20971 -20972
  21299 -21300
  21626 -21627
  21954 -21955
  22282 -22283
  22609 -22610
  22937 -22938
  23265 -23266
  23592 -23593
  23920 -23921
  24248 -24249
  24575 -24576
  24903 -24904
  25231 -25232
  25559 -25560
  25886 -25887
  26214 -26215
  26542 -26543
  26869 -26870
  27197 -27198
  27525 -27526
  27852 -27853
  28180 -28181
  28508 -28509
  28835 -28836
  29163 -29164
  29491 -29492
  29818 -29819
  30146 -30147
  30474 -30475
  30801 -30802
  31129 -31130
  31457 -31458
  31784 -31785
  32112 -32113
  32440 -32441
  32767 -32767 

I guess this is cause by using one of the optimized round methods, but couldn’t find the right spot to change this.


#6

Just looks like a difference between the direction of rounding of positive and negative integers… Luckily I don’t think any human has good enough hearing to be able to detect the difference.


#7

yes, this is definitely far from being audible, but still an issue for metering fanatics, or in cases where its important that an original and inverted wav-file you create actually cancel each other out. (we use this from time to time, as a quick way to test the plugin-output for dropouts or other minor glitches ).


#8

ok… but to change it would mean using a different rounding method. It currently uses roundToInt(), but you could try just doing a simple cast-to-int instead. Probably not as fast as using roundToInt though.


#9

If the performance impact is limited to writing wav files, or other cases where you explicitly use these converters then I’d definitely vote for the exact rounding - If it’s a small change for you, and the performance decrease is negligible compared to the time used to actually write the data to disk.


#10

I’m not actually even certain that the rounding would be any different - if you’ve already got a way to test it, you could try replacing the roundToInt() calls in the data converter file, and see if it makes a difference?