Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rewriting Java code to JS - creating an audio from bytes?

I'm trying to rewrite some (very simple) android code I found written in Java into a static HTML5 app (I don't need a server to do anything, I'd like to keep it that way). I have extensive background in web development, but basic understanding of Java, and even less knowledge in Android development.

The only function of the app is to take some numbers and convert them into an audio chirp from bytes. I have absolutely no problem translating the mathematical logic into JS. Where I'm having trouble is when it gets to actually producing the sound. This is the relevant parts of the original code:

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

// later in the code:

AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STATIC);

// some math, and then:

track.write(sound, 0, sound.length); // sound is an array of bytes

How do I do this in JS? I can use a dataURI to produce the sound from the bytes, but does that allow me to control the other information here (i.e., sample rate, etc.)? In other words: What's the simplest, most accurate way to do this in JS?

update

I have been trying to replicate what I found in this answer. This is the relevant part of my code:

window.onload = init;
var context;    // Audio context
var buf;        // Audio buffer

function init() {
if (!window.AudioContext) {
    if (!window.webkitAudioContext) {
        alert("Your browser does not support any AudioContext and cannot play back this audio.");
        return;
    }
        window.AudioContext = window.webkitAudioContext;
    }

    context = new AudioContext();
}

function playByteArray( bytes ) {
    var buffer = new Uint8Array( bytes.length );
    buffer.set( new Uint8Array(bytes), 0 );

    context.decodeAudioData(buffer.buffer, play);
}

function play( audioBuffer ) {
    var source    = context.createBufferSource();
    source.buffer = audioBuffer;
    source.connect( context.destination );
    source.start(0);
}

However, when I run this I get this error:

Uncaught (in promise) DOMException: Unable to decode audio data

Which I find quite extraordinary, as it's such a general error it manages to beautifully tell me exactly squat about what is wrong. Even more surprising, when I debugged this step by step, even though the chain of the errors starts (expectedly) with the line context.decodeAudioData(buffer.buffer, play); it actually runs into a few more lines within the jQuery file (3.2.1, uncompressed), going through lines 5208, 5195, 5191, 5219, 5223 and lastly 5015 before erroring out. I have no clue why jQuery has anything to do with it, and the error gives me no idea what to try. Any ideas?

like image 458
yuvi Avatar asked Mar 30 '17 19:03

yuvi


1 Answers

If bytes is an ArrayBuffer it is not necessary to create a Uint8Array. You can pass ArrayBuffer bytes as parameter to AudioContext.decodeAudioData() which returns a Promise, chain .then() to .decodeAudioData(), call with play function as parameter.

At javascript at stacksnippets, <input type="file"> element is used to accept upload of audio file, FileReader.prototype.readAsArrayBuffer() creates ArrayBuffer from File object, which is passed to playByteArray.

window.onload = init;
var context; // Audio context
var buf; // Audio buffer
var reader = new FileReader(); // to create `ArrayBuffer` from `File`

function init() {
  if (!window.AudioContext) {
    if (!window.webkitAudioContext) {
      alert("Your browser does not support any AudioContext and cannot play back this audio.");
      return;
    }
    window.AudioContext = window.webkitAudioContext;
  }

  context = new AudioContext();
}

function handleFile(file) {
  console.log(file);
  reader.onload = function() {
    console.log(reader.result instanceof ArrayBuffer);
    playByteArray(reader.result); // pass `ArrayBuffer` to `playByteArray`
  }
  reader.readAsArrayBuffer(file);
};

function playByteArray(bytes) {
  context.decodeAudioData(bytes)
  .then(play)
  .catch(function(err) {
    console.error(err);
  });
}

function play(audioBuffer) {
  var source = context.createBufferSource();
  source.buffer = audioBuffer;
  source.connect(context.destination);
  source.start(0);
}
<input type="file" accepts="audio/*" onchange="handleFile(this.files[0])" />
like image 154
guest271314 Avatar answered Oct 21 '22 09:10

guest271314