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