]> gitweb @ CieloNegro.org - wavpack.git/blobdiff - Codec/Audio/WavPack/Metadata.hs
EntropyVars
[wavpack.git] / Codec / Audio / WavPack / Metadata.hs
index 78a99fc5e232dd7e168c971d30d1209a80cd92de..f88224161d18b7435b40e78a77262060ff4477fe 100644 (file)
@@ -9,23 +9,35 @@ module Codec.Audio.WavPack.Metadata
     , SubBlock
 
     , Dummy(..)
+    , DecorrTerms(..)
+    , DecorrWeights(..)
+    , DecorrSamples(..)
+    , EntropyVars(..)
+    , RIFFHeader(..)
+    , RIFFTrailer(..)
     , Unknown(..)
     )
     where
+import Codec.Audio.WavPack.Internal
 import Control.Monad
 import Data.Binary
 import Data.Binary.Get
 import Data.Binary.Put
 import Data.Bits
 import qualified Data.ByteString.Lazy as L
+import Data.Int
+import qualified Data.Strict as S
 import Data.Typeable
+import qualified Data.Vector.Unboxed as UV
 import Prelude.Unicode
 
 -- | Type class for every metadata sub-blocks.
 class (Binary α, Eq α, Show α, Typeable α) ⇒ Metadata α where
-    -- | Get the metadata ID (mandatory).
+    -- | Get the metadata ID without odd-size bit nor large-block bit
+    -- (mandatory).
     metaID   ∷ α → Word8
-    -- | Get the size of metadata (optional).
+    -- | Get the size of metadata, excluding the metadata header
+    -- (optional).
     metaSize ∷ α → Word32
     metaSize = fromIntegral ∘ L.length ∘ runPut ∘ put
     -- | Cast a 'SubBlock' to this type of metadata (optional).
@@ -48,18 +60,19 @@ instance Metadata SubBlock where
 instance Binary SubBlock where
     put (SubBlock a)
         = let size     = metaSize a
-              oddBit   = if odd size   then 0x40 else 0
-              largeBit = if size > 255 then 0x80 else 0
+              size'    = size + 1
+              oddBit   = if odd size     then 0x40 else 0
+              largeBit = if size > 0x1FE then 0x80 else 0
               idWord   = metaID a .|. oddBit .|. largeBit
           in
             do putWord8 idWord
-               if size > 255 then
+               if size > 0x1FE then
                    -- Don't forget about the endianness.
