From fc40399026b42ff6bf024906e6b718dc045b507a Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Thu, 13 Jul 2023 16:09:59 +0000 Subject: [PATCH] added wav decoders workers --- .../app/public/workers/audioStreamProcess.js | 43 +++ packages/app/public/workers/wav-decoder.js | 260 ++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 packages/app/public/workers/audioStreamProcess.js create mode 100644 packages/app/public/workers/wav-decoder.js diff --git a/packages/app/public/workers/audioStreamProcess.js b/packages/app/public/workers/audioStreamProcess.js new file mode 100644 index 00000000..10a7f6f8 --- /dev/null +++ b/packages/app/public/workers/audioStreamProcess.js @@ -0,0 +1,43 @@ +let port + +async function stream(url) { + return (await fetch(url)).body +} + +async function* process(reader) { + while (true) { + const { value, done } = await reader.read() + + if (done) { + break; + } + + yield port.postMessage(value, [value.buffer]) + } +} + +onmessage = async (e) => { + 'use strict' + + if (!port) { + [port] = e.ports; + port.onmessage = event => postMessage(event.data) + } + + const { url, codec } = e.data + + const _stream = await stream(url) + const reader = _stream.getReader() + + while (true) { + const { value, done } = await reader.read() + + if (done) { + break; + } + + port.postMessage(value, [value.buffer]) + } + + console.log('read/write done') +} \ No newline at end of file diff --git a/packages/app/public/workers/wav-decoder.js b/packages/app/public/workers/wav-decoder.js new file mode 100644 index 00000000..152b0ed0 --- /dev/null +++ b/packages/app/public/workers/wav-decoder.js @@ -0,0 +1,260 @@ +function MohayonaoWavDecoder(opts) { + this.readerMeta = false + this.opts = opts || {} +} + +MohayonaoWavDecoder.prototype.decodeChunk = function (arrayBuffer) { + return new Promise(resolve => { + resolve(this.decodeChunkSync(arrayBuffer)) + }) +} + +MohayonaoWavDecoder.prototype.decodeChunkSync = function (arrayBuffer) { + let reader = new MohayonaoReader(new DataView(arrayBuffer)) + + // first call should parse RIFF meta data and store for subsequent reads + if (!this.readerMeta) { + this._init(reader) + } + + let audioData = this._decodeData(reader) + + if (audioData instanceof Error) { + throw audioData + } + + return audioData +} + +MohayonaoWavDecoder.prototype._init = function (reader) { + if (reader.string(4) !== "RIFF") { + throw new TypeError("Invalid WAV file") + } + + reader.uint32() // skip file length + + if (reader.string(4) !== "WAVE") { + throw new TypeError("Invalid WAV file") + } + + let dataFound = false, chunkType, chunkSize + + do { + chunkType = reader.string(4) + chunkSize = reader.uint32() + + switch (chunkType) { + case "fmt ": + this.readerMeta = this._decodeMetaInfo(reader, chunkSize) + + if (this.readerMeta instanceof Error) { + throw this.readerMeta; + } + + break; + case "data": + dataFound = true + break; + default: + reader.skip(chunkSize) + break; + } + } while (!dataFound) +} + +MohayonaoWavDecoder.prototype._decodeMetaInfo = function (reader, chunkSize) { + const formats = { + 0x0001: "lpcm", + 0x0003: "lpcm" + } + + const formatId = reader.uint16() + + if (!formats.hasOwnProperty(formatId)) { + return new TypeError("Unsupported format in WAV file: 0x" + formatId.toString(16)) + } + + const meta = { + formatId, + floatingPoint: formatId === 0x0003, + numberOfChannels: reader.uint16(), + sampleRate: reader.uint32(), + byteRate: reader.uint32(), + blockSize: reader.uint16(), + bitDepth: reader.uint16() + } + + reader.skip(chunkSize - 16) + + const decoderOption = meta.floatingPoint ? "f" : this.opts.symmetric ? "s" : "" + meta.readerMethodName = "pcm" + meta.bitDepth + decoderOption + + if (!reader[meta.readerMethodName]) { + return new TypeError("Not supported bit depth: " + meta.bitDepth) + } + + return meta +} + +MohayonaoWavDecoder.prototype._decodeData = function (reader) { + let chunkSize = reader.remain() + let length = Math.floor(chunkSize / this.readerMeta.blockSize) + let channelData = new Array(this.readerMeta.numberOfChannels) + + for (let ch = 0; ch < this.readerMeta.numberOfChannels; ch++) { + channelData[ch] = new Float32Array(length) + } + + if (!reader[this.readerMeta.readerMethodName]) { + throw new Error(`Reader for [${this.readerMeta.readerMethodName}] not found or not supported bit depth.`) + } + + const read = reader[this.readerMeta.readerMethodName].bind(reader) + + const numChannels = this.readerMeta.numberOfChannels; + + for (let i = 0; i < length; i++) { + for (let ch = 0; ch < numChannels; ch++) { + channelData[ch][i] = read(); + } + } + + return { + channelData, + length, + numberOfChannels: this.readerMeta.numberOfChannels, + sampleRate: this.readerMeta.sampleRate, + }; +} + + +function MohayonaoReader(dataView) { + this.view = dataView; + this.pos = 0; +} + +MohayonaoReader.prototype.remain = function () { + return this.view.byteLength - this.pos; +} + +MohayonaoReader.prototype.skip = function (n) { + this.pos += n; +} + +MohayonaoReader.prototype.uint8 = function () { + const data = this.view.getUint8(this.pos, true); + this.pos += 1; + return data; +} + +MohayonaoReader.prototype.int16 = function () { + const data = this.view.getInt16(this.pos, true); + this.pos += 2; + return data; +} + +MohayonaoReader.prototype.uint16 = function () { + const data = this.view.getUint16(this.pos, true); + this.pos += 2; + return data; +} + +MohayonaoReader.prototype.uint32 = function () { + const data = this.view.getUint32(this.pos, true); + this.pos += 4; + return data; +} + +MohayonaoReader.prototype.string = function (n) { + let data = ""; + for (let i = 0; i < n; i++) { + data += String.fromCharCode(this.uint8()); + } + return data; +} + +MohayonaoReader.prototype.pcm8 = function () { + const data = this.view.getUint8(this.pos) - 128; + this.pos += 1; + return data < 0 ? data / 128 : data / 127; +} + +MohayonaoReader.prototype.pcm8s = function () { + const data = this.view.getUint8(this.pos) - 127.5; + this.pos += 1; + return data / 127.5; +} + +MohayonaoReader.prototype.pcm16 = function () { + const data = this.view.getInt16(this.pos, true); + this.pos += 2; + return data < 0 ? data / 32768 : data / 32767; +} + +MohayonaoReader.prototype.pcm16s = function () { + const data = this.view.getInt16(this.pos, true); + this.pos += 2; + return data / 32768; +} + +MohayonaoReader.prototype.pcm24 = function () { + let x0 = this.view.getUint8(this.pos + 0); + let x1 = this.view.getUint8(this.pos + 1); + let x2 = this.view.getUint8(this.pos + 2); + let xx = (x0 + (x1 << 8) + (x2 << 16)); + let data = xx > 0x800000 ? xx - 0x1000000 : xx; + this.pos += 3; + return data < 0 ? data / 8388608 : data / 8388607; +} + +MohayonaoReader.prototype.pcm24s = function () { + let x0 = this.view.getUint8(this.pos + 0); + let x1 = this.view.getUint8(this.pos + 1); + let x2 = this.view.getUint8(this.pos + 2); + let xx = (x0 + (x1 << 8) + (x2 << 16)); + let data = xx > 0x800000 ? xx - 0x1000000 : xx; + this.pos += 3; + return data / 8388608; +} + +MohayonaoReader.prototype.pcm32 = function () { + const data = this.view.getInt32(this.pos, true); + this.pos += 4; + return data < 0 ? data / 2147483648 : data / 2147483647; +} + +MohayonaoReader.prototype.pcm32s = function () { + const data = this.view.getInt32(this.pos, true); + this.pos += 4; + return data / 2147483648; +} + +MohayonaoReader.prototype.pcm32f = function () { + const data = this.view.getFloat32(this.pos, true); + this.pos += 4; + return data; +} + +MohayonaoReader.prototype.pcm64f = function () { + const data = this.view.getFloat64(this.pos, true); + this.pos += 8; + return data; +} + +const decoder = new MohayonaoWavDecoder() + +self.onmessage = (event) => { + const { decode, sessionId } = event.data + + if (decode) { + const decoded = decoder.decodeChunkSync(event.data.decode) + + self.postMessage( + { decoded, sessionId }, + [ + decoded.channelData[0].buffer, + decoded.channelData[1].buffer + ] + ) + } +} \ No newline at end of file