changeset 262:de60329cfc9f

Factor out sound stuff
author Neil Muller <drnlmuller@gmail.com>
date Fri, 08 Apr 2011 11:29:37 +0200
parents 7668243695f4
children 44cd7cfd2de3
files data/sounds/silence.ogg data/sounds/sources.txt skaapsteker/__main__.py skaapsteker/cutscene.py skaapsteker/engine.py skaapsteker/level.py skaapsteker/levelscene.py skaapsteker/menuscene.py skaapsteker/sound.py
diffstat 9 files changed, 98 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
Binary file data/sounds/silence.ogg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/sounds/sources.txt	Fri Apr 08 11:29:37 2011 +0200
@@ -0,0 +1,6 @@
+Sources for the various sound files:
+
+[silent.ogg]
+Generated 2 secs of silence - dd if=/dev/zero of=silence.pcm bs=176400 count=2 ; oggenc -r silence.pcm
+Generated by Neil Muller, Aug 2010
+Not copyrightable.
--- a/skaapsteker/__main__.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/__main__.py	Fri Apr 08 11:29:37 2011 +0200
@@ -8,10 +8,11 @@
 from pygame.locals import SWSURFACE
 
 from . import options
-from .constants import SCREEN, FREQ, BITSIZE, CHANNELS, BUFFER, DEBUG
+from .constants import SCREEN, DEBUG
 from .engine import Engine
 from .levelscene import LevelScene
 from .menuscene import MenuScene
+from .sound import SoundSystem
 
 
 def parse_args(args):
@@ -39,27 +40,17 @@
     level = parse_args(sys.argv)
     pygame.display.init()
     pygame.font.init()
-    if options['sound']:
-        try:
-            pygame.mixer.init(FREQ, BITSIZE, CHANNELS, BUFFER)
-        except pygame.error, exc:
-            raise
-            # TODO: bail out to no_sound(exc)
-    else:
-        # Ensure get_sound returns nothing, so everything else just works
-        # TODO: bail out to disable_sound()
-        pass
-
+    soundsystem = SoundSystem(options['sound'])
     pygame.display.set_mode(SCREEN, SWSURFACE)
     #pygame.display.set_icon(pygame.image.load(
     #    data.filepath('icons/nine_tales24x24.png')))
     pygame.display.set_caption("Nine Tales")
 
-    engine = Engine()
+    engine = Engine(soundsystem)
     if level is not None:
-        engine.change_scene(LevelScene(engine.game_state, level))
+        engine.change_scene(LevelScene(engine.game_state, soundsystem, level))
     else:
-        engine.change_scene(MenuScene(engine.game_state))
+        engine.change_scene(MenuScene(engine.game_state, soundsystem))
     try:
         engine.run()
     except KeyboardInterrupt:
--- a/skaapsteker/cutscene.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/cutscene.py	Fri Apr 08 11:29:37 2011 +0200
@@ -9,15 +9,14 @@
 from .widgets.text import Text, ButtonSet, TextButton, unindent_text
 
 class CutScene(Scene):
-    def __init__(self, game_state, text, background, music=None):
-        super(CutScene, self).__init__(game_state)
+    def __init__(self, game_state, soundsystem, text, background, music=None):
+        super(CutScene, self).__init__(game_state, soundsystem)
         self.background = data.load_image('backgrounds/' + background)
         self.start_time = pygame.time.get_ticks()
         self.run_time = 60000 # ms
 
         self._background_music = None
-        if music and pygame.mixer.get_init():
-            self._background_music = data.filepath(music)
+        self._background_music = music
 
         text_widget = Text(text, pygame.Rect(20, 20, 800-40, 600-40),
                            size=24, shadow='gray', wrap=True)
@@ -56,15 +55,10 @@
 
     def enter(self):
         if self._background_music:
-            pygame.mixer.music.load(self._background_music)
-            pygame.mixer.music.play(-1)
-
-    def leave(self):
-        if self._background_music:
-            pygame.mixer.music.stop()
+            self._soundsystem.play_background_music(self._background_music)
 
 
-def opening_cutscene(game_state):
+def opening_cutscene(game_state, soundsystem):
     text = u"""
     Many moons ago, an evil nine-tailed kitsune, a fearsome fox god, ruled the
     land.
@@ -82,4 +76,4 @@
     The kitsune stole your tail. Now it’s time to get it back.
     """
     text = unindent_text(text)
-    return CutScene(game_state, text, 'background_01_back.png')
+    return CutScene(game_state, soundsystem, text, 'background_01_back.png')
--- a/skaapsteker/engine.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/engine.py	Fri Apr 08 11:29:37 2011 +0200
@@ -8,7 +8,7 @@
 
 class Engine(object):
 
-    def __init__(self):
+    def __init__(self, soundsystem):
         # avoid circular imports
         from .gamestate import GameState
         self._framerate = 60
@@ -16,8 +16,10 @@
         self._fpss = [self._framerate] * 100
         self._cur_frame = 0
         self.game_state = GameState("game.json")
+        self.soundsystem = soundsystem
 
     def change_scene(self, next_scene):
+        self.soundsystem.stop_music()
         if self._current_scene is not None:
             self._current_scene.leave()
         self._current_scene = next_scene
@@ -51,9 +53,10 @@
 
 class Scene(object):
 
-    def __init__(self, game_state):
+    def __init__(self, game_state, soundsystem):
         self.widgets = []
         self.game_state = game_state
+        self._soundsystem = soundsystem
 
     def post(self, ev):
         """Post an event to pygame's event loop."""
--- a/skaapsteker/level.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/level.py	Fri Apr 08 11:29:37 2011 +0200
@@ -53,34 +53,32 @@
 
 
 class Level(object):
