0
votes

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 ByteStrings 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 Floats instead of Doubles. 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).

2
How do you want to decode those 24 bits into a Float? You had mentioned Data.Binary.IEEE754 in your previous question. In the source there is an example of decoding a 16 bit float, I would expect you need something similar for your 24 bits and then you can use Data.Binary to decode your stream.ryachza
Good luck with using that lib, it reads samples into a nested linked list. Last time I needed something like this I developed wave: hackage.haskell.org/package/wave. Without knowing your untimate goal it's hard to advise what is the best way to do what you want to do.Mark Karpov
@Mark I'm not planning to use that lib. I've written my own library that streams the content of the .wav file using Conduits. The only part I use from the library is the ByteString to Double conversion. But I'm looking for something more efficient.Thomas Vanhelden
@ryachza I don't know how I want to decode those 24 bits into a Float. That's my problem. I do not understand the example for 16 bits enough to make my own version for 24 bits.Thomas Vanhelden
@Mark Why is my ultimate goal important to solving this subproblem? I just need a way to convert my ByteString, I get from reading 3 bytes from the data chunk of the .wav file, to a Float (between -1 and 1). E.g. hGet gives me this ByteString: "\131\237\242". It should be converted to a float containing -0.10212671756744385Thomas Vanhelden

2 Answers

3
votes

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.

0
votes

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.