sndfile-enumerators-0.4.0: src/Sound/Iteratee/Codecs/Common.hs
{-# LANGUAGE ScopedTypeVariables #-}
module Sound.Iteratee.Codecs.Common (
stringRead4
,joinMaybe
,convFunc
)
where
import Sound.Iteratee.Base
import qualified Data.Iteratee as I
import Data.MutableIter as Iter
import qualified Data.MutableIter.IOBuffer as IB
import Foreign
import Control.Monad (replicateM, liftM)
import Control.Monad.CatchIO
import Control.Monad.IO.Class
import Data.Char (chr)
import Data.Int.Int24
import Data.Word.Word24
-- =====================================================
-- useful type synonyms
type IOB m el = IOBuffer m el
-- determine host endian-ness
be :: IO Bool
be = fmap (==1) $ with (1 :: Word16) (\p -> peekByteOff p 1 :: IO Word8)
-- convenience function to read a 4-byte ASCII string
stringRead4 :: MonadCatchIO m => MIteratee (IOB r Word8) m String
stringRead4 = (liftM . map) (chr . fromIntegral) $ replicateM 4 Iter.head
unroll8 :: (MonadCatchIO m) => MIteratee (IOB r Word8) m (Maybe (IOB r Word8))
unroll8 = liftI step
where
step (I.Chunk buf) = guardNull buf (liftI step) $
idone (Just buf) (I.Chunk IB.empty)
step stream = idone Nothing stream
-- When unrolling to a Word8, use the specialized unroll8 function
-- because we actually don't need to do anything
{-# RULES "unroll8" forall n. unroller n = unroll8 #-}
unroller :: (Storable a, MonadCatchIO m) =>
Int
-> MIteratee (IOB r Word8) m (Maybe (IOB r a))
unroller wSize = liftI step
where
step (I.Chunk buf) = guardNull buf (liftI step) $ do
len <- liftIO $ IB.length buf
if len < wSize then liftIO (IB.copyBuffer buf) >>= liftI . step'
else if len `rem` wSize == 0
then do
buf' <- liftIO $ convert_vec buf
idone (Just buf') (I.Chunk IB.empty)
else let newLen = (len `div` wSize) * wSize
in do
(h, t) <- liftIO $ IB.splitAt buf newLen
h' <- liftIO $ convert_vec h
idone (Just h') (I.Chunk t)
step stream = idone Nothing stream
step' i (I.Chunk buf) = guardNull buf (liftI (step' i)) $ do
l <- liftIO $ IB.length buf
iLen <- liftIO $ IB.length i
newbuf <- liftIO $ IB.append i buf
if l+iLen < wSize then liftI (step' newbuf)
else do
newLen <- liftIO $ IB.length newbuf
let newLen' = (newLen `div` wSize) * wSize
(h,t) <- liftIO $ IB.splitAt newbuf newLen'
h' <- liftIO $ convert_vec h
idone (Just h') (I.Chunk t)
step' _i stream = idone Nothing stream
convert_vec vec = IB.castBuffer vec >>= hostToLE
hostToLE :: (Monad m, Storable a) => IOB r a -> m (IOB r a)
hostToLE vec = let be' = unsafePerformIO be in if be'
then error "wrong endian-ness. Ask the maintainer to implement hostToLE"
{-
(fp, off, len) = VB.toForeignPtr vec
wSize = sizeOf $ Vec.head vec
in
loop wSize fp len off
-}
else return vec
{-
where
loop _wSize _fp 0 _off = return vec
loop wSize fp len off = do
FFP.withForeignPtr fp (swapBytes wSize . flip FP.plusPtr off)
loop wSize fp (len - 1) (off + 1)
-}
{-
swapBytes :: Int -> ForeignPtr a -> IO ()
swapBytes wSize fp = withForeignPtr fp $ \p -> case wSize of
1 -> return ()
2 -> do
(w1 :: Word8) <- peekByteOff p 0
(w2 :: Word8) <- peekByteOff p 1
pokeByteOff p 0 w2
pokeByteOff p 1 w1
3 -> do
(w1 :: Word8) <- peekByteOff p 0
(w3 :: Word8) <- peekByteOff p 2
pokeByteOff p 0 w3
pokeByteOff p 2 w1
4 -> do
(w1 :: Word8) <- peekByteOff p 0
(w2 :: Word8) <- peekByteOff p 1
(w3 :: Word8) <- peekByteOff p 2
(w4 :: Word8) <- peekByteOff p 3
pokeByteOff p 0 w4
pokeByteOff p 1 w3
pokeByteOff p 2 w2
pokeByteOff p 3 w1
_ -> error "swapBytes called with wordsize > 4"
w8 :: Word8
w8 = 0
-}
w16 :: Word16
w16 = 0
w24 :: Word24
w24 = 0
w32 :: Word32
w32 = 0
-- |Convert Word8s to Doubles
convFunc :: (MonadCatchIO m) =>
AudioFormat
-> ForeignPtr Int
-> ForeignPtr Double
-> MIteratee (IOBuffer r Word8) m (IOBuffer r Double)
convFunc (AudioFormat _nc _sr 8) offp bufp = do
mbuf <- unroll8
liftIO $ maybe (error "error in convFunc") (IB.mapBuffer
(normalize 8 . (fromIntegral :: Word8 -> Int8)) offp bufp) mbuf
convFunc (AudioFormat _nc _sr 16) offp bufp = do
mbuf <- unroller (sizeOf w16)
liftIO $ maybe (error "error in convFunc") (IB.mapBuffer
(normalize 16 . (fromIntegral :: Word16 -> Int16)) offp bufp) mbuf
convFunc (AudioFormat _nc _sr 24) offp bufp = do
mbuf <- unroller (sizeOf w24)
liftIO $ maybe (error "error in convFunc") (IB.mapBuffer
(normalize 24 . (fromIntegral :: Word24 -> Int24)) offp bufp) mbuf
convFunc (AudioFormat _nc _sr 32) offp bufp = do
mbuf <- unroller (sizeOf w32)
liftIO $ maybe (error "error in convFunc") (IB.mapBuffer
(normalize 32 . (fromIntegral :: Word32 -> Int32)) offp bufp) mbuf
convFunc _ _ _ = MIteratee $ I.throwErr (I.iterStrExc "Invalid wave bit depth")
-- ---------------------
-- convenience functions
-- |Convert (Maybe []) to []. Nothing maps to an empty list.
joinMaybe :: Maybe [a] -> [a]
joinMaybe Nothing = []
joinMaybe (Just a) = a
-- |Normalize a given value for the provided bit depth.
-- This uses wave-standard normalization. I'll support more formats
-- if/when it becomes necessary.
normalize :: Integral a => BitDepth -> a -> Double
normalize 8 = \a -> let m = 1 / 128 in m * (fromIntegral a - 128)
normalize bd = \a -> if a > 0
then fromIntegral a * mPos
else fromIntegral a * mNeg
where
mPos = 1/ (fromIntegral (1 `shiftL` fromIntegral (bd - 1) :: Integer) - 1)
mNeg = 1/ fromIntegral (1 `shiftL` fromIntegral (bd - 1) :: Integer)