Skip to content

Commit 4022661

Browse files
committed
Add support for floating point wave files.
This adds support for floating point wav files and fix #60729.
1 parent 28b3a8a commit 4022661

File tree

5 files changed

+73
-9
lines changed

5 files changed

+73
-9
lines changed
25.9 KB
Binary file not shown.

Lib/test/audiotests.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ def tearDown(self):
2727
unlink(TESTFN)
2828

2929
def check_params(self, f, nchannels, sampwidth, framerate, nframes,
30-
comptype, compname):
30+
comptype, compname, encoding):
3131
self.assertEqual(f.getnchannels(), nchannels)
3232
self.assertEqual(f.getsampwidth(), sampwidth)
3333
self.assertEqual(f.getframerate(), framerate)
3434
self.assertEqual(f.getnframes(), nframes)
3535
self.assertEqual(f.getcomptype(), comptype)
3636
self.assertEqual(f.getcompname(), compname)
37+
self.assertEqual(f.getencoding(), encoding)
3738

3839
params = f.getparams()
3940
self.assertEqual(params,
@@ -51,13 +52,17 @@ def check_params(self, f, nchannels, sampwidth, framerate, nframes,
5152

5253

5354
class AudioWriteTests(AudioTests):
55+
readonly = False
5456

5557
def create_file(self, testfile):
58+
if self.readonly:
59+
self.skipTest('Read only file format')
5660
f = self.fout = self.module.open(testfile, 'wb')
5761
f.setnchannels(self.nchannels)
5862
f.setsampwidth(self.sampwidth)
5963
f.setframerate(self.framerate)
6064
f.setcomptype(self.comptype, self.compname)
65+
f.setencoding(self.encoding)
6166
return f
6267

6368
def check_file(self, testfile, nframes, frames):
@@ -67,13 +72,14 @@ def check_file(self, testfile, nframes, frames):
6772
self.assertEqual(f.getframerate(), self.framerate)
6873
self.assertEqual(f.getnframes(), nframes)
6974
self.assertEqual(f.readframes(nframes), frames)
75+
self.assertEqual(f.getencoding(), self.encoding)
7076

7177
def test_write_params(self):
7278
f = self.create_file(TESTFN)
7379
f.setnframes(self.nframes)
7480
f.writeframes(self.frames)
7581
self.check_params(f, self.nchannels, self.sampwidth, self.framerate,
76-
self.nframes, self.comptype, self.compname)
82+
self.nframes, self.comptype, self.compname, self.encoding)
7783
f.close()
7884

7985
def test_write_context_manager_calls_close(self):
@@ -257,7 +263,7 @@ def test_read_params(self):
257263
f = self.f = self.module.open(self.sndfilepath)
258264
#self.assertEqual(f.getfp().name, self.sndfilepath)
259265
self.check_params(f, self.nchannels, self.sampwidth, self.framerate,
260-
self.sndfilenframes, self.comptype, self.compname)
266+
self.sndfilenframes, self.comptype, self.compname, self.encoding)
261267

262268
def test_close(self):
263269
with open(self.sndfilepath, 'rb') as testfile:

