Python 85 - Stereo
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!