Playing sound using pygame (python) - python

As of right now I have a program that plays individual sounds based on a certain key presses. Each key press successfully plays the correct sound based on the key pressed, but the sound is cut off when the next sound from a different key begins to play. I want to know how to have my program play the sounds from each key in their entirety, even when another key is pressed and a new sound begins to play (I want the sounds to play simultaneously). I am using the pygame and keyboard libraries.
Here is the function I am using to play sounds for the keys:
# 'keys' refers to a dictionary that has the key press strings and sound file names stored as key-value pairs.
key = keyboard.read_key()
def sound(key):
play = keys.get(key, 'sounds/c2.mp3')
pygame.mixer.init()
pygame.mixer.music.load(play)
pygame.mixer.music.play()
If you need more context, please tell me and I will update my question.

In pygame, you can play multiple sound tracks simultaneously using channels. Note that channels only support wav or ogg files.
Here is some sample code. Press keys 1-3 to start a track. You can also start the same track multiple times.
import pygame
lst = [
'Before The Brave - Free.wav', # key 1
'Imagine Dragons - Demons.wav', # key 2
'Shinedown-How Did You Love.wav' # key 3
]
pygame.init()
size = (250, 250)
screen = pygame.display.set_mode(size)
pygame.mixer.init()
print("channel cnt",pygame.mixer.get_num_channels()) # max track count (8 on my machine)
while True:
pygame.time.Clock().tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
idx = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_1]: idx = 1
if keys[pygame.K_2]: idx = 2
if keys[pygame.K_3]: idx = 3
if (idx):
ch = pygame.mixer.find_channel() # find open channel, returns None if all channels used
snd = pygame.mixer.Sound(lst[idx-1]) # create sound object, must be wav or ogg
if (ch): ch.play(snd) # play on channel if available

Related

Get any objects (Rect, Surfaces...) at position / coordinates X, Y