-    def __init__(self, leveldef, player):
+    def __init__(self, leveldef, player, soundsystem):
         self.name = leveldef
         self.level_data = json.loads(data.load('levels/' + leveldef + '.json').read())
         self.sprites = LayeredUpdates()
+        self._soundsystem = soundsystem
         self.build_backgrounds()
         self.build_tiles()
         self.setup_enemies()
         self.setup_doorways()
         self.setup_player(player)
         self._background_music = None
-        if 'music' in self.level_data and mixer.get_init():
-            self._background_music = data.filepath('music/' + self.level_data['music'])
+        if 'music' in self.level_data:
+            # soundsystem will call data.filepath
+            self._background_music = 'music/' + self.level_data['music']
 
     def build_backgrounds(self):
         self.backgrounds = []
         for background in self.level_data['backgrounds']:
             self.backgrounds.append(data.load_image('backgrounds/' + background))
 
+    def enter(self):
+        if self._background_music:
+            self._soundsystem.play_background_music(self._background_music)
 
     def leave(self):
-        if self._background_music:
-            mixer.music.stop()
-
-
-    def enter(self):
-        if self._background_music:
-            mixer.music.load(self._background_music)
-            mixer.music.play(-1)
+        pass
 
     def build_tiles(self):
         self.tileset = TileSet(self.level_data['tileset'])
--- a/skaapsteker/levelscene.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/levelscene.py	Fri Apr 08 11:29:37 2011 +0200
@@ -16,11 +16,11 @@
 
 class LevelScene(engine.Scene):
 
-    def __init__(self, game_state, leveldef):
-        super(LevelScene, self).__init__(game_state)
+    def __init__(self, game_state, soundsystem, leveldef):
+        super(LevelScene, self).__init__(game_state, soundsystem)
 
         self._player = player.Player(game_state.world)
-        self._level = level.Level(leveldef, self._player)
+        self._level = level.Level(leveldef, self._player, soundsystem)
         self._leveldef = leveldef
         self._player_dead = False
         self._dialogue = None
@@ -67,18 +67,18 @@
     def _quit(self, pause=True):
         import menuscene # avoid circular import
         if pause:
-            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, self))
+            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, self._soundsystem, self))
         else:
             # FIXME: When starting the game, we shoudl ensure we have sane
             # states
             if self._player_dead:
                 self._player.restore()
-            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, None))
+            engine.ChangeScene.post(menuscene.MenuScene(self.game_state, self._soundsystem, None))
 
     def _restart(self):
         if self._player_dead:
             self._player.restore()
-        engine.ChangeScene.post(LevelScene(self.game_state, self._leveldef))
+        engine.ChangeScene.post(LevelScene(self.game_state, self._soundsystem, self._leveldef))
 
     def _toggle_pause(self):
         if self._paused:
--- a/skaapsteker/menuscene.py	Fri Apr 08 10:40:16 2011 +0200
+++ b/skaapsteker/menuscene.py	Fri Apr 08 11:29:37 2011 +0200
@@ -8,8 +8,8 @@
 
 
 class MenuScene(Scene):
-    def __init__(self, game_state, cur_game=None):
-        super(MenuScene, self).__init__(game_state)
+    def __init__(self, game_state, soundsystem, cur_game=None):
+        super(MenuScene, self).__init__(game_state, soundsystem)
         self.widgets.append(Text("MENU:", (50, 50), color='white', size=48))
         self.cur_game = cur_game
         menu_options = [
@@ -32,11 +32,11 @@
         if data == 'resume':
             ChangeScene.post(self.cur_game)
         elif data == 'cutscene':
-            ChangeScene.post(opening_cutscene(self.game_state))
+            ChangeScene.post(opening_cutscene(self.game_state, self._soundsystem))
         elif data == 'quit':
             pygame.event.post(pygame.event.Event(QUIT))
         else:
-            ChangeScene.post(LevelScene(self.game_state, data))
+            ChangeScene.post(LevelScene(self.game_state, self._soundsystem, data))
 
     def draw(self, surface, engine):
         surface.fill(pygame.Color('black'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skaapsteker/sound.py	Fri Apr 08 11:29:37 2011 +0200
@@ -0,0 +1,56 @@
+"""Support for playing sounds and music"""
+
+from pygame import mixer
+import pygame
+from . import data
+from .constants import FREQ, BITSIZE, CHANNELS, BUFFER
+
+class SoundSystem(object):
+
+    def __init__(self, want_sound):
+        if want_sound:
+            # See if we can actually enabled sound
+            try:
+               mixer.init(FREQ, BITSIZE, CHANNELS, BUFFER)
+               test_sound = mixer.Sound(data.filepath('sounds/silence.ogg'))
+               test_sound.play()
+               self.sound_enabled = True
+            except pygame.error:
+               print 'Unable to enable sound'
+               self.sound_enabled = False
+        else:
+            self.sound_enabled = False
+
+        self._sounds = {}
+
+
+    def play_background_music(self, track_name):
+        if self.sound_enabled:
+            try:
+                mixer.music.load(data.filepath(track_name))
+                mixer.music.play(-1)  # Loop forever
+            except pygame.error:
+                print 'Unable to load track'
+
+    def stop_music(self):
+        if self.sound_enabled:
+            mixer.music.stop()
+
+    def load_sound(self, key, track_name):
+        if key in self._sounds:
+            # First caller wins on duplicate keys
+            return
+        if not self.sound_enabled:
+            self._sounds[key] = None
+        else:
+            self._sounds[key] = pygame.sound.Sound(data.filepath(track_name))
+
+    def play_sound(self, key):
+        sound = self._sounds.get(key, None)
+        if sound:
+            sound.play()
+
+    def stop_all_sounds(self):
+        if self.sound_enabled:
+            mixer.stop()
+