]> gitweb @ CieloNegro.org - EsounD.git/blob - Sound/EsounD.hs
16e85a5ddd4a61f9fba39eb1a81781ca70ce2197
[EsounD.git] / Sound / EsounD.hs
1 -- | Type-safe bindings to EsounD with monadic regions.
2 module Sound.EsounD
3     ( Frame
4
5     , Channels
6     , Mono
7     , Stereo
8
9     , Player
10     , openPlayer
11     )
12     where
13
14 import Bindings.EsounD
15 import Control.Monad.IO.Class
16 import Control.Monad.Trans.Region as R
17 import Control.Monad.Trans.Region.OnExit
18 import Control.Monad.Unicode
19 import Data.Bits
20 import Data.Int
21 import Foreign.C.String
22 import Foreign.C.Types
23 import Foreign.Ptr
24 import Network
25 import Prelude.Unicode
26 import System.IO
27 import System.IO.SaferFileHandles.Unsafe
28 import System.Posix.IO
29 import System.Posix.Types
30
31 class Frame fr where
32     frameFmt ∷ fr → C'esd_format_t
33
34 instance Frame Int8 where
35     frameFmt _ = c'ESD_BITS8
36
37 instance Frame Int16 where
38     frameFmt _ = c'ESD_BITS16
39
40 class Channels ch where
41     channelFmt ∷ ch → C'esd_format_t
42
43 data Mono
44 instance Channels Mono where
45     channelFmt _ = c'ESD_MONO
46
47 data Stereo
48 instance Channels Stereo where
49     channelFmt _ = c'ESD_STEREO
50
51
52 -- ^ An ESD handle for playing a stream.
53 data Player fr ch (r ∷ * → *)
54     = Player {
55         plRate   ∷ !Int
56       -- THINKME: We really want to use RegionalFileHandle but we
57       -- can't, because safer-file-handles currently provides no ways
58       -- to wrap ordinary handles.
59       , plHandle ∷ !Handle
60       , plCloseH ∷ !(CloseHandle r)
61       }
62
63 instance Dup (Player fr ch) where
64     dup pl = do ch' ← R.dup (plCloseH pl)
65                 return pl { plCloseH = ch' }
66
67 -- | Open an ESD handle for playing a stream.
68 openPlayer ∷ ∀fr ch s pr.
69                ( Frame fr
70                , Channels ch
71                , MonadIO pr
72                )
73            ⇒ Int          -- ^ sample rate for the stream.
74            → HostName     -- ^ host to connect to.
75            → Maybe String -- ^ name used to identify this stream to
76                            --   ESD (if any).
77            → RegionT s pr (Player fr ch (RegionT s pr))
78 openPlayer rate host name
79     = do h  ← liftIO openSocket
80          ch ← onExit $ sanitizeIOError $ closeSocket h
81          return Player {
82                       plRate   = rate
83                     , plHandle = h
84                     , plCloseH = ch
85                     }
86     where
87       fmt :: C'esd_format_t
88       fmt = frameFmt   ((⊥) ∷ fr) .&.
89             channelFmt ((⊥) ∷ ch) .&.
90             c'ESD_STREAM            .&.
91             c'ESD_PLAY
92
93       openSocket :: IO Handle
94       openSocket = withCString    host $ \hostPtr →
95                    withCStrOrNull name $ \namePtr →
96                        c'esd_play_stream
97                        fmt
98                        (fromIntegral rate)
99                        hostPtr
100                        namePtr
101                        ≫= wrapSocket "esd_play_stream() returned an error"
102
103 wrapSocket :: String -> CInt → IO Handle
104 wrapSocket e (-1) = fail e
105 wrapSocket _ fd   = fdToHandle (Fd fd)
106
107 closeSocket :: Handle → IO ()
108 closeSocket h = do (Fd fd) ← handleToFd h
109                    _       ← c'esd_close (fromIntegral fd)
110                    return ()
111
112 withCStrOrNull :: Maybe String → (CString → IO a) → IO a
113 withCStrOrNull Nothing  f = f nullPtr
114 withCStrOrNull (Just s) f = withCString s f