So the question is simple:
Given a Surface, let's call it screen and x,y coordinates, can I get anything that lays at that coordinates on that Surface?
For example, let's say we have typical, Player attack, and if the attack reach the Enemy position x,y then enemy dies.
So given this simple app (is an example only not a real app)
import pygame as pg
from pygame.math import Vector2
# pygame constants
CLOCK = pg.time.Clock()
WIN_SIZE = (1280, 640)
# pygame setup
pg.init()
# screen
window = pg.display.set_mode(WIN_SIZE, 0, 32)
background = pg.Surface(WIN_SIZE)
player = pg.Surface(Vector2(12, 64))
player_rect = player.get_rect(topleft=Vector2(150, 150))
player_attack = False
player.fill((102, 255, 178))
player_attack_range = 20 # player can hit at min 20 pixel from target
enemy = pg.Surface(Vector2(12, 64))
enemy_rect = player.get_rect(topleft=Vector2(175, 150))
enemy.fill(pg.Color("green"))
while True:
background.fill((0, 0, 0)) # screen clear
# Render enemy
attacked = False
if player_attack:
# !!!!! HERE !!!!!
# Now we check if the playuer is close enough to the enemy, so we MUST know the enemy pos
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
if not attacked:
enemy.fill(pg.Color("green"))
background.blit(enemy, enemy_rect.topleft)
# Render player
background.blit(player, player_rect.topleft)
# Events
for event in pg.event.get():
if event.type == pg.QUIT or (
event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE): # x button and esc terminates the game!
exit(1)
# ............. Mouse ............. #
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
player_attack = True
if event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
player_attack = False
pg.display.update() # 2) Update the game
window.blit(background, (0, 0)) # 3) Repaint the screen
CLOCK.tick(60) # 4) Wait 60 Frames
When is attacked
Now I always seen it done this way more or less:
distance_x = abs(player_rect.x - enemy_rect.x)
if distance_x > player_attack_range:
attacked = True
enemy.fill(pg.Color("red"))
With this example, I'm not pointing out the code implementation but the fact that, the player must know the target position and then check whether or not the target is hit
But what I want to know, let's say I don't know the enemy position, and the player just attacks, is there a way that we can get what's currently on the surface at the attack range?
So do something like
attacked_area_x = abs(player_rect.x + player_attack_range) # only care of x coords
rects_or_surfaces_in_area = background.what_have_we_got_here(Vector(attacked_area, 0))
for r in rects_or_surfaces_in_area:
print("Hit!")
Update
So By checking MDN documentation of Game Development MDN I actually find a game algorithm / Technique that is similar (but concept is the same) of my solution.
Is called the Broad Phase
From the documentation:
road phase should give you a list of entities that could be colliding. This can be implemented with a spacial data structure that will give you a rough idea of where the entity exists and what exist around it. Some examples of spacial data structures are Quad Trees, R-Trees or a Spacial Hashmap.
So yes, it seems one of many good approach to solve this problem.
So, after some research and thanks to Rabbid76 and his answer here How do I detect collision in pygame? which covers in details the most common collisions in Pygame, it seems that what I was looking for natively is just not possible.
Maybe is normal, I'm also new to game development and maybe what I want to do just doesn't make any sense, but I bet it does.
The scenario I'm facing is, just one player with a sword hitting, so I asked my self, why should I need to know prior what objects lie on the sword path and check if is hit, instead, do the hit and request to the parent screen "what object are in the sword path"? , which, is for sure faster because we don't need to know what object that have collision are (and avoid a for loop and check for each react/surface).
So, let's say there are many many object that have collision and a player may hit it, it would be way faster do don't know what' there but request it instead to the surface, so basically the surface should be aware of its children / objects.
I tried a bit the Surface.subsurface() and the Surface.get_parent() but couldn't make it work, anyway still, in a surface area we may have many thinks like:
draws, Rect, Surfaces, Sprites, Imgs etc...
I have only 2 solutions in my mind:
Map the objects coordinates
This only really works if, the entities are static, if so, then we could create a dict with key x:y coordinates and then check if the sword is in within a certain x:y and exist in the dict, then the entity is hit.
But with entity then moves by themself, is a nigtmare and will have a worst problem than before, you would need to update the keys at each frame and so on..
Make it 'distance' sensible
So, this could apply to each entity that is moving and may or may not hit something that has a collision. But staying on the example context, let's say we are iterating thourgh entities / object that at each frame are updating their position.
We could check how distant are from the player, let's say 2 chunks away (or the sword lenght) and collect them in a list
Then we that list, we check if, once the sword is updated, is hitting anything close by.
Still, pretty sure there are better ways (without changing or extending pygame its self).
And maybe, by extending pygame, there may be a way to implement a 'surface aware' class that when we request a specific Rect area, it tells us what's there.

Pygame - can I make music have an introduction and then a loop point?

I'm currently working on a game in Pygame, and I've been trying to think of a way to have a music track that loops at a point that isn't the beginning of the track. So essentially, it plays an introduction, then moves onto another section that repeats without revisiting that introduction.
I've thought of a couple of ways that almost worked, but they have problems.
The first was to have two separate audio files for the introduction and the looping section, then to use pygame.music.set_endevent(), and just load the second audio file once the first is finished. This left quite an obvious gap and click though.
The second was to also use two audio files but to queue in the second as the first is loaded. The problem with this is that it seems like you can't change the play mode from 0 (play once) to -1 (looping) for the new queued track...
I feel like there has to be a way of doing this, I'd really appreciate any help.
In the example below, PyGame's sound channels are used for multiple tracks. Here an event is created, such that after 1500 milliseconds, a second sound in played (at the same time as the looping track).
For your suggested use-case, the code could play the intro-music at start, but also set an event-timer for /intro-length/ milliseconds in the future. When that timer-event is received, the looping-part of your music could play continuously, as the intro should have just stopped. Using multiple channels, it should not matter if the two sounds overlap by a few milliseconds (of silence/fadeout), as long as the user does not perceive it of course! Maybe it will be tricky to get the timing 100% correct on vastly different systems, but it should get you close.
Note that in the example, the sounds are already initialised into PyGame Sound objects, I expect this would cut-down on startup latency.
import pygame
# Window size
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 400
DARK_BLUE = ( 3, 5, 54)
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
pygame.display.set_caption("Multi Sound with Timer")
### sound
# create separate Channel objects for simultaneous playback
channel1 = pygame.mixer.Channel(0) # argument must be int
channel2 = pygame.mixer.Channel(1)
# Rain sound from: https://www.freesoundslibrary.com/sound-of-rain-falling-mp3/ (CC BY 4.0)
rain_sound = pygame.mixer.Sound( 'rain-falling.ogg' )
channel1.play( rain_sound, -1 ) # loop the rain sound forever
# Car Horn sound from: https://www.freesoundslibrary.com/car-horn-sound-effect/ (CC BY 4.0)
horn_sound = pygame.mixer.Sound( 'car-horn.ogg' )
# Create a timer, which will (after the delay-time) post an event to the main loop
pygame.time.set_timer( pygame.USEREVENT, 1500 ) # play the horn in 1500 milliseconds
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.USEREVENT ):
# Timer expired, play the sound
channel2.play( horn_sound )
# Movement keys
#keys = pygame.key.get_pressed()
#if ( keys[pygame.K_UP] ):
# print("up")
# Update the window, but not more than 60fps
window.fill( DARK_BLUE )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()

