{-# LANGUAGE BangPatterns , UnboxedTuples , UnicodeSyntax #-} -- | Lazy bitstrings based on 'L.ByteString' which treats a byte -- stream as a bit sequence in the same way as WavPack's manner. module Codec.Audio.WavPack.BitString ( -- * The BitString Type BitString -- * Construction , (∅) , empty , singleton -- * Basic Interface , cons , snoc , head , uncons , tail , null , length -- * Conversion from/to 'L.ByteString' , fromByteString , toByteString -- * Substrings , take , drop , splitAt ) where import qualified Data.ByteString.Lazy as L import Data.Bits import Data.Int import qualified Data.Strict as S import Data.Word import Prelude hiding (drop, head, length, null, splitAt, tail, take) import Prelude.Unicode -- | The BitString Type. data BitString = BitString { leftRem ∷ !Remnant , leftBytes ∷ !L.ByteString -- | reversed , rightBytes ∷ !L.ByteString , rightRem ∷ !Remnant } deriving Show -- | /O(n)/ The bitstrings must have finite lengths to test the -- equality. instance Eq BitString where a == b = leftRem a' ≡ leftRem b' ∧ leftBytes a' ≡ leftBytes b' ∧ rightRem a' ≡ rightRem b' where a' = normalise a b' = normalise b normalise ∷ BitString → BitString normalise bs | remLen (leftRem bs) ≡ 8 = normalise $ bs { leftRem = remEmpty , leftBytes = L.cons (remByte $ leftRem bs) $ leftBytes bs } | remLen (rightRem bs) ≡ 8 = normalise $ bs { rightBytes = L.cons (remByte $ rightRem bs) $ rightBytes bs , rightRem = remEmpty } | otherwise = bs { leftBytes = leftBytes bs `L.append` (L.reverse $ rightBytes bs) , rightBytes = L.empty } data Remnant = Remnant { -- | bit length from 0 to 8 remLen ∷ !Int -- | current byte @(0 <= remByte <= (1 `'shiftL'` 'remLen') - 1)@ , remByte ∷ !Word8 } deriving (Eq, Show) remEmpty ∷ Remnant remEmpty = Remnant 0 0 remNull ∷ Remnant → Bool remNull = (0 ≡) ∘ remLen remSingleton ∷ Bool -> Remnant remSingleton b = Remnant { remLen = 1 , remByte = if b then 1 else 0 } byteToRem ∷ Word8 → Remnant byteToRem w = Remnant { remLen = 8 , remByte = w } remToStr ∷ Remnant → L.ByteString remToStr r | remNull r = L.empty | otherwise = L.singleton $ remByte r consRem ∷ Bool → Remnant → (# Remnant, S.Maybe Word8 #) consRem b r | remLen r ≡ 8 = (# remSingleton b, S.Just $ remByte r #) | remLen r ≡ 7 = (# remEmpty, S.Just w' #) | otherwise = let !r' = r { remLen = remLen r + 1 , remByte = w' } in (# r', S.Nothing #) where w' = (remByte r `shiftL` 1) .|. if b then 1 else 0 snocRem ∷ Remnant → Bool → (# S.Maybe Word8, Remnant #) snocRem r b | remLen r ≡ 8 = (# S.Just $ remByte r, remSingleton b #) | remLen r ≡ 7 = (# S.Just w', remEmpty #) | otherwise = let !r' = r { remLen = remLen r + 1 , remByte = w' } in (# S.Nothing, r' #) where w' | b = bit (remLen r) .|. remByte r | otherwise = remByte r unconsRem ∷ Remnant → (# S.Maybe Bool, Remnant #) unconsRem r | remNull r = (# S.Nothing, remEmpty #) | otherwise = let !b = remByte r `testBit` 0 !r' = Remnant { remLen = remLen r - 1 , remByte = remByte r `shiftR` 1 } in (# S.Just b, r' #) unconsBytes ∷ L.ByteString → (# S.Maybe Remnant, L.ByteString #) unconsBytes bs = case L.uncons bs of Just (!w, bs') → (# S.Just (byteToRem w), bs' #) Nothing → (# S.Nothing, L.empty #) appendRem ∷ Remnant → Remnant → Remnant appendRem a b = Remnant { remLen = remLen a + remLen b , remByte = (remByte a `shiftL` remLen b) .|. remByte b } splitRemAt ∷ Integral n ⇒ n → Remnant → (# S.Maybe Remnant, Remnant #) splitRemAt n r | remLen r ≥ fromIntegral n = let !h = Remnant { remLen = fromIntegral n , remByte = remByte r .&. ((1 `shiftL` fromIntegral n) - 1) } !r' = Remnant { remLen = remLen r - fromIntegral n , remByte = remByte r `shiftR` fromIntegral n } in (# S.Just h, r' #) | otherwise = (# S.Nothing, remEmpty #) -- | /O(1)/ The same as 'empty'. (∅) ∷ BitString (∅) = empty -- | /O(1)/ The empty 'BitString'. empty ∷ BitString empty = BitString { leftRem = remEmpty , leftBytes = L.empty , rightBytes = L.empty , rightRem = remEmpty } -- | /O(1)/ Convert a 'Bool' into a 'BitString'. singleton ∷ Bool → BitString singleton b = BitString { leftRem = remSingleton b , leftBytes = L.empty , rightBytes = L.empty , rightRem = remEmpty } -- | /O(1)/ Prepend a bit to the beginning of a 'BitString'. cons ∷ Bool → BitString → BitString cons b bs = case consRem b $ leftRem bs of (# lr', S.Just w #) → bs { leftRem = lr' , leftBytes = L.cons' w $ leftBytes bs } (# lr', S.Nothing #) → bs { leftRem = lr' } -- | /O(1)/ Append a bit to the end of a 'BitString'. snoc ∷ BitString → Bool → BitString snoc bs b = case snocRem (rightRem bs) b of (# S.Just w, rr' #) → bs { rightBytes = L.cons' w $ rightBytes bs , rightRem = rr' } (# S.Nothing, rr' #) → bs { rightRem = rr' } -- | /amortized O(1)/ Extract the first bit of a 'BitString', which -- must be non-empty. head ∷ BitString → Bool head bs = case uncons bs of Just (b, _ ) → b Nothing → error "head: empty BitString" -- | /amortized O(1)/ Extract the bits after the head of a -- 'BitString', which must be non-empty. tail ∷ BitString → BitString tail bs = case uncons bs of Just (_, bs') → bs' Nothing → error "tail: empty BitString" -- | /amortized O(1)/ Extract the the head and tail of a 'BitString', -- returning 'Nothing' if it's empty. uncons ∷ BitString → Maybe (Bool, BitString) uncons bs = case unconsRem $ leftRem bs of (# S.Just b, lr' #) → Just (b, bs { leftRem = lr' }) (# S.Nothing, _ #) → case unconsBytes $ leftBytes bs of (# S.Just lr, lb' #) → let !bs' = bs { leftRem = lr , leftBytes = lb' } in uncons bs' (# S.Nothing, _ #) → if L.null $ rightBytes bs then case unconsRem $ rightRem bs of (# S.Just b, rr' #) → Just (b, bs { rightRem = rr' }) (# S.Nothing, _ #) → Nothing else let !bs' = bs { leftBytes = L.reverse $ rightBytes bs } in uncons bs' -- | /O(1)/ Test whether a 'BitString' is empty. null ∷ BitString → Bool null bs = remNull (leftRem bs) ∧ L.null (leftBytes bs) ∧ L.null (rightBytes bs) ∧ remNull (rightRem bs) -- | /O(n)/ @'length' bs@ returns the number of bits in @bs@. @bs@ -- must have a finite length. length ∷ Integral n ⇒ BitString → n length bs = fromIntegral $ ( fromIntegral (remLen $ leftRem bs) + L.length (leftBytes bs) ⋅ 8 + L.length (rightBytes bs) ⋅ 8 + fromIntegral (remLen $ rightRem bs) ) {-# SPECIALISE length ∷ BitString → Int64 #-} -- | /O(1)/ Convert a 'L.ByteString' into a 'BitString'. fromByteString ∷ L.ByteString → BitString fromByteString bs = BitString { leftRem = remEmpty , leftBytes = bs , rightBytes = L.empty , rightRem = remEmpty } -- | /O(n)/ @'toByteString' bs@ converts @bs@ into 'L.ByteString', -- padding incomplete bytes to single bytes with necessary 0's at -- their MSB. Thus the following equation does not hold when the -- length of @bs@ isn't multiple of 8. -- -- > fromByteString . toByteString = id -- -- But the following always holds true. -- -- > toByteString . fromByteString = id -- -- Note that @bs@ must have a finite length. toByteString ∷ BitString → L.ByteString toByteString bs = L.concat [ remToStr $ leftRem bs , leftBytes bs , L.reverse $ rightBytes bs , remToStr $ rightRem bs ] -- | /O(n)/ @'take' n bs@ returns the prefix of @bs@ of length -- @n@. @bs@ must have at least @n@ bits. take ∷ Integral n ⇒ n → BitString → BitString take n xs = case splitAt n xs of (taken, _) → taken -- | /O(n)/ @'drop' n bs@ returns the suffix of @bs@ after the first -- @n@ bits. @bs@ must have at least @n@ bits. drop ∷ Integral n ⇒ n → BitString → BitString drop n xs = case splitAt n xs of (_, dropped) → dropped -- | /O(n)/ @'splitAt' n bs@ is equivalent to @('take' n bs, 'drop' n -- bs)@. splitAt ∷ Integral n ⇒ n → BitString → (BitString, BitString) splitAt n bs = case splitRemAt n $ leftRem bs of (# S.Just h, lr' #) -- The leftRem has enough bits. → let !h' = BitString { leftRem = h , leftBytes = L.empty , rightBytes = L.empty , rightRem = remEmpty } in (h', bs { leftRem = lr' }) (# S.Nothing, _ #) → let !bytesToTake = (n - fromIntegral (remLen $ leftRem bs)) `div` 8 !amortised = leftBytes bs `L.append` (L.reverse $ rightBytes bs) -- 0 ≤ bitsToTake < 8 - remLen (leftRem bs) -- ∴ remLen (leftRem bs) + bitsToTake < 8 !bitsToTake = n - bytesToTake ⋅ 8 - fromIntegral (remLen $ leftRem bs) in case (# bytesToTake, L.uncons amortised #) of (#_, Just (!w, amor') #) -- There is at least one byte in the byte pool -- (amortised). → let !h1 = leftRem bs (# h2, lr' #) = case splitRemAt bitsToTake $ byteToRem w of (# S.Just h2', lr'' #) → (# h2', lr'' #) (# S.Nothing , _ #) → error "internal error" !h = h1 `appendRem` h2 (bytes, lb') = L.splitAt (fromIntegral bytesToTake) amor' in if L.length bytes ≡ fromIntegral bytesToTake then -- The byte pool actuall has at least -- bytesToTake bytes. let !h' = BitString { leftRem = h , leftBytes = bytes , rightBytes = L.empty , rightRem = remEmpty } !bs' = BitString { leftRem = lr' , leftBytes = lb' , rightBytes = L.empty , rightRem = rightRem bs } in (h', bs') else error "splitAt: not enough bits" (# 0, Nothing #) -- No bytes are in the byte pool but the -- rightRem may have enough bits. → case splitRemAt bitsToTake $ rightRem bs of (# S.Just h, rr' #) → let !h' = BitString { leftRem = leftRem bs , leftBytes = L.empty , rightBytes = L.empty , rightRem = h } !bs' = BitString { leftRem = remEmpty , leftBytes = L.empty , rightBytes = L.empty , rightRem = rr' } in (h', bs') (# S.Nothing, _ #) → error "splitAt: not enough bits" (# _, Nothing #) -- No bytes are in the byte pool but more than 8 -- bits are requested. The rightRem can't have -- that many bits. → error "splitAt: not enough bits"