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