Search code examples
pythonaudiowavwavedownsampling

wave write function not working, what am I doing wrong?


I am trying to halve the existing sampling rate of a folder full of .wav files. This is the only way I have found to do it but it is not working. The read part works just fine up until f.close(), then the wave.write part causes the error.

import wave
import contextlib
import os

for file_name in os.listdir(os.getcwd()):
    if file_name.endswith(".wav"):
        with contextlib.closing(wave.open(file_name, 'rb')) as f:
            rate = f.getframerate()
            new_rate = rate/2
            f.close()
            with contextlib.closing(wave.open(file_name, 'wb')) as f:
                rate = f.setframerate(new_rate)

This is the output when I run it.

Traceback (most recent call last):
  File "C:\Users\hsash\OneDrive\Desktop\used AR1-20210513T223533Z-001 - Copy (2)\sounds\python code.py", line 36, in <module>
    rate = f.setframerate(new_rate)
  File "C:\Users\hsash\AppData\Local\Programs\Python\Python39\lib\contextlib.py", line 303, in __exit__
    self.thing.close()
  File "C:\Users\hsash\AppData\Local\Programs\Python\Python39\lib\wave.py", line 444, in close
    self._ensure_header_written(0)
  File "C:\Users\hsash\AppData\Local\Programs\Python\Python39\lib\wave.py", line 462, in _ensure_header_written
    raise Error('# channels not specified')
wave.Error: # channels not specified

Solution

  • It says right there that #channels not specified. When you are opening a wavefile for writing, python sets all of the header fields to zero irrespectively of the current state of the file.

    In order to make sure that the other fields are saved you need to copy them over from the old file when you read it the first time.

    In the snippet below I'm using getparams and setparams to copy the header fields over and I'm using readframes and writeframes to copy the wave data.

    import wave
    import contextlib
    import os
    
    for file_name in os.listdir(os.getcwd()):
        if file_name.endswith(".wav"):
            with contextlib.closing(wave.open(file_name, 'rb')) as f:
                rate = f.getframerate()
                params = f.getparams()
    
                frames = f.getnframes()
                data = f.readframes(frames)
    
    
                new_rate = rate/2
                f.close()
                with contextlib.closing(wave.open(file_name, 'wb')) as f:
                    f.setparams(params)
                    f.setframerate(new_rate)
                    f.writeframes(data)