## Hearing the Mixer in Action

Let's write a script to experiment with sound effects in Pygame. If you run Listing 10-4 you will see a white screen with a mouse cursor. Click anywhere on the screen to throw out a silver ball that falls under gravity and plays a sound effect when it bounces off the edge or bottom of the screen (see Figure 10-5).

When the update method of the Ball class detects that the sprite has hit the edge or bottom of the screen, it reverses the direction of the sprite and calls the play method of the Sound object. The sprite's x coordinate is used to calculate the volume of the left and right speakers, so that the sound appears to be emitted from the point where the sprite has hit the screen boundary. The effect is quite convincingâ€”if you close your eyes, you should still be able to tell where the ball bounces!

If you create a lot of sprites by clicking the mouse quickly, you will probably be able to detect that some bounces stop producing the sound effect. This happens because all available channels are used up playing the same sound effect, and new sounds can only play when a channel becomes free.

Figure 10-5. bouncesound.py

Listing 10-4. Mixer in Action (bouncesound.py) SCREEN_SIZE = (640, 480)

# In pixels per second, per second GRAVITY = 250.0

# Increase for more bounciness, but don't BOUNCINESS = 0.7

over 1!

import pygame from pygame.locals import *

from random import randint from gameobjects.vector2 import Vector2

def stereo_pan(x_coord, screen_width):

right_volume = float(x_coord) / screen_width left_volume = 1.0 - right_volume return (left_volume, right_volume)

class Ball(object):

def _init_(self, position, speed, image, bounce_sound):

self.position = Vector2(position) self.speed = Vector2(speed) self.image = image self.bounce_sound = bounce_sound self.age = 0.0

def update(self, time_passed):

screen_width, screen_height = SCREEN_SIZE

# Has the ball bounced? bounce = False

# Has the ball hit the bottom of the screen? if y + h >= screen_height:

self.speed.y = -self.speed.y * BOUNCINESS self.position.y = screen_height - h / 2.0 - 1.0 bounce = True

# Has the ball hit the left of the screen? if x <= 0:

self.speed.x = -self.speed.x * BOUNCINESS self.position.x = w / 2.0 + 1 bounce = True

# Has the ball hit the right of the screen? elif x + w >= screen_width:

self.speed.x = -self.speed.x * BOUNCINESS self.position.x = screen_width - w / 2.0 - 1 bounce = True

# Do time based movement self.position += self.speed * time_passed

# Add gravity self.speed.y += time_passed * GRAVITY

if bounce:

self.play_bounce_sound()

self.age += time_passed def play_bounce_sound(self):

channel = self.bounce_sound.play()

if channel is not None:

# Get the left and right volumes left, right = stereo_pan(self.position.x, SCREEN_SIZE[0]) channel.set_volume(left, right)

def render(self, surface):

# Draw the sprite center at self.position w, h = self.image.get_size()

# Initialize 44KHz 16-bit stereo sound pygame.mixer.pre_init(44l00, -16, 2, 1024*4)

pygame.init()

screen = pygame.display.set_mode(SCREEN_SIZE, 0)

pygame.mouse.set_visible(False)

clock = pygame.time.Clock()

# Load the sound file bounce_sound = pygame.mixer.Sound("bounce.wav")

while True:

for event in pygame.event.get():

if event.type == QUIT: return if event.type == MOUSEBUTTONDOWN:

# Create a new ball at the mouse position random_speed = ( randint(-400, 400), randint(-300, 0) ) new_ball = Ball( event.pos, random_speed, ball_image, bounce_sound ) balls.append(new_ball)

time_passed_seconds = clock.tick() / 1000.

for ball in balls:

ball.update(time_passed_seconds) ball.render(screen)

# Make a note of any balls that are older than 10 seconds if ball.age > 10.0:

# remove any 'dead' balls from the main list for ball in dead_balls:

balls.remove(ball)

# Draw the mouse cursor mouse_pos = pygame.mouse.get_pos() screen.blit(mouse_image, mouse_pos)

pygame.display.update()