Python Asteroids on GitHub

TIL that I had my speakers swapped left for right. And how to do stereo in pygame.

A bit more browsing in pygame told me that this sequence of code would play a sound in stereo, balanced by fractions between 0 and 1:

``````    def play(self, name, frac_right=0.5):
if name in self.catalog:
sound = self.catalog[name]
count = sound.get_num_channels()
if count == 0:
chan = self.catalog[name].play()
chan.set_volume(1 - frac_right, frac_right)
``````

To my surprise, the sound seemed wrong, and swapping the values seemed right. I examined my speakers and they did not specify which was right or left. Some searching through the questions on the site where I got them told me that the one with two wires was “right”. So I swapped them. Now, with this code, the ship’s roar comes through the correct speakers, depending where it is on the screen.

``````    def power_on(self, dt):
self._accelerating = True
frac_right = self.position.x/u.SCREEN_SIZE
player.play("accelerate", frac_right)
accel = dt * self._acceleration.rotate(-self._angle)
self.accelerate_by(accel)
``````

This is rather a crock, though, making everyone compute the fraction. So now that I have a proof of concept, let’s instead pass in the location and let Sounds sort it out.

``````    def power_on(self, dt):
self._accelerating = True
player.play("accelerate", self._location)
accel = dt * self._acceleration.rotate(-self._angle)
self.accelerate_by(accel)
``````

And …

``````class Sounds:
def play(self, name, location):
if name in self.catalog:
sound = self.catalog[name]
count = sound.get_num_channels()
if count == 0:
chan = self.catalog[name].play()
if location:
frac_left, frac_right = location.stereo_fractions()
else:
frac_left, frac_right = 0.5, 0.5
chan.set_volume(frac_left, frac_right)

class MovableLocation:
def stereo_fractions(self):
frac_right = self.position.x/u.SCREEN_SIZE
return 1-frac_right, frac_right
``````

That does the job and passes all the tests. Commit: ship acceleration sound is now in stereo. Sounds supports stereo by passing location to play.

That’s nearly good but the play method seems a bit messy. Let’s extract some goodies.

``````    def play(self, name, location):
if name in self.catalog:
sound = self.catalog[name]
count = sound.get_num_channels()
if count == 0:
chan = self.catalog[name].play()
self.set_volume(chan, location)

@staticmethod
def set_volume(chan, location):
if location:
frac_left, frac_right = location.stereo_fractions()
else:
frac_left, frac_right = 0.5, 0.5
chan.set_volume(frac_left, frac_right)
``````

Or, for extra credit:

``````class Sounds:
def play(self, name, location=None):
if name in self.catalog:
sound = self.catalog[name]
count = sound.get_num_channels()
if count == 0:
chan = self.catalog[name].play()
self.set_volume(chan, location)

@staticmethod
def set_volume(chan, location):
frac_left, frac_right = location.stereo_fractions() if location else (0.5, 0.5)
chan.set_volume(frac_left, frac_right)
``````

Better? Too fancy? Perhaps too fancy, and I’ve seen some odd messages come out while testing this.

My attempt to write a test around this idea tells me there’s definitely something spooky going on with returning two values and then using that `if` construct. A better thing to do is this:

``````class MovableLocation:
def stereo_right(self):
return self.position.x/u.SCREEN_SIZE

class Sounds:
@staticmethod
def set_volume(chan, location):
frac_right = location.stereo_right() if location else 0.5
chan.set_volume(1-frac_right, frac_right)
``````

There was something I didn’t understand about returning the two values, and this is more clear and generally simpler anyway.

Commit: improve stereo code involving location and sounds.

## Summary

All I came here to do was to get the acceleration roar in stereo. I promise I’ll provide a video with sound as soon as I find out an easy way to do it without going full-on video editor.

See you next time!