#1408 uses [node-addon-api](https://github.com/nodejs/node-addon-api) to call C API from JavaScript, whereas this pull request uses WebAssembly to call C API from JavaScript.
300 lines
7.3 KiB
JavaScript
300 lines
7.3 KiB
JavaScript
|
|
function freeConfig(config, Module) {
|
|
if ('buffer' in config) {
|
|
Module._free(config.buffer);
|
|
}
|
|
|
|
if ('config' in config) {
|
|
freeConfig(config.config, Module)
|
|
}
|
|
|
|
if ('segmentation' in config) {
|
|
freeConfig(config.segmentation, Module)
|
|
}
|
|
|
|
if ('embedding' in config) {
|
|
freeConfig(config.embedding, Module)
|
|
}
|
|
|
|
if ('clustering' in config) {
|
|
freeConfig(config.clustering, Module)
|
|
}
|
|
|
|
Module._free(config.ptr);
|
|
}
|
|
|
|
function initSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(
|
|
config, Module) {
|
|
const modelLen = Module.lengthBytesUTF8(config.model || '') + 1;
|
|
const n = modelLen;
|
|
const buffer = Module._malloc(n);
|
|
|
|
const len = 1 * 4;
|
|
const ptr = Module._malloc(len);
|
|
|
|
let offset = 0;
|
|
Module.stringToUTF8(config.model || '', buffer + offset, modelLen);
|
|
offset += modelLen;
|
|
|
|
offset = 0;
|
|
Module.setValue(ptr, buffer + offset, 'i8*');
|
|
|
|
return {
|
|
buffer: buffer, ptr: ptr, len: len,
|
|
}
|
|
}
|
|
|
|
function initSherpaOnnxOfflineSpeakerSegmentationModelConfig(config, Module) {
|
|
if (!('pyannote' in config)) {
|
|
config.pyannote = {
|
|
model: '',
|
|
};
|
|
}
|
|
|
|
const pyannote = initSherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(
|
|
config.pyannote, Module);
|
|
|
|
const len = pyannote.len + 3 * 4;
|
|
const ptr = Module._malloc(len);
|
|
|
|
let offset = 0;
|
|
Module._CopyHeap(pyannote.ptr, pyannote.len, ptr + offset);
|
|
offset += pyannote.len;
|
|
|
|
Module.setValue(ptr + offset, config.numThreads || 1, 'i32');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, config.debug || 0, 'i32');
|
|
offset += 4;
|
|
|
|
const providerLen = Module.lengthBytesUTF8(config.provider || 'cpu') + 1;
|
|
const buffer = Module._malloc(providerLen);
|
|
Module.stringToUTF8(config.provider || 'cpu', buffer, providerLen);
|
|
Module.setValue(ptr + offset, buffer, 'i8*');
|
|
|
|
return {
|
|
buffer: buffer,
|
|
ptr: ptr,
|
|
len: len,
|
|
config: pyannote,
|
|
};
|
|
}
|
|
|
|
function initSherpaOnnxSpeakerEmbeddingExtractorConfig(config, Module) {
|
|
const modelLen = Module.lengthBytesUTF8(config.model || '') + 1;
|
|
const providerLen = Module.lengthBytesUTF8(config.provider || 'cpu') + 1;
|
|
const n = modelLen + providerLen;
|
|
const buffer = Module._malloc(n);
|
|
|
|
const len = 4 * 4;
|
|
const ptr = Module._malloc(len);
|
|
|
|
let offset = 0;
|
|
Module.stringToUTF8(config.model || '', buffer + offset, modelLen);
|
|
offset += modelLen;
|
|
|
|
Module.stringToUTF8(config.provider || 'cpu', buffer + offset, providerLen);
|
|
offset += providerLen;
|
|
|
|
offset = 0
|
|
Module.setValue(ptr + offset, buffer, 'i8*');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, config.numThreads || 1, 'i32');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, config.debug || 0, 'i32');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, buffer + modelLen, 'i8*');
|
|
offset += 4;
|
|
|
|
return {
|
|
buffer: buffer,
|
|
ptr: ptr,
|
|
len: len,
|
|
};
|
|
}
|
|
|
|
function initSherpaOnnxFastClusteringConfig(config, Module) {
|
|
const len = 2 * 4;
|
|
const ptr = Module._malloc(len);
|
|
|
|
let offset = 0;
|
|
Module.setValue(ptr + offset, config.numClusters || -1, 'i32');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, config.threshold || 0.5, 'float');
|
|
offset += 4;
|
|
|
|
return {
|
|
ptr: ptr,
|
|
len: len,
|
|
};
|
|
}
|
|
|
|
function initSherpaOnnxOfflineSpeakerDiarizationConfig(config, Module) {
|
|
if (!('segmentation' in config)) {
|
|
config.segmentation = {
|
|
pyannote: {model: ''},
|
|
numThreads: 1,
|
|
debug: 0,
|
|
provider: 'cpu',
|
|
};
|
|
}
|
|
|
|
if (!('embedding' in config)) {
|
|
config.embedding = {
|
|
model: '',
|
|
numThreads: 1,
|
|
debug: 0,
|
|
provider: 'cpu',
|
|
};
|
|
}
|
|
|
|
if (!('clustering' in config)) {
|
|
config.clustering = {
|
|
numClusters: -1,
|
|
threshold: 0.5,
|
|
};
|
|
}
|
|
|
|
const segmentation = initSherpaOnnxOfflineSpeakerSegmentationModelConfig(
|
|
config.segmentation, Module);
|
|
|
|
const embedding =
|
|
initSherpaOnnxSpeakerEmbeddingExtractorConfig(config.embedding, Module);
|
|
|
|
const clustering =
|
|
initSherpaOnnxFastClusteringConfig(config.clustering, Module);
|
|
|
|
const len = segmentation.len + embedding.len + clustering.len + 2 * 4;
|
|
const ptr = Module._malloc(len);
|
|
|
|
let offset = 0;
|
|
Module._CopyHeap(segmentation.ptr, segmentation.len, ptr + offset);
|
|
offset += segmentation.len;
|
|
|
|
Module._CopyHeap(embedding.ptr, embedding.len, ptr + offset);
|
|
offset += embedding.len;
|
|
|
|
Module._CopyHeap(clustering.ptr, clustering.len, ptr + offset);
|
|
offset += clustering.len;
|
|
|
|
Module.setValue(ptr + offset, config.minDurationOn || 0.2, 'float');
|
|
offset += 4;
|
|
|
|
Module.setValue(ptr + offset, config.minDurationOff || 0.5, 'float');
|
|
offset += 4;
|
|
|
|
return {
|
|
ptr: ptr, len: len, segmentation: segmentation, embedding: embedding,
|
|
clustering: clustering,
|
|
}
|
|
}
|
|
|
|
class OfflineSpeakerDiarization {
|
|
constructor(configObj, Module) {
|
|
const config =
|
|
initSherpaOnnxOfflineSpeakerDiarizationConfig(configObj, Module)
|
|
// Module._MyPrint(config.ptr);
|
|
|
|
const handle =
|
|
Module._SherpaOnnxCreateOfflineSpeakerDiarization(config.ptr);
|
|
|
|
freeConfig(config, Module);
|
|
|
|
this.handle = handle;
|
|
this.sampleRate =
|
|
Module._SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(this.handle);
|
|
this.Module = Module
|
|
|
|
this.config = configObj;
|
|
}
|
|
|
|
free() {
|
|
this.Module._SherpaOnnxDestroyOfflineSpeakerDiarization(this.handle);
|
|
this.handle = 0
|
|
}
|
|
|
|
setConfig(configObj) {
|
|
if (!('clustering' in configObj)) {
|
|
return;
|
|
}
|
|
|
|
const config =
|
|
initSherpaOnnxOfflineSpeakerDiarizationConfig(configObj, this.Module);
|
|
|
|
this.Module._SherpaOnnxOfflineSpeakerDiarizationSetConfig(
|
|
this.handle, config.ptr);
|
|
|
|
freeConfig(config, Module);
|
|
|
|
this.config.clustering = configObj.clustering;
|
|
}
|
|
|
|
process(samples) {
|
|
const pointer =
|
|
this.Module._malloc(samples.length * samples.BYTES_PER_ELEMENT);
|
|
this.Module.HEAPF32.set(samples, pointer / samples.BYTES_PER_ELEMENT);
|
|
|
|
let r = this.Module._SherpaOnnxOfflineSpeakerDiarizationProcess(
|
|
this.handle, pointer, samples.length);
|
|
this.Module._free(pointer);
|
|
|
|
let numSegments =
|
|
this.Module._SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r);
|
|
|
|
let segments =
|
|
this.Module._SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(
|
|
r);
|
|
|
|
let ans = [];
|
|
|
|
let sizeOfSegment = 3 * 4;
|
|
for (let i = 0; i < numSegments; ++i) {
|
|
let p = segments + i * sizeOfSegment
|
|
|
|
let start = this.Module.HEAPF32[p / 4 + 0];
|
|
let end = this.Module.HEAPF32[p / 4 + 1];
|
|
let speaker = this.Module.HEAP32[p / 4 + 2];
|
|
|
|
ans.push({start: start, end: end, speaker: speaker});
|
|
}
|
|
|
|
this.Module._SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments);
|
|
this.Module._SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r);
|
|
|
|
return ans;
|
|
}
|
|
}
|
|
|
|
function createOfflineSpeakerDiarization(Module, myConfig) {
|
|
let config = {
|
|
segmentation: {
|
|
pyannote: {model: './segmentation.onnx'},
|
|
debug: 1,
|
|
},
|
|
embedding: {
|
|
model: './embedding.onnx',
|
|
debug: 1,
|
|
},
|
|
clustering: {numClusters: -1, threshold: 0.5},
|
|
minDurationOn: 0.3,
|
|
minDurationOff: 0.5,
|
|
};
|
|
|
|
if (myConfig) {
|
|
config = myConfig;
|
|
}
|
|
|
|
return new OfflineSpeakerDiarization(config, Module);
|
|
}
|
|
|
|
if (typeof process == 'object' && typeof process.versions == 'object' &&
|
|
typeof process.versions.node == 'string') {
|
|
module.exports = {
|
|
createOfflineSpeakerDiarization,
|
|
};
|
|
}
|