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!