diff options
author | 3gg <3gg@shellblade.net> | 2024-12-31 17:02:31 -0800 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2024-12-31 17:02:31 -0800 |
commit | 8984aede0162f6bdcfc2dc0a54f563a3b1ff5684 (patch) | |
tree | a80cbe5897edc2e2e6b06e87173624e0f4f4a377 | |
parent | bbc4e34213147f30068a00bb56ebb871114e26dd (diff) |
Add enough audio for background music.
-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 | } | ||