zoom-cache-1.2.1.1: Data/Iteratee/ZoomCache.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS -Wall #-}
----------------------------------------------------------------------
{- |
Module : Data.Iteratee.ZoomCache
Copyright : Conrad Parker
License : BSD3-style (see LICENSE)
Maintainer : Conrad Parker <conrad@metadecks.org>
Stability : unstable
Portability : unknown
Iteratee reading of ZoomCache files.
A typical usage, using the iteratee @iter@ to process the level 3 summaries
from the track called \"rainfall\":
@
I.fileDriverRandom (enumCacheFile standardIdentifiers .
I.joinI . filterTracksByName [\"rainfall\"] .
I.joinI . enumSummaryLevel 3 $ iter) filename
@
Similarly, using the iteratee @rawIter@ to process the raw data from the
track called \"rainfall\":
@
I.fileDriverRandom (enumCacheFile standardIdentifiers .
I.joinI . filterTracksByName [\"rainfall\"] .
I.joinI . enumPackets $ rawIter) filename
@
-}
----------------------------------------------------------------------
module Data.Iteratee.ZoomCache (
-- * Types
Block(..)
, BlockData(..)
-- * Reading zoom-cache files and ByteStrings
, enumCacheFile
, wholeTrackSummary
, wholeTrackSummaryUTC
, iterHeaders
, enumBlock
, enumBlockIncomplete
, enumBlockTrackNo
-- * Seeking
, seekTimeStamp
, seekUTCTime
-- * Stream enumeratees
, enumPackets
, enumPacketsUTC
, enumSummaryLevel
, enumSummaries
, enumSummaryUTCLevel
, enumSummariesUTC
, filterTracksByName
, filterTracks
-- * Low-level access to SampleOffsets
, enumPacketSOs
, enumSummarySOLevel
, enumSummarySOs
, enumCTPSO
, enumCTSO
) where
import Control.Applicative
import Control.Monad (replicateM)
import Control.Monad.Trans (MonadIO)
import Data.Bits
import Data.ByteString (ByteString)
import Data.Functor.Identity
import Data.Int
import Data.IntMap (IntMap)
import qualified Data.IntMap as IM
import Data.Iteratee (Iteratee)
import qualified Data.Iteratee as I
import qualified Data.Iteratee.Offset as OffI
import Data.Iteratee.ZLib
import Data.Maybe
import Data.Ratio
import Data.Time
import System.Posix.Types (FileOffset)
import Data.Iteratee.ZoomCache.Seek
import Data.Iteratee.ZoomCache.Utils
import Data.Offset
import Data.ZoomCache.Common
import Data.ZoomCache.Format
import Data.ZoomCache.Multichannel.Internal (supportMultichannel)
import Data.ZoomCache.Numeric.Delta
import Data.ZoomCache.Types
----------------------------------------------------------------------
data Block = Block
{ blkFile :: CacheFile
, blkTrack :: TrackNo
, blkData :: !BlockData
}
data BlockData = BlockPacket !PacketSO | BlockSummary !ZoomSummarySO
instance Timestampable Block where
timestamp (Block c t (BlockPacket p)) = timestamp (packetFromCTPSO (c,t,p))
timestamp (Block c t (BlockSummary s)) = timestamp (summaryFromCTSO (c,t,s))
----------------------------------------------------------------------
-- | Read the summary of an entire track.
wholeTrackSummary :: (Functor m, MonadIO m)
=> TrackNo
-> Iteratee [Offset Block] m (TrackSpec, ZoomSummary)
wholeTrackSummary trackNo = I.joinI $
filterTracks [trackNo] . I.joinI . enumCTSO $ f <$> I.last
where
f :: (CacheFile, TrackNo, ZoomSummarySO) -> (TrackSpec, ZoomSummary)
f ctso@(cf, _, _) = (fromJust $ IM.lookup trackNo (cfSpecs cf),
summaryFromCTSO ctso)
-- | Read the summary of an entire track.
wholeTrackSummaryUTC :: (Functor m, MonadIO m)
=> TrackNo
-> Iteratee [Offset Block] m (TrackSpec, Maybe ZoomSummaryUTC)
wholeTrackSummaryUTC trackNo = I.joinI $
filterTracks [trackNo] . I.joinI . enumCTSO $ f <$> I.last
where
f :: (CacheFile, TrackNo, ZoomSummarySO) -> (TrackSpec, Maybe ZoomSummaryUTC)
f ctso@(cf, _, _) = (fromJust $ IM.lookup trackNo (cfSpecs cf),
summaryUTCFromCTSO ctso)
----------------------------------------------------------------------
-- | Filter just the raw data
enumPackets :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [Packet] m a
enumPackets = I.joinI . enumCTPSO . I.mapChunks (map packetFromCTPSO)
-- | Convert a CTPSO triple into a Packet
packetFromCTPSO :: (CacheFile, TrackNo, PacketSO) -> Packet
packetFromCTPSO (cf, trackNo, pso) = packetFromPacketSO r pso
where
r = specRate . fromJust . IM.lookup trackNo . cfSpecs $ cf
----------------------------------------------------------------------
-- | Filter just the raw data, timestamped by UTC
enumPacketsUTC :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [PacketUTC] m a
enumPacketsUTC = I.joinI . enumCTPSO . I.mapChunks (catMaybes . map packetUTCFromCTPSO)
-- | Convert a CTPSO triple into a Packet
packetUTCFromCTPSO :: (CacheFile, TrackNo, PacketSO) -> Maybe PacketUTC
packetUTCFromCTPSO (cf, trackNo, pso) = toPacket <$> base'm
where
toPacket base = packetUTCFromPacketSO base r pso
r = specRate . fromJust . IM.lookup trackNo . cfSpecs $ cf
base'm = baseUTC . cfGlobal $ cf
----------------------------------------------------------------------
-- | Filter summaries at a particular summary level
enumSummaryLevel :: (Functor m, Monad m)
=> Int
-> I.Enumeratee [Offset Block] [ZoomSummary] m a
enumSummaryLevel level =
I.joinI . enumSummaries .
I.filter (\(ZoomSummary s) -> summaryLevel s == level)
{-# INLINE enumSummaryLevel #-}
-- | Filter summaries at all levels
enumSummaries :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [ZoomSummary] m a
enumSummaries = I.joinI . enumCTSO . I.mapChunks (map summaryFromCTSO)
{-# INLINE enumSummaries #-}
-- | Convert a CTSO triple into a ZoomSummary
summaryFromCTSO :: (CacheFile, TrackNo, ZoomSummarySO) -> ZoomSummary
summaryFromCTSO (cf, trackNo, (ZoomSummarySO zso)) =
ZoomSummary (summaryFromSummarySO r zso)
where
r = specRate . fromJust . IM.lookup trackNo . cfSpecs $ cf
----------------------------------------------------------------------
-- | Filter summaries at a particular summary level
enumSummaryUTCLevel :: (Functor m, Monad m)
=> Int
-> I.Enumeratee [Offset Block] [ZoomSummaryUTC] m a
enumSummaryUTCLevel level =
I.joinI . enumSummariesUTC .
I.filter (\(ZoomSummaryUTC s) -> summaryUTCLevel s == level)
-- | Filter summaries at all levels
enumSummariesUTC :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [ZoomSummaryUTC] m a
enumSummariesUTC = I.joinI . enumCTSO . I.mapChunks (catMaybes . map summaryUTCFromCTSO)
-- | Convert a CTSO triple into a ZoomSummaryUTC
summaryUTCFromCTSO :: (CacheFile, TrackNo, ZoomSummarySO) -> Maybe ZoomSummaryUTC
summaryUTCFromCTSO (cf, trackNo, (ZoomSummarySO zso)) = toZS <$> base'm
where
toZS base = ZoomSummaryUTC (summaryUTCFromSummarySO base r zso)
r = specRate . fromJust . IM.lookup trackNo . cfSpecs $ cf
base'm = baseUTC . cfGlobal $ cf
----------------------------------------------------------------------
-- | Filter just the raw data
enumPacketSOs :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [PacketSO] m a
enumPacketSOs = I.joinI . enumCTPSO . I.mapChunks (map (\(_,_,p) -> p))
-- | Filter summaries at a particular summary level
enumSummarySOLevel :: (Functor m, Monad m)
=> Int
-> I.Enumeratee [Offset Block] [ZoomSummarySO] m a
enumSummarySOLevel level =
I.joinI . enumSummarySOs .
I.filter (\(ZoomSummarySO s) -> summarySOLevel s == level)
-- | Filter summaries at all levels
enumSummarySOs :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [ZoomSummarySO] m a
enumSummarySOs = I.joinI . enumCTSO . I.mapChunks (map (\(_,_,s) -> s))
-- | Filter raw data
enumCTPSO :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [(CacheFile, TrackNo, PacketSO)] m a
enumCTPSO = I.mapChunks (catMaybes . map (toCTPSO . unwrapOffset))
where
toCTPSO :: Block -> Maybe (CacheFile, TrackNo, PacketSO)
toCTPSO (Block c t (BlockPacket p)) = Just (c, t, p)
toCTPSO _ = Nothing
-- | Filter summaries
enumCTSO :: (Functor m, Monad m)
=> I.Enumeratee [Offset Block] [(CacheFile, TrackNo, ZoomSummarySO)] m a
enumCTSO = I.mapChunks (catMaybes . map (toCTSO . unwrapOffset))
where
toCTSO :: Block -> Maybe (CacheFile, TrackNo, ZoomSummarySO)
toCTSO (Block c t (BlockSummary s)) = Just (c, t, s)
toCTSO _ = Nothing
-- | Filter to a given list of track names
filterTracksByName :: (Functor m, Monad m)
=> CacheFile
-> [ByteString]
-> I.Enumeratee [Offset Block] [Offset Block] m a
filterTracksByName CacheFile{..} names = filterTracks tracks
where
tracks :: [TrackNo]
tracks = IM.keys (IM.filter f cfSpecs)
f :: TrackSpec -> Bool
f ts = specName ts `elem` names
-- | Filter to a given list of track numbers
filterTracks :: (Functor m, Monad m)
=> [TrackNo]
-> I.Enumeratee [Offset Block] [Offset Block] m a
filterTracks tracks = I.filter (fil . unwrapOffset)
where
fil :: Block -> Bool
fil b = (blkTrack b) `elem` tracks
{-# INLINE filterTracks #-}
-- | An enumeratee of a zoom-cache file, from the global header onwards.
-- The global and track headers will be transparently read, and the
-- 'CacheFile' visible in the 'Block' elements.
enumCacheFile :: (Functor m, MonadIO m)
=> [IdentifyCodec]
-> I.Enumeratee (Offset ByteString) [Offset Block] m a
enumCacheFile identifiers iter = do
fi <- iterHeaders identifiers
enumBlock fi iter
{-# INLINE enumCacheFile #-}
-- | An enumeratee of zoom-cache data, after global and track headers
-- have been read, or if the 'CacheFile' has been acquired elsewhere.
-- This version skips parsing of all tracks other than the specified 'TrackNo'.
--
-- This function should only be used in applications where only one track is
-- used from a file; if you need to process multiple tracks independently then
-- give each an iteratee filtered by filterTracks or filterTracksByName, and
-- run these in parallel on the output of 'enumCacheFile' or 'enumBlock'.
-- Using this function multiple times in parallel will duplicate some parsing.
enumBlockTrackNo :: (Functor m, MonadIO m)
=> CacheFile
-> TrackNo
-> I.Enumeratee (Offset ByteString) [Offset Block] m a
enumBlockTrackNo cf0 trackNo = I.unfoldConvStreamCheck I.eneeCheckIfDoneIgnore go cf0
where
go :: (Functor m, MonadIO m)
=> CacheFile
-> Iteratee (Offset ByteString) m (CacheFile, [Offset Block])
go cf = do
o <- OffI.tell
header <- OffI.takeBS 8
case parseHeader header of
Just PacketHeader -> do
(_, packet) <- OffI.convOffset $ readPacketTrackNo (cfSpecs cf) trackNo
let res = maybe [] (\p -> [Offset o (Block cf trackNo (BlockPacket p))]) packet
return (cf, res)
Just SummaryHeader -> do
(_, summary) <- OffI.convOffset $ readSummaryBlockTrackNo (cfSpecs cf) trackNo
let res = maybe [] (\s -> [Offset o (Block cf trackNo (BlockSummary s))]) summary
return (cf, res)
_ -> return (cf, [])
-- | An iteratee of zoom-cache which produces a singleton list of zoom-cache
-- stream, if it can.
iterBlock :: (Functor m, MonadIO m)
=> CacheFile
-> Iteratee (Offset ByteString) m (CacheFile, [Offset Block])
iterBlock cf = {-# SCC "iterBlock" #-} do
I.dropWhile (/= headerMarker)
o <- OffI.tell
header <- OffI.takeBS 8
case parseHeader header of
Just PacketHeader -> do
(!trackNo, packet) <- OffI.convOffset $ readPacket (cfSpecs cf)
return $! maybe (cf, []) (retPacket trackNo o) packet
Just SummaryHeader -> do
(!trackNo, summary) <- OffI.convOffset $ readSummaryBlock (cfSpecs cf)
return $! maybe (cf, []) (retSummary trackNo o) summary
_ -> return (cf, [])
where
retPacket trackNo o p = (cf', [Offset o (Block cf' trackNo (BlockPacket p))])
where
ms = toMS <$> (flip timeStampFromSO) (packetSOExit p) <$> r
r = specRate <$> IM.lookup trackNo (cfSpecs cf)
!cfO' = offs' ms o (cfOffsets cf)
cf' = cf { cfOffsets = cfO' }
retSummary trackNo o (ZoomSummarySO s) =
(cf', [Offset o (Block cf' trackNo (BlockSummary (ZoomSummarySO s)))])
where
ms = toMS <$> (flip timeStampFromSO) (summarySOExit s) <$> r
r = specRate <$> IM.lookup trackNo (cfSpecs cf)
!cfO' = offs' ms o (cfOffsets cf)
cf' = cf { cfOffsets = cfO' }
toMS :: TimeStamp -> Int
toMS (TS ts) = floor . (* 1000.0) $ ts
offs' :: Maybe Int -> FileOffset -> IntMap FileOffset -> IntMap FileOffset
offs' Nothing _ = id
offs' (Just x) o = IM.alter f x
where
f Nothing = Just o
f (Just o0) = Just (min o0 o)
{-# INLINE iterBlock #-}
-- | An iteratee of zoom-cache data, after global and track headers
-- have been read, or if the 'CacheFile' has been acquired elsewhere.
enumBlock :: (Functor m, MonadIO m)
=> CacheFile
-> I.Enumeratee (Offset ByteString) [Offset Block] m a
enumBlock = I.unfoldConvStreamCheck I.eneeCheckIfDonePass iterBlock
{-# INLINE enumBlock #-}
-- | A version of enumBlock which won't fail with an EofException if the last
-- bit is incomplete (perhaps still being written to).
enumBlockIncomplete :: (Functor m, MonadIO m) =>
CacheFile
-> I.Enumeratee (Offset ByteString) [Offset Block] m a
enumBlockIncomplete = I.unfoldConvStreamCheck I.eneeCheckIfDonePass $
catchAndIgnoreAcc iterBlock
catchAndIgnoreAcc :: (Monad m, I.NullPoint a, I.Nullable s) =>
(acc -> Iteratee s m (acc, a)) -> acc
-> Iteratee s m (acc, a)
catchAndIgnoreAcc fi acc = I.checkErr (fi acc) >>= either onErr return
where
onErr _ = return (acc, I.empty)
------------------------------------------------------------
data HeaderType = GlobalHeader | TrackHeader | PacketHeader | SummaryHeader
parseHeader :: ByteString -> Maybe HeaderType
parseHeader h
| h == globalHeader = Just GlobalHeader
| h == trackHeader = Just TrackHeader
| h == packetHeader = Just PacketHeader
| h == summaryHeader = Just SummaryHeader
| otherwise = Nothing
------------------------------------------------------------
-- Global, track headers
-- | Parse only the global and track headers of a zoom-cache file, returning
-- a 'CacheFile'
iterHeaders :: (Functor m, Monad m)
=> [IdentifyCodec]
-> I.Iteratee (Offset ByteString) m CacheFile
iterHeaders identifiers = iterGlobal >>= go
where
iterGlobal :: (Functor m, Monad m)
=> Iteratee (Offset ByteString) m CacheFile
iterGlobal = do
header <- OffI.takeBS 8
case parseHeader header of
Just GlobalHeader -> mkCacheFile <$> (OffI.convOffset readGlobalHeader)
_ -> error "No global header"
go :: (Functor m, Monad m)
=> CacheFile
-> Iteratee (Offset ByteString) m CacheFile
go fi = do
header <- OffI.takeBS 8
case parseHeader header of
Just TrackHeader -> do
(trackNo, spec) <- readTrackHeader identifiers
let fi' = fi{cfSpecs = IM.insert trackNo spec (cfSpecs fi)}
if (fiFull fi')
then return fi'
else go fi'
_ -> return fi
readGlobalHeader :: (Functor m, Monad m)
=> Iteratee ByteString m Global
readGlobalHeader = do
v <- readVersion
checkVersion v
n <- readInt32be
jDay <- readIntegerVLC
sN <- readIntegerVLC
sD <- readIntegerVLC
let !u = if (sD == 0)
then Nothing
else Just $ UTCTime (ModifiedJulianDay jDay) (fromRational (sN % sD))
return $ Global v n u
where
checkVersion (Version major minor)
| major == versionMajor && minor >= versionMinor = return ()
| otherwise = error "Unsupported zoom-cache version"
readTrackHeader :: (Functor m, Monad m)
=> [IdentifyCodec]
-> Iteratee (Offset ByteString) m (TrackNo, TrackSpec)
readTrackHeader identifiers = do
trackNo <- readInt32be
(drType, delta, zlib) <- readFlags
rate <- readRational64be
identLength <- readInt32be
trackType <- maybe (error "Unknown track type") return =<< readCodecMultichannel identLength
nameLength <- readInt32be
name <- OffI.takeBS nameLength
return (trackNo, TrackSpec trackType delta zlib drType rate name)
where
readCodecMultichannel = readCodec (supportMultichannel identifiers)
------------------------------------------------------------
-- Packet, Summary reading
enumInflateZlib :: (MonadIO m) => I.Enumeratee ByteString ByteString m a
enumInflateZlib = enumInflate Zlib defaultDecompressParams
readPacketPred :: (Functor m, MonadIO m)
=> IntMap TrackSpec
-> ((TrackNo, SampleOffset, SampleOffset) -> Bool)
-> Iteratee ByteString m (TrackNo, Maybe PacketSO)
readPacketPred specs p = do
trackNo <- readInt32be
entryTime <- SO <$> readInt64be
exitTime <- SO <$> readInt64be
count <- readInt32be
byteLength <- readInt32be
packet <- if (p (trackNo, entryTime, exitTime))
then do
readPacketData specs trackNo entryTime exitTime count byteLength
else do
I.drop byteLength
return Nothing
return (trackNo, packet)
readPacketTrackNo :: (Functor m, MonadIO m)
=> IntMap TrackSpec
-> TrackNo
-> Iteratee ByteString m (TrackNo, Maybe PacketSO)
readPacketTrackNo specs wantTrackNo =
readPacketPred specs (\(trackNo, _, _) -> trackNo == wantTrackNo)
readPacket :: (Functor m, MonadIO m)
=> IntMap TrackSpec
-> Iteratee ByteString m (TrackNo, Maybe PacketSO)
readPacket specs = readPacketPred specs (const True)
readPacketData :: (Functor m, MonadIO m)
=> IntMap TrackSpec
-> TrackNo
-> SampleOffset -> SampleOffset
-> Int
-> Int
-> Iteratee ByteString m (Maybe PacketSO)
readPacketData specs trackNo entryTime exitTime count byteLength =
case IM.lookup trackNo specs of
Just TrackSpec{..} -> do
let readDTS :: (Functor m, Monad m)
=> Iteratee ByteString m (ZoomRaw, [SampleOffset])
readDTS = readDataSampleOffsets specType specDeltaEncode specSRType
(d, ts) <- if specZlibCompress
then do
z <- I.joinI $ enumInflateZlib I.stream2stream
return $ runner1 $ I.enumPure1Chunk z readDTS
else readDTS
return . Just $
(PacketSO trackNo entryTime exitTime count d ts)
Nothing -> do
I.drop byteLength
return Nothing
where
runner1 :: Identity (I.Iteratee s Identity c) -> c
runner1 = runIdentity . I.run . runIdentity
readRawCodec :: (Functor m, Monad m)
=> Codec -> Bool
-> Iteratee ByteString m ZoomRaw
readRawCodec (Codec a) delta = ZoomRaw . f <$> replicateM count (readRawAs a)
where
f | delta = deltaDecodeRaw
| otherwise = id
readRawAs :: (ZoomReadable a, Functor m, Monad m)
=> a -> Iteratee ByteString m a
readRawAs = const readRaw
readDataSampleOffsets :: (Functor m, Monad m)
=> Codec -> Bool -> SampleRateType
-> Iteratee ByteString m (ZoomRaw, [SampleOffset])
readDataSampleOffsets codec delta drType = do
d <- readRawCodec codec delta
ts <- readSampleOffsets drType
return (d, ts)
readSampleOffsets :: (Functor m, Monad m)
=> SampleRateType
-> Iteratee ByteString m [SampleOffset]
readSampleOffsets drType = map SO <$> case drType of
ConstantSR -> do
return $ take count [unSO entryTime ..]
VariableSR -> do
deltaDecode <$> replicateM count readInt64be
readSummaryBlockPred :: (Functor m, Monad m)
=> IntMap TrackSpec
-> ((TrackNo, Int, SampleOffset, SampleOffset) -> Bool)
-> Iteratee ByteString m (TrackNo, Maybe ZoomSummarySO)
readSummaryBlockPred specs p = do
trackNo <- readInt32be
lvl <- readInt32be
entryTime <- SO <$> readInt64be
exitTime <- SO <$> readInt64be
byteLength <- readInt32be
summary <- if (p (trackNo, lvl, entryTime, exitTime))
then do
readSummaryBlockData specs trackNo lvl entryTime exitTime byteLength
else do
I.drop byteLength
return Nothing
return (trackNo, summary)
readSummaryBlockTrackNo :: (Functor m, Monad m)
=> IntMap TrackSpec
-> TrackNo
-> Iteratee ByteString m (TrackNo, Maybe ZoomSummarySO)
readSummaryBlockTrackNo specs wantTrackNo =
readSummaryBlockPred specs (\(trackNo, _, _, _) -> trackNo == wantTrackNo)
readSummaryBlock :: (Functor m, Monad m)
=> IntMap TrackSpec
-> Iteratee ByteString m (TrackNo, Maybe ZoomSummarySO)
readSummaryBlock specs = readSummaryBlockPred specs (const True)
readSummaryBlockData :: (Functor m, Monad m)
=> IntMap TrackSpec
-> TrackNo
-> Int
-> SampleOffset -> SampleOffset
-> Int
-> Iteratee ByteString m (Maybe ZoomSummarySO)
readSummaryBlockData specs trackNo lvl entryTime exitTime byteLength =
case IM.lookup trackNo specs of
Just TrackSpec{..} -> do
Just <$> readSummaryCodec specType
-- sd <- readSummaryCodec specType
-- return $ Just sd
Nothing -> do
I.drop byteLength
return Nothing
where
readSummaryCodec :: (Functor m, Monad m)
=> Codec
-> Iteratee ByteString m ZoomSummarySO
readSummaryCodec (Codec a) = do
ZoomSummarySO <$> (SummarySO trackNo lvl entryTime exitTime <$> readSummaryAs a)
readSummaryAs :: (ZoomReadable a, Functor m, Monad m)
=> a -> Iteratee ByteString m (SummaryData a)
readSummaryAs = const readSummary
{-# INLINE readSummaryBlockData #-}
----------------------------------------------------------------------
-- zoom-cache datatype parsers
readVersion :: (Functor m, Monad m)
=> Iteratee ByteString m Version
readVersion = Version <$> readInt16be <*> readInt16be
readFlags :: (Functor m, Monad m)
=> Iteratee (Offset ByteString) m (SampleRateType, Bool, Bool)
readFlags = do
(n :: Int16) <- readInt16be
let drType = case n .&. 1 of
0 -> ConstantSR
_ -> VariableSR
delta = case n .&. 2 of
0 -> False
_ -> True
zlib = case n .&. 4 of
0 -> False
_ -> True
return (drType, delta, zlib)