diff options
| -rw-r--r-- | Demos/Pong/Main.hs | 31 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | Spear.cabal | 4 | ||||
| -rw-r--r-- | Spear/App.hs | 31 | ||||
| -rw-r--r-- | Spear/Sound/Sound.hs | 101 | ||||
| -rw-r--r-- | Spear/Sound/State.hs | 54 |
6 files changed, 203 insertions, 22 deletions
diff --git a/Demos/Pong/Main.hs b/Demos/Pong/Main.hs index b93325d..f77136f 100644 --- a/Demos/Pong/Main.hs +++ b/Demos/Pong/Main.hs | |||
| @@ -13,6 +13,8 @@ import Spear.Physics.Collision | |||
| 13 | import Spear.Render.Core.Pipeline | 13 | import Spear.Render.Core.Pipeline |
| 14 | import Spear.Render.Core.State | 14 | import Spear.Render.Core.State |
| 15 | import Spear.Render.Immediate | 15 | import Spear.Render.Immediate |
| 16 | import Spear.Sound.Sound | ||
| 17 | import Spear.Sound.State | ||
| 16 | import Spear.Window | 18 | import Spear.Window |
| 17 | 19 | ||
| 18 | import Control.Monad (when) | 20 | import Control.Monad (when) |
| @@ -20,10 +22,11 @@ import Data.Maybe (mapMaybe) | |||
| 20 | 22 | ||
| 21 | 23 | ||
| 22 | data GameState = GameState | 24 | data GameState = GameState |
| 23 | { window :: Window | 25 | { context :: AppContext |
| 24 | , renderCoreState :: RenderCoreState | 26 | , renderCoreState :: RenderCoreState |
| 25 | , immRenderState :: ImmRenderState | 27 | , immRenderState :: ImmRenderState |
| 26 | , viewProjection :: Matrix4 | 28 | , viewProjection :: Matrix4 |
| 29 | , backgroundMusic :: SoundSource | ||
| 27 | , world :: [GameObject] | 30 | , world :: [GameObject] |
| 28 | } | 31 | } |
| 29 | 32 | ||
| @@ -35,10 +38,19 @@ app = App options initGame endGame step render resize | |||
| 35 | main :: IO () | 38 | main :: IO () |
| 36 | main = runApp app | 39 | main = runApp app |
| 37 | 40 | ||
| 38 | initGame :: Window -> Game () GameState | 41 | initGame :: AppContext -> Game () GameState |
| 39 | initGame window = do | 42 | initGame context = do |
| 40 | (immRenderState, renderCoreState) <- runSiblingGame newImmRenderer newRenderCoreState | 43 | (immRenderState, renderCoreState) <- runSiblingGame newImmRenderer newRenderCoreState |
| 41 | return $ GameState window renderCoreState immRenderState Matrix4.id newWorld | 44 | (music, soundState') <- flip runSiblingGame (appSoundState context) $ do |
| 45 | musicBuffer <- loadAudioFile "/home/jeanne/Casual Tiki Party Main.wav" | ||
| 46 | music <- makeSoundSource | ||
| 47 | liftIO $ do | ||
| 48 | setSoundSourceBuffer music musicBuffer | ||
| 49 | setSoundLoopMode music Loop | ||
| 50 | playSounds [music] | ||
| 51 | return music | ||
| 52 | let context' = context { appSoundState = soundState' } | ||
| 53 | return $ GameState context' renderCoreState immRenderState Matrix4.id music newWorld | ||
| 42 | 54 | ||
| 43 | endGame :: Game GameState () | 55 | endGame :: Game GameState () |
| 44 | endGame = do | 56 | endGame = do |
| @@ -48,13 +60,12 @@ endGame = do | |||
| 48 | 60 | ||
| 49 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool | 61 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool |
| 50 | step elapsed dt inputEvents = do | 62 | step elapsed dt inputEvents = do |
| 51 | gs <- get | 63 | gameState <- get |
| 52 | events <- processInput (window gs) | 64 | events <- processInput (appWindow . context $ gameState) |
| 53 | --when (events /= []) $ liftIO . putStrLn $ show events | 65 | --when (events /= []) $ liftIO . putStrLn $ show events |
| 54 | modify $ \gs -> | 66 | modify $ \gameState -> gameState |
| 55 | gs | 67 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gameState) |
| 56 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gs) | 68 | } |
| 57 | } | ||
| 58 | return (not $ exitRequested inputEvents) | 69 | return (not $ exitRequested inputEvents) |
| 59 | 70 | ||
| 60 | processInput :: Window -> Game GameState [GameEvent] | 71 | processInput :: Window -> Game GameState [GameEvent] |
| @@ -12,9 +12,7 @@ Installation (Ubuntu) | |||
| 12 | Install dependencies, then build with cabal: | 12 | Install dependencies, then build with cabal: |
| 13 | 13 | ||
| 14 | ``` | 14 | ``` |
| 15 | $ sudo apt install libxxf86vm-dev libglfw3-dev | 15 | $ sudo apt install libxxf86vm-dev libglfw3-dev libopenal-dev libopenalut-dev |
| 16 | $ git clone https://github.com/jeannekamikaze/Spear.git | ||
| 17 | $ cd Spear | ||
| 18 | $ cabal build | 16 | $ cabal build |
| 19 | ``` | 17 | ``` |
| 20 | 18 | ||
diff --git a/Spear.cabal b/Spear.cabal index 306ef6a..ed37d66 100644 --- a/Spear.cabal +++ b/Spear.cabal | |||
| @@ -14,6 +14,8 @@ data-dir: "" | |||
| 14 | library | 14 | library |
| 15 | build-depends: | 15 | build-depends: |
| 16 | GLFW-b -any, | 16 | GLFW-b -any, |
| 17 | OpenAL -any, | ||
| 18 | ALUT -any, | ||
| 17 | OpenGL >= 3, | 19 | OpenGL >= 3, |
| 18 | OpenGLRaw -any, | 20 | OpenGLRaw -any, |
| 19 | StateVar -any, | 21 | StateVar -any, |
| @@ -81,6 +83,8 @@ library | |||
| 81 | Spear.Scene.Graph | 83 | Spear.Scene.Graph |
| 82 | Spear.Scene.Loader | 84 | Spear.Scene.Loader |
| 83 | Spear.Scene.SceneResources | 85 | Spear.Scene.SceneResources |
| 86 | Spear.Sound.Sound | ||
| 87 | Spear.Sound.State | ||
| 84 | Spear.Step | 88 | Spear.Step |
| 85 | Spear.Sys.Store | 89 | Spear.Sys.Store |
| 86 | Spear.Sys.Store.ID | 90 | Spear.Sys.Store.ID |
diff --git a/Spear/App.hs b/Spear/App.hs index 1520eee..8c0371e 100644 --- a/Spear/App.hs +++ b/Spear/App.hs | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | module Spear.App | 1 | module Spear.App |
| 2 | ( App(..) | 2 | ( App(..) |
| 3 | , AppOptions(..) | 3 | , AppOptions(..) |
| 4 | , AppContext(..) | ||
| 4 | , Elapsed | 5 | , Elapsed |
| 5 | , Dt | 6 | , Dt |
| 6 | , Step | 7 | , Step |
| @@ -10,13 +11,16 @@ module Spear.App | |||
| 10 | ) | 11 | ) |
| 11 | where | 12 | where |
| 12 | 13 | ||
| 13 | import Control.Monad | ||
| 14 | import Data.Fixed (mod') | ||
| 15 | import GHC.Float | ||
| 16 | import Spear.Game | 14 | import Spear.Game |
| 17 | import Spear.Sys.Timer as Timer | 15 | import Spear.Sound.Sound |
| 16 | import Spear.Sound.State | ||
| 17 | import Spear.Sys.Timer as Timer | ||
| 18 | import Spear.Window | 18 | import Spear.Window |
| 19 | 19 | ||
| 20 | import Control.Monad | ||
| 21 | import Data.Fixed (mod') | ||
| 22 | import GHC.Float | ||
| 23 | |||
| 20 | -- | Time elapsed. | 24 | -- | Time elapsed. |
| 21 | type Elapsed = Double | 25 | type Elapsed = Double |
| 22 | 26 | ||
| @@ -48,13 +52,18 @@ defaultAppOptions = AppOptions | |||
| 48 | -- | Application state. | 52 | -- | Application state. |
| 49 | data App s = App | 53 | data App s = App |
| 50 | { appOptions :: AppOptions | 54 | { appOptions :: AppOptions |
| 51 | , initApp :: Window -> Game () s | 55 | , initApp :: AppContext -> Game () s |
| 52 | , endApp :: Game s () | 56 | , endApp :: Game s () |
| 53 | , stepApp :: Step s | 57 | , stepApp :: Step s |
| 54 | , renderApp :: Game s () | 58 | , renderApp :: Game s () |
| 55 | , resizeApp :: WindowEvent -> Game s () | 59 | , resizeApp :: WindowEvent -> Game s () |
| 56 | } | 60 | } |
| 57 | 61 | ||
| 62 | -- | Application context. | ||
| 63 | data AppContext = AppContext | ||
| 64 | { appWindow :: Window | ||
| 65 | , appSoundState :: SoundState | ||
| 66 | } | ||
| 58 | 67 | ||
| 59 | -- | Run the application. | 68 | -- | Run the application. |
| 60 | runApp :: App s -> IO () | 69 | runApp :: App s -> IO () |
| @@ -62,10 +71,14 @@ runApp app = | |||
| 62 | let ops = appOptions app | 71 | let ops = appOptions app |
| 63 | w = windowWidth ops | 72 | w = windowWidth ops |
| 64 | h = windowHeight ops | 73 | h = windowHeight ops |
| 65 | in withWindow (w, h) (title ops) $ \window -> flip evalGame () $ do | 74 | in withWindow (w, h) (title ops) $ \window -> |
| 66 | gameState <- initApp app window | 75 | withSoundContext $ flip evalGame () $ do |
| 67 | (result, endGameState) <- runSubGame (loop app window) gameState | 76 | soundState <- evalSiblingGame initSoundSystem () |
| 68 | runSubGame' (endApp app) endGameState | 77 | let appContext = AppContext window soundState |
| 78 | gameState <- initApp app appContext | ||
| 79 | (result, endGameState) <- runSubGame (loop app window) gameState | ||
| 80 | runSubGame' (endApp app) endGameState | ||
| 81 | runSiblingGame' destroySoundSystem soundState | ||
| 69 | 82 | ||
| 70 | -- | Convert FPS to desired delta time. | 83 | -- | Convert FPS to desired delta time. |
| 71 | fpsToDdt :: Int -> TimeDelta | 84 | fpsToDdt :: Int -> TimeDelta |
diff --git a/Spear/Sound/Sound.hs b/Spear/Sound/Sound.hs new file mode 100644 index 0000000..53a1a46 --- /dev/null +++ b/Spear/Sound/Sound.hs | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | module Spear.Sound.Sound | ||
| 2 | ( LoopMode(..) | ||
| 3 | , withSoundContext | ||
| 4 | , initSoundSystem | ||
| 5 | , destroySoundSystem | ||
| 6 | , loadAudioFile | ||
| 7 | , deleteSoundBuffer | ||
| 8 | , makeSoundSource | ||
| 9 | , deleteSoundSource | ||
| 10 | , setSoundSourceBuffer | ||
| 11 | , setSoundLoopMode | ||
| 12 | , playSounds | ||
| 13 | ) | ||
| 14 | where | ||
| 15 | |||
| 16 | import Spear.Game | ||
| 17 | import Spear.Sound.State | ||
| 18 | |||
| 19 | import Data.Set as Set | ||
| 20 | import Data.StateVar (($=)) | ||
| 21 | import qualified Sound.ALUT as AL | ||
| 22 | |||
| 23 | |||
| 24 | data LoopMode | ||
| 25 | = SingleShot | ||
| 26 | | Loop | ||
| 27 | deriving (Show) | ||
| 28 | |||
| 29 | |||
| 30 | -- | Create the sound context and run an IO action within the context. | ||
| 31 | withSoundContext :: IO a -> IO a | ||
| 32 | withSoundContext action = AL.withProgNameAndArgs AL.runALUT $ | ||
| 33 | \name args -> action | ||
| 34 | |||
| 35 | -- | Initialize the sound system. | ||
| 36 | initSoundSystem :: Game () SoundState | ||
| 37 | initSoundSystem = return newSoundState | ||
| 38 | |||
| 39 | -- | Destroy the sound system. | ||
| 40 | destroySoundSystem :: Game SoundState () | ||
| 41 | destroySoundSystem = do | ||
| 42 | state <- get | ||
| 43 | mapM_ release' (toList $ buffers state) | ||
| 44 | mapM_ release' (toList $ sources state) | ||
| 45 | put newSoundState | ||
| 46 | |||
| 47 | -- | Load an audio file. | ||
| 48 | loadAudioFile :: FilePath -> Game SoundState SoundBuffer | ||
| 49 | loadAudioFile path = do | ||
| 50 | alBuffer <- liftIO $ AL.createBuffer (AL.File path) | ||
| 51 | resourceKey <- register $ AL.deleteObjectName alBuffer | ||
| 52 | let buffer = SoundBuffer alBuffer resourceKey | ||
| 53 | modify (\state -> state { | ||
| 54 | buffers = Set.insert buffer (buffers state) | ||
| 55 | }) | ||
| 56 | return buffer | ||
| 57 | |||
| 58 | -- | Delete the sound buffer. | ||
| 59 | deleteSoundBuffer :: SoundBuffer -> Game SoundState () | ||
| 60 | deleteSoundBuffer buffer = do | ||
| 61 | modify (\state -> state { | ||
| 62 | buffers = Set.delete buffer (buffers state) | ||
| 63 | }) | ||
| 64 | release' buffer | ||
| 65 | |||
| 66 | -- | Create a sound source. | ||
| 67 | -- | ||
| 68 | -- The new source sounds flat, like background music or sound effects in a 2D | ||
| 69 | -- game. Change the source's (and listener's) properties to simulate 3D sound. | ||
| 70 | makeSoundSource :: Game SoundState SoundSource | ||
| 71 | makeSoundSource = do | ||
| 72 | alSource <- AL.genObjectName | ||
| 73 | resourceKey <- register $ AL.deleteObjectName alSource | ||
| 74 | let source = SoundSource alSource resourceKey | ||
| 75 | modify (\state -> state { | ||
| 76 | sources = Set.insert source (sources state) | ||
| 77 | }) | ||
| 78 | return source | ||
| 79 | |||
| 80 | -- | Delete the sound source. | ||
| 81 | deleteSoundSource :: SoundSource -> Game SoundState () | ||
| 82 | deleteSoundSource source = do | ||
| 83 | modify (\state -> state { | ||
| 84 | sources = Set.delete source (sources state) | ||
| 85 | }) | ||
| 86 | release' source | ||
| 87 | |||
| 88 | -- | Set the sound that the sound source emits. | ||
| 89 | setSoundSourceBuffer :: SoundSource -> SoundBuffer -> IO () | ||
| 90 | setSoundSourceBuffer source buffer = | ||
| 91 | AL.buffer (alSource source) $= Just (alBuffer buffer) | ||
| 92 | |||
| 93 | -- | Set the sound's loop mode. | ||
| 94 | setSoundLoopMode :: SoundSource -> LoopMode -> IO () | ||
| 95 | setSoundLoopMode source mode = AL.loopingMode (alSource source) $= alMode mode | ||
| 96 | where alMode SingleShot = AL.OneShot | ||
| 97 | alMode Loop = AL.Looping | ||
| 98 | |||
| 99 | -- | Play the sound sources. | ||
| 100 | playSounds :: [SoundSource] -> IO () | ||
| 101 | playSounds = AL.play . (alSource <$>) | ||
diff --git a/Spear/Sound/State.hs b/Spear/Sound/State.hs new file mode 100644 index 0000000..d843de5 --- /dev/null +++ b/Spear/Sound/State.hs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | module Spear.Sound.State where | ||
| 2 | |||
| 3 | import Spear.Game | ||
| 4 | |||
| 5 | import Data.Hashable | ||
| 6 | import Data.Set as Set | ||
| 7 | import qualified Sound.ALUT as AL | ||
| 8 | |||
| 9 | |||
| 10 | -- | A sound buffer. | ||
| 11 | data SoundBuffer = SoundBuffer | ||
| 12 | { alBuffer :: AL.Buffer | ||
| 13 | , bufferResource :: ReleaseKey | ||
| 14 | } | ||
| 15 | |||
| 16 | -- | A sound source. | ||
| 17 | data SoundSource = SoundSource | ||
| 18 | { alSource :: AL.Source | ||
| 19 | , sourceResource :: ReleaseKey | ||
| 20 | } | ||
| 21 | |||
| 22 | -- | Sound state. | ||
| 23 | data SoundState = SoundState | ||
| 24 | { buffers :: Set SoundBuffer | ||
| 25 | , sources :: Set SoundSource | ||
| 26 | } | ||
| 27 | |||
| 28 | |||
| 29 | instance ResourceClass SoundBuffer where | ||
| 30 | getResource = bufferResource | ||
| 31 | |||
| 32 | instance ResourceClass SoundSource where | ||
| 33 | getResource = sourceResource | ||
| 34 | |||
| 35 | instance Eq SoundBuffer where | ||
| 36 | a == b = alBuffer a == alBuffer b | ||
| 37 | |||
| 38 | instance Eq SoundSource where | ||
| 39 | a == b = alSource a == alSource b | ||
| 40 | |||
| 41 | instance Ord SoundBuffer where | ||
| 42 | a < b = alBuffer a < alBuffer b | ||
| 43 | a <= b = alBuffer a <= alBuffer b | ||
| 44 | |||
| 45 | instance Ord SoundSource where | ||
| 46 | a < b = alSource a < alSource b | ||
| 47 | a <= b = alSource a <= alSource b | ||
| 48 | |||
| 49 | |||
| 50 | newSoundState :: SoundState | ||
| 51 | newSoundState = SoundState | ||
| 52 | { buffers = Set.empty | ||
| 53 | , sources = Set.empty | ||
| 54 | } | ||
