Generating sine wave sound in Python - python

I need to generate a sine wave sound in Python, and I need to be able to control frequency, duration, and relative volume. By 'generate' I mean that I want it to play though the speakers immediately, not save to a file.
What is the easiest way to do this?

Version with numpy:
import time
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 = 5.0 # in seconds, may be float
f = 440.0 # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
samples = (np.sin(2 * np.pi * np.arange(fs * duration) * f / fs)).astype(np.float32)
# per #yahweh comment explicitly convert to bytes sequence
output_bytes = (volume * samples).tobytes()
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
start_time = time.time()
stream.write(output_bytes)
print("Played sound for {:.2f} seconds".format(time.time() - start_time))
stream.stop_stream()
stream.close()
p.terminate()
Version without numpy:
import array
import math
import time
import pyaudio
p = pyaudio.PyAudio()
volume = 0.5 # range [0.0, 1.0]
fs = 44100 # sampling rate, Hz, must be integer
duration = 5.0 # in seconds, may be float
f = 440.0 # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
num_samples = int(fs * duration)
samples = [volume * math.sin(2 * math.pi * k * f / fs) for k in range(0, num_samples)]
# per #yahweh comment explicitly convert to bytes sequence
output_bytes = array.array('f', samples).tobytes()
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
start_time = time.time()
stream.write(output_bytes)
print("Played sound for {:.2f} seconds".format(time.time() - start_time))
stream.stop_stream()
stream.close()
p.terminate()

ivan-onys gave an excellent answer, but there is a little addition to it:
this script will produce 4 times shorter sound than expected because Pyaudio write method needs string data of float32, but when you pass numpy array to this method, it converts whole array as entity to a string, therefore you have to convert data in numpy array to the byte sequence yourself like this:
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32).tobytes()
and you have to change this line as well:
stream.write(samples)

One of the more consistent andeasy to install ways to deal with sound in Python is the Pygame multimedia libraries.
I'd recomend using it - there is the pygame.sndarray submodule that allows you to manipulate numbers in a data vector that become a high-level sound object that can be playerd in the pygame.mixer module.
The documentation in the pygame.org site should be enough for using the sndarray module.

Today for Python 3.5+ the best way is to install the packages recommended by the developer.
http://people.csail.mit.edu/hubert/pyaudio/
For Debian do
sudo apt-get install python3-all-dev portaudio19-dev
before trying to install pyaudio

The script from ivan_onys produces a signal that is four times shorter than intended. If a TypeError is returned when volume is a float, try adding .tobytes() to the following line instead.
stream.write((volume*samples).tobytes())
#mm_ float32 = 32 bits, and 8 bits = 1 byte, so float32 = 4 bytes. When samples are passed to stream.write as float32, byte count (duration) is divided by 4. Writing samples back .tobytes() corrects for quartering the sample count when writing to float32.

I the bregman lab toolbox you have a set of functions that does exactly what you want. This python module is a little bit buggy but you can adapt this code to get your own functions

Related

pyaudio not working, error "SystemError: PY_SSIZE_T_CLEAN macro must be defined for '#' formats"

I'm trying to write a python script that produces a sine wave audio:
import pyaudio
import numpy as np
p = pyaudio.PyAudio()
volume = 0.5 # range [0.0, 1.0]
fs = 44100 # sampling rate, Hz, must be integer
duration = 1.0 # in seconds, may be float
f = 440.0 # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
stream.write(volume*samples)
stream.stop_stream()
stream.close()
p.terminate()
But when I try to execute in VSCode,I get:
SystemError: PY_SSIZE_T_CLEAN macro must be defined for '#' formats
I'm running Python 3.10.4

Generating a 'wave-based sine wav' sound, verses a regular sine wav sound

