iteratee-0.4.0: Examples/Wave.hs
{-# LANGUAGE RankNTypes, FlexibleContexts #-}
{-
This module is not meant primarily for instructive and pedagogical purposes.
As such, it is not fully featured, and sacrifices performance and generality
for clarity of code.
-}
module Data.Iteratee.Codecs.Wave {-# DEPRECATED "This will be moved to a separate package in the future" #-} (
WAVEDE (..),
WAVEDE_ENUM (..),
WAVE_CHUNK (..),
AudioFormat (..),
waveReader,
readRiff,
waveChunk,
chunkToString,
dictReadFormat,
dictReadFirstFormat,
dictReadLastFormat,
dictReadFirstData,
dictReadLastData,
dictReadData,
dictProcessData
)
where
import Prelude as P
import Control.Monad (join)
import Control.Monad.Trans (lift)
import Data.Iteratee
import qualified Data.Iteratee as Iter
import Data.Iteratee.Binary
import Data.Char (chr, ord)
import Data.Int
import Data.Word
import Data.Bits (shiftL)
import Data.Maybe
import qualified Data.IntMap as IM
-- =====================================================
-- WAVE libary code
-- useful type synonyms
-- |A WAVE directory is a list associating WAVE chunks with
-- a record WAVEDE
type WAVEDict = IM.IntMap [WAVEDE]
data WAVEDE = WAVEDE{
wavede_count :: Int, -- ^length of chunk
wavede_type :: WAVE_CHUNK, -- ^type of chunk
wavede_enum :: WAVEDE_ENUM -- ^enumerator to get values of chunk
}
type EnumeratorM sFrom sTo m a = Iteratee sTo m a -> m (Iteratee sFrom m a)
joinL :: (Monad m, Nullable s) => m (Iteratee s m a) -> Iteratee s m a
joinL = join . lift
data WAVEDE_ENUM =
WEN_BYTE (forall a. EnumeratorM [Word8] [Word8] IO a)
| WEN_DUB (forall a. EnumeratorM [Word8] [Double] IO a)
-- |Standard WAVE Chunks
data WAVE_CHUNK = WAVE_FMT -- ^Format
| WAVE_DATA -- ^Data
| WAVE_OTHER String -- ^Other
deriving (Eq, Ord, Show)
instance Enum WAVE_CHUNK where
fromEnum WAVE_FMT = 1
fromEnum WAVE_DATA = 2
fromEnum (WAVE_OTHER _) = 3
toEnum 1 = WAVE_FMT
toEnum 2 = WAVE_DATA
toEnum 3 = WAVE_OTHER ""
toEnum _ = error "Invalid enumeration value"
-- -----------------
-- wave chunk reading/writing functions
-- |Convert a string to WAVE_CHUNK type
waveChunk :: String -> Maybe WAVE_CHUNK
waveChunk str
| str == "fmt " = Just WAVE_FMT
| str == "data" = Just WAVE_DATA
| P.length str == 4 = Just $ WAVE_OTHER str
| otherwise = Nothing
-- |Convert a WAVE_CHUNK to the representative string
chunkToString :: WAVE_CHUNK -> String
chunkToString WAVE_FMT = "fmt "
chunkToString WAVE_DATA = "data"
chunkToString (WAVE_OTHER str) = str
-- -----------------
data AudioFormat = AudioFormat {
numberOfChannels :: NumChannels, -- ^Number of channels in the audio data
sampleRate :: SampleRate, -- ^Sample rate of the audio
bitDepth :: BitDepth -- ^Bit depth of the audio data
} deriving (Show, Eq)
type NumChannels = Integer
type SampleRate = Integer
type BitDepth = Integer
-- convenience function to read a 4-byte ASCII string
stringRead4 :: Monad m => Iteratee [Word8] m String
stringRead4 = do
s1 <- Iter.head
s2 <- Iter.head
s3 <- Iter.head
s4 <- Iter.head
return $ map (chr . fromIntegral) [s1, s2, s3, s4]
-- -----------------
-- |The library function to read the WAVE dictionary
waveReader :: Iteratee [Word8] IO (Maybe WAVEDict)
waveReader = do
readRiff
tot_size <- endianRead4 LSB
readRiffWave
chunks_m <- findChunks $ fromIntegral tot_size
loadDict $ joinM chunks_m
-- |Read the RIFF header of a file.
readRiff :: Iteratee [Word8] IO ()
readRiff = do
cnt <- heads $ fmap (fromIntegral . ord) "RIFF"
if cnt == 4 then return () else throwErr $ iterStrExc "Bad RIFF header"
-- | Read the WAVE part of the RIFF header.
readRiffWave :: Iteratee [Word8] IO ()
readRiffWave = do
cnt <- heads $ fmap (fromIntegral . ord) "WAVE"
if cnt == 4 then return () else throwErr $ iterStrExc "Bad RIFF/WAVE header"
-- | An internal function to find all the chunks. It assumes that the
-- stream is positioned to read the first chunk.
findChunks :: Int -> Iteratee [Word8] IO (Maybe [(Int, WAVE_CHUNK, Int)])
findChunks n = findChunks' 12 []
where
findChunks' offset acc = do
typ <- stringRead4
count <- endianRead4 LSB
case waveChunk typ of
Nothing -> (throwErr . iterStrExc $ "Bad subchunk descriptor: " ++ show typ)
>> return Nothing
Just chk -> let newpos = offset + 8 + count in
case newpos >= fromIntegral n of
True -> return . Just $ reverse $
(fromIntegral offset, chk, fromIntegral count) : acc
False -> do
Iter.seek $ fromIntegral newpos
findChunks' newpos $
(fromIntegral offset, chk, fromIntegral count) : acc
loadDict :: [(Int, WAVE_CHUNK, Int)] ->
Iteratee [Word8] IO (Maybe WAVEDict)
loadDict = P.foldl read_entry (return (Just IM.empty))
where
read_entry dictM (offset, typ, count) = dictM >>=
maybe (return Nothing) (\dict -> do
enum_m <- readValue dict offset typ count
case (enum_m, IM.lookup (fromEnum typ) dict) of
(Just enum, Nothing) -> --insert new entry
return . Just $ IM.insert (fromEnum typ)
[WAVEDE (fromIntegral count) typ enum] dict
(Just enum, Just _vals) -> --existing entry
return . Just $ IM.update
(\ls -> Just $ ls ++ [WAVEDE (fromIntegral count) typ enum])
(fromEnum typ) dict
(Nothing, _) -> return (Just dict)
)
readValue :: WAVEDict ->
Int -> -- Offset
WAVE_CHUNK -> -- Chunk type
Int -> -- Count
Iteratee [Word8] IO (Maybe WAVEDE_ENUM)
readValue _dict offset _ 0 = do
throwErr . iterStrExc $ "Zero count in the entry of chunk at: " ++ show offset
return Nothing
readValue dict offset WAVE_DATA count = do
fmt_m <- dictReadLastFormat dict
case fmt_m of
Just fmt ->
return . Just . WEN_DUB $ \iter_dub -> return $ do
Iter.seek (8 + fromIntegral offset)
let iter = Iter.convStream (convFunc fmt) iter_dub
joinI . joinI . Iter.take count $ iter
Nothing -> do
throwErr . iterStrExc $ "No valid format for data chunk at: " ++ show offset
return Nothing
-- return the WaveFormat iteratee
readValue _dict offset WAVE_FMT count =
return . Just . WEN_BYTE $ \iter -> return $ do
Iter.seek (8 + fromIntegral offset)
Iter.joinI $ Iter.take count iter
-- for WAVE_OTHER, return Word8s and maybe the user can parse them
readValue _dict offset (WAVE_OTHER _str) count =
return . Just . WEN_BYTE $ \iter -> return $ do
Iter.seek (8 + fromIntegral offset)
Iter.joinI $ Iter.take count iter
-- |Convert Word8s to Doubles
convFunc :: AudioFormat -> Iteratee [Word8] IO [Double]
convFunc (AudioFormat _nc _sr 8) = fmap
((:[]) . normalize 8 . (fromIntegral :: Word8 -> Int8))
Iter.head
convFunc (AudioFormat _nc _sr 16) = fmap
((:[]) . normalize 16 . (fromIntegral :: Word16 -> Int16))
(endianRead2 LSB)
convFunc (AudioFormat _nc _sr 24) = fmap
((:[]) . normalize 24 . (fromIntegral :: Word32 -> Int32))
(endianRead3 LSB)
convFunc (AudioFormat _nc _sr 32) = fmap
((:[]) . normalize 32 . (fromIntegral :: Word32 -> Int32))
(endianRead4 LSB)
convFunc _ = error "unrecognized audio format in convFunc"
eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe = either (const Nothing) Just
-- |An Iteratee to read a wave format chunk
sWaveFormat :: Iteratee [Word8] IO (Maybe AudioFormat)
sWaveFormat = do
f' <- endianRead2 LSB --data format, 1==PCM
nc <- endianRead2 LSB
sr <- endianRead4 LSB
Iter.drop 6
bd <- endianRead2 LSB
case f' == 1 of
True -> return . Just $ AudioFormat (fromIntegral nc)
(fromIntegral sr)
(fromIntegral bd)
False -> return Nothing
-- ---------------------
-- functions to assist with reading from the dictionary
-- |Read the first format chunk in the WAVE dictionary.
dictReadFirstFormat :: WAVEDict -> Iteratee [Word8] IO (Maybe AudioFormat)
dictReadFirstFormat dict = case IM.lookup (fromEnum WAVE_FMT) dict of
Just [] -> return Nothing
Just ((WAVEDE _ WAVE_FMT (WEN_BYTE enum)) : _xs) -> joinIM $ enum sWaveFormat
_ -> return Nothing
-- |Read the last fromat chunk from the WAVE dictionary. This is useful
-- when parsing all chunks in the dictionary.
dictReadLastFormat :: WAVEDict -> Iteratee [Word8] IO (Maybe AudioFormat)
dictReadLastFormat dict = case IM.lookup (fromEnum WAVE_FMT) dict of
Just [] -> return Nothing
Just xs -> let (WAVEDE _ WAVE_FMT (WEN_BYTE enum)) = last xs in
joinIM $ enum sWaveFormat
_ -> return Nothing
-- |Read the specified format chunk from the WAVE dictionary
dictReadFormat :: Int -> --Index in the format chunk list to read
WAVEDict -> --Dictionary
Iteratee [Word8] IO (Maybe AudioFormat)
dictReadFormat ix dict = case IM.lookup (fromEnum WAVE_FMT) dict of
Just xs -> let (WAVEDE _ WAVE_FMT (WEN_BYTE enum)) = (!!) xs ix in
joinIM $ enum sWaveFormat
_ -> return Nothing
-- |Read the first data chunk in the WAVE dictionary.
dictReadFirstData :: WAVEDict -> Iteratee [Word8] IO (Maybe [Double])
dictReadFirstData dict = case IM.lookup (fromEnum WAVE_DATA) dict of
Just [] -> return Nothing
Just ((WAVEDE _ WAVE_DATA (WEN_DUB enum)) : _xs) -> do
e <- joinIM $ enum Iter.stream2list
return $ Just e
_ -> return Nothing
-- |Read the last data chunk in the WAVE dictionary.
dictReadLastData :: WAVEDict -> Iteratee [Word8] IO (Maybe [Double])
dictReadLastData dict = case IM.lookup (fromEnum WAVE_DATA) dict of
Just [] -> return Nothing
Just xs -> let (WAVEDE _ WAVE_DATA (WEN_DUB enum)) = last xs in do
e <- joinIM $ enum Iter.stream2list
return $ Just e
_ -> return Nothing
-- |Read the specified data chunk from the WAVE dictionary.
dictReadData :: Int -> --Index in the data chunk list to read
WAVEDict -> --Dictionary
Iteratee [Word8] IO (Maybe [Double])
dictReadData ix dict = case IM.lookup (fromEnum WAVE_DATA) dict of
Just xs -> let (WAVEDE _ WAVE_DATA (WEN_DUB enum)) = (!!) xs ix in do
e <- joinIM $ enum Iter.stream2list
return $ Just e
_ -> return Nothing
-- |Read the specified data chunk from the dictionary, applying the
-- data to the specified Iteratee.
dictProcessData :: Int -> -- Index in the data chunk list to read
WAVEDict -> -- Dictionary
Iteratee [Double] IO a ->
Iteratee [Word8] IO (Maybe a)
dictProcessData ix dict iter = case IM.lookup (fromEnum WAVE_DATA) dict of
Just xs -> let (WAVEDE _ WAVE_DATA (WEN_DUB enum)) = (!!) xs ix in do
e <- joinIM $ enum iter
return $ Just e
_ -> return Nothing
-- ---------------------
-- convenience functions
-- |Convert (Maybe []) to []. Nothing maps to an empty list.
joinM :: Maybe [a] -> [a]
joinM Nothing = []
joinM (Just a) = a
-- |Normalize a given value for the provided bit depth.
normalize :: Integral a => BitDepth -> a -> Double
normalize 8 a = (fromIntegral a - 128) / 128
normalize bd a = case (a > 0) of
True -> fromIntegral a / divPos
False -> fromIntegral a / divNeg
where
divPos = fromIntegral (1 `shiftL` fromIntegral (bd - 1) :: Int) - 1
divNeg = fromIntegral (1 `shiftL` fromIntegral (bd - 1) :: Int)