Thomas Vanhelden
Thomas Vanhelden

Reputation: 897

Convert wav audio format ByteString to Floats

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).

Upvotes: 0

Views: 769

Answers (2)

ryachza
ryachza

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

ondra
ondra

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

Related Questions