7 module Codec.Audio.WavPack.Block
15 import Codec.Audio.WavPack.Metadata
17 import Data.Binary.BitPut (putBit, putNBits, runBitPut)
18 import Data.Binary.Get
19 import Data.Binary.Put
20 import Data.Binary.Strict.BitGet (getBit, getAsWord8, runBitGet)
21 import qualified Data.Binary.Strict.BitGet as BG
22 import qualified Data.ByteString as S
23 import qualified Data.ByteString.Lazy as L
24 import qualified Data.Strict as S
25 import Prelude.Unicode
27 -- | The WavPack block.
30 blockHeader ∷ !BlockHeader
31 , blockMetadata ∷ [SubBlock]
35 instance Binary Block where
37 = do put $ blockHeader b
38 mapM_ put $ blockMetadata b
41 subs ← getSubBlocks (bhSize header)
44 , blockMetadata = subs
47 -- Be aware; the Get monad is like a strict State monad so the entire
48 -- sub-blocks are parsed at once. This might not be what you want
50 getSubBlocks ∷ Integral n ⇒ n → Get [SubBlock]
51 getSubBlocks 0 = return $! []
52 getSubBlocks !blockSize
53 = do before ← bytesRead
56 rest ← getSubBlocks $ blockSize - fromIntegral (after - before)
60 getSubBlocksLazily ∷ Integral n
63 → (# [SubBlock], L.ByteString #)
64 getSubBlocksLazily src 0 = (# [], src #)
65 getSubBlocksLazily src !blockSize
66 = let (sub, src', consumed)
67 = runGetState get src 0
69 = getSubBlocksLazily src' $
70 blockSize - fromIntegral consumed
72 (# sub : subs, src'' #)
74 -- | The preamble to every block in both the .wv and .wvc files.
77 -- | size of entire block (excluding the header)
79 -- | 0x402 to 0x410 are currently valid for decode
81 -- | track number (0 if not used, like now)
83 -- | track sub-index (0 if not used, like now)
85 -- | total samples for entire file, but this is only valid if
86 -- 'bhBlockIndex' == 0 and a value of -1 indicates unknown
88 , bhTotalSamples ∷ !Word32
89 -- | index of first sample in block relative to beginning of
90 -- file (normally this would start at 0 for the first block)
91 , bhBlockIndex ∷ !Word32
92 -- | number of samples in this block (0 = no audio)
93 , bhBlockSamples ∷ !Word32
94 -- | various flags for id and decoding
95 , bhFlags ∷ !BlockFlags
96 -- | crc for actual decoded data
101 instance Binary BlockHeader where
103 = do putWord8 119 -- 'w'
107 putWord32le $ bhSize bh + 32 - 8
108 putWord16le $ bhVersion bh
109 putWord8 $ bhTrackNo bh
110 putWord8 $ bhIndexNo bh
111 putWord32le $ bhTotalSamples bh
112 putWord32le $ bhBlockIndex bh
113 putWord32le $ bhBlockSamples bh
115 putWord32le $ bhCRC bh
117 get = do skip 4 -- "wvpk"
119 version ← getWord16le
122 totalSamples ← getWord32le
123 blockIndex ← getWord32le
124 blockSamples ← getWord32le
127 return $! BlockHeader {
128 bhSize = size + 8 - 32
129 , bhVersion = version
130 , bhTrackNo = trackNo
131 , bhIndexNo = indexNo
132 , bhTotalSamples = totalSamples
133 , bhBlockIndex = blockIndex
134 , bhBlockSamples = blockSamples
139 -- | Various flags for decoding blocks
143 bfBytesPerSample ∷ !Word8
144 -- | 'False' = stereo output; 'True' = mono output
146 -- | 'False' = lossless mode; 'True' = hybrid mode
148 -- | 'False' = true stereo; 'True' = joint stereo (mid/side)
149 , bfJointStereo ∷ !Bool
150 -- | 'False' = independent channels; 'True' = cross-channel
152 , bfCrossDecorr ∷ !Bool
153 -- | 'False' = flat noise spectrum in hybrid; 'True' = hybrid
155 , bfHybridShape ∷ !Bool
156 -- | 'False' = integer data; 'True' = floating point data
157 , bfFloatData ∷ !Bool
158 -- | 'True' = extended size integers (> 24-bit); 'False' =
160 , bfExtendedInt ∷ !Bool
161 -- | 'False' = hybrid mode parameters control noise level;
162 -- 'True' = hybrid mode parameters control bitrate
163 , bfHybridBitrate ∷ !Bool
164 -- | 'True' = hybrid noise balanced between channels
165 , bfHybridBalance ∷ !Bool
166 -- | 'True' = initial block in sequence (for multichannel)
167 , bfInitialBlock ∷ !Bool
168 -- | 'True' = final block in sequence (for multichannel)
169 , bfFinalBlock ∷ !Bool
170 -- | amount of data left-shift after decode (0-31 places)
171 , bfLeftShift ∷ !Word8
172 -- | maximum magnitude of decoded data (number of bits integers
174 , bfMaxMagnitude ∷ !Word8
175 -- | sampling rate ('Nothing' = unknown/custom)
176 , bfSamplingRate ∷ !(S.Maybe Int)
177 -- | 'True' = use IIR for negative hybrid noise shaping
178 , bfIIRShaping ∷ !Bool
179 -- | 'True' = false stereo (data is mono but output is stereo)
180 , bfFalseStereo ∷ !Bool
184 instance Binary BlockFlags where
186 = let !bs = runBitPut $
187 do putBit False -- reserved
188 putBit $ bfFalseStereo bf
189 putBit $ bfIIRShaping bf
190 putNBits 2 (0 ∷ Word8) -- reserved
191 putNBits 4 $ encodeSamplingRate $ bfSamplingRate bf
192 putNBits 5 $ bfMaxMagnitude bf
193 putNBits 5 $ bfLeftShift bf
194 putBit $ bfFinalBlock bf
195 putBit $ bfInitialBlock bf
196 putBit $ bfHybridBalance bf
197 putBit $ bfHybridBitrate bf
198 putBit $ bfExtendedInt bf
199 putBit $ bfFloatData bf
200 putBit $ bfHybridShape bf
201 putBit $ bfCrossDecorr bf
202 putBit $ bfJointStereo bf
205 putNBits 2 $! bfBytesPerSample bf - 1
207 putLazyByteString (L.reverse bs)
209 get = do bs ← getBytes 4
210 let !r = runBitGet (S.reverse bs) $
211 do BG.skip 1 -- reserved
214 BG.skip 2 -- reserved
215 samplingRate ← getAsWord8 4
216 maxMagnitude ← getAsWord8 5
217 leftShift ← getAsWord8 5
219 initialBlock ← getBit
220 hybridBalance ← getBit
221 hybridBitrate ← getBit
229 bytesPerSample ← getAsWord8 2
230 return $! BlockFlags {
231 bfBytesPerSample = bytesPerSample + 1
234 , bfJointStereo = jointStereo
235 , bfCrossDecorr = crossDecorr
236 , bfHybridShape = hybridShape
237 , bfFloatData = floatData
238 , bfExtendedInt = extendedInt
239 , bfHybridBitrate = hybridBitrate
240 , bfHybridBalance = hybridBalance
241 , bfInitialBlock = initialBlock
242 , bfFinalBlock = finalBlock
243 , bfLeftShift = leftShift
244 , bfMaxMagnitude = maxMagnitude
245 , bfSamplingRate = decodeSamplingRate samplingRate
246 , bfIIRShaping = iirShaping
247 , bfFalseStereo = falseStereo
251 Right bf → return $! bf
253 encodeSamplingRate ∷ S.Maybe Int → Word8
254 encodeSamplingRate (S.Just 6000) = 0x00
255 encodeSamplingRate (S.Just 8000) = 0x01
256 encodeSamplingRate (S.Just 9600) = 0x02
257 encodeSamplingRate (S.Just 11025) = 0x03
258 encodeSamplingRate (S.Just 12000) = 0x04
259 encodeSamplingRate (S.Just 16000) = 0x05
260 encodeSamplingRate (S.Just 22050) = 0x06
261 encodeSamplingRate (S.Just 24000) = 0x07
262 encodeSamplingRate (S.Just 32000) = 0x08
263 encodeSamplingRate (S.Just 44100) = 0x09
264 encodeSamplingRate (S.Just 48000) = 0x0A
265 encodeSamplingRate (S.Just 64000) = 0x0B
266 encodeSamplingRate (S.Just 88200) = 0x0C
267 encodeSamplingRate (S.Just 96000) = 0x0D
268 encodeSamplingRate (S.Just 192000) = 0x0E
269 encodeSamplingRate _ = 0x0F
271 decodeSamplingRate ∷ Word8 → S.Maybe Int
272 decodeSamplingRate 0x00 = S.Just 6000
273 decodeSamplingRate 0x01 = S.Just 8000
274 decodeSamplingRate 0x02 = S.Just 9600
275 decodeSamplingRate 0x03 = S.Just 11025
276 decodeSamplingRate 0x04 = S.Just 12000
277 decodeSamplingRate 0x05 = S.Just 16000
278 decodeSamplingRate 0x06 = S.Just 22025
279 decodeSamplingRate 0x07 = S.Just 24000
280 decodeSamplingRate 0x08 = S.Just 32000
281 decodeSamplingRate 0x09 = S.Just 44100
282 decodeSamplingRate 0x0A = S.Just 48000
283 decodeSamplingRate 0x0B = S.Just 64000
284 decodeSamplingRate 0x0C = S.Just 88200
285 decodeSamplingRate 0x0D = S.Just 96000
286 decodeSamplingRate 0x0E = S.Just 192000
287 decodeSamplingRate _ = S.Nothing
289 -- | Read WavPack blocks in a given stream lazily.
290 readBlocks ∷ L.ByteString → [Block]
292 = case findNextBlock src of
293 (# S.Just block, src' #)
294 → block : readBlocks src'
298 findNextBlock ∷ L.ByteString
299 → (# S.Maybe Block, L.ByteString #)
301 = case L.uncons src of
303 → (# S.Nothing, L.empty #)
311 tryGetBlock ∷ L.ByteString → (# S.Maybe Block, L.ByteString #)
313 = case L.splitAt 32 src of
315 → case L.length header ≡ 32 of
317 → case L.take 4 header ≡ headerMagic of
319 -- Found the magic "wvpk". Let's parse
320 -- the header and see if it's really a
321 -- header we can accept.
322 → case runGet get header of
323 bh → if isGoodHeader bh then
324 case getSubBlocksLazily rest $ bhSize bh of
328 , blockMetadata = subs
331 (# S.Just blk, rest' #)
333 findNextBlock $ L.tail src
335 → findNextBlock $ L.tail src
337 → (# S.Nothing, L.empty #)
339 headerMagic ∷ L.ByteString
340 headerMagic = L.pack [119, 118, 112, 107] -- "wvpk"
342 isGoodHeader ∷ BlockHeader → Bool
344 -- Rule #1: bhSize + 32 - 8 ≡ 0 (mod 2)
345 | odd $ bhSize bh = False
346 -- Rule #2: bhSize + 32 - 8 ≤ 0x00100000
347 | bhSize bh + 32 - 8 > 0x100000 = False
348 -- Rule #3: bhSize + 32 - 8 > 24
349 | bhSize bh + 32 - 8 ≤ 24 = False
350 -- Rule #4: 0x402 ≤ bhVersion ≤ 0x410
351 | bhVersion bh < 0x402 = False
352 | bhVersion bh > 0x410 = False
353 -- Rule #5: bhBlockSamples < 0x00030000
354 | bhBlockSamples bh ≥ 0x30000 = False
355 -- Now it passed all the tests...