class SoundDecorator(FrameDecorator): def __init__(self, parent_component=NullComponent(), sound_file_path=''): self.sound_file_path = sound_file_path self.parent = parent_component self.sound = Sound(sound_file_path) self.start_time = 0 def get_parent(self): return self.parent def set_parent(self, new_parent): self.parent = new_parent def get_landmarks(self): self.parent.get_landmarks() def get_image(self): self.play() return self.parent.get_image() def play(self): if not self.is_playing(): self.start_time = time() self.sound.play() def is_playing(self): now = time() return self.sound.get_length() > now - self.start_time def stop(self): self.sound.stop() self.start_time = 0 def copy(self): return SoundDecorator(parent_component=self.parent.copy(), sound_file_path=self.sound_file_path)
def thunder(self, inc): wait = random.randint(0, 2+inc) while (self._last == wait): wait = random.randint(0, 2+inc) self._last = wait print "intensity:", wait thunder = Sound("%s/thunder_0%d.wav" % (self._data_folder, wait)) if wait < 6: self.lightning.set_C(ON) self.lightning.send() time.sleep(random.uniform(0.5, 1.5)) self.lightning.set_C(OFF) self.lightning.send() time.sleep(wait) if wait < 6: self.lightning.set_B(OFF) self.lightning.send() thunder.play() if wait < 6: time.sleep(0.3) self.lightning.set_B(ON) self.lightning.send() time.sleep(thunder.get_length()-0.3) thunder.fadeout(200) time.sleep(wait)
class SoundDecorator(FrameDecorator): def __init__(self, parent_component=NullComponent(), sound_file_path=''): self.sound_file_path = sound_file_path self.parent = parent_component self.sound = Sound(sound_file_path) self.start_time = 0 def get_parent(self): return self.parent def set_parent(self, new_parent): self.parent = new_parent def get_landmarks(self): self.parent.get_landmarks() def get_image(self): self.play() return self.parent.get_image() def play(self): if not self.is_playing(): self.start_time = time() self.sound.play() def is_playing(self): now = time() return self.sound.get_length() > now - self.start_time def stop(self): self.sound.stop() self.start_time = 0 def copy(self): return SoundDecorator(parent_component=self.parent.copy(), sound_file_path=self.sound_file_path)
def prep_startup_sound(): if STARTUP_FILENAME: global STARTUP_SOUND global STARTUP_DUR STARTUP_SOUND = Sound(STARTUP_PATH) STARTUP_DUR = STARTUP_SOUND.get_length()
def prep_shutdown_sound(): global SHUTDOWN_SOUND global SHUTDOWN_DUR SHUTDOWN_SOUND = Sound(SHUTDOWN_PATH) SHUTDOWN_SOUND.set_volume(.1) SHUTDOWN_DUR = SHUTDOWN_SOUND.get_length()
class Sound(object): """ todo is to wrap all the channel methods so they only work on the sound. This could happen either by checking if the sound is playing on the channel or possibly by calling methods on the sound itself. mixer documentation: https://www.pygame.org/docs/ref/mixer.html This class ads support for 3D positioning. Just pass the position of the sound in a tuple of (x,y) coordinates after the path of the file. """ def __init__(self, filename, position=None, single_channel=True): self.name = filename self.pos = position # should be a tuple (x, y) self.channel = None self.paused = False self.playing = False self.stopped = False # this is to tell a callback if the sound was stopped by a stop function or not. self.single_channel = single_channel self.callback = lambda e: None # the callback is passed this object as an argument and is triggered at the end of the sound self._id = "sound-%s-%s" % (self.name, id(self)) try: self.sound = Mixer_sound(filename) except: raise Exception("Unable to open file %r" % filename) def play(self, loops=0, maxtime=0, fade_ms=0): self.playing = True self.stopped = False if not self.channel or ( (not self.single_channel or self.channel.get_sound() != self.sound) and self.channel.get_busy()): self.channel = find_channel() or self.channel self.channel.play(self.sound, loops, maxtime, fade_ms) if self.pos: playing_sounds.append(self) self.set_volume() event_queue.schedule( function=self.check_if_finished, repeats=-1, delay=0, name=self._id ) # this uses the channel.get_busy to figure out if the sound has finished playing. # event_queue.schedule(function=self.finish, repeats=1, delay=self.get_length()-0.09, name=self._id) # This does the same as above, but uses the length of the sound to schedule an end event. The problem with this is that if one pauses the sound, the event still runs. The pro is that the end event can be faster than the actual sound. def get_volume(self): return self.channel.get_volume() def move_pos(self, x=0, y=0): cx, cy = self.pos self.set_pos(cx + x, cy + y) return self.pos def set_pos(self, x, y): self.pos = (float(x), float(y)) if (self.channel): self.channel.set_volume(*position.stereo(*self.pos)) def get_pos(self): return self.pos def get_length(self): return self.sound.get_length() def toggle_pause(self): """This function can be called to pause and unpause a sound without the script needing to handle the check for paused and unpaused. If the sound is paused when this function is called, then the sound is unpaused and if the sound is playing when this function is called, then the sound is paused. It's very good for buttons to play and pause sounds.""" if not self.channel or not self.playing: self.play() elif self.paused: self.unpause() else: self.pause() def unpause(self): if not self.channel: return False self.channel.unpause() self.paused = False def is_paused(self): return self.paused def pause(self): if not self.channel: return False self.channel.pause() self.paused = True def stop(self): """This stops the channel from playing.""" event_queue.unschedule(self._id) if self in playing_sounds: playing_sounds.remove(self) self.playing = False self.paused = False self.stopped = True self.unpause() if self.channel: self.channel.stop() self.finish() def stop_sound(self): """This stops the sound object from playing rather than the channel""" self.sound.stop() self.unpaws() self.playing = False mixer_queue.remove(self.channel) if self in playing_sounds: playing_sounds.remove(self) def get_busy(self): """Returns if the channel is active. This is used for triggering the callback""" if self.channel: return self.channel.get_busy() return False def check_if_finished(self): """This runs every tick to see if the channel is done. if it is done, then it runs the finish method and removes itself from the event_queue.""" if not self.get_busy(): self.finish() event_queue.unschedule(self._id) def toggle_playing(self): if self.playing: self.stop() else: self.play() def set_volume(self, volume=1, right=None): """Sets the volume if there is a channel. If there is a position, then volume is adjusted to be that position. If no arguments are passed, then it will update the volume to be the current pos, or set volume back to 1.""" if not self.channel: return 0 if volume > 1: volume = 1 elif volume <= 0: self.channel.set_volume(0) return 0 if not self.pos and not right: self.channel.set_volume(volume) elif right: self.channel.set_volume(volume, right) else: x, y = self.pos if x == 0 and y == 0: self.channel.set_volume(volume) return 0 self.channel.set_volume(*position.stereo(x / volume, y / volume)) def finish(self): self.playing = False self.paused = False self.callback(self)
class IntroAnimator(object): """This class is in charge of animating the Title Screen's introductory animation. Attributes: is_running: A Boolean indicating whether the animation is currently being shown. voice: A PyGame Sound with the announcer stating the game's title. voice_timer: An integer counter that keeps track of how many update cycles have passed since the voice clip started playing. voice_has_played: A Boolean indicating whether the voice clip has already played. voice_duration: An integer for the duration of the voice clip, in update cycles. """ def __init__(self): """Declare and initialize instance variables.""" self.is_running = False self.voice = Sound(VOICE_PATH) self.voice_duration = (self.voice.get_length() * FRAME_RATE) self.voice_timer = 0 self.voice_has_played = False def update(self, time, bg, logo, sound_channel): """Update the animation's processes. The animation will proceed as follows: 1. Scroll the background up from the bottom edge until it fills the screen. 2. Fade in the logo. 3. As it fades in, play the voice clip. 4. Once the voice clip finishes, play the title theme and activate the logo's animation. Args: time: A float for the time elapsed, in seconds, since the last update cycle. bg: The Title Screen's background Animation. logo: The game logo Animation. sound_channel: The PyGame Channel that will be used to play the announcer's voice. """ if bg.exact_pos[1] > 0.0: self.move_background_up(bg, time) elif logo.image.get_alpha() < 255: self.fade_in_logo(logo) if (logo.image.get_alpha() >= FADE_DELAY and (not self.voice_has_played)): sound_channel.play(self.voice) self.voice_has_played = True elif (self.voice_has_played and self.voice_timer < self.voice_duration): self.voice_timer += 1 elif self.voice_timer >= self.voice_duration: pygame.mixer.music.play(-1) logo.is_animated = True self.is_running = False def move_background_up(self, bg, time): """Move the background up at a set speed. The background will not move past the top edge of the screen. Args: bg: The Title Screen's background Animation. time: A float for the amount of time, in seconds, elapsed since the last update cycle. """ distance = -1 * BG_SCROLL_SPEED * time bg.move(0, distance) if bg.exact_pos[1] < 0.0: distance = 0.0 - bg.exact_pos[1] bg.move(0, distance) def fade_in_logo(self, logo): """Gradually increase the opacity of the game logo. Args: logo: The game logo Animation. """ old_alpha = logo.image.get_alpha() logo.image.set_alpha(old_alpha + FADE_LOGO_RATE) def skip_intro(self, bg, logo, sound_channel): """Skip to the end of animation immediately. Args: bg: The Title Screen's background Animation. logo: The game logo Animation. sound_channel: The PyGame Channel that will be used to play the announcer's voice. """ if not self.voice_has_played: sound_channel.play(self.voice) bg.move(0, -1 * bg.rect[1]) pygame.mixer.music.play(-1) logo.image.set_alpha(255) logo.is_animated = True self.is_running = False def reset(self, bg, logo): """Prepare the animation to be shown. This method must be called before the first call to update() to ensure that all components are ready for use. If not, bad things will happen... (maybe) Args: bg: The Title Screen's background Animation. logo: The game logo Animation. """ pygame.mixer.stop() pygame.mixer.music.load(MUSIC_PATH) self.voice_timer = 0 self.voice_has_played = False bg.exact_pos = (0.0, SCREEN_SIZE[1] + BG_OFFSET) logo.is_animated = False logo.image.set_alpha(0) self.is_running = True