Adam Matan
Adam Matan

Reputation: 136381

Python library for playing fixed-frequency sound

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

Answers (6)

mark0392
mark0392

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

Gringo Suave
Gringo Suave

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

Aditya Shankar
Aditya Shankar

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

tom10
tom10

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

jfs
jfs

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

Monkey
Monkey

Reputation: 1866

You can use the Python binding of the SDL (Simple Direct Media Library).

Upvotes: 0

Related Questions