1

There are many Q&A's about converting blobs or Uint8Array to base64. But I have been unable to find how to convert from 32-bit arrays to base64. Here is an attempt.

function p(msg) { console.log(msg) }

let wav1 = [0.1,0.2,0.3]
let wav = new Float32Array(wav1)
p(`Len array to encrypt=${wav.length}`)
let omsg = JSON.stringify({onset: { id: 'abc', cntr: 1234}, wav: atob(wav) })
p(omsg)

The atob gives:

 Uncaught InvalidCharacterError: Failed to execute 'atob' on 'Window': 
 The string to be decoded is not correctly encoded."

What intermediate step is needed to allow proper encoding of the floats to base64 ? Note that I have also tried TweetNacl-util instead of atob this way:

 nacl.util.encodeBase64(wav)

This results in the same error.

Update Using JSON.stringify directly converts each float element into its ascii equivalent - which bloats the datasize . For the above that is:

"0.10000000149011612,"1":0.20000000298023224,"2":0.30000001192092896

We are transferring large arrays so this is a suboptimal solution.

Update The crucial element of the solution in the accepted answer is using Float32Array(floats).buffer . I was unaware of the buffer attribute.

WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560
  • 1
    It should be [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa) if you're encoding to base64... But `btoa` works on strings, not `Float32Array`s. Why not just let `JSON.stringify` do the serialization? – Heretic Monkey Sep 02 '20 at 21:44
  • Does `JSON.stringify` retain the float arrays as 4 bytes per element? I see it coming out as the ascii version of each element - which is like 16 bytes per element (see update to question) – WestCoastProjects Sep 02 '20 at 21:53
  • 1
    Use whatever you saw for Uint8Array on an Uint8Array view of your Float32Array's buffer. The Typed Array is just a view, you can create many of these on the same buffer without any new data assigned. But how come you can't transfer the binary data directly? (Send the buffer directly) – Kaiido Sep 02 '20 at 23:24
  • @Kaiido The data will be encrypted next: can not do that on floats afaik. In any case the `Tweet-Nacl` library does not support it. – WestCoastProjects Sep 04 '20 at 03:06
  • The data is binary data, that you have a Float32Array view over that data doesn't change that. Once again TypedArrays are only views. If some code needs to read the data as Uint8Array, then give them a Uint8Array view of your underlying buffer. There is no float or whatever only data. – Kaiido Sep 04 '20 at 03:11
  • @Kaiido the `.buffer` on the `TypedArray` is what I was missing. – WestCoastProjects Sep 04 '20 at 03:15

1 Answers1

4

The problem with your current code is that nacl.util.encodeBase64() takes in either a string, Array, or Uint8Array. Since your input isn't an Array or Uint8Array, it assumes you want to pass it in as a string.

The solution, of course, is to encode it into a Uint8Array first, then encode the Uint8Array into base64. When you decode, first decode the base64 into a Uint8Array, then convert the Uint8Array back into your Float32Array. This can be done using JavaScript ArrayBuffer.

const floatSize = 4;

function floatArrayToBytes(floats) {
    var output = floats.buffer; // Get the ArrayBuffer from the float array
    return new Uint8Array(output); // Convert the ArrayBuffer to Uint8s.
}

function bytesToFloatArray(bytes) {
    var output = bytes.buffer; // Get the ArrayBuffer from the Uint8Array.
    return new Float32Array(output); // Convert the ArrayBuffer to floats.
}

var encoded = nacl.util.encodeBase64(floatArrayToBytes(wav)) // Encode
var decoded = bytesToFloatArray(nacl.util.decodeBase64(encoded)) // Decode

If you don't like functions, here's some one-liners!

var encoded = nacl.util.encodeBase64(new Uint8Array(wav.buffer)) // Encode
var decoded = new Float32Array(nacl.util.decodeBase64(encoded).buffer) // Decode
Yves M.
  • 29,855
  • 23
  • 108
  • 144
id01
  • 1,491
  • 1
  • 14
  • 21