Reputation: 136381
I have a mosquito problem in my house. This wouldn't usually concern a programmers' community; However, I've seen some devices that claim to deter these nasty creatures by playing a 17Khz tone. I would like to do this using my laptop.
One method would be creating an MP3 with a a single, fixed-frequency tone (This can easily done by audacity), opening it with a python library and playing it repeatedly.
The second would be playing a sound using the computer built-in speaker. I'm looking for something similar to QBasic Sound:
SOUND 17000, 100
Is there a python library for that?
Upvotes: 46
Views: 64893
Reputation: 1
This solution uses pyaudio library and plays square wave sound:
from pyaudio import PyAudio
import time
p = PyAudio() # create PyAudio oobject
stream = p.open(format=p.get_format_from_width(1), # open stream
channels=1,
rate=41900,
output=True)
def square_wave(freq, duration, volume, framerate) :
total=int(round(framerate*(duration/1000))) # calculate length of audio in samples
i=0 # played samples counter
sample_threshold=int(round(framerate*(0.5/freq))) # how much frames to do silence/sound (the frequence delay in samples)
samples_last=0 # played samples counter (resets each sample_threshold)
value=int(round(volume*255)) # the value to yield
while i<=total : # play until the sound ends
yield value # yield the value
samples_last+=1 # count played sample
if samples_last>=sample_threshold : # if played samples reach the threshold...
samples_last=0 # reset counter
value=0 if value!=0 else int(round(volume*255)) # switch value to volume or back to 0
i+=1 # total counter increment
def playtone(freq, duration, volume) :
framerate=41900
volume/=100 # recalculate volume from percentage format to 0-1
data=tuple(square_wave(freq, duration, volume, framerate)) # get the data of square_wave as tuple
stream.write(bytes(bytearray(data))) # play it (TODO: split in chunks to instant ^C abort)
#playtone(1000, 500, 100) # 1000hz sound for 500 ms (1 second = 1000 ms) with full (100%) volume
Upvotes: 0
Reputation: 31930
I streamlined jfs' answer for Python3.6+ and made some minor improvements:
import math
from pyaudio import PyAudio, paUInt8
def generate_sine_wave(frequency, duration, volume=0.2, sample_rate=22050):
''' Generate a tone at the given frequency.
Limited to unsigned 8-bit samples at a given sample_rate.
The sample rate should be at least double the frequency.
'''
if sample_rate < (frequency * 2):
print('Warning: sample_rate must be at least double the frequency '
f'to accurately represent it:\n sample_rate {sample_rate}'
f' ≯ {frequency*2} (frequency {frequency}*2)')
num_samples = int(sample_rate * duration)
rest_frames = num_samples % sample_rate
pa = PyAudio()
stream = pa.open(
format=paUInt8,
channels=1, # mono
rate=sample_rate,
output=True,
)
# make samples
s = lambda i: volume * math.sin(2 * math.pi * frequency * i / sample_rate)
samples = (int(s(i) * 0x7F + 0x80) for i in range(num_samples))
# write several samples at a time
for buf in zip( *([samples] * sample_rate) ):
stream.write(bytes(buf))
# fill remainder of frameset with silence
stream.write(b'\x80' * rest_frames)
stream.stop_stream()
stream.close()
pa.terminate()
generate_sine_wave(
# see http://www.phy.mtu.edu/~suits/notefreqs.html
frequency=523.25, # Hz, waves per second C6
duration=1.2, # seconds to play sound
volume=0.25, # 0..1 how loud it is
sample_rate=22050, # number of samples per second: 11025, 22050, 44100
)
Upvotes: 1
Reputation: 742
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)
Upvotes: 6
Reputation: 69242
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
Upvotes: 29
Reputation: 414695
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.
Upvotes: 28
Reputation: 1866
You can use the Python binding of the SDL (Simple Direct Media Library).
Upvotes: 0