Reputation: 145
I've created a simple text-based game in Python which I'm using in conjunction with libPd (wrapper for Pure Data). All the game code was written before the audio was implemented and works as intended; similarly the libPd script itself also works perfectly on its own. However getting them to play nice together is proving to be tricky.
I assume it's to do with while loops and my usage of them.
Below is an extract from the game code -
while True:
command = raw_input().lower()
if command == "commands":
print '"look around"'
print '"explore"'
print '"inventory"'
print '"examine"'
print '"take"'
print '"combine"'
print '"quit"'
elif command == "look" or command == "look around":
char.look()
...etc... ...etc...
While the libPd script on its own is as follows -
while True:
if not ch.get_queue():
for x in range(BUFFERSIZE):
if x % BLOCKSIZE == 0:
outbuf = m.process(inbuf)
samples[selector][x][0] = outbuf[(x % BLOCKSIZE) * 2]
samples[selector][x][1] = outbuf[(x % BLOCKSIZE) * 2 + 1]
ch.queue(sounds[selector])
selector = int(not selector)
libpd_release()
I originally tried indenting the entire game code within the libPd section but that caused the audio to only play once a command was typed, stopping once the print messages had been returned.
How do I go about combining the two so that the music is constant whilst the player is free to run through the rest of the commands/game?
Upvotes: 1
Views: 386
Reputation: 3831
The while True:
loops in your code are "blocking", which means that while that loop is running then no other loop runs on the same thread. A simple example of running multiple threads is:
import threading
import time
class MyGame(object):
def __init__(self):
# an "event" is used to tell the threads when to stop
# this is "thread safe", meaning it can be used by a number
# of different threads without causing problems
self.stop_event = threading.Event()
# create and start a thread to run your sound
self.sound_thread = threading.Thread(target=self.sound, args=[self.stop_event])
print "Starting sound thread"
self.sound_thread.start()
# create and start a thread to run your game
self.game_thread = threading.Thread(target=self.game, args=[self.stop_event])
print "Starting game thread"
self.game_thread.start()
def sound(self, stop_event):
print "Entering sound thread"
while not stop_event.is_set():
# put the bit that used to be in the While True loop in here
time.sleep(0.5)
print "Exiting sound thread"
def game(self, stop_event):
print "Entering game thread"
while not stop_event.is_set():
# put the bit that used to be in the While True loop in here
time.sleep(0.5)
print "Exiting game thread"
def stop(self):
"""Used to stop the threads from running"""
print "Stopping threads"
self.stop_event.set()
self.sound_thread.join()
print "Sound thread stopped"
self.game_thread.join()
print "Game thread stopped"
Have a look at the Python Threading documentation for more information - its very thorough. To run the program you would do something like:
game = MyGame()
time.sleep(2)
game.stop()
In your console you will see something like
>>> Starting sound thread
>>> Entering sound thread
>>> Starting game thread
>>> Entering game thread
>>> Stopping threads
>>> Exiting sound thread
>>> Sound thread stopped
>>> Exiting game thread
>>> Game thread stopped
Upvotes: 1
Reputation: 365717
Your problem is that you have to sit around waiting for raw_input()
to return, but at the same time you have to keep processing audio messages off the queue as soon as they come in. How can you do both at the same time?
First, there's the event-loop style that you're using today.
If you can write a function that waits on either input or audio messages, whichever comes first you can rewrite your program around a loop that waits on that function. This is hard to do in general. (GUI frameworks and network server frameworks can help, but either would be a bit silly for your text game.)
You can fake it by only waiting a short time for each line, e.g., by using select.select
on sys.stdin
with a short timeout. But this is a lot of work, and it's hard to balance responsiveness and performance with a design like that.
Alternatively, you can use threads. Here's what it would look like:
def play_music():
while True:
if not ch.get_queue():
for x in range(BUFFERSIZE):
if x % BLOCKSIZE == 0:
outbuf = m.process(inbuf)
samples[selector][x][0] = outbuf[(x % BLOCKSIZE) * 2]
samples[selector][x][1] = outbuf[(x % BLOCKSIZE) * 2 + 1]
ch.queue(sounds[selector])
selector = int(not selector)
libpd_release()
play_music_thread = threading.Thread(target=play_music)
play_music_thread.daemon = True
play_music_thread.start()
while True:
command = raw_input().lower()
if command == "commands":
print '"look around"'
print '"explore"'
print '"inventory"'
print '"examine"'
print '"take"'
print '"combine"'
print '"quit"'
elif command == "look" or command == "look around":
char.look()
If you want to be able to do clean shutdown, instead of just having the music thread killed in the middle of whatever it's doing when you quit, it's a bit more complicated… but not that much. Basically, with a Condition
, Event
, Queue
, or just a boolean variable and a Lock
, you can easily build a way to signal the background music thread from the main thread.
Upvotes: 1