1

I'm trying to make a soundboard webapp where you can add audio files to the website and they will be stored inside of the indexed db to be then fetched and played.

I was thinking of also adding a feature that would let you download the whole pack of audio files as a single file so that other people would be able to import your pack. The data would look something like this:

const data = [
  {
    title: 'audio1',
    description: 'blabla'
    audio: AudioBuffer //or some other buffer
  },{
    title: 'audio2',
    description: 'blablabla'
    audio: AudioBuffer
  }
]

The question is, how would i go at downloading this as a single file? There is probably a way with blobs etc but i have no idea where to look for. Obviously i would also have to decode this back to be used

anthumchris
  • 8,245
  • 2
  • 28
  • 53
Specy
  • 189
  • 2
  • 13
  • I would suggest you to find and use a library to this. I have used merge-files before for similar tasks. https://www.npmjs.com/package/merge-files – Poku Dec 17 '21 at 20:21
  • @Poku it's not really much of an issue of storing more than one thing, the hard part is saving the object which contains both strings and an audio buffer. Also, I need to do it client side – Specy Dec 17 '21 at 20:33

1 Answers1

1

It's worth avoiding external libraries and keeping your application lean. Consider building a downloadable binary file structured like:

*************************************************
|  MARKER  |  META  |  AUDIO  |  AUDIO  |  ...  |  
*************************************************
// Example 32 MiB audio buffers
const audio1 =   new Uint8Array(32 * 1024**2 /   Uint8Array.BYTES_PER_ELEMENT)
const audio2 = new Float32Array(32 * 1024**2 / Float32Array.BYTES_PER_ELEMENT)
​
// Metadata for your file
const meta = new TextEncoder().encode(JSON.stringify([
  { title: 'audio1', length: audio1.byteLength }, 
  { title: 'audio2', length: audio2.byteLength }, 
]))
​
// use 32-bit integer to store byte index where audio begins (metadata ends)
const marker = new Uint32Array([
  meta.byteLength + 4 // include 4 bytes for this 32-bit integer
])
​
function initDownload() {
  const a = document.createElement('a')
  a.href = URL.createObjectURL(new Blob([
    marker,
    meta,
    audio1,
    audio2,
  ], { type: 'application/octet-stream' }))
  a.download = 'saved-audio-project.bin'
  a.click()
}

function parseFile(buffer) {  // ArrayBuffer of file
  const metaLength = new DataView(buffer).getUint32(0, true)
  let readOffset = 4 // 4-byte marker length
  const audioFiles = JSON.parse(
    new TextDecoder().decode(
      buffer.slice(readOffset, readOffset += metaLength)
    )
  )  
  audioFiles.forEach(audio => audio.data = new Float32Array(
    buffer.slice(readOffset, readOffset += audio.length)
  ))
  return audioFiles
}
anthumchris
  • 8,245
  • 2
  • 28
  • 53
  • This was actually one of the ways I was thinking of making it, but didn't think much of it. So to recap, marker says how long the metadata is, so when decoding it I'll have to split the array at the length given by the marker, first part is the metadata then the remaining data will be the audio files, which I can split at length based off the metadata length. Now one thing, the AudioBuffer is a Float32Array, not a UInt8Array, how do I do handle that? – Specy Dec 18 '21 at 19:00
  • 1
    No worries, [`Blob()`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob) takes are of that for you and the underlying `ArrayBuffer` is still used. You can change the typed arrays, just keep `Uint32Array` for a padded/scalable marker. – anthumchris Dec 18 '21 at 19:03
  • 1
    ah ok, so i guess the .byteLength takes care of the length of the array accordingly too, I'll try this way and come back here in case I can't figure things out. Thanks! – Specy Dec 18 '21 at 19:14
  • Ok so i tried this, im having an issue with the decoding, when running the `const metaLength = new DataView(buffer).getUint32(0)` I get a number which is waaay off, did an example with 3 audio files and i get 2332098560, The code for the encoder is here: https://github.com/Specy/soundboard/blob/29786b0ba94ccbd0cb8c944481d962257cda8754/src/stores/Packs.ts#L108 Decoder is here: (to finish) https://github.com/Specy/soundboard/blob/main/src/utils/PackImporter.ts Website is here: https://soundboard.specy.app/ – Specy Dec 20 '21 at 13:49
  • `getUint32(0, true)` ([`littleEndian`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32#parameters)). Looks like the bytes are being read backwards, hence the inverted large number. – anthumchris Dec 20 '21 at 15:24
  • Yup after 30 mins of debugging stuff i came to the same conclusion, it works perfecty now, i managed to download and import everything successfully, thanks a lot! – Specy Dec 20 '21 at 15:33