5 , MultiParamTypeClasses
9 -- | EsounD controlling handles.
10 module Sound.EsounD.Controller
33 import Bindings.EsounD
34 import Control.Exception.Peel
35 import Control.Monad.IO.Class
36 import Control.Monad.IO.Peel
37 import Control.Monad.Trans.Region
38 import Control.Monad.Trans.Region.OnExit
39 import Control.Monad.Unicode
41 import Data.StorableVector.Lazy as L
42 import Foreign.C.Types
44 import Foreign.Storable
46 import Prelude.Unicode
47 import Sound.EsounD.Internals
48 import System.IO.SaferFileHandles.Unsafe
49 import System.Posix.IO hiding (dup)
50 import System.Posix.Types
53 -- ^ An opaque ESD handle for controlling ESD.
54 data Controller (r ∷ ★ → ★)
57 , coCloseH ∷ !(FinalizerHandle r)
60 instance Dup Controller where
61 dup co = do ch' ← dup (coCloseH co)
62 return co { coCloseH = ch' }
64 -- | Open an ESD handle for controlling ESD.
65 openController ∷ MonadPeelIO pr
66 ⇒ Maybe HostName -- ^ host to connect to.
67 → RegionT s pr (Controller (RegionT s pr))
70 do s ← liftIO openSocket
71 ch ← onExit $ sanitizeIOError $ closeSocket' s
78 openSocket = withCStrOrNull host $ \hostPtr →
79 c'esd_open_sound hostPtr
82 wrapSocket' ∷ Monad m ⇒ CInt → m Fd
83 wrapSocket' (-1) = fail ( printf "esd_open_sound(%s) returned an error"
86 wrapSocket' fd = return $ Fd fd
88 closeSocket' ∷ Fd → IO ()
90 = do _ ← c'esd_close $ fdToCInt fd
94 fdToCInt (Fd fd) = fromIntegral fd
96 -- | Lock the ESD so that it won't accept connections from remote
98 lock ∷ ( AncestorRegion pr cr
106 c'esd_lock (fdToCInt $ coSocket co)
107 ≫= failOnError "esd_lock(fd) returned an error" (≤ 0)
110 -- | Unlock the ESD so that it will accept connections from remote
112 unlock ∷ ( AncestorRegion pr cr
120 c'esd_unlock (fdToCInt $ coSocket co)
121 ≫= failOnError "esd_unlock(fd) returned an error" (≤ 0)
124 -- | Let ESD stop playing sounds and release its connection to the
125 -- audio device so that other processes may use it.
126 standby ∷ ( AncestorRegion pr cr
134 c'esd_standby (fdToCInt $ coSocket co)
135 ≫= failOnError "esd_standby(fd) returned an error" (≤ 0)
138 -- | Let ESD attempt to reconnect to the audio device and start
139 -- playing sounds again.
140 resume ∷ ( AncestorRegion pr cr
148 c'esd_resume (fdToCInt $ coSocket co)
149 ≫= failOnError "esd_resume(fd) returned an error" (≤ 0)
152 -- | An opaque ESD sample handle.
153 data Sample (r ∷ ★ → ★)
156 , saCtrl ∷ !(Controller r)
157 , saCloseH ∷ !(FinalizerHandle r)
160 instance Dup Sample where
161 dup sa = do ctrl' ← dup (saCtrl sa)
162 ch' ← dup (saCloseH sa)
168 class (Frame fr, Channels ch) ⇒ SampleSource fr ch dvec where
169 -- | Cache a sample in the server.
170 cacheSample ∷ (MonadPeelIO pr)
171 ⇒ Controller (RegionT s pr)
172 → Maybe String -- ^ name used to identify this sample to
173 → Int -- ^ sample rate
174 → dvec -- ^ frames in deinterleaved vectors
175 → RegionT s pr (Sample (RegionT s pr))
177 instance Frame fr ⇒ SampleSource fr Mono (L.Vector fr) where
178 cacheSample co name rate v
189 do h ← fdToHandle $ coSocket co
191 (Fd fd) ← handleToFd h
192 c'esd_confirm_sample_cache fd
193 ≫= failOnError "esd_confirm_sample_cache(fd) returned an error" (< 0)
196 instance Frame fr ⇒ SampleSource fr Stereo (L.Vector fr, L.Vector fr) where
197 cacheSample co name rate (l, r)
208 do h ← fdToHandle $ coSocket co
209 _ ← L.hPut h (interleave l r)
210 (Fd fd) ← handleToFd h
211 c'esd_confirm_sample_cache fd
212 ≫= failOnError "esd_confirm_sample_cache(fd) returned an error" (< 0)
215 createSample ∷ ∀fr ch s pr.
220 ⇒ Controller (RegionT s pr)
226 → RegionT s pr (Sample (RegionT s pr))
227 createSample co name rate _ _ size
229 do sid ← liftIO newCache
230 ch ← onExit $ sanitizeIOError $ deleteCache sid
238 fmt = frameFmt ((⊥) ∷ fr) .|.
239 channelFmt ((⊥) ∷ ch) .|.
243 newCache = withCStrOrNull name $ \namePtr →
245 (fdToCInt $ coSocket co)
250 ≫= failOnError ( printf "esd_sample_cache(%s, %s, %s, %s, %s) returned an error"
258 deleteCache ∷ CInt → IO ()
260 = c'esd_sample_free (fdToCInt $ coSocket co) sid
261 ≫= failOnError ( printf "esd_sample_free(%s) returned an error"
267 -- | Play a cached sample once.
268 playSample ∷ ( AncestorRegion pr cr
276 c'esd_sample_play (fdToCInt $ coSocket $ saCtrl sa) (saID sa)
277 ≫= failOnError ( printf "esd_sample_play(%s, %s) returned an error"
278 (show $ coSocket $ saCtrl sa)
283 -- | Play a cached sample repeatedly.
284 loopSample ∷ ( AncestorRegion pr cr
292 c'esd_sample_loop (fdToCInt $ coSocket $ saCtrl sa) (saID sa)
293 ≫= failOnError ( printf "esd_sample_loop(%s, %s) returned an error"
294 (show $ coSocket $ saCtrl sa)
299 -- | Stop a looping sample at end.
300 stopSample ∷ ( AncestorRegion pr cr
308 c'esd_sample_stop (fdToCInt $ coSocket $ saCtrl sa) (saID sa)
309 ≫= failOnError ( printf "esd_sample_stop(%s, %s) returned an error"
310 (show $ coSocket $ saCtrl sa)
315 -- | Stop a playing sample immediately.
316 killSample ∷ ( AncestorRegion pr cr
324 c'esd_sample_kill (fdToCInt $ coSocket $ saCtrl sa) (saID sa)
325 ≫= failOnError ( printf "esd_sample_kill(%s, %s) returned an error"
326 (show $ coSocket $ saCtrl sa)
333 -- | A data type to represent the server info.
337 , serverFrameType ∷ !FrameType
338 , serverChannels ∷ !NumChannels
339 , serverSampleRate ∷ !Int
351 -- | Retrieve server properties.
352 getServerInfo ∷ ( AncestorRegion pr cr
360 bracket retrieve dispose extract
362 retrieve ∷ IO (Ptr C'esd_server_info)
363 retrieve = do siPtr ← c'esd_get_server_info (fdToCInt $ coSocket co)
364 if siPtr ≡ nullPtr then
365 fail "esd_resume(fd) returned an error"
369 dispose ∷ Ptr C'esd_server_info → IO ()
370 dispose = c'esd_free_server_info
372 extract ∷ Ptr C'esd_server_info → IO ServerInfo
378 c'esd_server_info'version si
380 = case c'esd_server_info'format si of
381 fmt | fmt .&. c'ESD_BITS8 ≡ 1 → Int8
382 | fmt .&. c'ESD_BITS16 ≡ 1 → Int16
383 | otherwise → error ("Unknown format: " ⧺ show fmt)
385 = case c'esd_server_info'format si of
386 fmt | fmt .&. c'ESD_MONO ≡ 1 → Mono
387 | fmt .&. c'ESD_STEREO ≡ 1 → Stereo
388 | otherwise → error ("Unknown format: " ⧺ show fmt)
391 c'esd_server_info'rate si