echogarden
Version:
An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.
447 lines • 14.3 kB
JavaScript
import * as readline from 'node:readline';
import { randomUUID, randomBytes } from 'node:crypto';
import { inspect } from 'node:util';
import { encodeHex } from '../encodings/Hex.js';
import { Timer } from './Timer.js';
const log = logToStderr;
export function concatUint8Arrays(arrays) {
return concatTypedArrays(Uint8Array, arrays);
}
export function concatFloat32Arrays(arrays) {
return concatTypedArrays(Float32Array, arrays);
}
function concatTypedArrays(TypedArrayConstructor, arrays) {
let totalLength = 0;
for (const array of arrays) {
totalLength += array.length;
}
const result = new TypedArrayConstructor(totalLength);
let writeOffset = 0;
for (const array of arrays) {
result.set(array, writeOffset);
writeOffset += array.length;
}
return result;
}
export function shuffleArray(array, randomGen) {
return shuffleArrayInPlace(array.slice(), randomGen);
}
export function shuffleArrayInPlace(array, randomGen) {
const vectorCount = array.length;
for (let i = 0; i < vectorCount - 1; i++) {
const value = array[i];
const targetIndex = randomGen.getIntInRange(i + 1, vectorCount);
array[i] = array[targetIndex];
array[targetIndex] = value;
}
return array;
}
export function writeToStderr(message) {
process.stderr.write(message);
}
export function printToStderr(message) {
if (typeof message == 'string') {
writeToStderr(message);
}
else {
writeToStderr(formatObjectToString(message));
}
}
export function logToStderr(message) {
printToStderr(message);
writeToStderr('\n');
}
export function formatObjectToString(obj) {
const formattedString = inspect(obj, {
showHidden: false,
depth: null,
colors: false,
maxArrayLength: null,
maxStringLength: null,
compact: 5,
});
return formattedString;
}
export function getRandomHexString(charCount = 32) {
if (charCount % 2 !== 0) {
throw new Error(`'charCount' must be an even number`);
}
const randomHex = encodeHex(randomBytes(charCount / 2));
return randomHex;
}
export function getRandomUUID(dashes = true) {
let uuid = randomUUID();
if (dashes == false) {
uuid = uuid.replaceAll('-', '');
}
return uuid;
}
export function sumArray(arr, valueGetter) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += valueGetter(arr[i]);
}
return sum;
}
export function roundToDigits(val, digits = 3) {
const multiplier = 10 ** digits;
return Math.round(val * multiplier) / multiplier;
}
export function sleep(timeMs) {
const timer = new Timer();
return new Promise((resolve) => {
const tickCallback = () => {
if (timer.elapsedTime < timeMs) {
//setImmediate(tickCallback)
setTimeout(tickCallback, 0);
}
else {
resolve();
}
};
//setImmediate(tickCallback)
setTimeout(tickCallback, 0);
});
}
export function yieldToEventLoop() {
return new Promise((resolve) => {
setImmediate(resolve);
});
}
export function printMatrix(matrix) {
const rowCount = matrix.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
log(matrix[rowIndex].join(', '));
}
}
export async function parseJson(jsonText, useJson5 = false) {
if (useJson5) {
const JSON5 = await import('json5');
return JSON5.parse(jsonText);
}
else {
return JSON.parse(jsonText);
}
}
export async function stringifyAndFormatJson(obj, useJson5 = false) {
let textContent;
if (useJson5) {
const JSON5 = await import('json5');
textContent = JSON5.stringify(obj, undefined, 4);
}
else {
textContent = JSON.stringify(obj, undefined, 4);
}
return textContent;
}
export async function parseJSONAndGetType(str, useJson5 = false) {
let parsedValue = undefined;
try {
parsedValue = await parseJson(str, useJson5);
}
catch (e) {
}
let jsonType = undefined;
if (parsedValue === null) {
jsonType = 'null';
}
else if (typeof parsedValue === 'string') {
jsonType = 'string';
}
else if (typeof parsedValue === 'number') {
jsonType = 'number';
}
else if (typeof parsedValue === 'boolean') {
jsonType = 'boolean';
}
else if (Array.isArray(parsedValue)) {
jsonType = 'array';
}
else if (typeof parsedValue === 'object') {
jsonType = 'object';
}
return {
parsedValue,
jsonType
};
}
export function secondsToHMS(totalSeconds) {
let remainingSeconds = totalSeconds;
const hours = Math.floor(remainingSeconds / 60 / 60);
remainingSeconds -= hours * 60 * 60;
const minutes = Math.floor(remainingSeconds / 60);
remainingSeconds -= minutes * 60;
const seconds = Math.floor(remainingSeconds);
remainingSeconds -= seconds;
const milliseconds = Math.floor(remainingSeconds * 1000);
return { hours, minutes, seconds, milliseconds };
}
export function secondsToMS(totalSeconds) {
const { hours, minutes, seconds, milliseconds } = secondsToHMS(totalSeconds);
return { minutes: (hours * 60) + minutes, seconds, milliseconds };
}
export function intsInRange(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
export function serializeMapToObject(map) {
const obj = {};
for (const [key, value] of map) {
obj[key] = value;
}
return obj;
}
export function deserializeObjectToMap(obj) {
const map = new Map();
for (const key in obj) {
map.set(key, obj[key]);
}
return map;
}
export function waitTimeout(timeout = 0) {
return new Promise((resolve) => setTimeout(() => {
resolve();
}, timeout));
}
export function waitImmediate() {
return new Promise((resolve) => setImmediate(() => {
resolve();
}));
}
export function waitNextTick() {
return new Promise((resolve) => process.nextTick(() => resolve()));
}
export function setupUnhandledExceptionListeners() {
process.on('unhandledRejection', (e) => {
log(`Unhandled promise rejection:\n ${e}`);
process.exit(1);
});
process.on('uncaughtException', function (e) {
log(`Uncaught exception:\n ${e}`);
process.exit(1);
});
}
export function setupProgramTerminationListeners(cleanupFunc) {
function exitProcess(exitCode = 0) {
if (cleanupFunc) {
cleanupFunc();
}
process.exit(exitCode);
}
process.on('SIGINT', () => exitProcess(0));
process.on('SIGQUIT', () => exitProcess(0));
process.on('SIGTERM', () => exitProcess(0));
if (process.stdin.isTTY) {
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
process.stdin.on('keypress', (str, key) => {
if (key.name == 'escape') {
exitProcess(0);
}
if (key.ctrl == true && key.name == 'c') {
exitProcess(0);
}
});
}
}
export function clip(num, min, max) {
if (num < min) {
return min;
}
if (num > max) {
return max;
}
return num;
}
export function readBinaryIncomingMessage(incomingMessage) {
return new Promise((resolve, reject) => {
const chunks = [];
incomingMessage.on('data', (chunk) => {
chunks.push(Uint8Array.from(chunk));
});
incomingMessage.on('end', () => {
resolve(concatUint8Arrays(chunks));
});
incomingMessage.on('error', (e) => {
reject(e);
});
});
}
export function splitFloat32Array(nums, partSize) {
const result = [];
for (let offset = 0; offset < nums.length; offset += partSize) {
result.push(nums.subarray(offset, offset + partSize));
}
return result;
}
export async function sha256AsHex(input) {
const crypto = await import('crypto');
const hash = crypto.createHash('sha256').update(input).digest('hex');
return hash;
}
export async function commandExists(command) {
const { default: commandExists } = await import('command-exists');
try {
await commandExists(command);
return true;
}
catch {
return false;
}
}
export async function resolveModuleMainPath(moduleName) {
const { resolve } = await import('import-meta-resolve');
const { fileURLToPath } = await import('url');
return fileURLToPath(resolve(moduleName, import.meta.url));
}
export function getWithDefault(value, defaultValue) {
if (value === undefined) {
return defaultValue;
}
else {
return value;
}
}
export function splitFilenameOnExtendedExtension(filenameWithExtension) {
let splitPoint = filenameWithExtension.length;
for (let i = filenameWithExtension.length - 1; i >= 0; i--) {
if (filenameWithExtension[i] == '.') {
if (/^[a-zA-Z0-9\.]+$/.test(filenameWithExtension.slice(i + 1))) {
splitPoint = i;
continue;
}
else {
break;
}
}
}
const name = filenameWithExtension.slice(0, splitPoint);
const ext = filenameWithExtension.slice(splitPoint + 1);
return [name, ext];
}
export async function resolveModuleScriptPath(moduleName) {
const { resolve } = await import('import-meta-resolve');
const scriptPath = resolve(moduleName, import.meta.url);
const { fileURLToPath } = await import('url');
return fileURLToPath(scriptPath);
}
export async function runOperationWithRetries(operationFunc, logger, operationName = 'Operation', delayBetweenRetries = 2000, maxRetries = 200) {
const { default: chalk } = await import('chalk');
for (let retryIndex = 1; retryIndex <= maxRetries; retryIndex++) {
try {
const result = await operationFunc();
return result;
}
catch (e) {
const { shouldCancelCurrentTask } = await import('../server/Worker.js');
if (shouldCancelCurrentTask()) {
throw new Error('Canceled');
}
logger.setAsActiveLogger();
logger.logTitledMessage(`Error`, e.message, chalk.redBright, 'error');
logger.log('', 'error');
logger.logTitledMessage(`${operationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright, 'error');
await sleep(delayBetweenRetries);
logger.log(``, 'warning');
logger.logTitledMessage(`Starting retry attempt`, `${retryIndex} / ${maxRetries}`, chalk.yellowBright, 'warning');
logger.log(``, 'warning');
logger.unsetAsActiveLogger();
}
}
throw new Error(`${operationName} failed after ${maxRetries} retry attempts`);
}
export function writeToStdinInChunks(process, buffer, chunkSize) {
const writeChunk = (chunkOffset) => {
if (chunkOffset >= buffer.length) {
process.stdin.end(); // End the stream after writing all chunks
return;
}
const startOffset = chunkOffset;
const endOffset = Math.min(chunkOffset + chunkSize, buffer.length);
const chunk = buffer.subarray(startOffset, endOffset);
if (!process.stdin.writable) {
return;
}
process.stdin.write(chunk, () => writeChunk(endOffset));
};
writeChunk(0);
}
export function getIntegerRange(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
export function isUint8Array(value) {
return value instanceof Uint8Array;
}
export async function isWasmSimdSupported() {
const wasmFeatureDetect = await import('wasm-feature-detect');
return wasmFeatureDetect.simd();
}
export function indexOfLastMatchingNumberInRange(values, targetValue, startIndex, endIndex) {
for (let i = endIndex - 1; i >= startIndex; i--) {
if (values[i] === targetValue) {
return i;
}
}
return -1;
}
export function encodeHTMLAngleBrackets(text) {
return text.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('&', '&');
}
export function getTopKIndexes(values, topCount, sort = true) {
if (topCount < 1) {
throw new Error(`Top count must be at least 1`);
}
const topKIndexes = new Uint32Array(Math.min(topCount, values.length));
// Initialize top k indexes with the first k elements
// (or less, if value list is shorter)
for (let i = 0; i < topKIndexes.length; i++) {
topKIndexes[i] = i;
}
// Return if value list is shorter or equal in length to topCount
if (values.length <= topCount) {
return topKIndexes;
}
////
let positionOfMinimum = -1;
let valueOfMinimum = -Infinity;
// Method to scan the top-k array and update the latest minimum value position and value
function updateMinimum() {
positionOfMinimum = 0;
valueOfMinimum = values[topKIndexes[0]];
for (let i = 1; i < topKIndexes.length; i++) {
const value = values[topKIndexes[i]];
if (value < valueOfMinimum) {
positionOfMinimum = i;
valueOfMinimum = value;
}
}
}
updateMinimum();
// Add remaining elements to the list, if needed
for (let insertedElementIndex = topCount; insertedElementIndex < values.length; insertedElementIndex++) {
const insertedElementValue = values[insertedElementIndex];
// If the inserted element's value is lesser or equal to the value
// of the smallest value on the list, skip it
if (insertedElementValue <= valueOfMinimum) {
continue;
}
// Replace the minimum element with the inserted element
topKIndexes[positionOfMinimum] = insertedElementIndex;
// Update the position and value of the minimum element
updateMinimum();
}
if (sort) {
topKIndexes.sort((a, b) => values[b] - values[a]);
}
return topKIndexes;
}
//# sourceMappingURL=Utilities.js.map