Reputation: 897
I'm working on sensor data that is stored in .wav files. The samples represent floating point numbers between -1 and 1.
I'm reading samples from the .wav file as ByteString
s and I need a way to convert this ByteString
to a Float
. So I'm looking for a function with the following signature:
toFloat :: ByteString -> Float
For example. I'm working with a .wav file that contains 1 channel, has a framerate of 48kHz and samples consist of 24 bits. This means that each sample consists of 3 bytes and I can read it from the .wav file like this:
hGet h 3
.
Here, h
is the handle of the .wav file.
How can I convert this ByteString I get from hGet
to a Float
(between -1 and 1)?
As you can see in my previous question, I'm currently converting the ByteString
to a Double
by first converting it to an Int32
(based on Data.WAVE). Since my samples are never bigger than 32 bits, I would like to use Float
s instead of Double
s. I'm also looking for a more efficient way of doing this conversion.
EDIT
I'm currently converting the ByteString
first to an Int32
and then to a Double
. This is done by bsToDouble
:
convertNBytesLen :: [Word8] -> Int32
convertNBytesLen = foldr accum 0
where accum bs a = 256 * a + fromIntegral bs
bsToDouble :: S.ByteString -> Int -> Double
bsToDouble bs n = if intV >= 0
then fromIntegral intV / 2147483647
else - (fromIntegral intV / (-2147483648))
where intV = convertNBytesLen (S.unpack bs) `shift` (32 - 8 * n)
The ByteString
as input to bsToDouble
comes straight from hGet h 3
and the integer is the amount of bytes in a sample (which is 3).
Upvotes: 0
Views: 769
Reputation: 4540
Does something like this help:
import Data.Int (Int32)
import Data.Bits ((.|.),(.&.),unsafeShiftL)
import Data.Word (Word32)
import Data.Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BSU
int32_24be :: BS.ByteString -> Int32
int32_24be = \s ->
let x = unsafeShiftL (fromIntegral (BSU.unsafeIndex s 0)) 16
.|. unsafeShiftL (fromIntegral (BSU.unsafeIndex s 1)) 8
.|. fromIntegral (BSU.unsafeIndex s 2)
:: Int32
y = fromIntegral x :: Word32
in fromIntegral (if x .&. 0x00800000 > 0 then y .|. 0xFF000000 else y .&. 0x00FFFFFF)
getFloat :: BS.ByteString -> Float
getFloat = (/ 2^^23) . fromIntegral . int32_24be
My thought is that the 24 bit values are integers, and you want to normalize them to a float between -1 and 1 (positive 1 excluded, though). If this is the case, I'm thinking you would be able to use getFloat
with Data.Binary.Get
to parse your stream 24 bits at a time.
Upvotes: 3
Reputation: 9331
I'm using this to convert to Double
, it seems it could help with floats too - it assumes the binary representation of the underlying number is the same as the in-memory representation.:
https://hackage.haskell.org/package/reinterpret-cast
wordToFloat :: Word32 -> Float
It seems however that the 24-bit in WAV would have different memory characteristics than your underlying platform - if you find the correct mantissa/exponent lengths, it should be quite easy to convert it to proper 32-bit float and use this function to do the conversion.
Upvotes: 0