Search code examples
pythonaudiopyaudiowave

Sound generated not being saved to a file, as it should


I generate a sound wave on frequency 440hz as expected in pyaudio, but even though I am using the same sample array to save a wav file, it does not save the same sound and I can´t figure out why

Here is the code:

import wave
import numpy as np
import pyaudio

p = pyaudio.PyAudio()

volume = 0.5  # range [0.0, 1.0]
fs = 44100  # sampling rate, Hz, must be integer
duration = 2.0  # in seconds, may be float
f = 440.0  # sine frequency, Hz, may be float
channels = 1

# open stream (2)
stream = p.open(format=pyaudio.paFloat32,
                channels=channels,
                rate=fs,
                output=True)


def get_value(i):
    return np.sin(f * np.pi * float(i) / float(fs))


samples = np.array([get_value(a) for a in range(0, fs)]).astype(np.float32)

for i in range(0, int(duration)):
    stream.write(samples, fs)

wf = wave.open("test.wav", 'wb')
wf.setnchannels(channels)
wf.setsampwidth(3)
wf.setframerate(fs)
wf.setnframes(int(fs * duration))
wf.writeframes(samples)
wf.close()

# stop stream (4)
stream.stop_stream()
stream.close()

# close PyAudio (5)
p.terminate()

https://gist.github.com/badjano/c727b20429295e2695afdbc601f2334b


Solution

  • I think the main problem is that you are using the float32 data type which is not supported by the wave module. You can use int16 or int32 or you can use 24-bit integers with some manual conversion. Since you are using wf.setsampwidth(3), I assume you want to use 24-bit data?

    I've written a little tutorial about the wave module (including how to handle 24-bit data) and an overview about different modules for handling sound files. You may also be interested in my tutorial about creating a simple signal.

    Since you are already using NumPy, I recommend using a library that supports NumPy arrays out-of-the-box and does all the conversions for you. My personal preference would be to use the soundfile module, but I'm quite biased. For playback, I would also recommend using a library that supports NumPy. Here my suggestion is the sounddevice module, but I'm very biased here as well.

    If you want to follow my suggestions, your code might become something like that (including the handling of volume and fixing a missing factor of 2 in the sinus' argument):

    from __future__ import division
    import numpy as np
    import sounddevice as sd
    import soundfile as sf
    
    volume = 0.5  # range [0.0, 1.0]
    fs = 44100  # sampling rate, Hz
    duration = 2.0  # in seconds
    f = 440.0  # sine frequency, Hz
    
    t = np.arange(int(duration * fs)) / fs
    samples = volume * np.sin(2 * np.pi * f * t)
    
    sf.write('myfile.wav', samples, fs, subtype='PCM_24')
    
    sd.play(samples, fs)
    sd.wait()
    

    UPDATE:

    If you want to keep using PyAudio, that's fine. But you'll have to manually convert the floating point array (with values from -1.0 to 1.0) to integers in the appropriate range, depending on the data type you want to use. The first link I mentioned above contains the file utility.py which has a function float2pcm() to do just that.

    Here's an abbreviated version of that function:

    def float2pcm(sig, dtype='int16'):
        i = np.iinfo(dtype)
        abs_max = 2 ** (i.bits - 1)
        offset = i.min + abs_max
        return (sig * abs_max + offset).clip(i.min, i.max).astype(dtype)