How to append items to a list one time each in python

Lets say I have a list called my_list and a function called my_function and my_function appends items to my_list based on which portion of the surface gameDisplay was clicked on. However whenever you hold down the mouse for more than one frame, it appends more than one of that item to my_list. This is not the result I am going for. I was wondering if there was a way you could do this without appending more than one of each item to my_list
Thanks for your help
You didn't show code but I guess you use pygame.mouse.get_pressed() which gives True all the time when you keep button pressed. And this can be your problem.
You can do one of two things:
use event.MOUSEBUTTONDOWN which is created only once - when button change state from not-pressed into pressed.
Or:
use extra variable which will remeber pygame.mouse.get_pressed() from previous frame. And then compare if now button is pressed but in previous frame was not-pressed then add element to list.
EDIT: old code from different question which use event.MOUSEBUTTONDOWN to change color.
#!/usr/bin/env python
# http://stackoverflow.com/questions/33856739/how-to-cycle-3-images-on-a-rect-button
import pygame
# - init -
pygame.init()
screen = pygame.display.set_mode((300,200))
# - objects -
# create three images with different colors
images = [
pygame.Surface((100,100)),
pygame.Surface((100,100)),
pygame.Surface((100,100)),
]
images[0].fill((255,0,0))
images[1].fill((0,255,0))
images[2].fill((0,0,255))
images_rect = images[0].get_rect()
# choose first image
index = 0
# - mainloop -
running = True
while running:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1 and images_rect.collidepoint(event.pos):
# cycle index
index = (index+1) % 3
# - draws -
screen.blit(images[index], images_rect)
pygame.display.flip()
# - end -
pygame.quit()
GitHub: furas/python-examples/pygame/button-click-cycle-color

psychopy logging time when participant stopped moving cursor

