How to convert binary data with primitive data types

Dear Community,

I have a binary of float32 representing audio data with floats in the range of [-1,1].
I want this audio data converted into a binary of signed 16 bit integers.
What is an efficient way of doing this in elexir? I tried:

  def to_linear16(<<>>) do
    <<>>
  end

  def to_linear16(<<v::float-size(32), r::binary>>) do
    Logger.debug("#{inspect(v)}")
    f =
      if v < 0 do
        <<trunc(v * 0x8000)::size(16)>>
      else
        <<trunc(v * 0x7FFF)::size(16)>>
      end

    f <> to_linear16(r)
  end

But there is something wrong:

  • I get an no function clause matching in to_linear16/1 also I am only giving it data, that is definitly multples of 4 bytes long.
  • I check the intermediate results via logging of the float32 extracted form the binary and they are not what I expect. In fact, if I put the same binary into Nx.from_tensor(b,:f32) I get different results.

So I am very puzzled here.

  • What is an efficient way of doing this in elixir or am I already on the right track here?
  • What am I doing wrong in the pattern matching? I am always taking a 32bit float from the beginning of the binary.So how could the matching fail?

Thank you!

Have you checked for endianness? Erlang assumes big-endian-arranged bytes when extracting floats the way you are doing it.

Demo:

bin = <<:math.pi()::float-32>> <> <<0, 0, 0>>
<<64, 73, 15, 219, 0, 0, 0>>

<<x::float-32, rest::binary>> = bin
<<64, 73, 15, 219, 0, 0, 0>>

x
3.1415927410125732

<<x::big-float-32, rest::binary>> = bin
<<64, 73, 15, 219, 0, 0, 0>>

x
3.1415927410125732

<<x::little-float-32, rest::binary>> = bin
<<64, 73, 15, 219, 0, 0, 0>>

x
-4.03314608963584e16

As you can see, by default big endian is used. So if your values are little-endian-arranged, you have to specify that explicitly.

In your example, try changing float-size(32) to little-float-size(32) and see if it works.

2 Likes

@RudolfVonKrugstein Thanks for accepting my answer. I think future readers (and myself) would still be curious to see how you solved your problem (with slight code edits), if you don’t mind?

Well, I solved it exactly as you said:

  def to_linear16(<<>>) do
    <<>>
  end

  # use littel-float-size here, as it seems our floats are littel endian
  def to_linear16(<<v::little-float-size(32), r::binary>>) do
    Logger.debug("#{inspect(v)}")
    f =
      if v < 0 do
        <<trunc(v * 0x8000)::size(16)>>
      else
        <<trunc(v * 0x7FFF)::size(16)>>
      end

    f <> to_linear16(r)
  end

I am still not sure, if this is an efficient way, but it works.

I might switch to the membrance framework, as it seems to have functionality like this build in.

1 Like