echogarden
Version:
An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.
272 lines • 10.1 kB
JavaScript
import * as AlawMulaw from 'alawmulaw';
import * as BinaryArrayConversion from '../utilities/BinaryArrayConversion.js';
import { SampleFormat } from '../codecs/WaveCodec.js';
/////////////////////////////////////////////////////////////////////////////////////////////
// Low level audio sample conversions
/////////////////////////////////////////////////////////////////////////////////////////////
export function encodeToAudioBuffer(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 BinaryArrayConversion.int8ToBuffer(float32ToInt8Pcm(interleavedChannels));
}
else if (targetBitDepth === 16) {
return BinaryArrayConversion.int16ToBufferLE(float32ToInt16Pcm(interleavedChannels));
}
else if (targetBitDepth === 24) {
return BinaryArrayConversion.int24ToBufferLE(float32ToInt24Pcm(interleavedChannels));
}
else if (targetBitDepth === 32) {
return BinaryArrayConversion.int32ToBufferLE(float32ToInt32Pcm(interleavedChannels));
}
else {
throw new Error(`Unsupported PCM bit depth: ${targetBitDepth}`);
}
}
else if (targetSampleFormat === SampleFormat.Float) {
if (targetBitDepth === 32) {
return BinaryArrayConversion.float32ToBufferLE(interleavedChannels);
}
else if (targetBitDepth === 64) {
return BinaryArrayConversion.float64ToBufferLE(BinaryArrayConversion.float32Tofloat64(interleavedChannels));
}
else {
throw new Error(`Unsupported float bit depth: ${targetBitDepth}`);
}
}
else if (targetSampleFormat === SampleFormat.Alaw) {
if (targetBitDepth === 8) {
return AlawMulaw.alaw.encode(float32ToInt16Pcm(interleavedChannels));
}
else {
throw new Error(`Unsupported alaw bit depth: ${targetBitDepth}`);
}
}
else if (targetSampleFormat === SampleFormat.Mulaw) {
if (targetBitDepth === 8) {
return AlawMulaw.mulaw.encode(float32ToInt16Pcm(interleavedChannels));
}
else {
throw new Error(`Unsupported mulaw bit depth: ${targetBitDepth}`);
}
}
else {
throw new Error(`Unsupported audio format: ${targetSampleFormat}`);
}
}
export function decodeToChannels(audioBuffer, channelCount, sourceBitDepth, sourceSampleFormat) {
let interleavedChannels;
if (sourceSampleFormat === SampleFormat.PCM) {
if (sourceBitDepth === 8) {
interleavedChannels = int8PcmToFloat32(BinaryArrayConversion.bufferToInt8(audioBuffer));
}
else if (sourceBitDepth === 16) {
interleavedChannels = int16PcmToFloat32(BinaryArrayConversion.bufferLEToInt16(audioBuffer));
}
else if (sourceBitDepth === 24) {
interleavedChannels = int24PcmToFloat32(BinaryArrayConversion.bufferLEToInt24(audioBuffer));
}
else if (sourceBitDepth === 32) {
interleavedChannels = int32PcmToFloat32(BinaryArrayConversion.bufferLEToInt32(audioBuffer));
}
else {
throw new Error(`Unsupported PCM bit depth: ${sourceBitDepth}`);
}
}
else if (sourceSampleFormat === SampleFormat.Float) {
if (sourceBitDepth === 32) {
interleavedChannels = BinaryArrayConversion.bufferLEToFloat32(audioBuffer);
}
else if (sourceBitDepth === 64) {
interleavedChannels = BinaryArrayConversion.float64Tofloat32(BinaryArrayConversion.bufferLEToFloat64(audioBuffer));
}
else {
throw new Error(`Unsupported float bit depth: ${sourceBitDepth}`);
}
}
else if (sourceSampleFormat === SampleFormat.Alaw) {
if (sourceBitDepth === 8) {
interleavedChannels = int16PcmToFloat32(AlawMulaw.alaw.decode(audioBuffer));
}
else {
throw new Error(`Unsupported alaw bit depth: ${sourceBitDepth}`);
}
}
else if (sourceSampleFormat === SampleFormat.Mulaw) {
if (sourceBitDepth === 8) {
interleavedChannels = int16PcmToFloat32(AlawMulaw.mulaw.decode(audioBuffer));
}
else {
throw new Error(`Unsupported mulaw bit depth: ${sourceBitDepth}`);
}
}
else {
throw new Error(`Unsupported audio format: ${sourceSampleFormat}`);
}
audioBuffer = new Uint8Array(0); // Zero the buffer reference to allow the GC to free up memory, if possible
return deInterleaveChannels(interleavedChannels, channelCount);
}
// Int8 PCM <-> Float32 conversion
export function int8PcmToFloat32(input) {
const sampleCount = input.length;
const output = new Float32Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
output[i] = input[i] / 128;
}
return output;
}
export function float32ToInt8Pcm(input) {
const sampleCount = input.length;
const output = new Int8Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const int8Sample = input[i] * 128;
if (int8Sample < -128) {
output[i] = -128;
}
else if (int8Sample > 127) {
output[i] = 127;
}
else {
output[i] = int8Sample;
}
}
return output;
}
// Int16 PCM <-> Float32 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] / 32768;
}
return output;
}
export function float32ToInt16Pcm(input) {
const sampleCount = input.length;
const output = new Int16Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const int16Sample = input[i] * 32768;
if (int16Sample < -32768) {
output[i] = -32768;
}
else if (int16Sample > 32767) {
output[i] = 32767;
}
else {
output[i] = int16Sample;
}
}
return output;
}
// Int24 PCM <-> Float32 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] / 8388608;
}
return output;
}
export function float32ToInt24Pcm(input) {
const sampleCount = input.length;
const output = new Int32Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const int24Sample = input[i] * 8388608;
if (int24Sample < -8388608) {
output[i] = -8388608;
}
else if (int24Sample > 8388607) {
output[i] = 8388607;
}
else {
output[i] = int24Sample;
}
}
return output;
}
// Int32 PCM <-> Float32 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] / 2147483648;
}
return output;
}
export function float32ToInt32Pcm(input) {
const sampleCount = input.length;
const output = new Int32Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const int32Sample = input[i] * 2147483648;
if (int32Sample < -2147483648) {
output[i] = -2147483648;
}
else if (int32Sample > 2147483647) {
output[i] = 2147483647;
}
else {
output[i] = int32Sample;
}
}
return output;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Channel interleaving
/////////////////////////////////////////////////////////////////////////////////////////////
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 === 0) {
throw new Error('0 channel count received');
}
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;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Utilities
/////////////////////////////////////////////////////////////////////////////////////////////
export function clampFloatSample(floatSample) {
if (floatSample < -1.0) {
return -1.0;
}
else if (floatSample > 1.0) {
return 1.0;
}
else {
return floatSample;
}
}
//# sourceMappingURL=AudioBufferConversion.js.map