Why does pygame.mixer.Sound().play() return None? - python

According to the pygame documentation, pygame.mixer.Sound().play() should return a Channel object. It actually does.
But sometimes, it seems to return None because the very next line, I get this error:
NoneType has no attribute set_volume
when I try to type
channel = music.play(-1)
channel.set_volume(0.5)
Of course the error can happen because the sound is very short, but the error can't come from there (Is 5'38" shorter than the time python needs to shift from one line to the next?)
I also Ctrl+H all the code to see if I typed somewhere channel = None (because I use multiple threads) - Nothing.
Does anybody had the same problem? Is that a pygame bug?
I use python 3.8.2, pygame 2.0.1 and Windows.
Currently I bypass the error rather than fix it like that:
channel = None
while channel is None:
channel = music.play()
channel.set_volume(0.5)
But... it doesn't seem to help very much: the game freezes, because pygame constantly returns None.

Sound.play() returns None if it can't find a channel to play the sound on, so you have to check the return value. Using the while loop is obviously a bad idea.
Note that you can either set the volumne of not only an entire Channel, but also on a Sound object, too. So you could set the volume of your music sound before trying to play it:
music.set_volume(0.5)
music.play()
If you want to make sure that Sound is played, you should get a Channel before and use that Channel to play that Sound, something like this:
# at the start of your game
# ensure there's always one channel that is never picked automatically
pygame.mixer.set_reserved(1)
...
# create your Sound and set the volume
music = pygame.mixer.Sound(...)
music.set_volume(0.5)
music.play()
# get a channel to play the sound
channel = pygame.mixer.find_channel(True) # should always find a channel
channel.play(music)

Related

Loading and playing music in python

So i'm trying to play music on pygame and so far I can successfully load and play the music on the program, but I can do that without variables and i'm not sure how to do it with variables involved.
I've already tried to store them inside a variable and playing them from the variable.
Menumusic = pygame.mixer.music.load("MainMenu.mp3")
Menumusic.play(-1, 0.0)
I expect the music to play but instead i get this as the output:
Menumusic.play(-1, 0.0)
AttributeError: 'NoneType' object has no attribute 'play'
According to the documentation you should call pygame.mixer.music.play() to start playback of the loaded music stream.
It's not very clear what you mean by playing from a variable but it seems to me that you want to change what music is played. You can't do without your variable Menumusic because it contains all the functionality needed to play the music.
myvariablemp3 = "MainMenu.mp3" # Change this to some way of varying the filename
Menumusic = pygame.mixer.music.load(myvariablemp3)
Menumusic.play(-1, 0.0)
Generally to hold a sound in a variable, the code needs to use pygame.mixer.Sound() to load the file in.
For example:
drum_beat = pygame.mixer.Sound("bass_drum.wav")
Later on in the code, the pre-loaded sound can be played by passing the result from that .Sound(...) call (in this case drum_beat) to pygame.mixer.Sound.play().
drum_beat = pygame.mixer.Sound("bass_drum.wav")
...
pygame.mixer.Sound.play( drum_beat )

How would I be able to pan a sound in pygame?

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?
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
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.
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:
pygame.mixer.Channel
pygame.mixer.Sound
pygame.mixer.Channel.set_volume

Music player for Python not working

