{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
module SDL.Audio
(
AudioDevice
, openAudioDevice
, closeAudioDevice
, OpenDeviceSpec(..)
, AudioDeviceUsage(..)
, Channels(..)
, Changeable(..)
, setAudioDeviceLocked
, LockState(..)
, PlaybackState(..)
, setAudioDevicePlaybackState
, AudioDeviceStatus(..)
, audioDeviceStatus
, AudioFormat(..)
, getAudioDeviceNames
, AudioSpec
, audioSpecFreq
, audioSpecFormat
, audioSpecChannels
, audioSpecSilence
, audioSpecSize
, audioSpecCallback
, getAudioDrivers
, currentAudioDriver
, AudioDriver
, audioDriverName
, audioInit
) where
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Bits
import Data.Data (Data)
import Data.IORef (newIORef, writeIORef, readIORef)
import Data.Int (Int8, Int16, Int32)
import Data.Text (Text)
import Data.Traversable (for)
import Data.Typeable
import Data.Word
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import Foreign.Ptr
import Foreign.Storable
import GHC.Exts (Constraint)
import GHC.Generics (Generic)
import SDL.Internal.Exception
import qualified Data.ByteString as BS
import qualified Data.Text.Encoding as Text
import qualified Data.Vector as V
import qualified Data.Vector.Storable.Mutable as MV
import qualified SDL.Raw.Audio as Raw
import qualified SDL.Raw.Enum as Raw
import qualified SDL.Raw.Types as Raw
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative
import Data.Foldable (Foldable)
import Data.Traversable (Traversable)
#endif
data OpenDeviceSpec = forall sampleType. OpenDeviceSpec
{ openDeviceFreq :: !(Changeable CInt)
, openDeviceFormat :: !(Changeable (AudioFormat sampleType))
, openDeviceChannels :: !(Changeable Channels)
, openDeviceSamples :: !Word16
, openDeviceCallback :: forall actualSampleType. AudioFormat actualSampleType -> MV.IOVector actualSampleType -> IO ()
, openDeviceUsage :: !AudioDeviceUsage
, openDeviceName :: !(Maybe Text)
} deriving (Typeable)
openAudioDevice :: MonadIO m => OpenDeviceSpec -> m (AudioDevice, AudioSpec)
openAudioDevice OpenDeviceSpec{..} = liftIO $
maybeWith (BS.useAsCString . Text.encodeUtf8) openDeviceName $ \cDevName -> do
anAudioFormatRef <- newIORef undefined
cb <- Raw.mkAudioCallback $ \_ buffer len -> do
fp <- newForeignPtr_ buffer
anAudioFormat <- readIORef anAudioFormatRef
case anAudioFormat of
AnAudioFormat audioFormat ->
case audioFormatStorable audioFormat of
Dict -> openDeviceCallback audioFormat
(MV.unsafeCast (MV.unsafeFromForeignPtr0 fp (fromIntegral len)))
with (desiredSpec cb) $ \desiredSpecPtr ->
alloca $ \actualSpecPtr -> do
devId <- throwIf0 "SDL.Audio.openAudioDevice" "SDL_OpenAudioDevice" $
Raw.openAudioDevice cDevName (encodeUsage openDeviceUsage) desiredSpecPtr actualSpecPtr changes
actual <- peek actualSpecPtr
let audioDevice = AudioDevice devId
anAudioFormat = decodeAudioFormat (Raw.audioSpecFormat actual)
spec =
case anAudioFormat of
AnAudioFormat audioFormat ->
AudioSpec { audioSpecFreq = Raw.audioSpecFreq actual
, audioSpecFormat = audioFormat
, audioSpecChannels = fromC "SDL.Audio.openAudioDevice" "audioSpecChannels" readChannels (Raw.audioSpecChannels actual)
, audioSpecSilence = Raw.audioSpecSilence actual
, audioSpecSize = Raw.audioSpecSize actual
, audioSpecSamples = Raw.audioSpecSamples actual
, audioSpecCallback = openDeviceCallback
}
writeIORef anAudioFormatRef anAudioFormat
return (audioDevice, spec)
where
changes = foldl (.|.) 0 [ foldChangeable (const Raw.SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) (const 0) openDeviceFreq
, foldChangeable (const Raw.SDL_AUDIO_ALLOW_FORMAT_CHANGE) (const 0) openDeviceFormat
, foldChangeable (const Raw.SDL_AUDIO_ALLOW_CHANNELS_CHANGE) (const 0) openDeviceChannels
]
channelsToWord8 Mono = 1
channelsToWord8 Stereo = 2
channelsToWord8 Quad = 4
channelsToWord8 FivePointOne = 6
readChannels 1 = Just Mono
readChannels 2 = Just Stereo
readChannels 4 = Just Quad
readChannels 6 = Just FivePointOne
readChannels _ = Nothing
desiredSpec cb = Raw.AudioSpec
{ Raw.audioSpecFreq = unpackChangeable openDeviceFreq
, Raw.audioSpecFormat = encodeAudioFormat (unpackChangeable openDeviceFormat)
, Raw.audioSpecChannels = channelsToWord8 (unpackChangeable openDeviceChannels)
, Raw.audioSpecSilence = 0
, Raw.audioSpecSize = 0
, Raw.audioSpecSamples = openDeviceSamples
, Raw.audioSpecCallback = cb
, Raw.audioSpecUserdata = nullPtr
}
audioFormatStorable :: AudioFormat sampleType -> Dict (Storable sampleType)
audioFormatStorable Signed8BitAudio = Dict
audioFormatStorable Unsigned8BitAudio = Dict
audioFormatStorable Signed16BitLEAudio = Dict
audioFormatStorable Signed16BitBEAudio = Dict
audioFormatStorable Signed16BitNativeAudio = Dict
audioFormatStorable Unsigned16BitLEAudio = Dict
audioFormatStorable Unsigned16BitBEAudio = Dict
audioFormatStorable Unsigned16BitNativeAudio = Dict
audioFormatStorable Signed32BitLEAudio = Dict
audioFormatStorable Signed32BitBEAudio = Dict
audioFormatStorable Signed32BitNativeAudio = Dict
audioFormatStorable FloatingLEAudio = Dict
audioFormatStorable FloatingBEAudio = Dict
audioFormatStorable FloatingNativeAudio = Dict
data Dict :: Constraint -> * where
Dict :: c => Dict c
closeAudioDevice :: MonadIO m => AudioDevice -> m ()
closeAudioDevice (AudioDevice d) = Raw.closeAudioDevice d
newtype AudioDevice = AudioDevice (Raw.AudioDeviceID)
deriving (Eq, Typeable)
getAudioDeviceNames :: MonadIO m => AudioDeviceUsage -> m (Maybe (V.Vector Text))
getAudioDeviceNames usage = liftIO $ do
n <- Raw.getNumAudioDevices usage'
if n == -1
then return Nothing
else fmap (Just . V.fromList) $
for [0 .. (n - 1)] $ \i -> do
cstr <- throwIfNull "SDL.Audio.getAudioDeviceNames" "SDL_GetAudioDeviceName" $
Raw.getAudioDeviceName i usage'
Text.decodeUtf8 <$> BS.packCString cstr
where usage' = encodeUsage usage
data AudioFormat sampleType where
Signed8BitAudio :: AudioFormat Int8
Unsigned8BitAudio :: AudioFormat Word8
Signed16BitLEAudio :: AudioFormat Int16
Signed16BitBEAudio :: AudioFormat Int16
Signed16BitNativeAudio :: AudioFormat Int16
Unsigned16BitLEAudio :: AudioFormat Word16
Unsigned16BitBEAudio :: AudioFormat Word16
Unsigned16BitNativeAudio :: AudioFormat Word16
Signed32BitLEAudio :: AudioFormat Int32
Signed32BitBEAudio :: AudioFormat Int32
Signed32BitNativeAudio :: AudioFormat Int32
FloatingLEAudio :: AudioFormat Float
FloatingBEAudio :: AudioFormat Float
FloatingNativeAudio :: AudioFormat Float
deriving instance Eq (AudioFormat sampleType)
deriving instance Ord (AudioFormat sampleType)
deriving instance Show (AudioFormat sampleType)
data AnAudioFormat where
AnAudioFormat :: AudioFormat sampleType -> AnAudioFormat
encodeAudioFormat :: AudioFormat sampleType -> Word16
encodeAudioFormat Signed8BitAudio = Raw.SDL_AUDIO_S8
encodeAudioFormat Unsigned8BitAudio = Raw.SDL_AUDIO_U8
encodeAudioFormat Signed16BitLEAudio = Raw.SDL_AUDIO_S16LSB
encodeAudioFormat Signed16BitBEAudio = Raw.SDL_AUDIO_S16MSB
encodeAudioFormat Signed16BitNativeAudio = Raw.SDL_AUDIO_S16SYS
encodeAudioFormat Unsigned16BitLEAudio = Raw.SDL_AUDIO_U16LSB
encodeAudioFormat Unsigned16BitBEAudio = Raw.SDL_AUDIO_U16MSB
encodeAudioFormat Unsigned16BitNativeAudio = Raw.SDL_AUDIO_U16SYS
encodeAudioFormat Signed32BitLEAudio = Raw.SDL_AUDIO_S32LSB
encodeAudioFormat Signed32BitBEAudio = Raw.SDL_AUDIO_S32MSB
encodeAudioFormat Signed32BitNativeAudio = Raw.SDL_AUDIO_S32SYS
encodeAudioFormat FloatingLEAudio = Raw.SDL_AUDIO_F32LSB
encodeAudioFormat FloatingBEAudio = Raw.SDL_AUDIO_F32MSB
encodeAudioFormat FloatingNativeAudio = Raw.SDL_AUDIO_F32SYS
decodeAudioFormat :: Word16 -> AnAudioFormat
decodeAudioFormat Raw.SDL_AUDIO_S8 = AnAudioFormat Signed8BitAudio
decodeAudioFormat Raw.SDL_AUDIO_U8 = AnAudioFormat Unsigned8BitAudio
decodeAudioFormat Raw.SDL_AUDIO_S16LSB = AnAudioFormat Signed16BitLEAudio
decodeAudioFormat Raw.SDL_AUDIO_S16MSB = AnAudioFormat Signed16BitBEAudio
decodeAudioFormat Raw.SDL_AUDIO_S16SYS = AnAudioFormat Signed16BitNativeAudio
decodeAudioFormat Raw.SDL_AUDIO_U16LSB = AnAudioFormat Unsigned16BitLEAudio
decodeAudioFormat Raw.SDL_AUDIO_U16MSB = AnAudioFormat Unsigned16BitBEAudio
decodeAudioFormat Raw.SDL_AUDIO_U16SYS = AnAudioFormat Unsigned16BitNativeAudio
decodeAudioFormat Raw.SDL_AUDIO_S32LSB = AnAudioFormat Signed32BitLEAudio
decodeAudioFormat Raw.SDL_AUDIO_S32MSB = AnAudioFormat Signed32BitBEAudio
decodeAudioFormat Raw.SDL_AUDIO_S32SYS = AnAudioFormat Signed32BitNativeAudio
decodeAudioFormat Raw.SDL_AUDIO_F32LSB = AnAudioFormat FloatingLEAudio
decodeAudioFormat Raw.SDL_AUDIO_F32MSB = AnAudioFormat FloatingBEAudio
decodeAudioFormat Raw.SDL_AUDIO_F32SYS = AnAudioFormat FloatingNativeAudio
decodeAudioFormat x = error ("decodeAudioFormat failed: Unknown format " ++ show x)
data Channels
= Mono
| Stereo
| Quad
| FivePointOne
deriving (Bounded,Data,Enum,Eq,Generic,Ord,Read,Show,Typeable)
data AudioSpec = forall sampleType. AudioSpec
{ audioSpecFreq :: !CInt
, audioSpecFormat :: !(AudioFormat sampleType)
, audioSpecChannels :: !Channels
, audioSpecSilence :: !Word8
, audioSpecSamples :: !Word16
, audioSpecSize :: !Word32
, audioSpecCallback :: AudioFormat sampleType -> MV.IOVector sampleType -> IO ()
}
deriving (Typeable)
data AudioDeviceUsage
= ForPlayback
| ForCapture
deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
encodeUsage :: Num a => AudioDeviceUsage -> a
encodeUsage usage =
case usage of
ForPlayback -> 0
ForCapture -> 1
data Changeable a
= Mandate !a
| Desire !a
deriving (Data, Foldable, Functor, Eq, Generic, Read, Show, Traversable, Typeable)
foldChangeable :: (a -> b) -> (a -> b) -> Changeable a -> b
foldChangeable f _ (Mandate a) = f a
foldChangeable _ g (Desire a) = g a
unpackChangeable :: Changeable a -> a
unpackChangeable = foldChangeable id id
data LockState
= Locked
| Unlocked
deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable)
setAudioDeviceLocked :: MonadIO m => AudioDevice -> LockState -> m ()
setAudioDeviceLocked (AudioDevice d) Locked = Raw.lockAudioDevice d
setAudioDeviceLocked (AudioDevice d) Unlocked = Raw.unlockAudioDevice d
data PlaybackState
= Pause
| Play
deriving (Bounded, Enum, Eq, Ord, Read, Data, Generic, Show, Typeable)
setAudioDevicePlaybackState :: MonadIO m => AudioDevice -> PlaybackState -> m ()
setAudioDevicePlaybackState (AudioDevice d) Pause = Raw.pauseAudioDevice d 1
setAudioDevicePlaybackState (AudioDevice d) Play = Raw.pauseAudioDevice d 0
data AudioDeviceStatus
= Playing
| Paused
| Stopped
deriving (Bounded, Enum, Eq, Ord, Read, Data, Generic, Show, Typeable)
audioDeviceStatus :: MonadIO m => AudioDevice -> m AudioDeviceStatus
audioDeviceStatus (AudioDevice d) = liftIO $
fromC "SDL.Audio.audioDeviceStatus" "SDL_AudioStatus" readStatus <$> Raw.getAudioDeviceStatus d
where
readStatus n = case n of
Raw.SDL_AUDIO_PLAYING -> Just Playing
Raw.SDL_AUDIO_STOPPED -> Just Stopped
Raw.SDL_AUDIO_PAUSED -> Just Paused
_ -> Nothing
newtype AudioDriver = AudioDriver Text
deriving (Eq, Show, Typeable)
audioDriverName :: AudioDriver -> Text
audioDriverName (AudioDriver t) = t
getAudioDrivers :: MonadIO m => m (V.Vector AudioDriver)
getAudioDrivers = liftIO $ do
n <- Raw.getNumAudioDrivers
fmap V.fromList $
for [0 .. (n - 1)] $ \i -> do
cstr <- Raw.getAudioDriver i
AudioDriver . Text.decodeUtf8 <$> BS.packCString cstr
audioInit :: MonadIO m => AudioDriver -> m ()
audioInit (AudioDriver n) = liftIO $ BS.useAsCString (Text.encodeUtf8 n) $
throwIfNeg_ "SDL.Audio.audioInit" "SDL_AudioInit" . Raw.audioInit
currentAudioDriver :: MonadIO m => m (Maybe Text)
currentAudioDriver =
liftIO $ maybePeek (fmap Text.decodeUtf8 . BS.packCString) =<< Raw.getCurrentAudioDriver