Python library for playing fixed-frequency sound

48,368

Solution 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.

Solution 2

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

Solution 3

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)

Solution 4

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
)
Share:
48,368
Adam Matan
Author by

Adam Matan

Team leader, developer, and public speaker. I build end-to-end apps using modern cloud infrastructure, especially serverless tools. My current position is R&amp;D Manager at Corvid by Wix.com, a serverless platform for rapid web app generation. My CV and contact details are available on my Github README.

Updated on February 13, 2020

Comments

  • Adam Matan
    Adam Matan over 4 years

    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?

  • tom10
    tom10 about 15 years
    By the way, although this will produce a sound, I really doubt it will deter mosquitoes, in fact, I doubt they could even hear it. The issue is that most insects don't hear using tympanic membranes like we do, but hear using sensory hairs. But sensory hairs are only sensitive to air velocity, not pressure, and by the time you get far from the speaker, it's almost all pressure with very little velocity. That is, they won't hear it unless they are standing right on your speaker.
  • Adam Matan
    Adam Matan about 15 years
    So the speaker wold have to be very powerful, and probably not a common PC speaker. Luckily, I am still a student in a science faculty - I will ask an entomologist and post the answer here.
  • Adam Matan
    Adam Matan about 15 years
    Cool. Can you tell anything about stability issues? The lest release is 0.2.
  • jfs
    jfs about 15 years
    @Udi Pasmon: PyAudiere is a simple wrapper for corresponding C++ library audiere.sourceforge.net
  • tom10
    tom10 over 9 years
    "Whereas human ears are pressure detectors, a mosquito's detects the particle velocity component of a sound field, which is restricted to the immediate vicinity of the sound source in acoustic near field. The mosquito's ears are insensitive to pressure fluctuations in the acoustic far field." ncbi.nlm.nih.gov/pmc/articles/PMC1636503
  • Foxichu
    Foxichu over 8 years
    The fancy sample-writing code doesn't work for samples of durations <1s. You can replace 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))) with samples = (int(s(t) * 0x7f + 0x80) for t in range(n_samples)) stream.write(bytes(bytearray(samples)))
  • natterstefan
    natterstefan over 6 years
    A friend of mine shared this link with me to get and install pyaudio: people.csail.mit.edu/hubert/pyaudio
  • Regis May
    Regis May about 6 years
    If you use this solution I get strange error messages sent to STDERR. How can I get rid of them? Please see stackoverflow.com/questions/50162431/… for details!
  • Tms91
    Tms91 over 4 years
    How can I register winsound in Django? stackoverflow.com/questions/59047909/…
  • Steve Hageman
    Steve Hageman over 2 years
    I tested this wonderful code with my FFT Analyzer and O-Scope and the code as written actually puts out twice the asked for frequency. This line: c = b * (2 * math.pi), should be changed to: c = b * (1.0 * math.pi) to put out the proper frequency. I would also like to add that the harmonics are better than -95 dBc when running a USB Sound Blaster Dongle - so this code generates a very good 16 bit quality Sine Wave. :-)