我家里有蚊虫问题。这通常与程序员社区无关;但是,我看到一些设备声称通过播放17KHz的音调来驱赶这些令人讨厌的生物。我想使用我的笔记本电脑来实现这个功能。
一个方法是创建一个只有一个固定频率音调的MP3文件(可以使用Audacity轻松完成),用Python库打开它并重复播放。
第二种方法是使用计算机内置扬声器播放声音。我正在寻找类似于QBasic Sound的东西:
SOUND 17000, 100
有没有适用于Python的相关库?
我家里有蚊虫问题。这通常与程序员社区无关;但是,我看到一些设备声称通过播放17KHz的音调来驱赶这些令人讨厌的生物。我想使用我的笔记本电脑来实现这个功能。
一个方法是创建一个只有一个固定频率音调的MP3文件(可以使用Audacity轻松完成),用Python库打开它并重复播放。
第二种方法是使用计算机内置扬声器播放声音。我正在寻找类似于QBasic Sound的东西:
SOUND 17000, 100
有没有适用于Python的相关库?
PyAudiere是一个简单的跨平台解决方案,用于解决以下问题:
>>> 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网站已经消失。可以通过wayback machine获取该网站和Python 2的二进制安装程序(debian,windows),例如,这里有源代码 pyaudiere-0.2.tar.gz
。
为了在Linux、Windows、OSX上同时支持Python 2和3,可以使用pyaudio
模块代替:
#!/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()
例子:
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
)
这是一个修改后(支持Python 3)的这个AskUbuntu答案。
samples = (int(s(t) * 0x7f + 0x80) for t in range(n_samples)) stream.write(bytes(bytearray(samples)))
这将允许您一次写入所有样本而不是分批写入。请注意,这两段代码的意思相同,只是后者更简洁易懂。 - Foxichu我将代码放在这里,因为它有助于程序员更清楚地了解代码的工作原理。解释已经写在代码里:
#!/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)
c = b * (2 * math.pi)
,应该改为:c = b * (1.0 * math.pi)
,以输出正确的频率。我还想补充一点,当使用USB声卡时,谐波小于-95 dBc,因此这段代码生成了非常好的16位质量正弦波。 :-) - Steve Hageman我简化了jfs关于Python3.6+的回答,并进行了一些小改进:
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
)
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