I want to be able to play multiple songs through a playlist using python, but it will only play the last song on the list. Please help.
from pygame import mixer # Load the required library
from os import listdir
k = listdir('C:/LOCAL')
print(k)
mixer.init()
for x in k:
y = "C:/LOCAL/" + x
print y
mixer.music.queue(y)
mixer.music.load(y)
mixer.music.play()
Your problem is that you assume that playing music with pygame will pause the program until the music is finished - which is not the case. As a result, it tries starting a song, and then it's starting another, and another, etc.
There are a few ways of trying to correct this. You could either:
Use pygame events and "tell" pygame to fire an event when the song finishes (though this requires a display surface (window) to be opened within pygame), or
Detect the length of the song, and sleep for that amount of time (which is more compatible with your current code).
I'm going to assume that you would like to do option 2, since your code works better with it.
To get the length of an MP3 file (I've not tried it with any other types), you could use the Mutagen library.
Some example code to get the length of an MP3 file (in seconds):
from mutagen.mp3 import MP3
tracklength = MP3("/path/to/song.mp3").info.length
Then you could substitute the path with y, and time.sleep for the amount of time returned, before continuing to the next iteration of the loop.
Hope that helps.
(also, you don't need to queue a file before loading it - just load and play)

Stop Winsound / Stop a thread on Python

Im writing a litle game on python using Tkinter (And by the way, i am not allowed to use any other non built in modules) and I want to play a background song when on the main window, wich is the one that holds the title, and the buttons to go to other windows and stuff...
So the thing is that I need that sound to stop when I enter to another window, but when I hit the button to go, to another window, the song keeps playing...
I'm using the winsound module, and had define a couple of functions, (That by the way, are wicked as hell) to play the song when I first run the program using threads...
So here is the deal, I want something to put in the function "killsound" so that I can add it to every button, and then when I press any button to open any other window, the sound will be killed.
I was hoping something like 'a.kill()' or 'a.stop()' but it didnt worked.
And I really don´t know how to use the SND_PURGE thing on winsound... Although I understand that SND_PURGE is no longer working on new windows OS (Got Win8.1)
Could you please help me?
Thank You! (And Sorry for the creepy english...)
def Play(nombre): #This function Is the core of the winsound function
ruta = os.path.join('Audio',nombre)
Reproducir= winsound.PlaySound(ruta,winsound.SND_FILENAME)
return Reproducir
def PlayThis():
while flag_play:
try:
return Play('prettiest weed.wav')
except:
return "Error"
def PlayThisThread():
global flag_play
flag_play= True
a=Thread(target=PlayThis, args=())
a.daemon = True
a.start()
PlayThisThread()
def killsound(): #This is the function I want, for killing sound.
global flag_play
flag_play = False
There are 2 major problems in your code:
global variable flag_play has to be placed inside the sound playback loop, in your case within the PlayThis() function
the Winsound module is aiming simple non-threaded usage. while the sound is playbacked, there is no chance to "softly" interrupt. It does not support any playback status reporting e.g. .isPlaying() nor any kind of .stop() that you need in order to kill it.
Solution:
try PyMedia package. Pymedia allows lower-level audio manipulation therefore more details have to be provided at the initialisation:
import time, wave, pymedia.audio.sound as sound
# little to do on the proper audio setup
f= wave.open( 'prettiest weed.wav', 'rb' )
sampleRate= f.getframerate() # reads framerate from the file
channels= f.getnchannels()
format= sound.AFMT_S16_LE # this sets the audio format to most common WAV with 16-bit codec PCM Linear Little Endian, use either pymedia or any external utils such as FFMPEG to check / corvert audio into a proper format.
audioBuffer = 300000 # you are able to control how much audio data is read
with the following assigment the "snd" becomes an instance of the class sound.Output and gives you bunch of useful audio methods:
snd= sound.Output( sampleRate, channels, format )
s= f.readframes( audioBuffer )
snd.play( s )
and finally your threaded playback loop might look as follows:
while snd.isPlaying():
global flag_play
if not flag_play: snd.stop() #here is where the playback gets interupted.
time.sleep( 0.05 )
f.close()
Please, let me know if you need more support on this.
I also created a 0.5 sec wavfile in Adobe Audition containing silence and attached it to the stop button, and this basically "stopped" playback of the previously played audio clip.
I found a way to do it, by adding a 0.5sec sound to a button, so that when I press the button it stops the background one to play the button one, and then stops all sounds on program.

Find the Length of a Song with Pygame

I'm building an Radio Automation Program, but I can't figure out how to have a timer countdown the number of seconds left in the song. I'm currently using Pygame and don't really want to load another toolkit just for this. So far I can make a timer count up using this:
import pygame
#setup music
track = "Music/Track02.wav"
pygame.mixer.music.load(track)
pygame.mixer.music.play()
print("Playing Music")
while(pygame.mixer.music.get_busy()):
print "\r"+str(pygame.mixer.music.get_pos()),
But I have no idea how to get the total length of the song and countdown without having played the song already.
You could also load the song as a normal sound and then check the length while using mixer.music for playing it.
a = pygame.mixer.Sound("test.wav")
print("length",a.get_length())
The mutagen.mp3 can be used to find the length of music.
Firstly: pip install mutagen
-> İmport:
from pygame import *
from mutagen.mp3 import MP3
-> Use:
mixer.music.load('example.mp3')
song = MP3('example.mp3')
songLength = song.info.length
Check the Documentation. According to the site, this function returns the length of a sound object in seconds. So, time remaining is simply (pygame.mixer.music.get_length() - pygame.mixer.music.get_pos())*-1.
That way, it displays as a negative number like most time remaining counters do in a music player. Note, I don't have pygame on this computer, so I can't test it. So, check it just to make sure.

Categories

Resources