-                   do putWord8 $ fromIntegral $ (size `shiftR`  1) .&. 0xFF
-                      putWord8 $ fromIntegral $ (size `shiftR`  9) .&. 0xFF
-                      putWord8 $ fromIntegral $ (size `shiftR` 17) .&. 0xFF
+                   do putWord8 $ fromIntegral $ (size' `shiftR`  1) .&. 0xFF
+                      putWord8 $ fromIntegral $ (size' `shiftR`  9) .&. 0xFF
+                      putWord8 $ fromIntegral $ (size' `shiftR` 17) .&. 0xFF
                  else
-                      putWord8 $ fromIntegral $ (size `shiftR`  1) .&. 0xFF
+                      putWord8 $ fromIntegral $ (size' `shiftR`  1) .&. 0xFF
                put a
                when (odd size) $ putWord8 0
 
@@ -82,7 +95,13 @@ instance Binary SubBlock where
              return $ runGet (getSubBlock rawID) subb
         where
           getSubBlock ∷ Word8 → Get SubBlock
-          getSubBlock 0x00 = fmap SubBlock (get ∷ Get Dummy)
+          getSubBlock 0x00 = fmap SubBlock (get ∷ Get Dummy        )
+          getSubBlock 0x02 = fmap SubBlock (get ∷ Get DecorrTerms  )
+          getSubBlock 0x03 = fmap SubBlock (get ∷ Get DecorrWeights)
+          getSubBlock 0x04 = fmap SubBlock (get ∷ Get DecorrSamples)
+          getSubBlock 0x05 = fmap SubBlock (get ∷ Get EntropyVars  )
+          getSubBlock 0x21 = fmap SubBlock (get ∷ Get RIFFHeader   )
+          getSubBlock 0x22 = fmap SubBlock (get ∷ Get RIFFTrailer  )
           getSubBlock unknownID
               = if unknownID .&. 0x20 ≡ 0 then
                     fail ("Unknown WavPack metadata ID: " ⧺ show unknownID)
@@ -114,10 +133,169 @@ instance Binary Dummy where
     put = putLazyByteString ∘ flip L.replicate 0x00 ∘ fromIntegral ∘ dumSize
     get = fmap (Dummy ∘ fromIntegral) remaining
 
+-- | Decorrelation terms and deltas.
+data DecorrTerms
+    = DecorrTerms {
+        -- | @[ (term, delta) ]@
+        dectVec ∷ !(UV.Vector (Int8, Int8))
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata DecorrTerms where
+    metaID _ = 0x02
+    metaSize = fromIntegral ∘ UV.length ∘ dectVec
+
+instance Binary DecorrTerms where
+    put = UV.mapM_ (putWord8 ∘ packDT) ∘ dectVec
+        where
+          packDT ∷ (Int8, Int8) → Word8
+          packDT (term, δ)
+              = fromIntegral ( (term + 5 .&. 0x1F)
+                               .|.
+                               ((δ `shiftL` 5) .&. 0xE0)
+                             )
+    get = do n   ← remaining
+             vec ← UV.replicateM (fromIntegral n) $ fmap unpackDT getWord8
+             -- THINKME: unpack.c(read_decorr_terms) reverses the
+             -- order only when they are decoding. I don't know why so
+             -- I leave it unreversed for now.
+             return $ DecorrTerms vec
+        where
+          unpackDT ∷ Word8 → (Int8, Int8)
+          unpackDT w
+              = let term = (fromIntegral $ w .&. 0x1F) - 5
+                    δ    = fromIntegral $ (w `shiftR` 5) .&. 0x07
+                in
+                  (term, δ)
+
+-- | Decorrelation weights.
+data DecorrWeights
+    = DecorrWeights {
+      -- | For mono blocks, this is a weight vector for the single
+      --   channel. For stereo blocks, it's interleaved as A, B, A, B,
+      --   ...
+        decwVec ∷ !(UV.Vector Int16)
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata DecorrWeights where
+    metaID _ = 0x03
+    metaSize = fromIntegral ∘ UV.length ∘ decwVec
+
+instance Binary DecorrWeights where
+    put = UV.mapM_ (putWord8 ∘ packWeight) ∘ decwVec
+    get = do n   ← remaining
+             vec ← UV.replicateM (fromIntegral n)
+                    $ fmap unpackWeight getWord8
+             -- THINKME: the same caution as DecorrTerms, but never
+             -- try to simply reverse the vector. Think about the
+             -- interleaving.
+             return $ DecorrWeights vec
+
+-- | Decorrelation samples
+data DecorrSamples
+    = DecorrSamples {
+      -- | The decorrelation sample vector stored in the metadata
+      --   as-is. Actual interpretation of the vector depends on the
+      --   number of channels and each corresponding decorrelation
+      --   terms.
+        decsVec ∷ !(UV.Vector Int32)
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata DecorrSamples where
+    metaID _ = 0x04
+    metaSize = fromIntegral ∘ (⋅ 2) ∘ UV.length ∘ decsVec
+
+instance Binary DecorrSamples where
+    put = UV.mapM_ (putWord16le ∘ fromIntegral ∘ log2s) ∘ decsVec
+    get = do n   ← remaining
+             vec ← UV.replicateM (fromIntegral $ n `div` 2)
+                    $ fmap (exp2s ∘ fromIntegral) getWord16le
+             return $ DecorrSamples vec
+
+-- | Median log2 values.
+data EntropyVars
+    = EntropyVars {
+      -- | Median log2 values for channel A, which always exists.
+        entVarA ∷ !(Word32, Word32, Word32)
+      -- | Median log2 values for channel B, which is absent when it's
+      --   mono.
+      , entVarB ∷ !(S.Maybe (Word32, Word32, Word32))
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata EntropyVars where
+    metaID _ = 0x05
+    metaSize ev
+        | S.isNothing $ entVarB ev =  6
+        | otherwise                = 12
+
+instance Binary EntropyVars where
+    put ev
+        = do putMedians $ entVarA ev
+             case entVarB ev of
+               S.Nothing    → return ()
+               S.Just medsB → putMedians medsB
+        where
+          -- THINKME: words.c(write_entropy_vars) is a destructive
+          -- subroutine. It calls read_entropy_vars() to read the
+          -- values back to compensate for the loss through the log
+          -- function.
+          putMedians ∷ (Word32, Word32, Word32) → Put
+          putMedians (med0, med1, med2)
+              = do putWord16le $ log2 med0
+                   putWord16le $ log2 med1
+                   putWord16le $ log2 med2
+
+    get = do medsA ← getMedians
+             medsB ← do isMono ← isEmpty
+                        if isMono then
+                            return S.Nothing
+                          else
+                            fmap S.Just getMedians
+             return $! EntropyVars medsA medsB
+        where
+          getMedians ∷ Get (Word32, Word32, Word32)
+          getMedians
+              = do med0 ← fmap exp2 getWord16le
+                   med1 ← fmap exp2 getWord16le
+                   med2 ← fmap exp2 getWord16le
+                   return (med0, med1, med2)
+
+-- | RIFF header for .wav files (before audio)
+data RIFFHeader
+    = RIFFHeader {
+        riffHeader ∷ L.ByteString
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata RIFFHeader where
+    metaID _ = 0x21
+
+instance Binary RIFFHeader where
+    put = putLazyByteString ∘ riffHeader
+    get = fmap RIFFHeader getRemainingLazyByteString
+
+-- | RIFF trailer for .wav files (after audio)
+data RIFFTrailer
+    = RIFFTrailer {
+        riffTrailer ∷ L.ByteString
+      }
+    deriving (Eq, Show, Typeable)
+
+instance Metadata RIFFTrailer where
+    metaID _ = 0x22
+
+instance Binary RIFFTrailer where
+    put = putLazyByteString ∘ riffTrailer
+    get = fmap RIFFTrailer getRemainingLazyByteString
+
 -- | Unknown but optional metadata found in the WavPack block.
 data Unknown
     = Unknown {
-        -- | The ID of this unknown metadata.
+        -- | The ID of this unknown metadata without odd-size bit nor
+        -- large-block bit.
         unkID   ∷ Word8
         -- | Raw data; must be less than 2^25 bytes long.
       , unkData ∷ L.ByteString
@@ -126,8 +304,7 @@ data Unknown
 
 instance Metadata Unknown where
     metaID   = unkID
-    metaSize = fromIntegral ∘ L.length ∘ unkData
 
 instance Binary Unknown where
     put = putLazyByteString ∘ unkData
-    get = (⊥)
+    get = error "unsupported operation"