Skip to content

Commit 4ee51f5

Browse files
committed
Add support for floating point wave files.
This adds support for floating point wav files and fix #60729.
1 parent a703f74 commit 4ee51f5

File tree

9 files changed

+142
-27
lines changed

9 files changed

+142
-27
lines changed

Lib/aifc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@
144144
warnings._deprecated(__name__, remove=(3, 13))
145145

146146

147+
AIFC_ENCODING_LINEAR_PCM = 1
148+
147149
class Error(Exception):
148150
pass
149151

@@ -400,6 +402,9 @@ def getsampwidth(self):
400402
def getframerate(self):
401403
return self._framerate
402404

405+
def getencoding(self):
406+
return AIFC_ENCODING_LINEAR_PCM
407+
403408
def getcomptype(self):
404409
return self._comptype
405410

@@ -675,6 +680,13 @@ def setnframes(self, nframes):
675680
def getnframes(self):
676681
return self._nframeswritten
677682

683+
def getencoding(self):
684+
return AIFC_ENCODING_LINEAR_PCM
685+
686+
def setencoding(self, encoding):
687+
if encoding != AIFC_ENCODING_LINEAR_PCM:
688+
raise Error("Unsupported encoding")
689+
678690
def setcomptype(self, comptype, compname):
679691
if self._nframeswritten:
680692
raise Error('cannot change parameters after starting to write')

Lib/sunau.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ def getcompname(self):
259259
else:
260260
return 'not compressed'
261261