I am using a rating scale. Participants use the 't' and 'b' keys to move the cursor along the scale. Each trial is currently 6 seconds long. If a participant stops pressing 't' or 'b' before 6 seconds are up, I want to log the time of the last keypress in my logfile. However, I'm not sure how to check which keypress is the last. I was thinking of logging the RT of the last keypress in the list, but code is checking for keypresses on every refresh. This is what I have so far:
trialNum=0
for eachPic in catPictures:
prevPos = 0
key=[]
b_list=[]
t_list=[]
timer = core.CountdownTimer(TrialDuration)
event.clearEvents() # get rid of other, unprocessed events
while timer.getTime() > 0:
for key in event.getKeys():
if key in ['escape']:
core.quit() # quit if they press escape
if key in ['b']:
# add keypress to list for each keypress. then move cursor proportionally to length of this list
b_list.append(key)
prevPos+=len(b_list)
if key in ['t']:
t_list.append(key)
prevPos-=len(t_list)
I would just have one list of keys and check the last element once the timer is up, i.e. after the while-loop (upon finish trial).
Don't initiate a whole new timer in each loop. Just reset it. Much more ressource-efficient.
Indent stuff in the while loop.
I don't understand why you move the cursor the distance of the number of previous key presses in that trial. It seems more reasonable to move it a fixed distance per key press. So I did that below.
Definitely check out Jeremy Gray's proposal of using the built-in psychopy.visual.RatingScale (another answer to this question).
Untested code:
timer = core.CountdownTimer(TrialDuration)
stepSize = 1
for eachPic in catPictures:
prevPos = 0 # keeps track of the slider position
rts=[] # used to keep track of what the latest reaction time was. Reset in the beginning of every trial.
timer.reset()
event.clearEvents() # get rid of other, unprocessed events
while timer.getTime() > 0:
for key, rt in event.getKeys(timeStamped=timer): # time keys to this clock
rts += [rt] # add this reaction time to the list
if key in ['escape']:
core.quit() # quit if they press escape
if key in ['b']:
# add keypress to list for each keypress. then move cursor proportionally to length of this list
prevPos+=stepSize
if key in ['t']:
prevPos-=stepSize
# Log here instead of print
print rts[-1]
For a given rating scale, rs, all of the subject's activity is available in rs.history, both during a trial and afterwards. The history is just a list of tuples, where each tuple is (rating, time). If the scale has started, the first tuple is always (None, 0.0). If the last two ratings are the same, then the subject accepted that rating. If they are different, the subject was moving around on the scale but had not accepted a rating at the point when the scale timed out.
Example history:
[(None, 0.0), (3, 0.777), (3, 1.396)]
from psychopy import visual, core
win = visual.Window()
rs = visual.RatingScale(win)
c = core.CountdownTimer(3)
while c.getTime() > 0:
rs.draw()
win.flip()
# print or log:
print rs.history # entire history
print rs.history[-1][1] # just the time of the last rating

skip a video (in a loop) based on keypress in PsychoPy using MovieStim2

I play 3 videos using MovieStim2 in PsychoPy. I suspect this issue stems from not really understanding loops, though.
I want to skip to the next video when the participant presses a key. I understand how my code quits when the participant presses "q", but I'm not sure how to make it skip to the next video when they press "b", for example. Here is my current code:
vidNum = 1
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
clock.reset()
#logfile.write("AfterForLoopTime,%s,Video %s\n" % (core.getTime(), vidNum))
# Create your movie stim.
mov = visual.MovieStim2(win, videopath+f,
size=640,
# pos specifies the /center/ of the movie stim location
pos=[0, 0],
flipVert=False,
flipHoriz=False,
) # loop=False - need to comment this to use a loop
# Start the movie stim by preparing it to play
shouldflip = mov.play()
logfile.write("AfterShouldflipLine58,%s, Video %s\n" % (clock.getTime(), vidNum))
while mov.status != visual.FINISHED:
# Only flip when a new frame should be displayed. Can significantly reduce
# CPU usage. This only makes sense if the movie is the only /dynamic/ stim
# displayed.
if shouldflip:
# Movie has already been drawn , so just draw text stim and flip
#text.draw()
win.flip()
else:
# Give the OS a break if a flip is not needed
time.sleep(0.001)
# Drawn movie stim again. Updating of movie stim frames as necessary
# is handled internally.
shouldflip = mov.draw()
# Check for action keys.....
for key in event.getKeys():
if key in ['escape', 'q']:
win.close()
core.quit()
I tried adding code similar to the last chunk which quits after q:
elif key in ['b']:
break
but I realize that I really want to break out of this loop:
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
However, this doesn't seem to work either
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
for key in event.getKeys():
if key in ['b']:
break
The loop you actually want to break is:
while mov.status != visual.FINISHED:
The easiest way I can think of is to just set your movie status to -1 (or visual.FINISHED) if the user hits the key.
For example:
if key in ['b']:
mov.status = -1
This will break you out of your current video from what I can tell.

Categories