3

I have a float32Array from decodeAudioData method, which I want to convert it to Uint8Array while preserving the float32 IEEE 754 audio data.

So far I tried,

var length = float32Array.length;

var emptyBuffer = new ArrayBuffer(length * 4);
var view = new DataView(emptyBuffer);

for (var i = 0; i < length; i++) {
  view.setFloat32(i * 4, float32Array[i], true);
}

var outputArray = new Uint8Array(length);

for (var j = 0; j < length; j++) {
  outputArray[j] = view.getUint8(j * 4);
}

return outputArray;

Edit:

I just need to hold on to the binary representation just as in this answer.

starkm
  • 859
  • 1
  • 10
  • 21
  • Why not just map a `Uint8Array` over the same buffer? Then you don't have to copy anything. – Pointy Mar 18 '18 at 14:09
  • Also you did not really describe what the problem is with your code. What goes wrong? Are there errors reported? – Pointy Mar 18 '18 at 14:10
  • No syntax error but providing a different output. – starkm Mar 18 '18 at 14:24
  • @starkm how does the output differ? Please include expected and actual results, or at least a small sample of such, in the question. – Joel Cornett Mar 18 '18 at 14:26
  • 1
    @Pointy Could you explain the mapping a little bit more? Is preserving IEEE 754 representation possible that way? – starkm Mar 18 '18 at 14:59
  • @starkm `var uint8arr = new Uint8Array(float32array.buffer);` - now you have an array with 4 times the length of the original, but it's looking at the exact same actual bytes. (It's 4x longer because a uint8 value is one byte while a float32 is of course 4.) – Pointy Mar 18 '18 at 17:26
  • @Pointy and what about the IEEE 754 representation? – starkm Mar 18 '18 at 17:28
  • @starkm I don't understand what you mean by that. Creating an "overlay" typed array that way does not change the values in the array at all. So if the float32 values start off valid, then making the uint8 array with the same buffer will not change anything. Of course, if you *update* elements of the uint8 array, that *will* change the corresponding float32 elements. But it's still not clear what you mean by "hold on the the binary representation". – Pointy Mar 18 '18 at 17:34
  • @starkm look at that first answer (from "Paul S") in that question you linked; that's exactly what I'm talking about. – Pointy Mar 18 '18 at 17:35
  • @Pointy Check out the answer (from "K3N"), and that is what I am talking about. I need to convert the numbers in the float array manually. – starkm Mar 18 '18 at 17:36
  • @starkm well in that case you would **not** be retaining the "binary representation" of the values, because the way the bits of a float32 are interpreted is completely different than the way the bits of a uint8 are interpreted. A uint8 is a simple 8-bit value, from 0 to 255. I don't see how that makes any sense. – Pointy Mar 18 '18 at 17:40
  • 1
    It would be **very** helpful if you could add to the question with specific input and output numeric values you want in the input and output arrays. – Pointy Mar 18 '18 at 17:41
  • @starkm one problem in the code you have is that float32 values are 4 bytes long, not 32. – Pointy Mar 18 '18 at 17:49
  • 2
    I notice that you're using buffers for audio which means your values will be between [1,-1] in float. If you convert this directly to 8-bit type you will only get 2 or 3 numbers in this range (-1,0 and 1 depending on rounding and sign). Are you trying to convert the data to a file format storage such as 8-bit 16-bit audio? In that case you need to scale the numbers to the interval those formats represents. There is no way to preserve the actual *numbers* in a float representation as 8-bit numbers unless they happen to be [0,255] or signed [-128,127] integers. –  Mar 18 '18 at 21:18

2 Answers2

3
var output = new Uint8Array(float32Array.length);

for (var i = 0; i < float32Array.length; i++) {
  var tmp = Math.max(-1, Math.min(1, float32Array[i]));
  tmp = tmp < 0 ? (tmp * 0x8000) : (tmp * 0x7FFF);
  tmp = tmp / 256;
  output[i] = tmp + 128;
}

return output;

Anyone got doubt in mind can test this easily with Audacity's Import Raw Data feature.

  1. Download the sample raw data which I decoded from a video file using Web Audio Api's decodeAudioData method.
  2. Convert the Float32Array that sample raw data is filled with to Uint8Array by using the method above (or use your own method e.g. new Uint8Array(float32Array.buffer) to hear the corrupted sizzling sound) and download the uint8 pcm file.

    forceDownload(new Blob([output], { type: 'application/octet-binary' }));

  3. Encode your downloaded data in Audacity using File-> Import -> Raw Data... Encoding should be set to Unsigned 8-bit PCM and Sample Rate should be 16000 Hz since the original decoded audio file was in 16000 Hz.

starkm
  • 859
  • 1
  • 10
  • 21
  • 1
    What's the reason for multiplying by 0x7FFF/0x8000, then dividing by 256? It looks like we are translating to a signed 2 byte short, then translating to a unsigned 1 byte int. Why not just go directly the unsigned 1 byte? Does it have something to do with the signedness if tmp is negative? – jgawrych May 11 '20 at 21:01
2

It's not very clear what you're asking; or, rather, what it appears you're asking is a thing that makes no sense.

A Float32Array instance is a view onto a buffer of "raw" byte values, like all typed arrays. Each element of the array represents 4 of those raw bytes. Extracting a value via a simple array lookup:

var n = float32array[1];

implicitly interprets those 4 bytes as an IEEE 32-bit floating point value, and then that value is converted to a standard JavaScript number. JavaScript numbers are always 64-bit IEEE floating point values.

Similarly, a Uint8Array is a view onto a buffer, and each element gives the unsigned integer value of one byte. That is,

var n = uint8array[1];

accesses that element, interprets it as an unsigned one-byte integer, and converts that to a JavaScript number.

So: if you want to examine a list of 32-bit floating point values as the raw integer value of each byte, you can create a Uint8Array that "shares" the same buffer as the Float32Array:

var uintShared = new Uint8Array(float32array.buffer);

The number values you'd see from looking at the Uint8Array values will not appear to have anything to do with the number values you get from looking at the Float32Array elements, which is to be expected.

On the other hand, if you want to create a new Uint8Array to hold the apparent values from the Float32Array, you can just create a new array of the same length and copy each value:

var uintCopy = new Uint8Array(float32array.length);
for (let i = 0; i < float32array.length; ++i)
  uintCopy[i] = float32array[i]; // deeply problematic; see below

Now that won't work too well, in general, because the numeric range of values in a Float32Array is vastly greater than that of values in the Uint8Array. For one thing, the 32-bit floating point values can be negative. What's more, even if you know that the floating point values are all integers in the range 0 to 255, you definitely will not get the same bit patterns in the Uint8Array, for the simple reason that a 32-bit floating point number is just not the same as an 8-bit unsigned integer. To "preserve the IEEE-754 representation" makes no sense.

So that's the reality of the situation. If you were to explain why you think you want to somehow cram all 32 bits of a 32-bit IEEE float into an 8-bit unsigned integer, it would be possible to provide a more directly helpful answer.

Pointy
  • 405,095
  • 59
  • 585
  • 614