I saw a question posted on Stack Overflow about generating a sound based on a sine wav in Python. Here was the code that produced the output I was looking for:
import pyaudio
import numpy as np
p = pyaudio.PyAudio()
volume = 0.5 # range [0.0, 1.0]
fs = 44100 # sampling rate, Hz, must be integer
duration = 1.0 # in seconds, may be float
f = 440.0 # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
stream.write(volume*samples)
stream.stop_stream()
stream.close()
p.terminate()
What if instead of me using the line...
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
I want to write whatever lines of code it would take, to where the samples (numpy array) would contain the values of a certain 'position' inside a specific wave file instead. Example, if I have a .wav file that has a sine wav stored inside the .wav file, and the .wav file is 1 second long, then it would 'replace' the line of code above. Is there a way to do that?

How to generate frequency sweep sound in python

So I'm trying to generate a controllable tone with python. I'm using PyAudio and the simple code is like this (source):
import pyaudio
import numpy as np
p = pyaudio.PyAudio()
fs = 44100 # sampling rate, Hz, must be integer
duration = 1.0 # in seconds, may be float
f = 440.0 # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
stream.write(samples.tobytes())
stream.stop_stream()
stream.close()
p.terminate()
the problem is when I played 2 different frequencies in sequence, it didn't play smoothly. I think it is because of the distortion between end of first frequency and beginning of second frequency. I'm trying to give a smooth transition with this code:
fstart = 440.0 # first frequency
fstop = 523.6 # second frequency
transition = 0.5 # transition time
finest = 10 # finest of transition
duration = 2.0 # duration of first and second frequency
samples = []
samples = np.asarray(samples)
# print(duration)
# print(fs*duration)
samples = np.append(samples, (np.sin(2*np.pi*np.arange(fs*duration)*fstart/fs)).astype(np.float32)).astype(np.float32)
for x in range(0, finest):
freq = fstart + (fstop-fstart)*x/finest
time = transition/finest
# print(time)
# print(fs*time)
samples = np.append(samples, (np.sin(2*np.pi*np.arange(fs*time)*freq/fs)).astype(np.float32)).astype(np.float32)
samples = np.append(samples, (np.sin(2*np.pi*np.arange(fs*duration)*fstop/fs)).astype(np.float32)).astype(np.float32)
but as expected, it also didn't go well. And when the finest is set to high, the sound was very distorted. Any idea to make this smooth like frequency sweep sound?
Thank you.

How to write pyaudio output into audio file?

I currently have the following code, which produces a sine wave of varying frequencies using the pyaudio module:
import pyaudio
import numpy as np
p = pyaudio.PyAudio()
volume = 0.5
fs = 44100
duration = 1
f = 440
samples = (np.sin(2 * np.pi * np.arange(fs * duration) * f /
fs)).astype(np.float32).tobytes()
stream = p.open(format = pyaudio.paFloat32,
channels = 1,
rate = fs,
output = True)
stream.write(samples)
However, instead of playing the sound, is there any way to make it so that the sound is written into an audio file?
Add this code at the top of your code.
from scipy.io.wavfile import write
Also, add this code at the bottom of your code.
This worked for me.
scaled = numpy.int16(s/numpy.max(numpy.abs(s)) * 32767)
write('test.wav', 44100, scaled)
Using scipy.io.wavfile.write as suggested by #h lee produced the desired results:
import numpy
from scipy.io.wavfile import write
volume = 1
sample_rate = 44100
duration = 10
frequency = 1000
samples = (numpy.sin(2 * numpy.pi * numpy.arange(sample_rate * duration)
* frequency / sample_rate)).astype(numpy.float32)
write('test.wav', sample_rate, samples)
Another example can be found on the documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.write.html
Handle your audio input as a numpy array like I did here in the second answer, but instead of just processing the frames and sending the data back to PyAudio, save each frame in a new output_array. Then when the processing is done you can use that output_array to write it to .wav or .mp3 file.
If you do that, however, the sound would still play. If you don't want to play the sound you have two options, either using the blocking mode or, if you wanna stick with the non blocking mode and the callbacks, do the following:
Erase output=True so that it is False as default.
Add a input=True parameter.
In your callback do not return ret_data, instead return None.
Keep count of the frames you've processed so that when you're done you return paComplete as second value of the returned tuple.

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
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)

Categories