262+
def getencoding(self):
263+
return self._encoding
264+
262265
def getparams(self):
263266
return _sunau_params(self.getnchannels(), self.getsampwidth(),
264267
self.getframerate(), self.getnframes(),
@@ -342,6 +345,7 @@ def initfp(self, file):
342345
self._datawritten = 0
343346
self._datalength = 0
344347
self._info = b''
348+
self._encoding = AUDIO_FILE_ENCODING_MULAW_8
345349
self._comptype = 'ULAW' # default is U-law
346350

347351
def setnchannels(self, nchannels):
@@ -362,6 +366,7 @@ def setsampwidth(self, sampwidth):
362366
if sampwidth not in (1, 2, 3, 4):
363367
raise Error('bad sample width')
364368
self._sampwidth = sampwidth
369+
self._update_encoding()
365370

366371
def getsampwidth(self):
367372
if not self._framerate:
@@ -405,6 +410,31 @@ def getcompname(self):
405410
else:
406411
return 'not compressed'
407412

413+
def setencoding(self, encoding):
414+
if self._nframeswritten:
415+
raise Error('cannot change parameters after starting to write')
416+
if encoding == AUDIO_FILE_ENCODING_LINEAR_8:
417+
self.setcomptype('NONE', None)
418+
self.setsampwidth(1)
419+
elif encoding == AUDIO_FILE_ENCODING_LINEAR_16:
420+
self.setcomptype('NONE', None)
421+
self.setsampwidth(2)
422+
elif encoding == AUDIO_FILE_ENCODING_LINEAR_24:
423+
self.setcomptype('NONE', None)
424+
self.setsampwidth(3)
425+
elif encoding == AUDIO_FILE_ENCODING_LINEAR_32:
426+
self.setcomptype('NONE', None)
427+
self.setsampwidth(4)
428+
elif encoding == AUDIO_FILE_ENCODING_MULAW_8:
429+
self.setcomptype('ULAW', None)
430+
self.setsampwidth(2)
431+
else:
432+
raise Error('unsupported encoding %r', encoding)
433+
assert self._encoding == encoding
434+
435+
def getencoding(self):
436+
return self._encoding
437+
408438
def setparams(self, params):
409439
nchannels, sampwidth, framerate, nframes, comptype, compname = params
410440
self.setnchannels(nchannels)
@@ -458,38 +488,40 @@ def close(self):
458488
#
459489
# private methods
460490
#
461-
462-
def _ensure_header_written(self):
463-
if not self._nframeswritten:
464-
if not self._nchannels:
465-
raise Error('# of channels not specified')
466-
if not self._sampwidth:
467-
raise Error('sample width not specified')
468-
if not self._framerate:
469-
raise Error('frame rate not specified')
470-
self._write_header()
471-
472-
def _write_header(self):
491+
def _update_encoding(self):
473492
if self._comptype == 'NONE':
474493
if self._sampwidth == 1:
475-
encoding = AUDIO_FILE_ENCODING_LINEAR_8
494+
self._encoding = AUDIO_FILE_ENCODING_LINEAR_8
476495
self._framesize = 1
477496
elif self._sampwidth == 2:
478-
encoding = AUDIO_FILE_ENCODING_LINEAR_16
497+
self._encoding = AUDIO_FILE_ENCODING_LINEAR_16
479498
self._framesize = 2
480499
elif self._sampwidth == 3:
481-
encoding = AUDIO_FILE_ENCODING_LINEAR_24
500+
self._encoding = AUDIO_FILE_ENCODING_LINEAR_24
482501
self._framesize = 3
483502
elif self._sampwidth == 4:
484-
encoding = AUDIO_FILE_ENCODING_LINEAR_32
503+
self._encoding = AUDIO_FILE_ENCODING_LINEAR_32
485504
self._framesize = 4
486505
else:
487506
raise Error('internal error')
488507
elif self._comptype == 'ULAW':
489-
encoding = AUDIO_FILE_ENCODING_MULAW_8
508+
self._encoding = AUDIO_FILE_ENCODING_MULAW_8
490509
self._framesize = 1
491510
else:
492511
raise Error('internal error')
512+
513+
def _ensure_header_written(self):
514+
if not self._nframeswritten:
515+
if not self._nchannels:
516+
raise Error('# of channels not specified')
517+
if not self._sampwidth:
518+
raise Error('sample width not specified')
519+
if not self._framerate:
520+
raise Error('frame rate not specified')
521+
self._write_header()
522+
523+
def _write_header(self):
524+
self._update_encoding()
493525
self._framesize = self._framesize * self._nchannels
494526
_write_u32(self._file, AUDIO_FILE_MAGIC)
495527
header_size = 25 + len(self._info)
@@ -505,7 +537,7 @@ def _write_header(self):
505537
self._form_length_pos = None
506538
_write_u32(self._file, length)
507539
self._datalength = length
508-
_write_u32(self._file, encoding)
540+
_write_u32(self._file, self._encoding)
509541
_write_u32(self._file, self._framerate)
510542
_write_u32(self._file, self._nchannels)
511543
self._file.write(self._info)
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_aifc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class AifcTest(audiotests.AudioWriteTests,
1818
module = aifc
1919
close_fd = True
2020
test_unseekable_read = None
21+
encoding = aifc.AIFC_ENCODING_LINEAR_PCM
2122

2223

2324
class AifcPCM8Test(AifcTest, unittest.TestCase):

Lib/test/test_sunau.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from test import audiotests
33
import io
44
import struct
5+
import sunau
56
import sys
67
from test.support import warnings_helper
78

@@ -21,6 +22,7 @@ class SunauPCM8Test(SunauTest, unittest.TestCase):
2122
sampwidth = 1
2223
framerate = 11025
2324
nframes = 48
25+
encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_8
2426
comptype = 'NONE'
2527
compname = 'not compressed'
2628
frames = bytes.fromhex("""\
@@ -38,6 +40,7 @@ class SunauPCM16Test(SunauTest, unittest.TestCase):
3840
sampwidth = 2
3941
framerate = 11025
4042
nframes = 48
43+
encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_16
4144
comptype = 'NONE'
4245
compname = 'not compressed'
4346
frames = bytes.fromhex("""\
@@ -57,6 +60,7 @@ class SunauPCM24Test(SunauTest, unittest.TestCase):
5760
sampwidth = 3
5861
framerate = 11025
5962
nframes = 48
63+
encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_24
6064
comptype = 'NONE'
6165
compname = 'not compressed'
6266
frames = bytes.fromhex("""\
@@ -82,6 +86,7 @@ class SunauPCM32Test(SunauTest, unittest.TestCase):
8286
sampwidth = 4
8387
framerate = 11025
8488
nframes = 48
89+
encoding = sunau.AUDIO_FILE_ENCODING_LINEAR_32
8590
comptype = 'NONE'
8691
compname = 'not compressed'
8792
frames = bytes.fromhex("""\
@@ -107,6 +112,7 @@ class SunauULAWTest(SunauTest, unittest.TestCase):
107112
sampwidth = 2
108113
framerate = 11025
109114
nframes = 48
115+
encoding = sunau.AUDIO_FILE_ENCODING_MULAW_8
110116
comptype = 'ULAW'
111117
compname = 'CCITT G.711 u-law'
112118
frames = bytes.fromhex("""\

Lib/test/test_wave.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class WavePCM8Test(WaveTest, unittest.TestCase):
1919
sampwidth = 1
2020
framerate = 11025
2121
nframes = 48
22+
encoding = wave.WAVE_FORMAT_PCM
2223
comptype = 'NONE'
2324
compname = 'not compressed'
2425
frames = bytes.fromhex("""\
@@ -36,6 +37,7 @@ class WavePCM16Test(WaveTest, unittest.TestCase):
3637
sampwidth = 2
3738
framerate = 11025
3839
nframes = 48
40+
encoding = wave.WAVE_FORMAT_PCM
3941
comptype = 'NONE'
4042
compname = 'not compressed'
4143
frames = bytes.fromhex("""\
@@ -57,6 +59,7 @@ class WavePCM24Test(WaveTest, unittest.TestCase):
5759
sampwidth = 3
5860
framerate = 11025
5961
nframes = 48
62+
encoding = wave.WAVE_FORMAT_PCM
6063
comptype = 'NONE'
6164
compname = 'not compressed'
6265
frames = bytes.fromhex("""\
@@ -84,6 +87,8 @@ class WavePCM24ExtTest(WaveTest, unittest.TestCase):
8487
sampwidth = 3
8588
framerate = 11025
8689
nframes = 48
90+
encoding = wave.WAVE_FORMAT_EXTENSIBLE
91+
readonly = True # Writing EXTENSIBLE wave format is not supported.
8792
comptype = 'NONE'
8893
compname = 'not compressed'
8994
frames = bytes.fromhex("""\
@@ -111,6 +116,7 @@ class WavePCM32Test(WaveTest, unittest.TestCase):
111116
sampwidth = 4
112117
framerate = 11025
113118
nframes = 48
119+
encoding = wave.WAVE_FORMAT_PCM
114120
comptype = 'NONE'
115121
compname = 'not compressed'
116122
frames = bytes.fromhex("""\
@@ -131,9 +137,34 @@ class WavePCM32Test(WaveTest, unittest.TestCase):
131137
frames = wave._byteswap(frames, 4)
132138

133139

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

139170

0 commit comments

Comments
 (0)