3

Basically, what I want to accomplish here, is a way to pan the sound while the game is running. I'd like to make the volumes (left and right) change depending on the position of the player. For now I've got a simple code that I thought that would be a good test:

pygame.mixer.init()

self.sound = pygame.mixer.Sound("System Of A Down - DDevil #06.mp3")
print("I could get here")
self.music = self.sound.play()

self.music.set_volume(1.0, 0)

First, I tried something similar but with pygame.mixer.music, but I came to realize that there's no way to change the volumes separately this way, or so I think, then I changed to the code presented here. Now it seems like the file is not able to be loaded, my guess is that the file is too big to be treated as a sound in pygame. Any idea of how I'd be able to make this work?

Matheus Fernando
  • 159
  • 3
  • 11

3 Answers3

5

You can pan on the Channel like this:

from os import split, join
import pygame
import pygame.examples.aliens
pygame.init()
# get path to an example punch.wav file that comes with pygame.
sound_path = join(split(pygame.examples.aliens.__file__)[0], 'data', 'punch.wav')
sound = pygame.mixer.Sound(sound_path)
# mixer can mix several sounds together at once.
# Find a free channel to play the sound on.
channel = pygame.mixer.find_channel()
# pan volume full loudness on the left, and silent on right.
channel.set_volume(1.0, 0.0)
channel.play(sound)

https://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Channel

René Dudfield
  • 591
  • 4
  • 10
1

It may be worth looking into a seperate audio library for this. In general i'd recommend PortAudio (which is C), but using the python bindings for it provided by PyAudio. This will give you much more control over the exact audio stream.

To simplify this even further, there is a library known as PyDub which is built ontop of PyAudio for a high level interface (it even has a specific pan method!).


from pydub import AudioSegment
from pydub.playback import play

backgroundMusic = AudioSegment.from_wav("music.wav")

# pan the audio 15% to the right
panned_right = backgroundMusic.pan(+0.15)

# pan the audio 50% to the left
panned_left = backgroundMusic.pan(-0.50)

#Play audio
while True:
    try:
       play(panned_left)
      #play(panned_right)

If this is too slow or doesn't provide effective real-time implementations then I would definately give PyAudio a go because you will also learn a lot more about audio processing in the process!

PS. if you do use PyAudio make sure to check out the callback techniques so that the game you are running can continue to run in parallel using different threads.

WoodyDev
  • 1,386
  • 1
  • 9
  • 19
  • It seems like using pydub the whole music is loaded and then the panning recalculates it all and creates a second file to be played, but this consumes a lot of time. Do you know a way to change the system's (Windows or Ubuntu) volume? – Matheus Fernando Feb 06 '18 at 13:33
  • In my experience I havent use PyDub as much as pure PortAudio but the interface seemed like a good place to start. Looking around, I found the [PySoundCard Library](https://github.com/bastibe/PySoundCard) which works as a similar way to PyAudio (uses PortAudio) but looks more performant (at first glance at least). If you check out the callback section you can see that you can modify the audio in Numpy arrays. I.e. you should be able to manipulate one "column" of audio individually as each block passes (worth reading up on threading). I would avoid system audio volume adjustments if you can. – WoodyDev Feb 06 '18 at 14:27
  • Thanks for your help, I've managed to find what was going wrong somehow. It seems like it wasn't possible to load a `.mp3` file to `pygame.mixer.Sound()`, so I basically converted the file to `.wav` format. I'll take a deeper look into PyAudio as well, since I'm interested in this sound processing. – Matheus Fernando Feb 06 '18 at 22:21
0

This answer certainly can help you.

Basically, you get the screen width, then you pan the left according to player.pos.x / screen_width, and the right according to 1 - player.pos.y / screen_width like that:


Pseudocode:

channel = music.play() # we will pan the music through the use of a channel
screen_width = screen.get_surface().get_width()

[main loop]:
    right = player.pos.x / screen_width
    left =  player.pos.x / screen_width

    channel.set_volume(left, right)

Documentation:

D_00
  • 1,440
  • 2
  • 13
  • 32