Lib/test/test_wave.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class WavePCM8Test(WaveTest, unittest.TestCase):
2222
sampwidth = 1
2323
framerate = 11025
2424
nframes = 48
25+
encoding = wave.WAVE_FORMAT_PCM
2526
comptype = 'NONE'
2627
compname = 'not compressed'
2728
frames = bytes.fromhex("""\
@@ -39,6 +40,7 @@ class WavePCM16Test(WaveTest, unittest.TestCase):
3940
sampwidth = 2
4041
framerate = 11025
4142
nframes = 48
43+
encoding = wave.WAVE_FORMAT_PCM
4244
comptype = 'NONE'
4345
compname = 'not compressed'
4446
frames = bytes.fromhex("""\
@@ -60,6 +62,7 @@ class WavePCM24Test(WaveTest, unittest.TestCase):
6062
sampwidth = 3
6163
framerate = 11025
6264
nframes = 48
65+
encoding = wave.WAVE_FORMAT_PCM
6366
comptype = 'NONE'
6467
compname = 'not compressed'
6568
frames = bytes.fromhex("""\
@@ -87,6 +90,8 @@ class WavePCM24ExtTest(WaveTest, unittest.TestCase):
8790
sampwidth = 3
8891
framerate = 11025
8992
nframes = 48
93+
encoding = wave.WAVE_FORMAT_EXTENSIBLE
94+
readonly = True # Writing EXTENSIBLE wave format is not supported.
9095
comptype = 'NONE'
9196
compname = 'not compressed'
9297
frames = bytes.fromhex("""\
@@ -114,6 +119,7 @@ class WavePCM32Test(WaveTest, unittest.TestCase):
114119
sampwidth = 4
115120
framerate = 11025
116121
nframes = 48
122+
encoding = wave.WAVE_FORMAT_PCM
117123
comptype = 'NONE'
118124
compname = 'not compressed'
119125
frames = bytes.fromhex("""\
@@ -134,9 +140,34 @@ class WavePCM32Test(WaveTest, unittest.TestCase):
134140
frames = wave._byteswap(frames, 4)
135141

136142

143+
class WaveIeeeFloatingPointTest(WaveTest, unittest.TestCase):
144+
sndfilename = 'pluck-float32.wav'
145+
sndfilenframes = 3307
146+
nchannels = 2
147+
sampwidth = 4
148+
framerate = 11025
149+
nframes = 48
150+
encoding = wave.WAVE_FORMAT_IEEE_FLOAT
151+
comptype = 'NONE'
152+
compname = 'not compressed'
153+
frames = bytes.fromhex("""\
154+
60598B3C001423BA 1FB4163F8054FA3B 0E4FC43E80C51D3D 53467EBF4030843D \
155+
FC84D0BE304C563D 3053113F40BEFC3C B72F00BFC03E583C E0FEDA3C805142BC \
156+
54510FBFE02638BD 569F16BF40FDCABD C060A63EECA421BE 3CE5523E2C3349BE \
157+
0C2E10BE14725BBE 5268E7BEDC3B6CBE 985AE03D80497ABE B4B606BEECB67EBE \
158+
B0B12E3FC87C6CBE 005519BD4C0F3EBE F8BD1B3EECDF03BE 924E9FBE588D8DBD \
159+
D4E150BF501711BD B079A0BD20FBFBBC 5863863D40760CBD 0E3C83BE40E217BD \
160+
04FF0B3EF07839BD E29AFB3E80A714BD B91007BFE042D3BC B5AD4D3F80CDA0BB \
161+
1AB1C3BEB04E023D D33A063FC0A8973D 8012F9BEE074EC3D 7341223FD415153E \
162+
D80409BE04A63A3E 00F27BBFBC25333E 0000803FFC29223E 000080BF38A7143E \
163+
3638133F283BEB3D 7C6E253F00CADB3D 686A02BE88FDF53D 920CC7BE28E1FB3D \
164+
185B5ABED8A2CE3D 5189463FC8A7A53D E88F8C3DF0FFA13D 1CE6AE3EE0A0B03D \
165+
DF90223F184EE43D 376768BF2CD8093E 281612BF60B3EE3D 2F26083F88B4A53D \
166+
""")
167+
137168
class MiscTestCase(unittest.TestCase):
138169
def test__all__(self):
139-
not_exported = {'WAVE_FORMAT_PCM', 'WAVE_FORMAT_EXTENSIBLE', 'KSDATAFORMAT_SUBTYPE_PCM'}
170+
not_exported = {'WAVE_FORMAT_PCM', 'WAVE_FORMAT_IEEE_FLOAT', 'WAVE_FORMAT_EXTENSIBLE', 'KSDATAFORMAT_SUBTYPE_PCM'}
140171
support.check__all__(self, wave, not_exported=not_exported)
141172

142173

Lib/wave.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
getsampwidth() -- returns sample width in bytes
1616
getframerate() -- returns sampling frequency
1717
getnframes() -- returns number of audio frames
18+
getencoding() -- returns frame encoding (WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT
19+
or WAVE_FORMAT_EXTENSIBLE)
1820
getcomptype() -- returns compression type ('NONE' for linear samples)
1921
getcompname() -- returns human-readable version of
2022
compression type ('not compressed' linear samples)
@@ -42,6 +44,9 @@
4244
setsampwidth(n) -- set the sample width
4345
setframerate(n) -- set the frame rate
4446
setnframes(n) -- set the number of frames
47+
setencoding(encoding)
48+
-- set the frame encoding. Only WAVE_FORMAT_PCM,
49+
WAVE_FORMAT_IEEE_FLOAT are supported.
4550
setcomptype(type, name)
4651
-- set the compression type and the
4752
human-readable compression type
@@ -80,6 +85,7 @@ class Error(Exception):
8085
pass
8186

8287
WAVE_FORMAT_PCM = 0x0001
88+
WAVE_FORMAT_IEEE_FLOAT = 0x0003
8389
WAVE_FORMAT_EXTENSIBLE = 0xFFFE
8490
# Derived from uuid.UUID("00000001-0000-0010-8000-00aa00389b71").bytes_le
8591
KSDATAFORMAT_SUBTYPE_PCM = b'\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x008\x9bq'
@@ -226,6 +232,10 @@ class Wave_read:
226232
available through the getsampwidth() method
227233
_framerate -- the sampling frequency
228234
available through the getframerate() method
235+
_encoding -- frame encoding
236+
One of WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT
237+
or WAVE_FORMAT_EXTENSIBLE available through
238+
getencoding() method
229239
_comptype -- the AIFF-C compression type ('NONE' if AIFF)
230240
available through the getcomptype() method
231241
_compname -- the human-readable AIFF-C compression type
@@ -338,6 +348,9 @@ def getparams(self):
338348
self.getframerate(), self.getnframes(),
339349
self.getcomptype(), self.getcompname())
340350

351+
def getencoding(self):
352+
return self._encoding
353+
341354
def setpos(self, pos):
342355
if pos < 0 or pos > self._nframes:
343356
raise Error('position not in range')
@@ -367,16 +380,16 @@ def readframes(self, nframes):
367380

368381
def _read_fmt_chunk(self, chunk):
369382
try:
370-
wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))
383+
self._encoding, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))
371384
except struct.error:
372385
raise EOFError from None
373-
if wFormatTag != WAVE_FORMAT_PCM and wFormatTag != WAVE_FORMAT_EXTENSIBLE:
374-
raise Error('unknown format: %r' % (wFormatTag,))
386+
if self._encoding not in (WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT, WAVE_FORMAT_EXTENSIBLE):
387+
raise Error('unknown format: %r' % (self._encoding,))
375388
try:
376389
sampwidth = struct.unpack_from('<H', chunk.read(2))[0]
377390
except struct.error:
378391
raise EOFError from None
379-
if wFormatTag == WAVE_FORMAT_EXTENSIBLE:
392+
if self._encoding == WAVE_FORMAT_EXTENSIBLE:
380393
try:
381394
cbSize, wValidBitsPerSample, dwChannelMask = struct.unpack_from('<HHL', chunk.read(8))
382395
# Read the entire UUID from the chunk
@@ -419,6 +432,8 @@ class Wave_write:
419432
set through the setsampwidth() or setparams() method
420433
_framerate -- the sampling frequency
421434
set through the setframerate() or setparams() method
435+
_encoding -- frame encoding
436+
set through setencoding() method
422437
_nframes -- the number of audio frames written to the header
423438
set through the setnframes() or setparams() method
424439
@@ -446,6 +461,7 @@ def initfp(self, file):
446461
self._file = file
447462
self._convert = None
448463
self._nchannels = 0
464+
self._encoding = WAVE_FORMAT_PCM
449465
self._sampwidth = 0
450466
self._framerate = 0
451467
self._nframes = 0
@@ -518,6 +534,16 @@ def setcomptype(self, comptype, compname):
518534
self._comptype = comptype
519535
self._compname = compname
520536

537+
def setencoding(self, encoding):
538+
if self._datawritten:
539+
raise Error('cannot change parameters after starting to write')
540+
if encoding not in (WAVE_FORMAT_IEEE_FLOAT, WAVE_FORMAT_PCM):
541+
raise Error('unsupported wave format')
542+
self._encoding = encoding
543+
544+
def getencoding(self):
545+
return self._encoding
546+
521547
def getcomptype(self):
522548
return self._comptype
523549

@@ -601,7 +627,7 @@ def _write_header(self, initlength):
601627
self._form_length_pos = None
602628
self._file.write(struct.pack('<L4s4sLHHLLHH4s',
603629
36 + self._datalength, b'WAVE', b'fmt ', 16,
604-
WAVE_FORMAT_PCM, self._nchannels, self._framerate,
630+
self._encoding, self._nchannels, self._framerate,
605631
self._nchannels * self._framerate * self._sampwidth,
606632
self._nchannels * self._sampwidth,
607633
self._sampwidth * 8, b'data'))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for floating point audio wave files.

0 commit comments

Comments
 (0)