Python library for playing fixed-frequency sound
I'm putting my code in here as it helps a programmer gain clarity over how the code works. Explanation is in the code itself:
#!/usr/bin/env python3
import pyaudio
import struct
import math
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
p = pyaudio.PyAudio()
def data_for_freq(frequency: float, time: float = None):
"""get frames for a fixed frequency for a specified time or
number of frames, if frame_count is specified, the specified
time is ignored"""
frame_count = int(RATE * time)
remainder_frames = frame_count % RATE
wavedata = []
for i in range(frame_count):
a = RATE / frequency # number of frames per wave
b = i / a
# explanation for b
# considering one wave, what part of the wave should this be
# if we graph the sine wave in a
# displacement vs i graph for the particle
# where 0 is the beginning of the sine wave and
# 1 the end of the sine wave
# which part is "i" is denoted by b
# for clarity you might use
# though this is redundant since math.sin is a looping function
# b = b - int(b)
c = b * (2 * math.pi)
# explanation for c
# now we map b to between 0 and 2*math.PI
# since 0 - 2*PI, 2*PI - 4*PI, ...
# are the repeating domains of the sin wave (so the decimal values will
# also be mapped accordingly,
# and the integral values will be multiplied
# by 2*PI and since sin(n*2*PI) is zero where n is an integer)
d = math.sin(c) * 32767
e = int(d)
wavedata.append(e)
for i in range(remainder_frames):
wavedata.append(0)
number_of_bytes = str(len(wavedata))
wavedata = struct.pack(number_of_bytes + 'h', *wavedata)
return wavedata
def play(frequency: float, time: float):
"""
play a frequency for a fixed time!
"""
frames = data_for_freq(frequency, time)
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, output=True)
stream.write(frames)
stream.stop_stream()
stream.close()
if __name__ == "__main__":
play(400, 1)
PyAudiere is a simple cross-platform solution for the problem:
>>> import audiere
>>> d = audiere.open_device()
>>> t = d.create_tone(17000) # 17 KHz
>>> t.play() # non-blocking call
>>> import time
>>> time.sleep(5)
>>> t.stop()
pyaudiere.org is gone. The site and binary installers for Python 2 (debian, windows) are available via the wayback machine e.g., here's source code pyaudiere-0.2.tar.gz
.
To support both Python 2 and 3 on Linux, Windows, OSX, pyaudio
module could be used instead:
#!/usr/bin/env python
"""Play a fixed frequency sound."""
from __future__ import division
import math
from pyaudio import PyAudio # sudo apt-get install python{,3}-pyaudio
try:
from itertools import izip
except ImportError: # Python 3
izip = zip
xrange = range
def sine_tone(frequency, duration, volume=1, sample_rate=22050):
n_samples = int(sample_rate * duration)
restframes = n_samples % sample_rate
p = PyAudio()
stream = p.open(format=p.get_format_from_width(1), # 8bit
channels=1, # mono
rate=sample_rate,
output=True)
s = lambda t: volume * math.sin(2 * math.pi * frequency * t / sample_rate)
samples = (int(s(t) * 0x7f + 0x80) for t in xrange(n_samples))
for buf in izip(*[samples]*sample_rate): # write several samples at a time
stream.write(bytes(bytearray(buf)))
# fill remainder of frameset with silence
stream.write(b'\x80' * restframes)
stream.stop_stream()
stream.close()
p.terminate()
Example:
sine_tone(
# see http://www.phy.mtu.edu/~suits/notefreqs.html
frequency=440.00, # Hz, waves per second A4
duration=3.21, # seconds to play sound
volume=.01, # 0..1 how loud it is
# see http://en.wikipedia.org/wiki/Bit_rate#Audio
sample_rate=22050 # number of samples per second
)
It is a modified (to support Python 3) version of this AskUbuntu answer.
The module winsound is included with Python, so there are no external libraries to install, and it should do what you want (and not much else).
import winsound
winsound.Beep(17000, 100)
It's very simple and easy, though is only available for Windows.
But:
A complete answer to this question should note that although this method will produce a sound, it will not deter mosquitoes. It's already been tested: see here and here