aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-12-31 17:02:31 -0800
committer3gg <3gg@shellblade.net>2024-12-31 17:02:31 -0800
commit8984aede0162f6bdcfc2dc0a54f563a3b1ff5684 (patch)
treea80cbe5897edc2e2e6b06e87173624e0f4f4a377
parentbbc4e34213147f30068a00bb56ebb871114e26dd (diff)
Add enough audio for background music.
-rw-r--r--Demos/Pong/Main.hs31
-rw-r--r--README.md4
-rw-r--r--Spear.cabal4
-rw-r--r--Spear/App.hs31
-rw-r--r--Spear/Sound/Sound.hs101
-rw-r--r--Spear/Sound/State.hs54
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
13import Spear.Render.Core.Pipeline 13import Spear.Render.Core.Pipeline
14import Spear.Render.Core.State 14import Spear.Render.Core.State
15import Spear.Render.Immediate 15import Spear.Render.Immediate
16import Spear.Sound.Sound
17import Spear.Sound.State
16import Spear.Window 18import Spear.Window
17 19
18import Control.Monad (when) 20import Control.Monad (when)
@@ -20,10 +22,11 @@ import Data.Maybe (mapMaybe)
20 22
21 23
22data GameState = GameState 24data 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
35main :: IO () 38main :: IO ()
36main = runApp app 39main = runApp app
37 40
38initGame :: Window -> Game () GameState 41initGame :: AppContext -> Game () GameState
39initGame window = do 42initGame 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
43endGame :: Game GameState () 55endGame :: Game GameState ()
44endGame = do 56endGame = do
@@ -48,13 +60,12 @@ endGame = do
48 60
49step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool 61step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool
50step elapsed dt inputEvents = do 62step 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
60processInput :: Window -> Game GameState [GameEvent] 71processInput :: Window -> Game GameState [GameEvent]
diff --git a/README.md b/README.md
index 386250d..3296c35 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,7 @@ Installation (Ubuntu)
12Install dependencies, then build with cabal: 12Install 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: ""
14library 14library
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 @@
1module Spear.App 1module 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)
11where 12where
12 13
13import Control.Monad
14import Data.Fixed (mod')
15import GHC.Float
16import Spear.Game 14import Spear.Game
17import Spear.Sys.Timer as Timer 15import Spear.Sound.Sound
16import Spear.Sound.State
17import Spear.Sys.Timer as Timer
18import Spear.Window 18import Spear.Window
19 19
20import Control.Monad
21import Data.Fixed (mod')
22import GHC.Float
23
20-- | Time elapsed. 24-- | Time elapsed.
21type Elapsed = Double 25type Elapsed = Double
22 26
@@ -48,13 +52,18 @@ defaultAppOptions = AppOptions
48-- | Application state. 52-- | Application state.
49data App s = App 53data 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.
63data AppContext = AppContext
64 { appWindow :: Window
65 , appSoundState :: SoundState
66 }
58 67
59-- | Run the application. 68-- | Run the application.
60runApp :: App s -> IO () 69runApp :: 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.
71fpsToDdt :: Int -> TimeDelta 84fpsToDdt :: 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 @@
1module 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)
14where
15
16import Spear.Game
17import Spear.Sound.State
18
19import Data.Set as Set
20import Data.StateVar (($=))
21import qualified Sound.ALUT as AL
22
23
24data LoopMode
25 = SingleShot
26 | Loop
27 deriving (Show)
28
29
30-- | Create the sound context and run an IO action within the context.
31withSoundContext :: IO a -> IO a
32withSoundContext action = AL.withProgNameAndArgs AL.runALUT $
33 \name args -> action
34
35-- | Initialize the sound system.
36initSoundSystem :: Game () SoundState
37initSoundSystem = return newSoundState
38
39-- | Destroy the sound system.
40destroySoundSystem :: Game SoundState ()
41destroySoundSystem = 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.
48loadAudioFile :: FilePath -> Game SoundState SoundBuffer
49loadAudioFile 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.
59deleteSoundBuffer :: SoundBuffer -> Game SoundState ()
60deleteSoundBuffer 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.
70makeSoundSource :: Game SoundState SoundSource
71makeSoundSource = 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.
81deleteSoundSource :: SoundSource -> Game SoundState ()
82deleteSoundSource 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.
89setSoundSourceBuffer :: SoundSource -> SoundBuffer -> IO ()
90setSoundSourceBuffer source buffer =
91 AL.buffer (alSource source) $= Just (alBuffer buffer)
92
93-- | Set the sound's loop mode.
94setSoundLoopMode :: SoundSource -> LoopMode -> IO ()
95setSoundLoopMode 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.
100playSounds :: [SoundSource] -> IO ()
101playSounds = 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 @@
1module Spear.Sound.State where
2
3import Spear.Game
4
5import Data.Hashable
6import Data.Set as Set
7import qualified Sound.ALUT as AL
8
9
10-- | A sound buffer.
11data SoundBuffer = SoundBuffer
12 { alBuffer :: AL.Buffer
13 , bufferResource :: ReleaseKey
14 }
15
16-- | A sound source.
17data SoundSource = SoundSource
18 { alSource :: AL.Source
19 , sourceResource :: ReleaseKey
20 }
21
22-- | Sound state.
23data SoundState = SoundState
24 { buffers :: Set SoundBuffer
25 , sources :: Set SoundSource
26 }
27
28
29instance ResourceClass SoundBuffer where
30 getResource = bufferResource
31
32instance ResourceClass SoundSource where
33 getResource = sourceResource
34
35instance Eq SoundBuffer where
36 a == b = alBuffer a == alBuffer b
37
38instance Eq SoundSource where
39 a == b = alSource a == alSource b
40
41instance Ord SoundBuffer where
42 a < b = alBuffer a < alBuffer b
43 a <= b = alBuffer a <= alBuffer b
44
45instance Ord SoundSource where
46 a < b = alSource a < alSource b
47 a <= b = alSource a <= alSource b
48
49
50newSoundState :: SoundState
51newSoundState = SoundState
52 { buffers = Set.empty
53 , sources = Set.empty
54 }