MATLAB: Why does audiowrite cast int16 different than the cast function

audiowritecastint16MATLAB

Hello all,
If we use the function audiowrite with a double as an input the audio array is cast to int16 and written in the .wav file.
If we use the cast function to convert the audio array to int16 the resulting array is not the same as the array imported using the audioread function.
An example to illustrate this:
>> y = [-1:0.2:1]
y =
-1.0000 -0.8000 -0.6000 -0.4000 -0.2000 0 0.2000 0.4000 0.6000 0.8000 1.0000
>> cast(y * 2^15, 'int16')
ans =
1×11 int16 row vector
-32768 -26214 -19661 -13107 -6554 0 6554 13107 19661 26214 32767
>> audiowrite('y.wav',y,44100)
>> y_test = audioread('y.wav','native')
y_test =
1×11 int16 row vector
-32768 -26215 -19661 -13108 -6554 0 6553 13107 19660 26214 32767
Notice how the second, fourth, seventh and ninth values are different by one.
What is interesting is that if we cast the array before we write it, the problem goes away. e.g. :
>> audiowrite('y_int16.wav',cast(y * 2^15,'int16'),44100)
>> y_int16_test = audioread('y_int16.wav','native')
y_int16_test =
1×11 int16 row vector
-32768 -26214 -19661 -13107 -6554 0 6554 13107 19661 26214 32767
Notice how the values are no longer different.
It would appear that the casting operation internal to the audiowrite function is different than the cast function. What is causing this difference when we audiowrite a double array? Is it a different rounding/truncation operation?
Thank you in advance,
A.

Best Answer

If you follow the code of audiowrite, the floating point array is never converted and is passed as is to asyncioimpl.OutputStream.write. That latter method, you can't see the code so it must be compiled code, not m-file, probably as part of audiofilesndfilewriterplugin.dll. Most likely, it's C code or similar and follow C rules for conversions from float to integer. The C rule truncates floating point values (rounds towards 0).
Your cast on the other hand uses matlab float to integer rules which is rule to nearest. Hence the discrepancy.
You can emulate the C rules if you wish (for audio, matlab rules are probably better) using fix.
audiowrite('y.wav', int16(fix(y * 2^15)), 44100) %has the same conversion rule as
audiowrite('y.wave', y, 44100)
Bottom line, matlab and C (and many other languages) don't follow the same rules when dealing with integer types. There's similar unexpected behaviour (for a C programmer) when dealing with integer division (matlab rounds by default, C truncates).
Note that you don't need to use cast to do the conversion to integer.
yint = int16(y); %simpler than cast(y, 'int16')