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