3 , ExistentialQuantification
6 -- | WavPack metadata sub-blocks
7 module Codec.Audio.WavPack.Metadata
23 import qualified Codec.Audio.WavPack.BitString as B
24 import Codec.Audio.WavPack.Internal
27 import Data.Binary.BitPut (putBit, putNBits, runBitPut)
28 import Data.Binary.Get
29 import Data.Binary.Put
30 import Data.Binary.Strict.BitGet (getBit, runBitGet)
31 import qualified Data.Binary.Strict.BitGet as BG
33 import qualified Data.ByteString as S
34 import qualified Data.ByteString.Lazy as L
36 import qualified Data.Strict as S
38 import qualified Data.Vector.Unboxed as UV
39 import Prelude.Unicode
41 -- | Type class for every metadata sub-blocks.
42 class (Binary α, Eq α, Show α, Typeable α) ⇒ Metadata α where
43 -- | Get the metadata ID without odd-size bit nor large-block bit
46 -- | Get the size of metadata, excluding the metadata header
49 metaSize = fromIntegral ∘ L.length ∘ runPut ∘ put
50 -- | Cast a 'SubBlock' to this type of metadata (optional).
51 fromSubBlock ∷ SubBlock → Maybe α
52 fromSubBlock (SubBlock a) = cast a
53 -- | Wrap the metadata into 'SubBlock' (optional).
54 toSubBlock ∷ α → SubBlock
57 -- | An opaque sub-block container.
58 data SubBlock = ∀α. Metadata α ⇒ SubBlock α
61 instance Metadata SubBlock where
62 metaID (SubBlock a) = metaID a
63 metaSize (SubBlock a) = metaSize a
67 instance Binary SubBlock where
69 = let size = metaSize a
71 oddBit = if odd size then 0x40 else 0
72 largeBit = if size > 0x1FE then 0x80 else 0
73 idWord = metaID a .|. oddBit .|. largeBit
77 -- Don't forget about the endianness.
78 do putWord8 $ fromIntegral $ (size' `shiftR` 1) .&. 0xFF
79 putWord8 $ fromIntegral $ (size' `shiftR` 9) .&. 0xFF
80 putWord8 $ fromIntegral $ (size' `shiftR` 17) .&. 0xFF
82 putWord8 $ fromIntegral $ (size' `shiftR` 1) .&. 0xFF
84 when (odd size) $ putWord8 0
86 get = do idWord ← getWord8
87 let isOdd = idWord .&. 0x40 ≢ 0
88 isLarge = idWord .&. 0x80 ≢ 0
89 rawID = idWord .&. (complement 0x40) .&. (complement 0x80)
90 adj = if isOdd then -1 else 0
91 size ← if isLarge then
95 return $ ( (fromIntegral sz2 `shiftL` 17) .|.
96 (fromIntegral sz1 `shiftL` 9) .|.
97 (fromIntegral sz0 `shiftL` 1)
100 fmap ((+ adj) ∘ (`shiftL` 1) ∘ fromIntegral) getWord8
101 subb ← getLazyByteString $ fromIntegral (size ∷ Word32)
102 return $ runGet (getSubBlock rawID) subb
104 getSubBlock ∷ Word8 → Get SubBlock
105 getSubBlock 0x00 = fmap SubBlock (get ∷ Get Dummy )
106 getSubBlock 0x02 = fmap SubBlock (get ∷ Get DecorrTerms )
107 getSubBlock 0x03 = fmap SubBlock (get ∷ Get DecorrWeights)
108 getSubBlock 0x04 = fmap SubBlock (get ∷ Get DecorrSamples)
109 getSubBlock 0x05 = fmap SubBlock (get ∷ Get EntropyVars )
110 getSubBlock 0x0A = fmap SubBlock (get ∷ Get WVBitstream )
111 getSubBlock 0x21 = fmap SubBlock (get ∷ Get RIFFHeader )
112 getSubBlock 0x22 = fmap SubBlock (get ∷ Get RIFFTrailer )
113 getSubBlock 0x25 = fmap SubBlock (get ∷ Get ConfigInfo )
114 getSubBlock unknownID
115 = if unknownID .&. 0x20 ≡ 0 then
116 fail ("Unknown WavPack metadata ID: " ⧺ show unknownID)
118 -- It's unknown but optional. We can safely ignore it.
119 fmap (SubBlock ∘ Unknown unknownID) getRemainingLazyByteString
121 instance Eq SubBlock where
122 (SubBlock a) == (SubBlock b)
125 instance Show SubBlock where
126 show (SubBlock a) = show a
128 -- | Dummy metadata to pad WavPack blocks.
131 -- | Must be less than 2^25 bytes long due to the limitation
132 -- of WavPack specification.
135 deriving (Eq, Show, Typeable)
137 instance Metadata Dummy where
141 instance Binary Dummy where
142 put = putLazyByteString ∘ flip L.replicate 0x00 ∘ fromIntegral ∘ dumSize
143 get = fmap (Dummy ∘ fromIntegral) remaining
145 -- | Decorrelation terms and deltas.
148 -- | @[ (term, delta) ]@
149 dectVec ∷ !(UV.Vector (Int8, Int8))
151 deriving (Eq, Show, Typeable)
153 instance Metadata DecorrTerms where
155 metaSize = fromIntegral ∘ UV.length ∘ dectVec
157 instance Binary DecorrTerms where
158 put = UV.mapM_ (putWord8 ∘ packDT) ∘ dectVec
160 packDT ∷ (Int8, Int8) → Word8
162 = fromIntegral ( (term + 5 .&. 0x1F)
164 ((δ `shiftL` 5) .&. 0xE0)
166 get = do n ← remaining
167 vec ← UV.replicateM (fromIntegral n) $ fmap unpackDT getWord8
168 -- THINKME: unpack.c(read_decorr_terms) reverses the
169 -- order only when they are decoding. I don't know why so
170 -- I leave it unreversed for now.
171 return $ DecorrTerms vec
173 unpackDT ∷ Word8 → (Int8, Int8)
175 = let term = (fromIntegral $ w .&. 0x1F) - 5
176 δ = fromIntegral $ (w `shiftR` 5) .&. 0x07
180 -- | Decorrelation weights.
183 -- | For mono blocks, this is a weight vector for the single
184 -- channel. For stereo blocks, it's interleaved as A, B, A, B,
186 decwVec ∷ !(UV.Vector Int16)
188 deriving (Eq, Show, Typeable)
190 instance Metadata DecorrWeights where
192 metaSize = fromIntegral ∘ UV.length ∘ decwVec
194 instance Binary DecorrWeights where
195 put = UV.mapM_ (putWord8 ∘ packWeight) ∘ decwVec
196 get = do n ← remaining
197 vec ← UV.replicateM (fromIntegral n)
198 $ fmap unpackWeight getWord8
199 -- THINKME: the same caution as DecorrTerms, but never
200 -- try to simply reverse the vector. Think about the
202 return $ DecorrWeights vec
204 -- | Decorrelation samples
207 -- | The decorrelation sample vector stored in the metadata
208 -- as-is. Actual interpretation of the vector depends on the
209 -- number of channels and each corresponding decorrelation
211 decsVec ∷ !(UV.Vector Int32)
213 deriving (Eq, Show, Typeable)
215 instance Metadata DecorrSamples where
217 metaSize = fromIntegral ∘ (⋅ 2) ∘ UV.length ∘ decsVec
219 instance Binary DecorrSamples where
220 put = UV.mapM_ (putWord16le ∘ fromIntegral ∘ log2s) ∘ decsVec
221 get = do n ← remaining
222 vec ← UV.replicateM (fromIntegral $ n `div` 2)
223 $ fmap (exp2s ∘ fromIntegral) getWord16le
224 return $ DecorrSamples vec
226 -- | Median log2 values.
229 -- | Median log2 values for channel A, which always exists.
230 entVarA ∷ !(Word32, Word32, Word32)
231 -- | Median log2 values for channel B, which is absent when it's
233 , entVarB ∷ !(S.Maybe (Word32, Word32, Word32))
235 deriving (Eq, Show, Typeable)
237 instance Metadata EntropyVars where
240 | S.isNothing $ entVarB ev = 6
243 instance Binary EntropyVars where
245 = do putMedians $ entVarA ev
247 S.Nothing → return ()
248 S.Just medsB → putMedians medsB
250 -- THINKME: words.c(write_entropy_vars) is a destructive
251 -- subroutine. It calls read_entropy_vars() to read the
252 -- values back to compensate for the loss through the log
254 putMedians ∷ (Word32, Word32, Word32) → Put
255 putMedians (med0, med1, med2)
256 = do putWord16le $ log2 med0
257 putWord16le $ log2 med1
258 putWord16le $ log2 med2
260 get = do medsA ← getMedians
261 medsB ← do isMono ← isEmpty
265 fmap S.Just getMedians
266 return $! EntropyVars medsA medsB
268 getMedians ∷ Get (Word32, Word32, Word32)
270 = do med0 ← fmap exp2 getWord16le
271 med1 ← fmap exp2 getWord16le
272 med2 ← fmap exp2 getWord16le
273 return (med0, med1, med2)
278 wvStream ∷ !B.BitString
280 deriving (Eq, Show, Typeable)
282 instance Metadata WVBitstream where
284 metaSize = (`div` 8) ∘ B.length ∘ wvStream
286 instance Binary WVBitstream where
287 put = putLazyByteString ∘ B.toByteString ∘ wvStream
288 get = fmap (WVBitstream ∘ B.fromByteString) getRemainingLazyByteString
290 -- | RIFF header for .wav files (before audio)
293 riffHeader ∷ !L.ByteString
295 deriving (Eq, Show, Typeable)
297 instance Metadata RIFFHeader where
299 metaSize = fromIntegral ∘ L.length ∘ riffHeader
301 instance Binary RIFFHeader where
302 put = putLazyByteString ∘ riffHeader
303 get = fmap RIFFHeader getRemainingLazyByteString
305 -- | RIFF trailer for .wav files (after audio)
308 riffTrailer ∷ !L.ByteString
310 deriving (Eq, Show, Typeable)
312 instance Metadata RIFFTrailer where
314 metaSize = fromIntegral ∘ L.length ∘ riffTrailer
316 instance Binary RIFFTrailer where
317 put = putLazyByteString ∘ riffTrailer
318 get = fmap RIFFTrailer getRemainingLazyByteString
320 -- | Configuration information.
325 -- | high quality mode
328 , cfgVeryHigh ∷ !Bool
329 -- | bitrate is kbps, not bits / sample
330 , cfgBitrateKbps ∷ !Bool
331 -- | automatic noise shaping
332 , cfgAutoShaping ∷ !Bool
333 -- | shaping mode specified
334 , cfgShapeOverride ∷ !Bool
335 -- | joint-stereo mode specified
336 , cfgJointOverride ∷ !Bool
337 -- | dynamic noise shaping
338 , cfgDynamicShaping ∷ !Bool
339 -- | create executable
340 , cfgCreateEXE ∷ !Bool
341 -- | create correction file
342 , cfgCreateWVC ∷ !Bool
343 -- | maximize hybrid compression
344 , cfgOptimizeWVC ∷ !Bool
345 -- | calc noise in hybrid mode
346 , cfgCalcNoise ∷ !Bool
347 -- | obsolete (for information)
348 , cfgLossyMode ∷ !Bool
349 -- | extra processing mode level (1-6)
350 , cfgExtraModeLevel ∷ !(S.Maybe Word8)
351 -- | no wvx stream w/ floats & big ints
353 -- | compute & store MD5 signature
354 , cfgMD5Checksum ∷ !Bool
355 -- | merge blocks of equal redundancy
356 , cfgMergeBlocks ∷ !Bool
357 -- | optimize for mono streams posing
358 , cfgOptimizeMono ∷ !Bool
360 deriving (Eq, Show, Typeable)
362 instance Metadata ConfigInfo where
365 | S.isJust $ cfgExtraModeLevel ci
370 instance Binary ConfigInfo where
372 = let !bs = runBitPut $
373 do putBit $ cfgOptimizeMono ci
374 putNBits 2 (0 ∷ Word8) -- unused
375 putBit $ cfgMergeBlocks ci
376 putBit $ cfgMD5Checksum ci
377 putBit $ cfgSkipWVX ci
378 putBit $ S.isJust $ cfgExtraModeLevel ci
379 putBit $ cfgLossyMode ci
380 putBit $ cfgCalcNoise ci
381 putNBits 2 (0 ∷ Word8) -- unused
382 putBit $ cfgOptimizeWVC ci
383 putBit $ cfgCreateWVC ci
384 putBit $ cfgCreateEXE ci
385 putBit $ cfgDynamicShaping ci
386 putBit $ cfgJointOverride ci
387 putBit $ cfgShapeOverride ci
388 putBit $ cfgAutoShaping ci
389 putBit $ cfgBitrateKbps ci
390 putBit $ cfgVeryHigh ci
392 putBit False -- unused
394 putBit False -- unused
396 do putLazyByteString (L.reverse bs)
397 case cfgExtraModeLevel ci of
398 S.Nothing → return ()
399 S.Just eml → putWord8 eml
401 get = do bs ← getBytes 3
402 eml ← do xmode ← fmap (¬) isEmpty
407 let r = runBitGet (S.reverse bs) $
408 do optimizeMono ← getBit
420 dynamicShaping ← getBit
421 jointOverride ← getBit
422 shapeOverride ← getBit
433 , cfgVeryHigh = veryHigh
434 , cfgBitrateKbps = bitrateKbps
435 , cfgAutoShaping = autoShaping
436 , cfgShapeOverride = shapeOverride
437 , cfgJointOverride = jointOverride
438 , cfgDynamicShaping = dynamicShaping
439 , cfgCreateEXE = createEXE
440 , cfgCreateWVC = createWVC
441 , cfgOptimizeWVC = optimizeWVC
442 , cfgCalcNoise = calcNoise
443 , cfgLossyMode = lossyMode
444 , cfgExtraModeLevel = if extraMode then
448 , cfgSkipWVX = skipWVX
449 , cfgMD5Checksum = md5Checksum
450 , cfgMergeBlocks = mergeBlocks
451 , cfgOptimizeMono = optimizeMono
458 -- | Unknown but optional metadata found in the WavPack block.
461 -- | The ID of this unknown metadata without odd-size bit nor
464 -- | Raw data; must be less than 2^25 bytes long.
465 , unkData ∷ L.ByteString
467 deriving (Eq, Show, Typeable)
469 instance Metadata Unknown where
472 instance Binary Unknown where
473 put = putLazyByteString ∘ unkData
474 get = error "unsupported operation"