Переписывание Java-кода в JS – создание звука из байтов?

Я пытаюсь переписать некоторый (очень простой) код Android, который я нашел написанным на Java, в статическое приложение HTML5 (мне не нужен сервер, чтобы что-то делать, я хотел бы сохранить его таким образом). У меня есть обширный опыт в области веб-разработки, но базовое понимание Java и даже меньше знаний в области разработки Android.

Единственная функция приложения – взять некоторые цифры и преобразовать их в звуковой чирп из байтов. У меня нет абсолютно никакой проблемы перевода математической логики в JS. Там, где у меня возникают проблемы, возникает вопрос, когда на самом деле получается звук. Это соответствующие части исходного кода:

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 

Как это сделать в JS? Я могу использовать dataURI для создания звука из байтов , но позволяет ли я управлять другой информацией здесь (например, частотой дискретизации и т. Д.)? Другими словами: что является самым простым и точным способом сделать это в JS?

Обновить

Я пытаюсь воспроизвести то, что я нашел в этом ответе . Это важная часть моего кода:

 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); } 

Однако, когда я запускаю это, я получаю эту ошибку:

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

Который я нахожу довольно экстраординарным, так как это такая общая ошибка, что ему удается красиво сказать, что я точно приседаю о том, что не так. Еще более удивительно, когда я отлаживал это шаг за шагом, хотя цепочка ошибок запускается (предположительно) с помощью строки context.decodeAudioData(buffer.buffer, play); Он фактически пробегает еще несколько строк в файле jQuery ( 3.2.1, несжатый ), проходя через строки 5208, 5195, 5191, 5219, 5223 и, наконец, 5015 перед ошибкой. Я не знаю, почему jQuery имеет к этому какое-то отношение, и ошибка не дает мне понять, что попробовать. Есть идеи?

Solutions Collecting From Web of "Переписывание Java-кода в JS – создание звука из байтов?"

Если bytes являются ArrayBuffer нет необходимости создавать Uint8Array . Вы можете передать bytes ArrayBuffer как параметр в AudioContext.decodeAudioData() который возвращает Promise , цепочку .then() в .decodeAudioData() , вызов с функцией play качестве параметра.

В javascript в stacksnippets элемент <input type="file"> используется для приема загрузки аудиофайла, FileReader.prototype.readAsArrayBuffer() создает ArrayBuffer из объекта File , который передается в 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])" /> 

Я решил это сам. Я больше читал в документах MDN, объясняющих AudioBuffer, и реализовал две важные вещи:

  1. Мне не нужно было decodeAudioData (поскольку я сам создаю данные, нет ничего, чтобы расшифровать). Я на самом деле взял этот бит из ответа, который я воспроизводил, и это ретроспектива, это было совершенно бесполезно.
  2. Поскольку я работаю с 16-битным PCM-стерео, это означает, что мне нужно использовать Float32Array (2 канала, каждый 16 бит).

Конечно, у меня все еще была проблема с некоторыми моими вычислениями, которые приводили к искаженному звуку, но что касается создания самого звука, я в конечном итоге сделал это очень простое решение:

 function playBytes(bytes) { var floats = new Float32Array(bytes.length); bytes.forEach(function( sample, i ) { floats[i] = sample / 32767; }); var buffer = context.createBuffer(1, floats.length, 48000), source = context.createBufferSource(); buffer.getChannelData(0).set(floats); source.buffer = buffer; source.connect(context.destination); source.start(0); } 

Возможно, я оптимизирую его немного дальше – часть 32767 должна произойти до этого, в той части, где я вычисляю данные, например. Кроме того, я создаю Float32Array с двумя каналами, а затем выводя один из них, потому что мне действительно не нужны оба. Я не мог понять, есть ли способ создать одноканальный моно файл с Int16Array, или если это даже необходимо \ лучше.

Во всяком случае, это по существу это. Это действительно просто базовое решение, с минимальным пониманием в моей части того, как правильно обрабатывать мои данные. Надеюсь, это поможет кому угодно.