0
$\begingroup$

While developing an application, I noticed strange behaviour while using scipy.io.wavfile to write audio files. The documentation says that floating-point data is written within a range between -1 and 1. However, files written this way seemingly can go past that. This is a MWE to illustrate what I mean:

import numpy as np
from scipy.io import wavfile

# %% Writing supposedly saturated file

A = 2
f = 5
fs = 44100
t = np.linspace(0, 1, fs + 1)

testSignal = A * np.sin(2 * np.pi * f * t)

wavfile.write('test.wav', fs, testSignal.astype(float))


# %% Reading file with intact values 

_, writtenData = wavfile.read('test.wav')

import matplotlib.pyplot as plt

plt.rcParams["figure.figsize"] = [7.50, 3.50]
plt.rcParams["figure.autolayout"] = True

plt.plot(t, writtenData)
plt.ylabel("Signal")
plt.xlabel("Time")
plt.show()

Signal imported from written file

As you can see, even though I explicitly specified that the array past was to be in the floating-point format (and beyond -1 to 1), I was able to retrieve it without alterations.

After all, what gives? Are .wav files in float format really only capable of storing data between (-1,1)? Is there any hidden conversion going on that I might be missing out on?

TIA

$\endgroup$

2 Answers 2

1
$\begingroup$

Check out the WAV file format specifications.

One option is for the file to store in WAVE_FORMAT_IEEE_FLOAT format. I suspect this is what the Python implementation is selecting, and why it works for other values like A = 200.

$\endgroup$
9
  • $\begingroup$ I'm not sure if I understand what that means. I tried to look up this standard/format but I didn't find much about it that was informative. Does it use a different range of numbers? A different number of bits? Is it effectively extending the possibilities of values you can export to in .wav? $\endgroup$ Jun 30, 2022 at 20:43
  • $\begingroup$ @JuhaP Not sure why the comment on the code says that 32bit floats have a maximum value of +1 and a minimum of -1. The mantissa component of the samples might be limited to that range, but there’s the exponent part that allows larger and smaller sizes. $\endgroup$
    – Peter K.
    Jul 1, 2022 at 11:06
  • $\begingroup$ Maybe np.iinfo(np.float32).max and np.iinfo(np.float32).min results the range for amplitude? $\endgroup$
    – Juha P
    Jul 1, 2022 at 12:14
  • $\begingroup$ @JuhaP Possibly, but I get an error when I try that: np.iinfo(np.float32) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Users/kootsoop/opt/anaconda3/lib/python3.8/site-packages/numpy/core/getlimits.py", line 656, in __init__ raise ValueError("Invalid integer data type %r." % (self.kind,)) ValueError: Invalid integer data type 'f'.. As it looks like iinfo is about integer types. $\endgroup$
    – Peter K.
    Jul 1, 2022 at 12:46
  • 1
    $\begingroup$ Aaa... , finfo() for float datatype ... float32 results [ -3.4028235e+38, 3.4028235e+38] so maybe not relation for the issue (float which OP used results [1.7976931348623157e+308, -1.7976931348623157e+308]. IIUC the python code, there's no functionality in write() function which would reduce the amplitude range to [-1,1]. $\endgroup$
    – Juha P
    Jul 1, 2022 at 13:24
0
$\begingroup$

Usually the wave writer / parsers are not expected to perform any processing on the PCM data. In this case expectation was to normalize the data to [-1.0, 1.0] (or) clip the signal once it goes beyond -1.0 to 1.0.

Popular tools like audacity also accept such wavefiles with float PCM values beyond [-1.0, 1.0]. However, by definition the scale for float PCM is [-1.0, 1.0]. For the same reason, the signal "appears" as clipped form of the original when visualized using audacity(popular audio processing/editing tool). Please find below screen shot of "test.wav", generated using code snippet shared by You, when visualized using audacity

enter image description here

From the screen shot, the signal appears to be clipped giving an impression that audacity performed clipping to the signal. In actual its not the case and it is the scale ([-1.0, 1.0]) that is the cause for this assumption.

Applying a gain of -6dB (since You chose amplitude to 2.0) gives the full sine signal as shown in the picture below.

enter image description here

As a conclusion, it looks like its not the responsibility of the wave creator (or) parser(in this case audacity parser) to actually carry out any pre(at the writer end) or post(at the reader end) processing on the data that is asked of them to write / read. It should be carried out at the application level calling these interfaces of wave reading or writing.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.