UNPKG

@echogarden/wave-codec

Version:

A fully-featured WAVE format encoder and decoder. Written in pure TypeScript.

240 lines 10.2 kB
import { encodeAlaw, decodeAlaw } from '../codecs/Alaw.js'; import { encodeMulaw, decodeMulaw } from '../codecs/Mulaw.js'; import * as BinaryArrayConversion from '../utilities/BinaryArrayConversion.js'; import { SampleFormat } from '../WaveFormatHeader.js'; import { clip } from '../utilities/Utilities.js'; ///////////////////////////////////////////////////////////////////////////////////////////// // Audio conversions to and from float32 PCM ///////////////////////////////////////////////////////////////////////////////////////////// export function float32ChannelsToBuffer(audioChannels, targetBitDepth = 16, targetSampleFormat = SampleFormat.PCM) { const interleavedChannels = interleaveChannels(audioChannels); audioChannels = []; // Zero the array references to allow the GC to free up memory, if possible if (targetSampleFormat === SampleFormat.PCM) { if (targetBitDepth === 8) { return float32ToUint8Pcm(interleavedChannels); } else if (targetBitDepth === 16) { return BinaryArrayConversion.int16ArrayToBytesLE(float32ToInt16Pcm(interleavedChannels)); } else if (targetBitDepth === 24) { return BinaryArrayConversion.int24ArrayToBytesLE(float32ToInt24Pcm(interleavedChannels)); } else if (targetBitDepth === 32) { return BinaryArrayConversion.int32ArrayToBytesLE(float32ToInt32Pcm(interleavedChannels)); } else { throw new Error(`Unsupported PCM bit depth: ${targetBitDepth}`); } } else if (targetSampleFormat === SampleFormat.Float) { if (targetBitDepth === 32) { return BinaryArrayConversion.float32ArrayToBytesLE(interleavedChannels); } else if (targetBitDepth === 64) { return BinaryArrayConversion.float64ArrayToBytesLE(BinaryArrayConversion.float32ArrayTofloat64Array(interleavedChannels)); } else { throw new Error(`Unsupported float bit depth: ${targetBitDepth}`); } } else if (targetSampleFormat === SampleFormat.Alaw) { if (targetBitDepth === 8) { return encodeAlaw(float32ToInt16Pcm(interleavedChannels)); } else { throw new Error(`Unsupported alaw bit depth: ${targetBitDepth}`); } } else if (targetSampleFormat === SampleFormat.Mulaw) { if (targetBitDepth === 8) { return encodeMulaw(float32ToInt16Pcm(interleavedChannels)); } else { throw new Error(`Unsupported mulaw bit depth: ${targetBitDepth}`); } } else { throw new Error(`Unsupported audio format: ${targetSampleFormat}`); } } export function bufferToFloat32Channels(audioBuffer, channelCount, sourceBitDepth, sourceSampleFormat) { let interleavedChannels; if (sourceSampleFormat === SampleFormat.PCM) { if (sourceBitDepth === 8) { interleavedChannels = uint8PcmToFloat32(audioBuffer); } else if (sourceBitDepth === 16) { interleavedChannels = int16PcmToFloat32(BinaryArrayConversion.bytesLEToInt16Array(audioBuffer)); } else if (sourceBitDepth === 24) { interleavedChannels = int24PcmToFloat32(BinaryArrayConversion.bytesLEToInt24Array(audioBuffer)); } else if (sourceBitDepth === 32) { interleavedChannels = int32PcmToFloat32(BinaryArrayConversion.bytesLEToInt32Array(audioBuffer)); } else { throw new Error(`Unsupported PCM bit depth: ${sourceBitDepth}`); } } else if (sourceSampleFormat === SampleFormat.Float) { if (sourceBitDepth === 32) { interleavedChannels = BinaryArrayConversion.bytesLEToFloat32Array(audioBuffer); } else if (sourceBitDepth === 64) { interleavedChannels = BinaryArrayConversion.float64ArrayTofloat32Array(BinaryArrayConversion.bytesLEToFloat64Array(audioBuffer)); } else { throw new Error(`Unsupported float bit depth: ${sourceBitDepth}`); } } else if (sourceSampleFormat === SampleFormat.Alaw) { if (sourceBitDepth === 8) { interleavedChannels = int16PcmToFloat32(decodeAlaw(audioBuffer)); } else { throw new Error(`Unsupported alaw bit depth: ${sourceBitDepth}`); } } else if (sourceSampleFormat === SampleFormat.Mulaw) { if (sourceBitDepth === 8) { interleavedChannels = int16PcmToFloat32(decodeMulaw(audioBuffer)); } else { throw new Error(`Unsupported mulaw bit depth: ${sourceBitDepth}`); } } else { throw new Error(`Unsupported audio format: ${sourceSampleFormat}`); } audioBuffer = new Uint8Array(0); // Zero the reference to allow the GC to free up memory, if possible return deinterleaveChannels(interleavedChannels, channelCount); } ///////////////////////////////////////////////////////////////////////////////////////////// // Uint8 PCM <-> Float32 PCM conversion ///////////////////////////////////////////////////////////////////////////////////////////// export function uint8PcmToFloat32(input) { const sampleCount = input.length; const output = new Float32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { output[i] = (input[i] - 128) * (1 / 128); } return output; } export function float32ToUint8Pcm(input) { const sampleCount = input.length; const output = new Uint8Array(sampleCount); for (let i = 0; i < sampleCount; i++) { let int8Sample = Math.round(input[i] * 128); int8Sample = clip(int8Sample, -128, 127); output[i] = int8Sample + 128; } return output; } ///////////////////////////////////////////////////////////////////////////////////////////// // Float32 PCM <-> Int16 PCM conversion ///////////////////////////////////////////////////////////////////////////////////////////// export function int16PcmToFloat32(input) { const sampleCount = input.length; const output = new Float32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { output[i] = input[i] * (1 / 32768); } return output; } export function float32ToInt16Pcm(input) { const sampleCount = input.length; const output = new Int16Array(sampleCount); for (let i = 0; i < sampleCount; i++) { let int16Sample = Math.round(input[i] * 32768); int16Sample = clip(int16Sample, -32768, 32767); output[i] = int16Sample; } return output; } ///////////////////////////////////////////////////////////////////////////////////////////// // Float32 PCM <-> Int24 PCM conversion (uses int32 for storage) ///////////////////////////////////////////////////////////////////////////////////////////// export function int24PcmToFloat32(input) { const sampleCount = input.length; const output = new Float32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { output[i] = input[i] * (1 / 8388608); } return output; } export function float32ToInt24Pcm(input) { const sampleCount = input.length; const output = new Int32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { let int24Sample = Math.round(input[i] * 8388608); int24Sample = clip(int24Sample, -8388608, 8388607); output[i] = int24Sample; } return output; } ///////////////////////////////////////////////////////////////////////////////////////////// // Float32 PCM <-> Int32 PCM conversion ///////////////////////////////////////////////////////////////////////////////////////////// export function int32PcmToFloat32(input) { const sampleCount = input.length; const output = new Float32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { output[i] = input[i] * (1 / 2147483648); } return output; } export function float32ToInt32Pcm(input) { const sampleCount = input.length; const output = new Int32Array(sampleCount); for (let i = 0; i < sampleCount; i++) { let int32Sample = Math.round(input[i] * 2147483648); int32Sample = clip(int32Sample, -2147483648, 2147483647); output[i] = int32Sample; } return output; } ///////////////////////////////////////////////////////////////////////////////////////////// // Channel interleaving and deinterleaving ///////////////////////////////////////////////////////////////////////////////////////////// export function interleaveChannels(channels) { const channelCount = channels.length; if (channelCount === 0) { throw new Error('Empty channel array received'); } if (channelCount === 1) { return channels[0]; } const sampleCount = channels[0].length; const result = new Float32Array(sampleCount * channelCount); let writeIndex = 0; for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) { for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) { result[writeIndex++] = channels[channelIndex][sampleIndex]; } } return result; } export function deinterleaveChannels(interleavedChannels, channelCount) { if (channelCount < 1) { throw new Error(`Invalid channel count of ${channelCount} received, which is smaller than 1`); } if (channelCount === 1) { return [interleavedChannels]; } if (interleavedChannels.length % channelCount !== 0) { throw new Error(`Size of interleaved channels (${interleaveChannels.length}) is not a multiple of channel count (${channelCount})`); } const sampleCount = interleavedChannels.length / channelCount; const channels = []; for (let i = 0; i < channelCount; i++) { channels.push(new Float32Array(sampleCount)); } let readIndex = 0; for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) { for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) { channels[channelIndex][sampleIndex] = interleavedChannels[readIndex++]; } } return channels; } //# sourceMappingURL=AudioBufferConversion.js.map