Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple Pygame Audio at a Frequency

How can I create a 440 Hz sound that plays smoothly forever using audio with Pygame? I assume this should be easy enough, but I don't want to use any stupid files to accomplish the task. The final intent of this is to play a note while a key is being held down as I have asked in another question. Any help would be greatly appreciated as I have wasted great quantities of time trying to find an answer to this.

like image 684
Paul Avatar asked Oct 19 '11 03:10

Paul


People also ask

How do I control the volume on pygame?

Right click on the speaker icon in the taskbar. Select Playback Devices. Select Speakers and Properties. Go to Enhancements tab and uncheck Equalizer and Loudness Equalization.

Does pygame have built in sounds?

With PyGame, you get two choices: Music or Sounds. Music will just play in the background when you call it to, and sounds will play at any time you call them to play.

Does Pygame support MP3?

Pygame can load WAV, MP3, or OGG files. The difference between these audio file formats is explained at http://invpy.com/formats. To play this sound, call the Sound object's play() method. If you want to immediately stop the Sound object from playing call the stop() method.


2 Answers

After getting entirely too many "ValueError: Array depth must match number of mixer channels", and other similar errors, I discovered this working example http://www.mail-archive.com/[email protected]/msg16140.html. It properly generates a multi-dimensional 16 bit integer array that works with a stereo mixer. Minimum working example below, mostly lifted from the previous link with the necessary pygame bits thrown in. Tested in Python 2.7.2 with pygame.ver '1.9.1release'.

This example will play a 440 Hz tone out of one speaker and a 550 Hz tone out of the other speaker in a stereo setup. After a bit of playing with the duration I discovered that audible clicks will pop up in the sound loop if you set the "duration" variable to anything other than a whole number.

import pygame
from pygame.locals import *

import math
import numpy

size = (1366, 720)

bits = 16
#the number of channels specified here is NOT 
#the channels talked about here http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.get_num_channels

pygame.mixer.pre_init(44100, -bits, 2)
pygame.init()
_display_surf = pygame.display.set_mode(size, pygame.HWSURFACE | pygame.DOUBLEBUF)


duration = 1.0          # in seconds
#freqency for the left speaker
frequency_l = 440
#frequency for the right speaker
frequency_r = 550

#this sounds totally different coming out of a laptop versus coming out of headphones

sample_rate = 44100

n_samples = int(round(duration*sample_rate))

#setup our numpy array to handle 16 bit ints, which is what we set our mixer to expect with "bits" up above
buf = numpy.zeros((n_samples, 2), dtype = numpy.int16)
max_sample = 2**(bits - 1) - 1

for s in range(n_samples):
    t = float(s)/sample_rate    # time in seconds

    #grab the x-coordinate of the sine wave at a given time, while constraining the sample to what our mixer is set to with "bits"
    buf[s][0] = int(round(max_sample*math.sin(2*math.pi*frequency_l*t)))        # left
    buf[s][1] = int(round(max_sample*0.5*math.sin(2*math.pi*frequency_r*t)))    # right

sound = pygame.sndarray.make_sound(buf)
#play once, then loop forever
sound.play(loops = -1)


#This will keep the sound playing forever, the quit event handling allows the pygame window to close without crashing
_running = True
while _running:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            _running = False
            break

pygame.quit()
like image 125
odd.meta Avatar answered Oct 20 '22 06:10

odd.meta


What kind of 440 Hz sound? There are many types of waves at "440 Hz": sine, saw, square, etc. You could have a flute playing an A, and that might count too.

Assuming you want a sine wave, it looks like you can generate a Sound object with pygame.sndarray.samples. (I haven't tested this) You can create the samples with:

samples = [math.sin(2.0 * math.pi * frequency * t / sample_rate) for t in xrange(0, duration_in_samples)]

This is hopefully basic sine wave stuff. frequency is the desired frequency, in Hz. sample_rate is the number of samples / second in the generated audio: 44100 Hz is a typical value. duration_in_samples is the length of the audio. (5 * 44100 == 5 seconds, if your audio is at a 44100 Hz sample rate.)

It looks like you might have to convert samples to a numpy.array before passing to pygame.sndarray.samples. The docs indicate that the audio should match the format returned by pygame.mixer.get_init, so adjust samples appropriately, but that's the basic idea. (mixer.get_init will tell you the sample_rate variable above, and whether you need to account for stereo, and if you need to adjust the amplitude of the wave or shift it.)

Make samples a integral number of waves long, and it should loop.

like image 5
Thanatos Avatar answered Oct 20 '22 05:10

Thanatos