Publish sherpa_onnx.har for HarmonyOS (#1572)

This commit is contained in:
Fangjun Kuang
2024-11-28 17:30:16 +08:00
committed by GitHub
parent 109fb799ca
commit 315d8e2a47
104 changed files with 6257 additions and 4378 deletions

181
.github/workflows/har.yaml vendored Normal file
View File

@@ -0,0 +1,181 @@
name: har
on:
push:
branches:
- master
# - ohos-har
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:
concurrency:
group: har-${{ github.ref }}
cancel-in-progress: true
jobs:
har:
name: Har
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: har-linux
- name: cache-toolchain
id: cache-toolchain-ohos
uses: actions/cache@v4
with:
path: command-line-tools
key: commandline-tools-linux-x64-5.0.5.200.zip
- name: Download toolchain
if: steps.cache-toolchain-ohos.outputs.cache-hit != 'true'
shell: bash
run: |
curl -SL -O https://huggingface.co/csukuangfj/harmonyos-commandline-tools/resolve/main/commandline-tools-linux-x64-5.0.5.200.zip
unzip commandline-tools-linux-x64-5.0.5.200.zip
rm commandline-tools-linux-x64-5.0.5.200.zip
- name: Set environment variable
shell: bash
run: |
echo "$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build-tools/cmake/bin" >> "$GITHUB_PATH"
which cmake
cmake --version
ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake
echo "===="
cat $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake
echo "===="
# echo "$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin" >> "$GITHUB_PATH"
ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin/
echo "--"
ls -lh $GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native/llvm/bin/*unknown*
cat $GITHUB_PATH
# /home/runner/work/onnxruntime-libs/onnxruntime-libs/command-line-tools/sdk/default/openharmony/native/llvm/bin/aarch64-unknown-linux-ohos-clang -v || true
export PATH=$PWD/command-line-tools/sdk/default/openharmony/native/llvm/bin:$PATH
echo "path: $PATH"
which aarch64-unknown-linux-ohos-clang++ || true
which aarch64-unknown-linux-ohos-clang || true
aarch64-unknown-linux-ohos-clang++ --version || true
aarch64-unknown-linux-ohos-clang --version || true
which armv7-unknown-linux-ohos-clang++
which armv7-unknown-linux-ohos-clang
armv7-unknown-linux-ohos-clang++ --version
armv7-unknown-linux-ohos-clang --version
which x86_64-unknown-linux-ohos-clang++
which x86_64-unknown-linux-ohos-clang
x86_64-unknown-linux-ohos-clang++ --version
x86_64-unknown-linux-ohos-clang --version
- name: Build libraries
shell: bash
run: |
export CMAKE_CXX_COMPILER_LAUNCHER=ccache
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
cmake --version
export OHOS_SDK_NATIVE_DIR="$GITHUB_WORKSPACE/command-line-tools/sdk/default/openharmony/native"
./build-ohos-arm64-v8a.sh
./build-ohos-x86-64.sh
- name: Build Har
shell: bash
run: |
export PATH="$GITHUB_WORKSPACE/command-line-tools/bin:$PATH"
which hvigorw
pushd harmony-os/SherpaOnnxHar
hvigorw --mode module -p product=default -p module=sherpa_onnx@default assembleHar --analyze=normal --parallel --incremental --no-daemon
ls -lh ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har
cp -v ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har ../../
popd
ls -lh *.har
- name: Collect result
shell: bash
run: |
SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
echo "SHERPA_ONNX_VERSION=$SHERPA_ONNX_VERSION" >> "$GITHUB_ENV"
mv sherpa_onnx.har sherpa_onnx-$SHERPA_ONNX_VERSION.har
- uses: actions/upload-artifact@v4
with:
name: sherpa-onnx-har
path: ./sherpa_onnx*.har
- name: Release jar
if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && github.event_name == 'push' && contains(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@v2
with:
file_glob: true
overwrite: true
file: ./*.har
# repo_name: k2-fsa/sherpa-onnx
# repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}
# tag: v1.10.32
- name: Publish to huggingface
if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
uses: nick-fields/retry@v3
with:
max_attempts: 20
timeout_seconds: 200
shell: bash
command: |
git config --global user.email "csukuangfj@gmail.com"
git config --global user.name "Fangjun Kuang"
rm -rf huggingface
export GIT_LFS_SKIP_SMUDGE=1
export GIT_CLONE_PROTECTION_ACTIVE=false
SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
echo "SHERPA_ONNX_VERSION $SHERPA_ONNX_VERSION"
git clone https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-harmony-os huggingface
cd huggingface
git fetch
git pull
git merge -m "merge remote" --ff origin main
d=har
mkdir -p $d
cp -v ../*.har $d/
git status
git lfs track "*.har"
git add .
git commit -m "add more hars"
git push https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-harmony-os main

View File

@@ -18,14 +18,13 @@
### Supported platforms
|Architecture| Android | iOS | Windows | macOS | linux |
|------------|---------|---------|------------|-------|-------|
| x64 | ✔️ | | ✔️ | ✔️ | ✔️ |
| x86 | ✔️ | | ✔️ | | |
| arm64 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| arm32 | ✔️ | | | | ✔️ |
| riscv64 | | | | | ✔️ |
|Architecture| Android | iOS | Windows | macOS | linux | HarmonyOS |
|------------|---------|---------|------------|-------|-------|-----------|
| x64 | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
| x86 | ✔️ | | ✔️ | | | |
| arm64 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| arm32 | ✔️ | | | | ✔️ | ✔️ |
| riscv64 | | | | | ✔️ | |
### Supported programming languages
@@ -65,6 +64,7 @@ on the following platforms and operating systems:
- Linux, macOS, Windows, openKylin
- Android, WearOS
- iOS
- HarmonyOS
- NodeJS
- WebAssembly
- [Raspberry Pi][Raspberry Pi]

View File

@@ -134,3 +134,9 @@ cp -fv $onnxruntime_dir/lib/libonnxruntime.so install/lib
rm -rf install/share
rm -rf install/lib/pkgconfig
d=../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/arm64-v8a
if [ -d $d ]; then
cp -v install/lib/libsherpa-onnx-c-api.so $d/
cp -v install/lib/libonnxruntime.so $d/
fi

View File

@@ -134,3 +134,9 @@ cp -fv $onnxruntime_dir/lib/libonnxruntime.so install/lib
rm -rf install/share
rm -rf install/lib/pkgconfig
d=../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/libs/x86_64
if [ -d $d ]; then
cp -v install/lib/libsherpa-onnx-c-api.so $d/
cp -v install/lib/libonnxruntime.so $d/
fi

1
harmony-os/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
!build-profile.json5

12
harmony-os/SherpaOnnxHar/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test
/.appanalyzer

View File

@@ -0,0 +1,10 @@
{
"app": {
"bundleName": "com.k2fsa.sherpa.onnx",
"vendor": "example",
"versionCode": 1000000,
"versionName": "1.0.0",
"icon": "$media:app_icon",
"label": "$string:app_name"
}
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "SherpaOnnxHar"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,18 @@
# Introduction
How to build `sherpa_onnx.har` from the command line:
```bash
git clone https://github.com/k2-fsa/sherpa-onnx
cd sherpa-onnx
./build-ohos-arm64-v8a.sh
./build-ohos-x86-64.sh
cd harmony-os/SherpaOnnxHar
hvigorw clean --no-daemon
hvigorw --mode module -p product=default -p module=sherpa_onnx@default assembleHar --analyze=normal --parallel --incremental --no-daemon
ls -lh ./sherpa_onnx/build/default/outputs/default/sherpa_onnx.har
```

View File

@@ -0,0 +1,44 @@
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "4.0.0(10)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
}
}
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
},
{
"name": "sherpa_onnx",
"srcPath": "./sherpa_onnx",
}
]
}

View File

@@ -0,0 +1,20 @@
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
}
}

View File

@@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@@ -0,0 +1,28 @@
{
"apiType": "stageMode",
"buildOption": {
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

View File

@@ -0,0 +1,6 @@
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,23 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation

View File

@@ -0,0 +1,10 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {}
}

View File

@@ -0,0 +1,43 @@
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}

View File

@@ -0,0 +1,12 @@
import hilog from '@ohos.hilog';
import BackupExtensionAbility, { BundleVersion } from '@ohos.application.BackupExtensionAbility';
export default class EntryBackupAbility extends BackupExtensionAbility {
async onBackup() {
hilog.info(0x0000, 'testTag', 'onBackup ok');
}
async onRestore(bundleVersion: BundleVersion) {
hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
}
}

View File

@@ -0,0 +1,17 @@
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}

View File

@@ -0,0 +1,52 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
],
}
]
}
}

View File

@@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,3 @@
{
"allowToBackupRestore": true
}

View File

@@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@@ -0,0 +1,35 @@
import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
})
})
}

View File

@@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@@ -0,0 +1,13 @@
{
"module": {
"name": "entry_test",
"type": "feature",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false
}
}

View File

@@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View File

@@ -0,0 +1,33 @@
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}

View File

@@ -0,0 +1,22 @@
{
"modelVersion": "5.0.0",
"dependencies": {
},
"execution": {
// "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
// "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
// "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
// "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
// "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
// "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
// "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
}
}

View File

@@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,13 @@
# Notes
## How to publish a package
Please see
- <https://ohpm.openharmony.cn/#/cn/help/publishrequirefile>
- <https://ohpm.openharmony.cn/#/cn/help/createandpublish>
- <https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-har-publish-V5>
## How to sign the HAP file from commandline
Please see
<https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-command-line-building-app-V5>

View File

@@ -0,0 +1,19 @@
{
"meta": {
"stableOrder": true
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19"
},
"packages": {
"@ohos/hypium@1.0.19": {
"name": "@ohos/hypium",
"version": "1.0.19",
"integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==",
"resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.19.har",
"registryType": "ohpm"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"modelVersion": "5.0.0",
"description": "Please describe the basic information.",
"dependencies": {
},
"devDependencies": {
"@ohos/hypium": "1.0.19"
}
}

View File

@@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

View File

@@ -0,0 +1,17 @@
/**
* Use these variables when you tailor your ArkTS code. They must be of the const type.
*/
export const HAR_VERSION = '1.0.0';
export const BUILD_MODE_NAME = 'debug';
export const DEBUG = true;
export const TARGET_NAME = 'default';
/**
* BuildProfile Class is used only for compatibility purposes.
*/
export default class BuildProfile {
static readonly HAR_VERSION = HAR_VERSION;
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
static readonly DEBUG = DEBUG;
static readonly TARGET_NAME = TARGET_NAME;
}

View File

@@ -0,0 +1,40 @@
export { readWave, readWaveFromBinary } from "libsherpa_onnx.so";
export {
CircularBuffer,
SileroVadConfig,
SpeechSegment,
Vad,
VadConfig,
} from './src/main/ets/components/Vad';
export {
Samples,
OfflineStream,
FeatureConfig,
OfflineTransducerModelConfig,
OfflineParaformerModelConfig,
OfflineNemoEncDecCtcModelConfig,
OfflineWhisperModelConfig,
OfflineTdnnModelConfig,
OfflineSenseVoiceModelConfig,
OfflineMoonshineModelConfig,
OfflineModelConfig,
OfflineLMConfig,
OfflineRecognizerConfig,
OfflineRecognizerResult,
OfflineRecognizer,
} from './src/main/ets/components/NonStreamingAsr';
export {
OnlineStream,
OnlineTransducerModelConfig,
OnlineParaformerModelConfig,
OnlineZipformer2CtcModelConfig,
OnlineModelConfig,
OnlineCtcFstDecoderConfig,
OnlineRecognizerConfig,
OnlineRecognizerResult,
OnlineRecognizer,
} from './src/main/ets/components/StreamingAsr';

View File

@@ -0,0 +1,12 @@
# Introduction
[sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) is one of the deployment
frameworks of [Next-gen Kaldi](https://github.com/k2-fsa).
It supports speech-to-text, text-to-speech, speaker diarization, and VAD using
onnxruntime without Internet connection.
It also supports embedded systems, Android, iOS, HarmonyOS,
Raspberry Pi, RISC-V, x86_64 servers, websocket server/client,
C/C++, Python, Kotlin, C#, Go, NodeJS, Java, Swift, Dart, JavaScript,
Flutter, Object Pascal, Lazarus, Rust, etc.

View File

@@ -0,0 +1,46 @@
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
"abiFilters": [
"arm64-v8a",
"x86_64",
],
},
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
},
"consumerFiles": [
"./consumer-rules.txt"
]
}
},
"nativeLib": {
"debugSymbol": {
"strip": true,
"exclude": []
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest"
}
]
}

View File

@@ -0,0 +1,6 @@
import { harTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,23 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation

View File

@@ -0,0 +1,18 @@
{
"meta": {
"stableOrder": true
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx": "libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx"
},
"packages": {
"libsherpa_onnx.so@src/main/cpp/types/libsherpa_onnx": {
"name": "libsherpa_onnx.so",
"version": "1.0.0",
"resolved": "src/main/cpp/types/libsherpa_onnx",
"registryType": "local"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"name": "sherpa_onnx",
"version": "1.0.0",
"description": "Speech-to-text, text-to-speech, and speaker diarization using Next-gen Kaldi without internet connection",
"main": "Index.ets",
"author": "The next-gen Kaldi team",
"license": "Apache-2.0",
"homepage": "https://github.com/k2-fsa/sherpa-onnx",
"repository": "https://github.com/k2-fsa/sherpa-onnx/tree/master/harmonyos-SherpaOnnxHar",
"dependencies": {
"libsherpa_onnx.so": "file:./src/main/cpp/types/libsherpa_onnx"
},
"keywords": [
"tts",
"asr",
"locally",
"diarization",
"privacy",
"open-source",
"speaker",
],
"bugs": {
"url": "https://github.com/k2-fsa/sherpa-onnx/issues"
},
}

View File

@@ -0,0 +1,69 @@
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.13.0)
project(myNpmLib)
# Disable warning about
#
# "The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is
# not set.
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
endif()
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
include(FetchContent)
FetchContent_Declare(node_addon_api
GIT_REPOSITORY "https://github.com/nodejs/node-addon-api.git"
GIT_TAG c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b
PATCH_COMMAND git checkout . && git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/my-patch.diff"
)
FetchContent_MakeAvailable(node_addon_api)
FetchContent_GetProperties(node_addon_api)
if(NOT node_addon_api_POPULATED)
message(STATUS "Downloading node-addon-api from")
FetchContent_Populate(node_addon_api)
endif()
message(STATUS "node-addon-api is downloaded to ${node_addon_api_SOURCE_DIR}")
include_directories(${node_addon_api_SOURCE_DIR})
add_library(sherpa_onnx SHARED
audio-tagging.cc
keyword-spotting.cc
non-streaming-asr.cc
non-streaming-speaker-diarization.cc
non-streaming-tts.cc
punctuation.cc
sherpa-onnx-node-addon-api.cc
speaker-identification.cc
spoken-language-identification.cc
streaming-asr.cc
vad.cc
wave-reader.cc
wave-writer.cc
)
add_library(sherpa_onnx_c_api SHARED IMPORTED)
set_target_properties(sherpa_onnx_c_api
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libsherpa-onnx-c-api.so)
add_library(onnxruntime SHARED IMPORTED)
set_target_properties(onnxruntime
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libs/${OHOS_ARCH}/libonnxruntime.so)
target_link_libraries(sherpa_onnx PUBLIC libace_napi.z.so
libhilog_ndk.z.so # for hilog
librawfile.z.so
sherpa_onnx_c_api onnxruntime
)

View File

@@ -0,0 +1,227 @@
// scripts/node-addon-api/src/audio-tagging.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineZipformerAudioTaggingModelConfig
GetAudioTaggingZipformerModelConfig(Napi::Object obj) {
SherpaOnnxOfflineZipformerAudioTaggingModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("zipformer") || !obj.Get("zipformer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("zipformer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxAudioTaggingModelConfig GetAudioTaggingModelConfig(
Napi::Object obj) {
SherpaOnnxAudioTaggingModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("model") || !obj.Get("model").IsObject()) {
return c;
}
Napi::Object o = obj.Get("model").As<Napi::Object>();
c.zipformer = GetAudioTaggingZipformerModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(ced, ced);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static Napi::External<SherpaOnnxAudioTagging> CreateAudioTaggingWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "You should pass an object as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxAudioTaggingConfig c;
memset(&c, 0, sizeof(c));
c.model = GetAudioTaggingModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(labels, labels);
SHERPA_ONNX_ASSIGN_ATTR_INT32(top_k, topK);
const SherpaOnnxAudioTagging *at = SherpaOnnxCreateAudioTagging(&c);
if (c.model.zipformer.model) {
delete[] c.model.zipformer.model;
}
if (c.model.ced) {
delete[] c.model.ced;
}
if (c.model.provider) {
delete[] c.model.provider;
}
if (c.labels) {
delete[] c.labels;
}
if (!at) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxAudioTagging>::New(
env, const_cast<SherpaOnnxAudioTagging *>(at),
[](Napi::Env env, SherpaOnnxAudioTagging *at) {
SherpaOnnxDestroyAudioTagging(at);
});
}
static Napi::External<SherpaOnnxOfflineStream>
AudioTaggingCreateOfflineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an audio tagging pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxAudioTagging *at =
info[0].As<Napi::External<SherpaOnnxAudioTagging>>().Data();
const SherpaOnnxOfflineStream *stream =
SherpaOnnxAudioTaggingCreateOfflineStream(at);
return Napi::External<SherpaOnnxOfflineStream>::New(
env, const_cast<SherpaOnnxOfflineStream *>(stream),
[](Napi::Env env, SherpaOnnxOfflineStream *stream) {
SherpaOnnxDestroyOfflineStream(stream);
});
}
static Napi::Object AudioTaggingComputeWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 3) {
std::ostringstream os;
os << "Expect only 3 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an audio tagging pointer as the first argument")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an offline stream pointer as the second argument")
.ThrowAsJavaScriptException();
return {};
}
if (!info[2].IsNumber()) {
Napi::TypeError::New(env,
"You should pass an integer as the third argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxAudioTagging *at =
info[0].As<Napi::External<SherpaOnnxAudioTagging>>().Data();
SherpaOnnxOfflineStream *stream =
info[1].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
int32_t top_k = info[2].As<Napi::Number>().Int32Value();
const SherpaOnnxAudioEvent *const *events =
SherpaOnnxAudioTaggingCompute(at, stream, top_k);
auto p = events;
int32_t k = 0;
while (p && *p) {
++k;
++p;
}
Napi::Array ans = Napi::Array::New(env, k);
for (uint32_t i = 0; i != k; ++i) {
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "name"),
Napi::String::New(env, events[i]->name));
obj.Set(Napi::String::New(env, "index"),
Napi::Number::New(env, events[i]->index));
obj.Set(Napi::String::New(env, "prob"),
Napi::Number::New(env, events[i]->prob));
ans[i] = obj;
}
SherpaOnnxAudioTaggingFreeResults(events);
return ans;
}
void InitAudioTagging(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createAudioTagging"),
Napi::Function::New(env, CreateAudioTaggingWrapper));
exports.Set(Napi::String::New(env, "audioTaggingCreateOfflineStream"),
Napi::Function::New(env, AudioTaggingCreateOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "audioTaggingCompute"),
Napi::Function::New(env, AudioTaggingComputeWrapper));
}

View File

@@ -0,0 +1,8 @@
# Node
[./c-api.h](./c-api.h) is a symbolic link to
https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h
If you are using Windows, then you need to manually replace this file with
https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/c-api/c-api.h
since Windows does not support symbolic links.

View File

@@ -0,0 +1 @@
../../../../../../../../../sherpa-onnx/c-api/c-api.h

View File

@@ -0,0 +1,266 @@
// scripts/node-addon-api/src/keyword-spotting.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// defined ./streaming-asr.cc
SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj);
// defined ./streaming-asr.cc
SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj);
static Napi::External<SherpaOnnxKeywordSpotter> CreateKeywordSpotterWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxKeywordSpotterConfig c;
memset(&c, 0, sizeof(c));
c.feat_config = GetFeatureConfig(o);
c.model_config = GetOnlineModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_trailing_blanks, numTrailingBlanks);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_score, keywordsScore);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_threshold, keywordsThreshold);
SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_file, keywordsFile);
SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_buf, keywordsBuf);
SHERPA_ONNX_ASSIGN_ATTR_INT32(keywords_buf_size, keywordsBufSize);
SherpaOnnxKeywordSpotter *kws = SherpaOnnxCreateKeywordSpotter(&c);
if (c.model_config.transducer.encoder) {
delete[] c.model_config.transducer.encoder;
}
if (c.model_config.transducer.decoder) {
delete[] c.model_config.transducer.decoder;
}
if (c.model_config.transducer.joiner) {
delete[] c.model_config.transducer.joiner;
}
if (c.model_config.paraformer.encoder) {
delete[] c.model_config.paraformer.encoder;
}
if (c.model_config.paraformer.decoder) {
delete[] c.model_config.paraformer.decoder;
}
if (c.model_config.zipformer2_ctc.model) {
delete[] c.model_config.zipformer2_ctc.model;
}
if (c.model_config.tokens) {
delete[] c.model_config.tokens;
}
if (c.model_config.provider) {
delete[] c.model_config.provider;
}
if (c.model_config.model_type) {
delete[] c.model_config.model_type;
}
if (c.keywords_file) {
delete[] c.keywords_file;
}
if (c.keywords_buf) {
delete[] c.keywords_buf;
}
if (!kws) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxKeywordSpotter>::New(
env, kws, [](Napi::Env env, SherpaOnnxKeywordSpotter *kws) {
SherpaOnnxDestroyKeywordSpotter(kws);
});
}
static Napi::External<SherpaOnnxOnlineStream> CreateKeywordStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass a keyword spotter pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream = SherpaOnnxCreateKeywordStream(kws);
return Napi::External<SherpaOnnxOnlineStream>::New(
env, stream, [](Napi::Env env, SherpaOnnxOnlineStream *stream) {
SherpaOnnxDestroyOnlineStream(stream);
});
}
static Napi::Boolean IsKeywordStreamReadyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
int32_t is_ready = SherpaOnnxIsKeywordStreamReady(kws, stream);
return Napi::Boolean::New(env, is_ready);
}
static void DecodeKeywordStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
SherpaOnnxDecodeKeywordStream(kws, stream);
}
static Napi::String GetKeywordResultAsJsonWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
const char *json = SherpaOnnxGetKeywordResultAsJson(kws, stream);
Napi::String s = Napi::String::New(env, json);
SherpaOnnxFreeKeywordResultJson(json);
return s;
}
void InitKeywordSpotting(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createKeywordSpotter"),
Napi::Function::New(env, CreateKeywordSpotterWrapper));
exports.Set(Napi::String::New(env, "createKeywordStream"),
Napi::Function::New(env, CreateKeywordStreamWrapper));
exports.Set(Napi::String::New(env, "isKeywordStreamReady"),
Napi::Function::New(env, IsKeywordStreamReadyWrapper));
exports.Set(Napi::String::New(env, "decodeKeywordStream"),
Napi::Function::New(env, DecodeKeywordStreamWrapper));
exports.Set(Napi::String::New(env, "getKeywordResultAsJson"),
Napi::Function::New(env, GetKeywordResultAsJsonWrapper));
}

View File

@@ -0,0 +1 @@
*.so

View File

@@ -0,0 +1,17 @@
# Introduction
You need to get the following four `.so` files using
- [build-ohos-arm64-v8a.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-arm64-v8a.sh)
- [build-ohos-x86-64.sh](https://github.com/k2-fsa/sherpa-onnx/blob/master/build-ohos-x86-64.sh)
```
.
├── README.md
├── arm64-v8a
│   ├── libonnxruntime.so
│   └── libsherpa-onnx-c-api.so
└── x86_64
├── libonnxruntime.so
└── libsherpa-onnx-c-api.so
```

View File

@@ -0,0 +1,63 @@
// scripts/node-addon-api/src/macros.h
//
// Copyright (c) 2024 Xiaomi Corporation
#ifndef SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_
#define SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_
#include <algorithm>
#include <string>
#if __OHOS__
#include "rawfile/raw_file_manager.h"
#include "hilog/log.h"
#undef LOG_DOMAIN
#undef LOG_TAG
// https://gitee.com/openharmony/docs/blob/145a084f0b742e4325915e32f8184817927d1251/en/contribute/OpenHarmony-Log-guide.md#hilog-api-usage-specifications
#define LOG_DOMAIN 0x6666
#define LOG_TAG "sherpa_onnx"
#endif
#define SHERPA_ONNX_ASSIGN_ATTR_STR(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsString()) { \
Napi::String _str = o.Get(#js_name).As<Napi::String>(); \
std::string s = _str.Utf8Value(); \
char *p = new char[s.size() + 1]; \
std::copy(s.begin(), s.end(), p); \
p[s.size()] = 0; \
\
c.c_name = p; \
} else if (o.Has(#js_name) && o.Get(#js_name).IsTypedArray()) { \
Napi::Uint8Array _array = o.Get(#js_name).As<Napi::Uint8Array>(); \
char *p = new char[_array.ElementLength() + 1]; \
std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \
p[_array.ElementLength()] = '\0'; \
\
c.c_name = p; \
} \
} while (0)
#define SHERPA_ONNX_ASSIGN_ATTR_INT32(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \
c.c_name = o.Get(#js_name).As<Napi::Number>().Int32Value(); \
} \
} while (0)
#define SHERPA_ONNX_ASSIGN_ATTR_FLOAT(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \
c.c_name = o.Get(#js_name).As<Napi::Number>().FloatValue(); \
} \
} while (0)
#define SHERPA_ONNX_DELETE_C_STR(p) \
do { \
if (p) { \
delete[] p; \
} \
} while (0)
#endif // SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_

View File

@@ -0,0 +1,14 @@
diff --git a/napi-inl.h b/napi-inl.h
index e7141c0..0fd90d8 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -2156,7 +2156,8 @@ inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value)
inline void* ArrayBuffer::Data() {
void* data;
- napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr);
+ size_t byte_length;
+ napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &byte_length);
NAPI_THROW_IF_FAILED(_env, status, nullptr);
return data;
}

View File

@@ -0,0 +1,487 @@
// scripts/node-addon-api/src/non-streaming-asr.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// defined in ./streaming-asr.cc
SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj);
static SherpaOnnxOfflineTransducerModelConfig GetOfflineTransducerModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTransducerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("transducer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner);
return c;
}
static SherpaOnnxOfflineParaformerModelConfig GetOfflineParaformerModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineParaformerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("paraformer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineNemoEncDecCtcModelConfig GetOfflineNeMoCtcModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineNemoEncDecCtcModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("nemoCtc") || !obj.Get("nemoCtc").IsObject()) {
return c;
}
Napi::Object o = obj.Get("nemoCtc").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineWhisperModelConfig GetOfflineWhisperModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineWhisperModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) {
return c;
}
Napi::Object o = obj.Get("whisper").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(language, language);
SHERPA_ONNX_ASSIGN_ATTR_STR(task, task);
SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings);
return c;
}
static SherpaOnnxOfflineMoonshineModelConfig GetOfflineMoonshineModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineMoonshineModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("moonshine") || !obj.Get("moonshine").IsObject()) {
return c;
}
Napi::Object o = obj.Get("moonshine").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(preprocessor, preprocessor);
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(uncached_decoder, uncachedDecoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(cached_decoder, cachedDecoder);
return c;
}
static SherpaOnnxOfflineTdnnModelConfig GetOfflineTdnnModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTdnnModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("tdnn") || !obj.Get("tdnn").IsObject()) {
return c;
}
Napi::Object o = obj.Get("tdnn").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineSenseVoiceModelConfig GetOfflineSenseVoiceModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineSenseVoiceModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("senseVoice") || !obj.Get("senseVoice").IsObject()) {
return c;
}
Napi::Object o = obj.Get("senseVoice").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(language, language);
SHERPA_ONNX_ASSIGN_ATTR_INT32(use_itn, useInverseTextNormalization);
return c;
}
static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) {
SherpaOnnxOfflineModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("modelConfig").As<Napi::Object>();
c.transducer = GetOfflineTransducerModelConfig(o);
c.paraformer = GetOfflineParaformerModelConfig(o);
c.nemo_ctc = GetOfflineNeMoCtcModelConfig(o);
c.whisper = GetOfflineWhisperModelConfig(o);
c.tdnn = GetOfflineTdnnModelConfig(o);
c.sense_voice = GetOfflineSenseVoiceModelConfig(o);
c.moonshine = GetOfflineMoonshineModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType);
SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit);
SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab);
SHERPA_ONNX_ASSIGN_ATTR_STR(telespeech_ctc, teleSpeechCtc);
return c;
}
static SherpaOnnxOfflineLMConfig GetOfflineLMConfig(Napi::Object obj) {
SherpaOnnxOfflineLMConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("lmConfig") || !obj.Get("lmConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("lmConfig").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(scale, scale);
return c;
}
static Napi::External<SherpaOnnxOfflineRecognizer>
CreateOfflineRecognizerWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
#if __OHOS__
// the last argument is the NativeResourceManager
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#else
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#endif
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineRecognizerConfig c;
memset(&c, 0, sizeof(c));
c.feat_config = GetFeatureConfig(o);
c.model_config = GetOfflineModelConfig(o);
c.lm_config = GetOfflineLMConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths);
SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty);
#if __OHOS__
std::unique_ptr<NativeResourceManager, decltype(&OH_ResourceManager_ReleaseNativeResourceManager)> mgr (OH_ResourceManager_InitNativeResourceManager(env, info[1]), &OH_ResourceManager_ReleaseNativeResourceManager);
const SherpaOnnxOfflineRecognizer *recognizer =
SherpaOnnxCreateOfflineRecognizerOHOS(&c, mgr.get());
#else
const SherpaOnnxOfflineRecognizer *recognizer =
SherpaOnnxCreateOfflineRecognizer(&c);
#endif
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.joiner);
SHERPA_ONNX_DELETE_C_STR(c.model_config.paraformer.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.nemo_ctc.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.language);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.task);
SHERPA_ONNX_DELETE_C_STR(c.model_config.tdnn.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.language);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.preprocessor);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.uncached_decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.cached_decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens);
SHERPA_ONNX_DELETE_C_STR(c.model_config.provider);
SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type);
SHERPA_ONNX_DELETE_C_STR(c.model_config.modeling_unit);
SHERPA_ONNX_DELETE_C_STR(c.model_config.bpe_vocab);
SHERPA_ONNX_DELETE_C_STR(c.model_config.telespeech_ctc);
SHERPA_ONNX_DELETE_C_STR(c.lm_config.model);
SHERPA_ONNX_DELETE_C_STR(c.decoding_method);
SHERPA_ONNX_DELETE_C_STR(c.hotwords_file);
SHERPA_ONNX_DELETE_C_STR(c.rule_fsts);
SHERPA_ONNX_DELETE_C_STR(c.rule_fars);
if (!recognizer) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineRecognizer>::New(
env, const_cast<SherpaOnnxOfflineRecognizer *>(recognizer),
[](Napi::Env env, SherpaOnnxOfflineRecognizer *recognizer) {
SherpaOnnxDestroyOfflineRecognizer(recognizer);
});
}
static Napi::External<SherpaOnnxOfflineStream> CreateOfflineStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env,
"You should pass an offline recognizer pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOfflineRecognizer>>().Data();
const SherpaOnnxOfflineStream *stream =
SherpaOnnxCreateOfflineStream(recognizer);
return Napi::External<SherpaOnnxOfflineStream>::New(
env, const_cast<SherpaOnnxOfflineStream *>(stream),
[](Napi::Env env, SherpaOnnxOfflineStream *stream) {
SherpaOnnxDestroyOfflineStream(stream);
});
}
static void AcceptWaveformOfflineWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOfflineStream *stream =
info[0].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return;
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("samples")) {
Napi::TypeError::New(env, "The argument object should have a field samples")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("samples").IsTypedArray()) {
Napi::TypeError::New(env, "The object['samples'] should be a typed array")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Has("sampleRate")) {
Napi::TypeError::New(env,
"The argument object should have a field sampleRate")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("sampleRate").IsNumber()) {
Napi::TypeError::New(env, "The object['samples'] should be a number")
.ThrowAsJavaScriptException();
return;
}
Napi::Float32Array samples = obj.Get("samples").As<Napi::Float32Array>();
int32_t sample_rate = obj.Get("sampleRate").As<Napi::Number>().Int32Value();
#if __OHOS__
// Note(fangjun): For unknown reasons on HarmonyOS, we need to divide it by
// sizeof(float) here
SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(),
samples.ElementLength() / sizeof(float));
#else
SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(),
samples.ElementLength());
#endif
}
static void DecodeOfflineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an offline recognizer pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOfflineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOfflineRecognizer>>().Data();
SherpaOnnxOfflineStream *stream =
info[1].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
SherpaOnnxDecodeOfflineStream(recognizer, stream);
}
static Napi::String GetOfflineStreamResultAsJsonWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineStream *stream =
info[0].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
const char *json = SherpaOnnxGetOfflineStreamResultAsJson(stream);
Napi::String s = Napi::String::New(env, json);
SherpaOnnxDestroyOfflineStreamResultJson(json);
return s;
}
void InitNonStreamingAsr(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineRecognizer"),
Napi::Function::New(env, CreateOfflineRecognizerWrapper));
exports.Set(Napi::String::New(env, "createOfflineStream"),
Napi::Function::New(env, CreateOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "acceptWaveformOffline"),
Napi::Function::New(env, AcceptWaveformOfflineWrapper));
exports.Set(Napi::String::New(env, "decodeOfflineStream"),
Napi::Function::New(env, DecodeOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "getOfflineStreamResultAsJson"),
Napi::Function::New(env, GetOfflineStreamResultAsJsonWrapper));
}

View File

@@ -0,0 +1,310 @@
// scripts/node-addon-api/src/non-streaming-speaker-diarization.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig
GetOfflineSpeakerSegmentationPyannoteModelConfig(Napi::Object obj) {
SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("pyannote") || !obj.Get("pyannote").IsObject()) {
return c;
}
Napi::Object o = obj.Get("pyannote").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineSpeakerSegmentationModelConfig
GetOfflineSpeakerSegmentationModelConfig(Napi::Object obj) {
SherpaOnnxOfflineSpeakerSegmentationModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("segmentation") || !obj.Get("segmentation").IsObject()) {
return c;
}
Napi::Object o = obj.Get("segmentation").As<Napi::Object>();
c.pyannote = GetOfflineSpeakerSegmentationPyannoteModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static SherpaOnnxSpeakerEmbeddingExtractorConfig
GetSpeakerEmbeddingExtractorConfig(Napi::Object obj) {
SherpaOnnxSpeakerEmbeddingExtractorConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("embedding") || !obj.Get("embedding").IsObject()) {
return c;
}
Napi::Object o = obj.Get("embedding").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static SherpaOnnxFastClusteringConfig GetFastClusteringConfig(
Napi::Object obj) {
SherpaOnnxFastClusteringConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("clustering") || !obj.Get("clustering").IsObject()) {
return c;
}
Napi::Object o = obj.Get("clustering").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_clusters, numClusters);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold);
return c;
}
static Napi::External<SherpaOnnxOfflineSpeakerDiarization>
CreateOfflineSpeakerDiarizationWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineSpeakerDiarizationConfig c;
memset(&c, 0, sizeof(c));
c.segmentation = GetOfflineSpeakerSegmentationModelConfig(o);
c.embedding = GetSpeakerEmbeddingExtractorConfig(o);
c.clustering = GetFastClusteringConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_on, minDurationOn);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_off, minDurationOff);
const SherpaOnnxOfflineSpeakerDiarization *sd =
SherpaOnnxCreateOfflineSpeakerDiarization(&c);
if (c.segmentation.pyannote.model) {
delete[] c.segmentation.pyannote.model;
}
if (c.segmentation.provider) {
delete[] c.segmentation.provider;
}
if (c.embedding.model) {
delete[] c.embedding.model;
}
if (c.embedding.provider) {
delete[] c.embedding.provider;
}
if (!sd) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineSpeakerDiarization>::New(
env, const_cast<SherpaOnnxOfflineSpeakerDiarization *>(sd),
[](Napi::Env env, SherpaOnnxOfflineSpeakerDiarization *sd) {
SherpaOnnxDestroyOfflineSpeakerDiarization(sd);
});
}
static Napi::Number OfflineSpeakerDiarizationGetSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return {};
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
int32_t sample_rate = SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(sd);
return Napi::Number::New(env, sample_rate);
}
static Napi::Array OfflineSpeakerDiarizationProcessWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return {};
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
if (!info[1].IsTypedArray()) {
Napi::TypeError::New(env, "Argument 1 should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array samples = info[1].As<Napi::Float32Array>();
const SherpaOnnxOfflineSpeakerDiarizationResult *r =
SherpaOnnxOfflineSpeakerDiarizationProcess(sd, samples.Data(),
samples.ElementLength());
int32_t num_segments =
SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r);
const SherpaOnnxOfflineSpeakerDiarizationSegment *segments =
SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(r);
Napi::Array ans = Napi::Array::New(env, num_segments);
for (int32_t i = 0; i != num_segments; ++i) {
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "start"), segments[i].start);
obj.Set(Napi::String::New(env, "end"), segments[i].end);
obj.Set(Napi::String::New(env, "speaker"), segments[i].speaker);
ans.Set(i, obj);
}
SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments);
SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r);
return ans;
}
static void OfflineSpeakerDiarizationSetConfigWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return;
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return;
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineSpeakerDiarizationConfig c;
memset(&c, 0, sizeof(c));
c.clustering = GetFastClusteringConfig(o);
SherpaOnnxOfflineSpeakerDiarizationSetConfig(sd, &c);
}
void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineSpeakerDiarization"),
Napi::Function::New(env, CreateOfflineSpeakerDiarizationWrapper));
exports.Set(
Napi::String::New(env, "getOfflineSpeakerDiarizationSampleRate"),
Napi::Function::New(env, OfflineSpeakerDiarizationGetSampleRateWrapper));
exports.Set(
Napi::String::New(env, "offlineSpeakerDiarizationProcess"),
Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper));
exports.Set(
Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"),
Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper));
}

View File

@@ -0,0 +1,329 @@
// scripts/node-addon-api/src/non-streaming-tts.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTtsVitsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("vits") || !obj.Get("vits").IsObject()) {
return c;
}
Napi::Object o = obj.Get("vits").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale, noiseScale);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale_w, noiseScaleW);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(length_scale, lengthScale);
SHERPA_ONNX_ASSIGN_ATTR_STR(dict_dir, dictDir);
return c;
}
static SherpaOnnxOfflineTtsModelConfig GetOfflineTtsModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTtsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("model") || !obj.Get("model").IsObject()) {
return c;
}
Napi::Object o = obj.Get("model").As<Napi::Object>();
c.vits = GetOfflineTtsVitsModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static Napi::External<SherpaOnnxOfflineTts> CreateOfflineTtsWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineTtsConfig c;
memset(&c, 0, sizeof(c));
c.model = GetOfflineTtsModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_num_sentences, maxNumSentences);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars);
SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&c);
if (c.model.vits.model) {
delete[] c.model.vits.model;
}
if (c.model.vits.lexicon) {
delete[] c.model.vits.lexicon;
}
if (c.model.vits.tokens) {
delete[] c.model.vits.tokens;
}
if (c.model.vits.data_dir) {
delete[] c.model.vits.data_dir;
}
if (c.model.vits.dict_dir) {
delete[] c.model.vits.dict_dir;
}
if (c.model.provider) {
delete[] c.model.provider;
}
if (c.rule_fsts) {
delete[] c.rule_fsts;
}
if (c.rule_fars) {
delete[] c.rule_fars;
}
if (!tts) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineTts>::New(
env, tts, [](Napi::Env env, SherpaOnnxOfflineTts *tts) {
SherpaOnnxDestroyOfflineTts(tts);
});
}
static Napi::Number OfflineTtsSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
int32_t sample_rate = SherpaOnnxOfflineTtsSampleRate(tts);
return Napi::Number::New(env, sample_rate);
}
static Napi::Number OfflineTtsNumSpeakersWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
int32_t num_speakers = SherpaOnnxOfflineTtsNumSpeakers(tts);
return Napi::Number::New(env, num_speakers);
}
static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("text")) {
Napi::TypeError::New(env, "The argument object should have a field text")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("text").IsString()) {
Napi::TypeError::New(env, "The object['text'] should be a string")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("sid")) {
Napi::TypeError::New(env, "The argument object should have a field sid")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("sid").IsNumber()) {
Napi::TypeError::New(env, "The object['sid'] should be a number")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("speed")) {
Napi::TypeError::New(env, "The argument object should have a field speed")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("speed").IsNumber()) {
Napi::TypeError::New(env, "The object['speed'] should be a number")
.ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (obj.Has("enableExternalBuffer") &&
obj.Get("enableExternalBuffer").IsBoolean()) {
enable_external_buffer =
obj.Get("enableExternalBuffer").As<Napi::Boolean>().Value();
}
Napi::String _text = obj.Get("text").As<Napi::String>();
std::string text = _text.Utf8Value();
int32_t sid = obj.Get("sid").As<Napi::Number>().Int32Value();
float speed = obj.Get("speed").As<Napi::Number>().FloatValue();
const SherpaOnnxGeneratedAudio *audio =
SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed);
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(audio->samples), sizeof(float) * audio->n,
[](Napi::Env /*env*/, void * /*data*/,
const SherpaOnnxGeneratedAudio *hint) {
SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint);
},
audio);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
Napi::Object ans = Napi::Object::New(env);
ans.Set(Napi::String::New(env, "samples"), float32Array);
ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
return ans;
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * audio->n);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
std::copy(audio->samples, audio->samples + audio->n, float32Array.Data());
Napi::Object ans = Napi::Object::New(env);
ans.Set(Napi::String::New(env, "samples"), float32Array);
ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
return ans;
}
}
void InitNonStreamingTts(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineTts"),
Napi::Function::New(env, CreateOfflineTtsWrapper));
exports.Set(Napi::String::New(env, "getOfflineTtsSampleRate"),
Napi::Function::New(env, OfflineTtsSampleRateWrapper));
exports.Set(Napi::String::New(env, "getOfflineTtsNumSpeakers"),
Napi::Function::New(env, OfflineTtsNumSpeakersWrapper));
exports.Set(Napi::String::New(env, "offlineTtsGenerate"),
Napi::Function::New(env, OfflineTtsGenerateWrapper));
}

View File

@@ -0,0 +1,135 @@
// scripts/node-addon-api/src/punctuation.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflinePunctuationModelConfig GetOfflinePunctuationModelConfig(
Napi::Object obj) {
SherpaOnnxOfflinePunctuationModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("model") || !obj.Get("model").IsObject()) {
return c;
}
Napi::Object o = obj.Get("model").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(ct_transformer, ctTransformer);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static Napi::External<SherpaOnnxOfflinePunctuation>
CreateOfflinePunctuationWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "You should pass an object as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflinePunctuationConfig c;
memset(&c, 0, sizeof(c));
c.model = GetOfflinePunctuationModelConfig(o);
const SherpaOnnxOfflinePunctuation *punct =
SherpaOnnxCreateOfflinePunctuation(&c);
if (c.model.ct_transformer) {
delete[] c.model.ct_transformer;
}
if (c.model.provider) {
delete[] c.model.provider;
}
if (!punct) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflinePunctuation>::New(
env, const_cast<SherpaOnnxOfflinePunctuation *>(punct),
[](Napi::Env env, SherpaOnnxOfflinePunctuation *punct) {
SherpaOnnxDestroyOfflinePunctuation(punct);
});
}
static Napi::String OfflinePunctuationAddPunctWraper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env,
"You should pass an offline punctuation pointer as the first argument")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsString()) {
Napi::TypeError::New(env, "You should pass a string as the second argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflinePunctuation *punct =
info[0].As<Napi::External<SherpaOnnxOfflinePunctuation>>().Data();
Napi::String js_text = info[1].As<Napi::String>();
std::string text = js_text.Utf8Value();
const char *punct_text =
SherpaOfflinePunctuationAddPunct(punct, text.c_str());
Napi::String ans = Napi::String::New(env, punct_text);
SherpaOfflinePunctuationFreeText(punct_text);
return ans;
}
void InitPunctuation(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflinePunctuation"),
Napi::Function::New(env, CreateOfflinePunctuationWrapper));
exports.Set(Napi::String::New(env, "offlinePunctuationAddPunct"),
Napi::Function::New(env, OfflinePunctuationAddPunctWraper));
}

View File

@@ -0,0 +1,47 @@
// scripts/node-addon-api/src/sherpa-onnx-node-addon-api.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include "napi.h" // NOLINT
void InitStreamingAsr(Napi::Env env, Napi::Object exports);
void InitNonStreamingAsr(Napi::Env env, Napi::Object exports);
void InitNonStreamingTts(Napi::Env env, Napi::Object exports);
void InitVad(Napi::Env env, Napi::Object exports);
void InitWaveReader(Napi::Env env, Napi::Object exports);
void InitWaveWriter(Napi::Env env, Napi::Object exports);
void InitSpokenLanguageID(Napi::Env env, Napi::Object exports);
void InitSpeakerID(Napi::Env env, Napi::Object exports);
void InitAudioTagging(Napi::Env env, Napi::Object exports);
void InitPunctuation(Napi::Env env, Napi::Object exports);
void InitKeywordSpotting(Napi::Env env, Napi::Object exports);
void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports);
Napi::Object Init(Napi::Env env, Napi::Object exports) {
InitStreamingAsr(env, exports);
InitNonStreamingAsr(env, exports);
InitNonStreamingTts(env, exports);
InitVad(env, exports);
InitWaveReader(env, exports);
InitWaveWriter(env, exports);
InitSpokenLanguageID(env, exports);
InitSpeakerID(env, exports);
InitAudioTagging(env, exports);
InitPunctuation(env, exports);
InitKeywordSpotting(env, exports);
InitNonStreamingSpeakerDiarization(env, exports);
return exports;
}
NODE_API_MODULE(addon, Init)

View File

@@ -0,0 +1,808 @@
// scripts/node-addon-api/src/speaker-identification.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>
CreateSpeakerEmbeddingExtractorWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "You should pass an object as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxSpeakerEmbeddingExtractorConfig c;
memset(&c, 0, sizeof(c));
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
const SherpaOnnxSpeakerEmbeddingExtractor *extractor =
SherpaOnnxCreateSpeakerEmbeddingExtractor(&c);
if (c.model) {
delete[] c.model;
}
if (c.provider) {
delete[] c.provider;
}
if (!extractor) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>::New(
env, const_cast<SherpaOnnxSpeakerEmbeddingExtractor *>(extractor),
[](Napi::Env env, SherpaOnnxSpeakerEmbeddingExtractor *extractor) {
SherpaOnnxDestroySpeakerEmbeddingExtractor(extractor);
});
}
static Napi::Number SpeakerEmbeddingExtractorDimWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be a speaker embedding extractor pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingExtractor *extractor =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data();
int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor);
return Napi::Number::New(env, dim);
}
static Napi::External<SherpaOnnxOnlineStream>
SpeakerEmbeddingExtractorCreateStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding extractor "
"pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingExtractor *extractor =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data();
const SherpaOnnxOnlineStream *stream =
SherpaOnnxSpeakerEmbeddingExtractorCreateStream(extractor);
return Napi::External<SherpaOnnxOnlineStream>::New(
env, const_cast<SherpaOnnxOnlineStream *>(stream),
[](Napi::Env env, SherpaOnnxOnlineStream *stream) {
SherpaOnnxDestroyOnlineStream(stream);
});
}
static Napi::Boolean SpeakerEmbeddingExtractorIsReadyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be a speaker embedding extractor pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingExtractor *extractor =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
int32_t is_ready =
SherpaOnnxSpeakerEmbeddingExtractorIsReady(extractor, stream);
return Napi::Boolean::New(env, is_ready);
}
static Napi::Float32Array SpeakerEmbeddingExtractorComputeEmbeddingWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2 && info.Length() != 3) {
std::ostringstream os;
os << "Expect only 2 or 3 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be a speaker embedding extractor pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (info.Length() == 3) {
if (info[2].IsBoolean()) {
enable_external_buffer = info[2].As<Napi::Boolean>().Value();
} else {
Napi::TypeError::New(env, "Argument 2 should be a boolean.")
.ThrowAsJavaScriptException();
}
}
SherpaOnnxSpeakerEmbeddingExtractor *extractor =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingExtractor>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
const float *v =
SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(extractor, stream);
int32_t dim = SherpaOnnxSpeakerEmbeddingExtractorDim(extractor);
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(v), sizeof(float) * dim,
[](Napi::Env /*env*/, void *data) {
SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(
reinterpret_cast<float *>(data));
});
return Napi::Float32Array::New(env, dim, arrayBuffer, 0);
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * dim);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, dim, arrayBuffer, 0);
std::copy(v, v + dim, float32Array.Data());
SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(v);
return float32Array;
}
}
static Napi::External<SherpaOnnxSpeakerEmbeddingManager>
CreateSpeakerEmbeddingManagerWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsNumber()) {
Napi::TypeError::New(env,
"You should pass an integer as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
int32_t dim = info[0].As<Napi::Number>().Int32Value();
const SherpaOnnxSpeakerEmbeddingManager *manager =
SherpaOnnxCreateSpeakerEmbeddingManager(dim);
if (!manager) {
Napi::TypeError::New(env, "Please check your input dim!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxSpeakerEmbeddingManager>::New(
env, const_cast<SherpaOnnxSpeakerEmbeddingManager *>(manager),
[](Napi::Env env, SherpaOnnxSpeakerEmbeddingManager *manager) {
SherpaOnnxDestroySpeakerEmbeddingManager(manager);
});
}
static Napi::Boolean SpeakerEmbeddingManagerAddWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("v")) {
Napi::TypeError::New(env, "The argument object should have a field v")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("v").IsTypedArray()) {
Napi::TypeError::New(env, "The object['v'] should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("name")) {
Napi::TypeError::New(env, "The argument object should have a field name")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("name").IsString()) {
Napi::TypeError::New(env, "The object['name'] should be a string")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array v = obj.Get("v").As<Napi::Float32Array>();
Napi::String js_name = obj.Get("name").As<Napi::String>();
std::string name = js_name.Utf8Value();
int32_t ok =
SherpaOnnxSpeakerEmbeddingManagerAdd(manager, name.c_str(), v.Data());
return Napi::Boolean::New(env, ok);
}
static Napi::Boolean SpeakerEmbeddingManagerAddListFlattenedWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("vv")) {
Napi::TypeError::New(env, "The argument object should have a field vv")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("vv").IsTypedArray()) {
Napi::TypeError::New(env, "The object['vv'] should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("name")) {
Napi::TypeError::New(env, "The argument object should have a field name")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("name").IsString()) {
Napi::TypeError::New(env, "The object['name'] should be a string")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("n")) {
Napi::TypeError::New(env, "The argument object should have a field n")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("n").IsNumber()) {
Napi::TypeError::New(env, "The object['n'] should be an integer")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array v = obj.Get("vv").As<Napi::Float32Array>();
Napi::String js_name = obj.Get("name").As<Napi::String>();
int32_t n = obj.Get("n").As<Napi::Number>().Int32Value();
std::string name = js_name.Utf8Value();
int32_t ok = SherpaOnnxSpeakerEmbeddingManagerAddListFlattened(
manager, name.c_str(), v.Data(), n);
return Napi::Boolean::New(env, ok);
}
static Napi::Boolean SpeakerEmbeddingManagerRemoveWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsString()) {
Napi::TypeError::New(env, "Argument 1 should be string")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::String js_name = info[1].As<Napi::String>();
std::string name = js_name.Utf8Value();
int32_t ok = SherpaOnnxSpeakerEmbeddingManagerRemove(manager, name.c_str());
return Napi::Boolean::New(env, ok);
}
static Napi::String SpeakerEmbeddingManagerSearchWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("v")) {
Napi::TypeError::New(env, "The argument object should have a field v")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("v").IsTypedArray()) {
Napi::TypeError::New(env, "The object['v'] should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("threshold")) {
Napi::TypeError::New(env,
"The argument object should have a field threshold")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("threshold").IsNumber()) {
Napi::TypeError::New(env, "The object['threshold'] should be a float")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array v = obj.Get("v").As<Napi::Float32Array>();
float threshold = obj.Get("threshold").As<Napi::Number>().FloatValue();
const char *name =
SherpaOnnxSpeakerEmbeddingManagerSearch(manager, v.Data(), threshold);
const char *p = name;
if (!p) {
p = "";
}
Napi::String js_name = Napi::String::New(env, p);
SherpaOnnxSpeakerEmbeddingManagerFreeSearch(name);
return js_name;
}
static Napi::Boolean SpeakerEmbeddingManagerVerifyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("v")) {
Napi::TypeError::New(env, "The argument object should have a field v")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("v").IsTypedArray()) {
Napi::TypeError::New(env, "The object['v'] should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("threshold")) {
Napi::TypeError::New(env,
"The argument object should have a field threshold")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("threshold").IsNumber()) {
Napi::TypeError::New(env, "The object['threshold'] should be a float")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("name")) {
Napi::TypeError::New(env, "The argument object should have a field name")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("name").IsString()) {
Napi::TypeError::New(env, "The object['name'] should be a string")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array v = obj.Get("v").As<Napi::Float32Array>();
float threshold = obj.Get("threshold").As<Napi::Number>().FloatValue();
Napi::String js_name = obj.Get("name").As<Napi::String>();
std::string name = js_name.Utf8Value();
int32_t found = SherpaOnnxSpeakerEmbeddingManagerVerify(manager, name.c_str(),
v.Data(), threshold);
return Napi::Boolean::New(env, found);
}
static Napi::Boolean SpeakerEmbeddingManagerContainsWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsString()) {
Napi::TypeError::New(env, "Argument 1 should be a string")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
Napi::String js_name = info[1].As<Napi::String>();
std::string name = js_name.Utf8Value();
int32_t exists =
SherpaOnnxSpeakerEmbeddingManagerContains(manager, name.c_str());
return Napi::Boolean::New(env, exists);
}
static Napi::Number SpeakerEmbeddingManagerNumSpeakersWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager);
return Napi::Number::New(env, num_speakers);
}
static Napi::Array SpeakerEmbeddingManagerGetAllSpeakersWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"You should pass a speaker embedding manager pointer "
"as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpeakerEmbeddingManager *manager =
info[0].As<Napi::External<SherpaOnnxSpeakerEmbeddingManager>>().Data();
int32_t num_speakers = SherpaOnnxSpeakerEmbeddingManagerNumSpeakers(manager);
if (num_speakers == 0) {
return {};
}
const char *const *all_speaker_names =
SherpaOnnxSpeakerEmbeddingManagerGetAllSpeakers(manager);
Napi::Array ans = Napi::Array::New(env, num_speakers);
for (uint32_t i = 0; i != num_speakers; ++i) {
ans[i] = Napi::String::New(env, all_speaker_names[i]);
}
SherpaOnnxSpeakerEmbeddingManagerFreeAllSpeakers(all_speaker_names);
return ans;
}
void InitSpeakerID(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createSpeakerEmbeddingExtractor"),
Napi::Function::New(env, CreateSpeakerEmbeddingExtractorWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingExtractorDim"),
Napi::Function::New(env, SpeakerEmbeddingExtractorDimWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingExtractorCreateStream"),
Napi::Function::New(env, SpeakerEmbeddingExtractorCreateStreamWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingExtractorIsReady"),
Napi::Function::New(env, SpeakerEmbeddingExtractorIsReadyWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingExtractorComputeEmbedding"),
Napi::Function::New(env,
SpeakerEmbeddingExtractorComputeEmbeddingWrapper));
exports.Set(Napi::String::New(env, "createSpeakerEmbeddingManager"),
Napi::Function::New(env, CreateSpeakerEmbeddingManagerWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingManagerAdd"),
Napi::Function::New(env, SpeakerEmbeddingManagerAddWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingManagerAddListFlattened"),
Napi::Function::New(env, SpeakerEmbeddingManagerAddListFlattenedWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingManagerRemove"),
Napi::Function::New(env, SpeakerEmbeddingManagerRemoveWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingManagerSearch"),
Napi::Function::New(env, SpeakerEmbeddingManagerSearchWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingManagerVerify"),
Napi::Function::New(env, SpeakerEmbeddingManagerVerifyWrapper));
exports.Set(Napi::String::New(env, "speakerEmbeddingManagerContains"),
Napi::Function::New(env, SpeakerEmbeddingManagerContainsWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingManagerNumSpeakers"),
Napi::Function::New(env, SpeakerEmbeddingManagerNumSpeakersWrapper));
exports.Set(
Napi::String::New(env, "speakerEmbeddingManagerGetAllSpeakers"),
Napi::Function::New(env, SpeakerEmbeddingManagerGetAllSpeakersWrapper));
}

View File

@@ -0,0 +1,188 @@
// scripts/node-addon-api/src/spoken-language-identification.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxSpokenLanguageIdentificationWhisperConfig
GetSpokenLanguageIdentificationWhisperConfig(Napi::Object obj) {
SherpaOnnxSpokenLanguageIdentificationWhisperConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) {
return c;
}
Napi::Object o = obj.Get("whisper").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings);
return c;
}
static Napi::External<SherpaOnnxSpokenLanguageIdentification>
CreateSpokenLanguageIdentificationWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "You should pass an object as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxSpokenLanguageIdentificationConfig c;
memset(&c, 0, sizeof(c));
c.whisper = GetSpokenLanguageIdentificationWhisperConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
const SherpaOnnxSpokenLanguageIdentification *slid =
SherpaOnnxCreateSpokenLanguageIdentification(&c);
if (c.whisper.encoder) {
delete[] c.whisper.encoder;
}
if (c.whisper.decoder) {
delete[] c.whisper.decoder;
}
if (c.provider) {
delete[] c.provider;
}
if (!slid) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxSpokenLanguageIdentification>::New(
env, const_cast<SherpaOnnxSpokenLanguageIdentification *>(slid),
[](Napi::Env env, SherpaOnnxSpokenLanguageIdentification *slid) {
SherpaOnnxDestroySpokenLanguageIdentification(slid);
});
}
static Napi::External<SherpaOnnxOfflineStream>
SpokenLanguageIdentificationCreateOfflineStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env,
"You should pass an offline language ID pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpokenLanguageIdentification *slid =
info[0]
.As<Napi::External<SherpaOnnxSpokenLanguageIdentification>>()
.Data();
SherpaOnnxOfflineStream *stream =
SherpaOnnxSpokenLanguageIdentificationCreateOfflineStream(slid);
return Napi::External<SherpaOnnxOfflineStream>::New(
env, stream, [](Napi::Env env, SherpaOnnxOfflineStream *stream) {
SherpaOnnxDestroyOfflineStream(stream);
});
}
static Napi::String SpokenLanguageIdentificationComputeWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline spoken language ID pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxSpokenLanguageIdentification *slid =
info[0]
.As<Napi::External<SherpaOnnxSpokenLanguageIdentification>>()
.Data();
SherpaOnnxOfflineStream *stream =
info[1].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
const SherpaOnnxSpokenLanguageIdentificationResult *r =
SherpaOnnxSpokenLanguageIdentificationCompute(slid, stream);
std::string lang = r->lang;
SherpaOnnxDestroySpokenLanguageIdentificationResult(r);
return Napi::String::New(env, lang);
}
void InitSpokenLanguageID(Napi::Env env, Napi::Object exports) {
exports.Set(
Napi::String::New(env, "createSpokenLanguageIdentification"),
Napi::Function::New(env, CreateSpokenLanguageIdentificationWrapper));
exports.Set(
Napi::String::New(env, "createSpokenLanguageIdentificationOfflineStream"),
Napi::Function::New(
env, SpokenLanguageIdentificationCreateOfflineStreamWrapper));
exports.Set(
Napi::String::New(env, "spokenLanguageIdentificationCompute"),
Napi::Function::New(env, SpokenLanguageIdentificationComputeWrapper));
}

View File

@@ -0,0 +1,731 @@
// scripts/node-addon-api/src/streaming-asr.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
/*
{
'featConfig': {
'sampleRate': 16000,
'featureDim': 80,
}
};
*/
SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj) {
SherpaOnnxFeatureConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("featConfig") || !obj.Get("featConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("featConfig").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate);
SHERPA_ONNX_ASSIGN_ATTR_INT32(feature_dim, featureDim);
return c;
}
/*
{
'transducer': {
'encoder': './encoder.onnx',
'decoder': './decoder.onnx',
'joiner': './joiner.onnx',
}
}
*/
static SherpaOnnxOnlineTransducerModelConfig GetOnlineTransducerModelConfig(
Napi::Object obj) {
SherpaOnnxOnlineTransducerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("transducer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner);
return c;
}
static SherpaOnnxOnlineZipformer2CtcModelConfig
GetOnlineZipformer2CtcModelConfig(Napi::Object obj) {
SherpaOnnxOnlineZipformer2CtcModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("zipformer2Ctc") || !obj.Get("zipformer2Ctc").IsObject()) {
return c;
}
Napi::Object o = obj.Get("zipformer2Ctc").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOnlineParaformerModelConfig GetOnlineParaformerModelConfig(
Napi::Object obj) {
SherpaOnnxOnlineParaformerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("paraformer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
return c;
}
SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj) {
SherpaOnnxOnlineModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("modelConfig").As<Napi::Object>();
c.transducer = GetOnlineTransducerModelConfig(o);
c.paraformer = GetOnlineParaformerModelConfig(o);
c.zipformer2_ctc = GetOnlineZipformer2CtcModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType);
SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit);
SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens_buf, tokensBuf);
SHERPA_ONNX_ASSIGN_ATTR_INT32(tokens_buf_size, tokensBufSize);
return c;
}
static SherpaOnnxOnlineCtcFstDecoderConfig GetCtcFstDecoderConfig(
Napi::Object obj) {
SherpaOnnxOnlineCtcFstDecoderConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("ctcFstDecoderConfig") ||
!obj.Get("ctcFstDecoderConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("ctcFstDecoderConfig").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(graph, graph);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active, maxActive);
return c;
}
static Napi::External<SherpaOnnxOnlineRecognizer> CreateOnlineRecognizerWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
#if __OHOS__
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#else
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#endif
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOnlineRecognizerConfig c;
memset(&c, 0, sizeof(c));
c.feat_config = GetFeatureConfig(o);
c.model_config = GetOnlineModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths);
// enableEndpoint can be either a boolean or an integer
if (o.Has("enableEndpoint") && (o.Get("enableEndpoint").IsNumber() ||
o.Get("enableEndpoint").IsBoolean())) {
if (o.Get("enableEndpoint").IsNumber()) {
c.enable_endpoint =
o.Get("enableEndpoint").As<Napi::Number>().Int32Value();
} else {
c.enable_endpoint = o.Get("enableEndpoint").As<Napi::Boolean>().Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule1_min_trailing_silence,
rule1MinTrailingSilence);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule2_min_trailing_silence,
rule2MinTrailingSilence);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(rule3_min_utterance_length,
rule3MinUtteranceLength);
SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty);
SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_buf, hotwordsBuf);
SHERPA_ONNX_ASSIGN_ATTR_INT32(hotwords_buf_size, hotwordsBufSize);
c.ctc_fst_decoder_config = GetCtcFstDecoderConfig(o);
#if __OHOS__
std::unique_ptr<NativeResourceManager, decltype(&OH_ResourceManager_ReleaseNativeResourceManager)> mgr (OH_ResourceManager_InitNativeResourceManager(env, info[1]), &OH_ResourceManager_ReleaseNativeResourceManager);
const SherpaOnnxOnlineRecognizer *recognizer =
SherpaOnnxCreateOnlineRecognizerOHOS(&c, mgr.get());
#else
const SherpaOnnxOnlineRecognizer *recognizer =
SherpaOnnxCreateOnlineRecognizer(&c);
#endif
if (c.model_config.transducer.encoder) {
delete[] c.model_config.transducer.encoder;
}
if (c.model_config.transducer.decoder) {
delete[] c.model_config.transducer.decoder;
}
if (c.model_config.transducer.joiner) {
delete[] c.model_config.transducer.joiner;
}
if (c.model_config.paraformer.encoder) {
delete[] c.model_config.paraformer.encoder;
}
if (c.model_config.paraformer.decoder) {
delete[] c.model_config.paraformer.decoder;
}
if (c.model_config.zipformer2_ctc.model) {
delete[] c.model_config.zipformer2_ctc.model;
}
if (c.model_config.tokens) {
delete[] c.model_config.tokens;
}
if (c.model_config.provider) {
delete[] c.model_config.provider;
}
if (c.model_config.model_type) {
delete[] c.model_config.model_type;
}
if (c.model_config.modeling_unit) {
delete[] c.model_config.modeling_unit;
}
if (c.model_config.bpe_vocab) {
delete[] c.model_config.bpe_vocab;
}
if (c.model_config.tokens_buf) {
delete[] c.model_config.tokens_buf;
}
if (c.decoding_method) {
delete[] c.decoding_method;
}
if (c.hotwords_file) {
delete[] c.hotwords_file;
}
if (c.rule_fsts) {
delete[] c.rule_fsts;
}
if (c.rule_fars) {
delete[] c.rule_fars;
}
if (c.hotwords_buf) {
delete[] c.hotwords_buf;
}
if (c.ctc_fst_decoder_config.graph) {
delete[] c.ctc_fst_decoder_config.graph;
}
if (!recognizer) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOnlineRecognizer>::New(
env, const_cast<SherpaOnnxOnlineRecognizer *>(recognizer),
[](Napi::Env env, SherpaOnnxOnlineRecognizer *recognizer) {
SherpaOnnxDestroyOnlineRecognizer(recognizer);
});
}
static Napi::External<SherpaOnnxOnlineStream> CreateOnlineStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env,
"You should pass an online recognizer pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
const SherpaOnnxOnlineStream *stream =
SherpaOnnxCreateOnlineStream(recognizer);
return Napi::External<SherpaOnnxOnlineStream>::New(
env, const_cast<SherpaOnnxOnlineStream *>(stream),
[](Napi::Env env, SherpaOnnxOnlineStream *stream) {
SherpaOnnxDestroyOnlineStream(stream);
});
}
static void AcceptWaveformWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOnlineStream *stream =
info[0].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return;
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("samples")) {
Napi::TypeError::New(env, "The argument object should have a field samples")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("samples").IsTypedArray()) {
Napi::TypeError::New(env, "The object['samples'] should be a typed array")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Has("sampleRate")) {
Napi::TypeError::New(env,
"The argument object should have a field sampleRate")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("sampleRate").IsNumber()) {
Napi::TypeError::New(env, "The object['samples'] should be a number")
.ThrowAsJavaScriptException();
return;
}
Napi::Float32Array samples = obj.Get("samples").As<Napi::Float32Array>();
int32_t sample_rate = obj.Get("sampleRate").As<Napi::Number>().Int32Value();
#if __OHOS__
SherpaOnnxOnlineStreamAcceptWaveform(stream, sample_rate, samples.Data(),
samples.ElementLength() / sizeof(float));
#else
SherpaOnnxOnlineStreamAcceptWaveform(stream, sample_rate, samples.Data(),
samples.ElementLength());
#endif
}
static Napi::Boolean IsOnlineStreamReadyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an online recognizer pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
int32_t is_ready = SherpaOnnxIsOnlineStreamReady(recognizer, stream);
return Napi::Boolean::New(env, is_ready);
}
static void DecodeOnlineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an online recognizer pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
SherpaOnnxDecodeOnlineStream(recognizer, stream);
}
static Napi::String GetOnlineStreamResultAsJsonWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an online recognizer pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
const char *json = SherpaOnnxGetOnlineStreamResultAsJson(recognizer, stream);
Napi::String s = Napi::String::New(env, json);
SherpaOnnxDestroyOnlineStreamResultJson(json);
return s;
}
static void InputFinishedWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOnlineStream *stream =
info[0].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
SherpaOnnxOnlineStreamInputFinished(stream);
}
static void ResetOnlineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an online recognizer pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
SherpaOnnxOnlineStreamReset(recognizer, stream);
}
static Napi::Boolean IsEndpointWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an online recognizer pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOnlineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOnlineRecognizer>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
int32_t is_endpoint = SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream);
return Napi::Boolean::New(env, is_endpoint);
}
static Napi::External<SherpaOnnxDisplay> CreateDisplayWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsNumber()) {
Napi::TypeError::New(env, "Expect a number as the argument")
.ThrowAsJavaScriptException();
return {};
}
int32_t max_word_per_line = info[0].As<Napi::Number>().Int32Value();
const SherpaOnnxDisplay *display = SherpaOnnxCreateDisplay(max_word_per_line);
return Napi::External<SherpaOnnxDisplay>::New(
env, const_cast<SherpaOnnxDisplay *>(display),
[](Napi::Env env, SherpaOnnxDisplay *display) {
SherpaOnnxDestroyDisplay(display);
});
}
static void PrintWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 3) {
std::ostringstream os;
os << "Expect only 3 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsNumber()) {
Napi::TypeError::New(env, "Argument 1 should be a number.")
.ThrowAsJavaScriptException();
return;
}
if (!info[2].IsString()) {
Napi::TypeError::New(env, "Argument 2 should be a string.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxDisplay *display =
info[0].As<Napi::External<SherpaOnnxDisplay>>().Data();
int32_t idx = info[1].As<Napi::Number>().Int32Value();
Napi::String text = info[2].As<Napi::String>();
std::string s = text.Utf8Value();
SherpaOnnxPrint(display, idx, s.c_str());
}
void InitStreamingAsr(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOnlineRecognizer"),
Napi::Function::New(env, CreateOnlineRecognizerWrapper));
exports.Set(Napi::String::New(env, "createOnlineStream"),
Napi::Function::New(env, CreateOnlineStreamWrapper));
exports.Set(Napi::String::New(env, "acceptWaveformOnline"),
Napi::Function::New(env, AcceptWaveformWrapper));
exports.Set(Napi::String::New(env, "isOnlineStreamReady"),
Napi::Function::New(env, IsOnlineStreamReadyWrapper));
exports.Set(Napi::String::New(env, "decodeOnlineStream"),
Napi::Function::New(env, DecodeOnlineStreamWrapper));
exports.Set(Napi::String::New(env, "getOnlineStreamResultAsJson"),
Napi::Function::New(env, GetOnlineStreamResultAsJsonWrapper));
exports.Set(Napi::String::New(env, "inputFinished"),
Napi::Function::New(env, InputFinishedWrapper));
exports.Set(Napi::String::New(env, "reset"),
Napi::Function::New(env, ResetOnlineStreamWrapper));
exports.Set(Napi::String::New(env, "isEndpoint"),
Napi::Function::New(env, IsEndpointWrapper));
exports.Set(Napi::String::New(env, "createDisplay"),
Napi::Function::New(env, CreateDisplayWrapper));
exports.Set(Napi::String::New(env, "print"),
Napi::Function::New(env, PrintWrapper));
}

View File

@@ -0,0 +1,35 @@
export const readWave: (filename: string, enableExternalBuffer: boolean = true) => {samples: Float32Array, sampleRate: number};
export const readWaveFromBinary: (data: Uint8Array, enableExternalBuffer: boolean = true) => {samples: Float32Array, sampleRate: number};
export const createCircularBuffer: (capacity: number) => object;
export const circularBufferPush: (handle: object, samples: Float32Array) => void;
export const circularBufferGet: (handle: object, index: number, n: number, enableExternalBuffer: boolean = true) => Float32Array;
export const circularBufferPop: (handle: object, n: number) => void;
export const circularBufferSize: (handle: object) => number;
export const circularBufferHead: (handle: object) => number;
export const circularBufferReset: (handle: object) => void;
export const createVoiceActivityDetector: (config: object, bufferSizeInSeconds: number, mgr?: object) => object;
export const voiceActivityDetectorAcceptWaveform: (handle: object, samples: Float32Array) => void;
export const voiceActivityDetectorIsEmpty: (handle: object) => boolean;
export const voiceActivityDetectorIsDetected: (handle: object) => boolean;
export const voiceActivityDetectorPop: (handle: object) => void;
export const voiceActivityDetectorClear: (handle: object) => void;
export const voiceActivityDetectorFront: (handle: object, enableExternalBuffer: boolean = true) => {samples: Float32Array, start: number};
export const voiceActivityDetectorReset: (handle: object) => void;
export const voiceActivityDetectorFlush: (handle: object) => void;
export const createOfflineRecognizer: (config: object, mgr?: object) => object;
export const createOfflineStream: (handle: object) => object;
export const acceptWaveformOffline: (handle: object, audio: object) => void;
export const decodeOfflineStream: (handle: object, streamHandle: object) => void;
export const getOfflineStreamResultAsJson: (streamHandle: object) => string;
export const createOnlineRecognizer: (config: object, mgr?: object) => object;
export const createOnlineStream: (handle: object) => object;
export const acceptWaveformOnline: (handle: object, audio: object) => void;
export const inputFinished: (streamHandle: object) => void;
export const isOnlineStreamReady: (handle: object, streamHandle: object) => boolean;
export const decodeOnlineStream: (handle: object, streamHandle: object) => void;
export const isEndpoint: (handle: object, streamHandle: object) => boolean;
export const reset: (handle: object, streamHandle: object) => void;
export const getOnlineStreamResultAsJson: (handle: object, streamHandle: object) => string;

View File

@@ -0,0 +1,6 @@
{
"name": "libsherpa_onnx.so",
"types": "./Index.d.ts",
"version": "1.0.0",
"description": "Please describe the basic information."
}

View File

@@ -0,0 +1,700 @@
// scripts/node-addon-api/src/vad.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static Napi::External<SherpaOnnxCircularBuffer> CreateCircularBufferWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsNumber()) {
Napi::TypeError::New(env, "You should pass an integer as the argument.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxCircularBuffer *buf =
SherpaOnnxCreateCircularBuffer(info[0].As<Napi::Number>().Int32Value());
return Napi::External<SherpaOnnxCircularBuffer>::New(
env, buf, [](Napi::Env env, SherpaOnnxCircularBuffer *p) {
SherpaOnnxDestroyCircularBuffer(p);
});
}
static void CircularBufferPushWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
if (!info[1].IsTypedArray()) {
Napi::TypeError::New(env, "Argument 1 should be a Float32Array.")
.ThrowAsJavaScriptException();
return;
}
Napi::Float32Array data = info[1].As<Napi::Float32Array>();
#if __OHOS__
// Note(fangjun): Normally, we don't need to divied it by sizeof(float).
// However, data.ElementLength() here returns number of bytes, not number of elements.
SherpaOnnxCircularBufferPush(buf, data.Data(), data.ElementLength() / sizeof(float));
#else
SherpaOnnxCircularBufferPush(buf, data.Data(), data.ElementLength());
#endif
}
// see https://github.com/nodejs/node-addon-api/blob/main/doc/typed_array.md
// https://github.com/nodejs/node-addon-examples/blob/main/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/typed_array_to_native.cc
static Napi::Float32Array CircularBufferGetWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 3 && info.Length() != 4) {
std::ostringstream os;
os << "Expect only 3 or 4 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
if (!info[1].IsNumber()) {
Napi::TypeError::New(env, "Argument 1 should be an integer (startIndex).")
.ThrowAsJavaScriptException();
return {};
}
if (!info[2].IsNumber()) {
Napi::TypeError::New(env, "Argument 2 should be an integer (n).")
.ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (info.Length() == 4) {
if (info[3].IsBoolean()) {
enable_external_buffer = info[3].As<Napi::Boolean>().Value();
} else {
Napi::TypeError::New(env, "Argument 3 should be a boolean.")
.ThrowAsJavaScriptException();
}
}
int32_t start_index = info[1].As<Napi::Number>().Int32Value();
int32_t n = info[2].As<Napi::Number>().Int32Value();
const float *data = SherpaOnnxCircularBufferGet(buf, start_index, n);
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(data), sizeof(float) * n,
[](Napi::Env /*env*/, void *p) {
SherpaOnnxCircularBufferFree(reinterpret_cast<const float *>(p));
});
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, n, arrayBuffer, 0);
return float32Array;
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * n);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, n, arrayBuffer, 0);
std::copy(data, data + n, float32Array.Data());
SherpaOnnxCircularBufferFree(data);
return float32Array;
}
}
static void CircularBufferPopWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
if (!info[1].IsNumber()) {
Napi::TypeError::New(env, "Argument 1 should be an integer (n).")
.ThrowAsJavaScriptException();
return;
}
int32_t n = info[1].As<Napi::Number>().Int32Value();
SherpaOnnxCircularBufferPop(buf, n);
}
static Napi::Number CircularBufferSizeWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
int32_t size = SherpaOnnxCircularBufferSize(buf);
return Napi::Number::New(env, size);
}
static Napi::Number CircularBufferHeadWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
int32_t size = SherpaOnnxCircularBufferHead(buf);
return Napi::Number::New(env, size);
}
static void CircularBufferResetWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an CircularBuffer pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxCircularBuffer *buf =
info[0].As<Napi::External<SherpaOnnxCircularBuffer>>().Data();
SherpaOnnxCircularBufferReset(buf);
}
static SherpaOnnxSileroVadModelConfig GetSileroVadConfig(
const Napi::Object &obj) {
SherpaOnnxSileroVadModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("sileroVad") || !obj.Get("sileroVad").IsObject()) {
return c;
}
Napi::Object o = obj.Get("sileroVad").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_silence_duration, minSilenceDuration);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_speech_duration, minSpeechDuration);
SHERPA_ONNX_ASSIGN_ATTR_INT32(window_size, windowSize);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(max_speech_duration, maxSpeechDuration);
return c;
}
static Napi::External<SherpaOnnxVoiceActivityDetector>
CreateVoiceActivityDetectorWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
#if __OHOS__
// the last argument is a NativeResourceManager
if (info.Length() != 3) {
std::ostringstream os;
os << "Expect only 3 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#else
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
#endif
if (!info[0].IsObject()) {
Napi::TypeError::New(env,
"You should pass an object as the first argument.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsNumber()) {
Napi::TypeError::New(env,
"You should pass an integer as the second argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxVadModelConfig c;
memset(&c, 0, sizeof(c));
c.silero_vad = GetSileroVadConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(sample_rate, sampleRate);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
float buffer_size_in_seconds = info[1].As<Napi::Number>().FloatValue();
#if __OHOS__
std::unique_ptr<NativeResourceManager, decltype(&OH_ResourceManager_ReleaseNativeResourceManager)> mgr(OH_ResourceManager_InitNativeResourceManager(env, info[2]), &OH_ResourceManager_ReleaseNativeResourceManager);
SherpaOnnxVoiceActivityDetector *vad =
SherpaOnnxCreateVoiceActivityDetectorOHOS(&c, buffer_size_in_seconds, mgr.get());
#else
SherpaOnnxVoiceActivityDetector *vad =
SherpaOnnxCreateVoiceActivityDetector(&c, buffer_size_in_seconds);
#endif
if (c.silero_vad.model) {
delete[] c.silero_vad.model;
}
if (c.provider) {
delete[] c.provider;
}
return Napi::External<SherpaOnnxVoiceActivityDetector>::New(
env, vad, [](Napi::Env env, SherpaOnnxVoiceActivityDetector *p) {
SherpaOnnxDestroyVoiceActivityDetector(p);
});
}
static void VoiceActivityDetectorAcceptWaveformWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
if (!info[1].IsTypedArray()) {
Napi::TypeError::New(
env, "Argument 1 should be a Float32Array containing samples")
.ThrowAsJavaScriptException();
return;
}
Napi::Float32Array samples = info[1].As<Napi::Float32Array>();
#if __OHOS__
// Note(fangjun): For unknown reasons, we need to use `/sizeof(float)` here for Huawei
SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples.Data(),
samples.ElementLength() / sizeof(float));
#else
SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples.Data(),
samples.ElementLength());
#endif
}
static Napi::Boolean VoiceActivityDetectorEmptyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
int32_t is_empty = SherpaOnnxVoiceActivityDetectorEmpty(vad);
return Napi::Boolean::New(env, is_empty);
}
static Napi::Boolean VoiceActivityDetectorDetectedWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
int32_t is_detected = SherpaOnnxVoiceActivityDetectorDetected(vad);
return Napi::Boolean::New(env, is_detected);
}
static void VoiceActivityDetectorPopWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
SherpaOnnxVoiceActivityDetectorPop(vad);
}
static void VoiceActivityDetectorClearWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
SherpaOnnxVoiceActivityDetectorClear(vad);
}
static Napi::Object VoiceActivityDetectorFrontWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1 && info.Length() != 2) {
std::ostringstream os;
os << "Expect only 1 or 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (info.Length() == 2) {
if (info[1].IsBoolean()) {
enable_external_buffer = info[1].As<Napi::Boolean>().Value();
} else {
Napi::TypeError::New(env, "Argument 1 should be a boolean.")
.ThrowAsJavaScriptException();
}
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
const SherpaOnnxSpeechSegment *segment =
SherpaOnnxVoiceActivityDetectorFront(vad);
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(segment->samples), sizeof(float) * segment->n,
[](Napi::Env /*env*/, void * /*data*/,
const SherpaOnnxSpeechSegment *hint) {
SherpaOnnxDestroySpeechSegment(hint);
},
segment);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, segment->n, arrayBuffer, 0);
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "start"), segment->start);
obj.Set(Napi::String::New(env, "samples"), float32Array);
return obj;
} else {
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * segment->n);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, segment->n, arrayBuffer, 0);
std::copy(segment->samples, segment->samples + segment->n,
float32Array.Data());
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "start"), segment->start);
obj.Set(Napi::String::New(env, "samples"), float32Array);
SherpaOnnxDestroySpeechSegment(segment);
return obj;
}
}
static void VoiceActivityDetectorResetWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
SherpaOnnxVoiceActivityDetectorReset(vad);
}
static void VoiceActivityDetectorFlushWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a VAD pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxVoiceActivityDetector *vad =
info[0].As<Napi::External<SherpaOnnxVoiceActivityDetector>>().Data();
SherpaOnnxVoiceActivityDetectorFlush(vad);
}
void InitVad(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createCircularBuffer"),
Napi::Function::New(env, CreateCircularBufferWrapper));
exports.Set(Napi::String::New(env, "circularBufferPush"),
Napi::Function::New(env, CircularBufferPushWrapper));
exports.Set(Napi::String::New(env, "circularBufferGet"),
Napi::Function::New(env, CircularBufferGetWrapper));
exports.Set(Napi::String::New(env, "circularBufferPop"),
Napi::Function::New(env, CircularBufferPopWrapper));
exports.Set(Napi::String::New(env, "circularBufferSize"),
Napi::Function::New(env, CircularBufferSizeWrapper));
exports.Set(Napi::String::New(env, "circularBufferHead"),
Napi::Function::New(env, CircularBufferHeadWrapper));
exports.Set(Napi::String::New(env, "circularBufferReset"),
Napi::Function::New(env, CircularBufferResetWrapper));
exports.Set(Napi::String::New(env, "createVoiceActivityDetector"),
Napi::Function::New(env, CreateVoiceActivityDetectorWrapper));
exports.Set(
Napi::String::New(env, "voiceActivityDetectorAcceptWaveform"),
Napi::Function::New(env, VoiceActivityDetectorAcceptWaveformWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorIsEmpty"),
Napi::Function::New(env, VoiceActivityDetectorEmptyWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorIsDetected"),
Napi::Function::New(env, VoiceActivityDetectorDetectedWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorPop"),
Napi::Function::New(env, VoiceActivityDetectorPopWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorClear"),
Napi::Function::New(env, VoiceActivityDetectorClearWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorFront"),
Napi::Function::New(env, VoiceActivityDetectorFrontWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorReset"),
Napi::Function::New(env, VoiceActivityDetectorResetWrapper));
exports.Set(Napi::String::New(env, "voiceActivityDetectorFlush"),
Napi::Function::New(env, VoiceActivityDetectorFlushWrapper));
}

View File

@@ -0,0 +1,171 @@
// scripts/node-addon-api/src/wave-reader.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static Napi::Object ReadWaveWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() > 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Argument 0 should be a string")
.ThrowAsJavaScriptException();
return {};
}
std::string filename = info[0].As<Napi::String>().Utf8Value();
bool enable_external_buffer = true;
if (info.Length() == 2) {
if (info[1].IsBoolean()) {
enable_external_buffer = info[1].As<Napi::Boolean>().Value();
} else {
Napi::TypeError::New(env, "Argument 1 should be a boolean")
.ThrowAsJavaScriptException();
return {};
}
}
const SherpaOnnxWave *wave = SherpaOnnxReadWave(filename.c_str());
if (!wave) {
std::ostringstream os;
os << "Failed to read '" << filename << "'";
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(wave->samples),
sizeof(float) * wave->num_samples,
[](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) {
SherpaOnnxFreeWave(hint);
},
wave);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "samples"), float32Array);
obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
return obj;
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
std::copy(wave->samples, wave->samples + wave->num_samples,
float32Array.Data());
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "samples"), float32Array);
obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
SherpaOnnxFreeWave(wave);
return obj;
}
}
static Napi::Object ReadWaveFromBinaryWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() > 2) {
std::ostringstream os;
os << "Expect only 1 or 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsTypedArray()) {
Napi::TypeError::New(env, "Argument 0 should be a float32 array")
.ThrowAsJavaScriptException();
return {};
}
Napi::Uint8Array data = info[0].As<Napi::Uint8Array>();
int32_t n = data.ElementLength();
const SherpaOnnxWave *wave = SherpaOnnxReadWaveFromBinaryData(reinterpret_cast<const char*>(data.Data()), n);
if (!wave) {
std::ostringstream os;
os << "Failed to read wave";
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (info.Length() == 2) {
if (info[1].IsBoolean()) {
enable_external_buffer = info[1].As<Napi::Boolean>().Value();
} else {
Napi::TypeError::New(env, "Argument 1 should be a boolean")
.ThrowAsJavaScriptException();
return {};
}
}
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(wave->samples),
sizeof(float) * wave->num_samples,
[](Napi::Env /*env*/, void * /*data*/, const SherpaOnnxWave *hint) {
SherpaOnnxFreeWave(hint);
},
wave);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "samples"), float32Array);
obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
return obj;
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * wave->num_samples);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, wave->num_samples, arrayBuffer, 0);
std::copy(wave->samples, wave->samples + wave->num_samples,
float32Array.Data());
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "samples"), float32Array);
obj.Set(Napi::String::New(env, "sampleRate"), wave->sample_rate);
SherpaOnnxFreeWave(wave);
return obj;
}
}
void InitWaveReader(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "readWave"),
Napi::Function::New(env, ReadWaveWrapper));
exports.Set(Napi::String::New(env, "readWaveFromBinary"),
Napi::Function::New(env, ReadWaveFromBinaryWrapper));
}

View File

@@ -0,0 +1,81 @@
// scripts/node-addon-api/src/wave-writer.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// (filename, {samples: samples, sampleRate: sampleRate}
static Napi::Boolean WriteWaveWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Argument 0 should be a string")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("samples")) {
Napi::TypeError::New(env, "The argument object should have a field samples")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("samples").IsTypedArray()) {
Napi::TypeError::New(env, "The object['samples'] should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("sampleRate")) {
Napi::TypeError::New(env,
"The argument object should have a field sampleRate")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("sampleRate").IsNumber()) {
Napi::TypeError::New(env, "The object['samples'] should be a number")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array samples = obj.Get("samples").As<Napi::Float32Array>();
int32_t sample_rate = obj.Get("sampleRate").As<Napi::Number>().Int32Value();
int32_t ok =
SherpaOnnxWriteWave(samples.Data(), samples.ElementLength(), sample_rate,
info[0].As<Napi::String>().Utf8Value().c_str());
return Napi::Boolean::New(env, ok);
}
void InitWaveWriter(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "writeWave"),
Napi::Function::New(env, WriteWaveWrapper));
}

View File

@@ -0,0 +1,21 @@
import hilog from '@ohos.hilog';
import testNapi from 'libsherpa_onnx.so';
@Component
export struct MainPage {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
})
}
.width('100%')
}
.height('100%')
}
}

View File

@@ -0,0 +1,162 @@
import {
acceptWaveformOffline,
createOfflineRecognizer,
createOfflineStream,
decodeOfflineStream,
getOfflineStreamResultAsJson,
} from 'libsherpa_onnx.so';
export interface Samples {
samples: Float32Array;
sampleRate: number;
}
export class OfflineStream {
public handle: object;
constructor(handle: object) {
this.handle = handle;
}
// obj is {samples: samples, sampleRate: sampleRate}
// samples is a float32 array containing samples in the range [-1, 1]
// sampleRate is a number
acceptWaveform(obj: Samples) {
acceptWaveformOffline(this.handle, obj)
}
}
export class FeatureConfig {
public sampleRate: number = 16000;
public featureDim: number = 80;
}
export class OfflineTransducerModelConfig {
public encoder: string = '';
public decoder: string = '';
public joiner: string = '';
}
export class OfflineParaformerModelConfig {
public model: string = '';
}
export class OfflineNemoEncDecCtcModelConfig {
public model: string = '';
}
export class OfflineWhisperModelConfig {
public encoder: string = '';
public decoder: string = '';
public language: string = '';
public task: string = 'transcribe';
public tailPaddings: number = -1;
}
export class OfflineTdnnModelConfig {
public model: string = '';
}
export class OfflineSenseVoiceModelConfig {
public model: string = '';
public language: string = '';
public useItn: boolean = false;
}
export class OfflineMoonshineModelConfig {
public preprocessor: string = '';
public encoder: string = '';
public uncachedDecoder: string = '';
public cachedDecoder: string = '';
}
export class OfflineModelConfig {
public transducer: OfflineTransducerModelConfig = new OfflineTransducerModelConfig();
public paraformer: OfflineParaformerModelConfig = new OfflineParaformerModelConfig();
public nemoCtc: OfflineNemoEncDecCtcModelConfig = new OfflineNemoEncDecCtcModelConfig();
public whisper: OfflineWhisperModelConfig = new OfflineWhisperModelConfig();
public tdnn: OfflineTdnnModelConfig = new OfflineTdnnModelConfig();
public tokens: string = '';
public numThreads: number = 1;
public debug: boolean = false;
public provider: string = "cpu";
public modelType: string = '';
public modelingUnit: string = "cjkchar";
public bpeVocab: string = '';
public telespeechCtc: string = '';
public senseVoice: OfflineSenseVoiceModelConfig = new OfflineSenseVoiceModelConfig();
public moonshine: OfflineMoonshineModelConfig = new OfflineMoonshineModelConfig();
}
export class OfflineLMConfig {
public model: string = '';
public scale: number = 1.0;
}
export class OfflineRecognizerConfig {
public featConfig: FeatureConfig = new FeatureConfig();
public modelConfig: OfflineModelConfig = new OfflineModelConfig();
public lmConfig: OfflineLMConfig = new OfflineLMConfig();
public decodingMethod: string = "greedy_search";
public maxActivePaths: number = 4;
public hotwordsFfile: string = '';
public hotwordsScore: number = 1.5;
public ruleFsts: string = '';
public ruleFars: string = '';
public blankPenalty: number = 0;
}
export class OfflineRecognizerResult {
public text: string = '';
public timestamps: number[] = [];
public tokens: string[] = [];
public json = '';
public lang: string = '';
public emotion: string = '';
public event: string = '';
}
interface OfflineRecognizerResultJson {
text: string;
timestamps: number[];
tokens: string[];
lang: string;
emotion: string;
event: string;
}
export class OfflineRecognizer {
public handle: object;
public config: OfflineRecognizerConfig;
constructor(config: OfflineRecognizerConfig, mgr?: object) {
this.handle = createOfflineRecognizer(config, mgr);
this.config = config
}
createStream(): OfflineStream {
const handle: object = createOfflineStream(this.handle);
return new OfflineStream(handle);
}
decode(stream: OfflineStream) {
decodeOfflineStream(this.handle, stream.handle);
}
getResult(stream: OfflineStream): OfflineRecognizerResult {
const jsonStr: string = getOfflineStreamResultAsJson(stream.handle);
let o = JSON.parse(jsonStr) as OfflineRecognizerResultJson;
const r = new OfflineRecognizerResult()
r.text = o.text
r.timestamps = o.timestamps;
r.tokens = o.tokens;
r.json = jsonStr;
r.lang = o.lang;
r.emotion = o.emotion;
r.event = o.event;
return r;
}
}

View File

@@ -0,0 +1,141 @@
import {
acceptWaveformOnline,
createOnlineRecognizer,
createOnlineStream,
decodeOnlineStream,
getOnlineStreamResultAsJson,
inputFinished,
isEndpoint,
isOnlineStreamReady,
reset,
} from 'libsherpa_onnx.so';
import { FeatureConfig, Samples } from './NonStreamingAsr';
export class OnlineStream {
public handle: object;
constructor(handle: object) {
this.handle = handle;
}
// obj is {samples: samples, sampleRate: sampleRate}
// samples is a float32 array containing samples in the range [-1, 1]
// sampleRate is a number
acceptWaveform(obj: Samples) {
acceptWaveformOnline(this.handle, obj)
}
inputFinished() {
inputFinished(this.handle)
}
}
export class OnlineTransducerModelConfig {
public encoder: string = '';
public decoder: string = '';
public joiner: string = '';
}
export class OnlineParaformerModelConfig {
public encoder: string = '';
public decoder: string = '';
}
export class OnlineZipformer2CtcModelConfig {
public model: string = '';
}
export class OnlineModelConfig {
public transducer: OnlineTransducerModelConfig = new OnlineTransducerModelConfig();
public paraformer: OnlineParaformerModelConfig = new OnlineParaformerModelConfig();
public zipformer2_ctc: OnlineZipformer2CtcModelConfig = new OnlineZipformer2CtcModelConfig();
public tokens: string = '';
public numThreads: number = 1;
public provider: string = "cpu";
public debug: boolean = false;
public modelType: string = '';
public modelingUnit: string = "cjkchar";
public bpeVocab: string = '';
}
export class OnlineCtcFstDecoderConfig {
public graph: string = '';
public maxActive: number = 3000;
}
export class OnlineRecognizerConfig {
public featConfig: FeatureConfig = new FeatureConfig();
public modelConfig: OnlineModelConfig = new OnlineModelConfig();
public decodingMethod: string = "greedy_search";
public maxActivePaths: number = 4;
public enableEndpoint: boolean = false;
public rule1MinTrailingSilence: number = 2.4;
public rule2MinTrailingSilence: number = 1.2;
public rule3MinUtteranceLength: number = 20;
public hotwordsFile: string = '';
public hotwordsScore: number = 1.5;
public ctcFstDecoderConfig: OnlineCtcFstDecoderConfig = new OnlineCtcFstDecoderConfig();
public ruleFsts: string = '';
public ruleFars: string = '';
public blankPenalty: number = 0;
}
interface OnlineRecognizerResultJson {
text: string;
timestamps: number[];
tokens: string[];
}
export class OnlineRecognizerResult {
public text: string = '';
public tokens: string[] = [];
public timestamps: number[] = [];
public json: string = '';
}
export class OnlineRecognizer {
public handle: object;
public config: OnlineRecognizerConfig
constructor(config: OnlineRecognizerConfig, mgr?: object) {
this.handle = createOnlineRecognizer(config, mgr);
this.config = config
}
createStream(): OnlineStream {
const handle: object = createOnlineStream(this.handle);
return new OnlineStream(handle);
}
isReady(stream: OnlineStream): boolean {
return isOnlineStreamReady(this.handle, stream.handle);
}
decode(stream: OnlineStream) {
decodeOnlineStream(this.handle, stream.handle);
}
isEndpoint(stream: OnlineStream): boolean {
return isEndpoint(this.handle, stream.handle);
}
reset(stream: OnlineStream) {
reset(this.handle, stream.handle);
}
getResult(stream: OnlineStream): OnlineRecognizerResult {
const jsonStr: string =
getOnlineStreamResultAsJson(this.handle, stream.handle);
let o = JSON.parse(jsonStr) as OnlineRecognizerResultJson;
const r = new OnlineRecognizerResult()
r.text = o.text
r.timestamps = o.timestamps;
r.tokens = o.tokens;
r.json = jsonStr;
return r;
}
}

View File

@@ -0,0 +1,133 @@
import {
circularBufferGet,
circularBufferHead,
circularBufferPop,
circularBufferPush,
circularBufferReset,
circularBufferSize,
createCircularBuffer,
createVoiceActivityDetector,
voiceActivityDetectorAcceptWaveform,
voiceActivityDetectorClear,
voiceActivityDetectorFlush,
voiceActivityDetectorFront,
voiceActivityDetectorIsDetected,
voiceActivityDetectorIsEmpty,
voiceActivityDetectorPop,
voiceActivityDetectorReset,
} from 'libsherpa_onnx.so';
export class SileroVadConfig {
public model: string;
public threshold: number;
public minSpeechDuration: number;
public minSilenceDuration: number;
public windowSize: number;
public constructor(model: string, threshold: number, minSpeechDuration: number, minSilenceDuration: number,
windowSize: number) {
this.model = model;
this.threshold = threshold;
this.minSpeechDuration = minSpeechDuration;
this.minSilenceDuration = minSilenceDuration;
this.windowSize = windowSize;
}
}
export class VadConfig {
public sileroVad: SileroVadConfig;
public sampleRate: number;
public debug: boolean;
public numThreads: number;
public constructor(sileroVad: SileroVadConfig, sampleRate: number, debug: boolean, numThreads: number) {
this.sileroVad = sileroVad;
this.sampleRate = sampleRate;
this.debug = debug;
this.numThreads = numThreads;
}
}
export class CircularBuffer {
private handle: object;
constructor(capacity: number) {
this.handle = createCircularBuffer(capacity);
}
// samples is a float32 array
push(samples: Float32Array) {
console.log(`here samples: ${samples}`);
circularBufferPush(this.handle, samples);
}
// return a float32 array
get(startIndex: number, n: number, enableExternalBuffer: boolean = true): Float32Array {
return circularBufferGet(
this.handle, startIndex, n, enableExternalBuffer);
}
pop(n: number) {
circularBufferPop(this.handle, n);
}
size(): number {
return circularBufferSize(this.handle);
}
head(): number {
return circularBufferHead(this.handle);
}
reset() {
circularBufferReset(this.handle);
}
}
export interface SpeechSegment {
samples: Float32Array;
start: number;
}
export class Vad {
public config: VadConfig;
private handle: object;
constructor(config: VadConfig, bufferSizeInSeconds?: number, mgr?: object) {
this.handle =
createVoiceActivityDetector(config, bufferSizeInSeconds, mgr);
this.config = config;
}
acceptWaveform(samples: Float32Array): void {
voiceActivityDetectorAcceptWaveform(this.handle, samples);
}
isEmpty(): boolean {
return voiceActivityDetectorIsEmpty(this.handle);
}
isDetected(): boolean {
return voiceActivityDetectorIsDetected(this.handle);
}
pop(): void {
voiceActivityDetectorPop(this.handle);
}
clear(): void {
voiceActivityDetectorClear(this.handle);
}
front(enableExternalBuffer = true): SpeechSegment {
return voiceActivityDetectorFront(this.handle, enableExternalBuffer);
}
reset(): void {
voiceActivityDetectorReset(this.handle);
}
flush(): void {
voiceActivityDetectorFlush(this.handle);
}
}

View File

@@ -0,0 +1,11 @@
{
"module": {
"name": "sherpa_onnx",
"type": "har",
"deviceTypes": [
"default",
"tablet",
"2in1"
]
}
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "page_show",
"value": "page from package"
}
]
}

View File

@@ -0,0 +1,35 @@
import hilog from '@ohos.hilog';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
})
})
}

View File

@@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@@ -0,0 +1,13 @@
{
"module": {
"name": "sherpa_onnx_test",
"type": "feature",
"deviceTypes": [
"default",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false
}
}

View File

@@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View File

@@ -0,0 +1,33 @@
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}

View File

@@ -1,227 +0,0 @@
// scripts/node-addon-api/src/audio-tagging.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineZipformerAudioTaggingModelConfig
GetAudioTaggingZipformerModelConfig(Napi::Object obj) {
SherpaOnnxOfflineZipformerAudioTaggingModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("zipformer") || !obj.Get("zipformer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("zipformer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxAudioTaggingModelConfig GetAudioTaggingModelConfig(
Napi::Object obj) {
SherpaOnnxAudioTaggingModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("model") || !obj.Get("model").IsObject()) {
return c;
}
Napi::Object o = obj.Get("model").As<Napi::Object>();
c.zipformer = GetAudioTaggingZipformerModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(ced, ced);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static Napi::External<SherpaOnnxAudioTagging> CreateAudioTaggingWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "You should pass an object as the only argument.")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxAudioTaggingConfig c;
memset(&c, 0, sizeof(c));
c.model = GetAudioTaggingModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(labels, labels);
SHERPA_ONNX_ASSIGN_ATTR_INT32(top_k, topK);
const SherpaOnnxAudioTagging *at = SherpaOnnxCreateAudioTagging(&c);
if (c.model.zipformer.model) {
delete[] c.model.zipformer.model;
}
if (c.model.ced) {
delete[] c.model.ced;
}
if (c.model.provider) {
delete[] c.model.provider;
}
if (c.labels) {
delete[] c.labels;
}
if (!at) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxAudioTagging>::New(
env, const_cast<SherpaOnnxAudioTagging *>(at),
[](Napi::Env env, SherpaOnnxAudioTagging *at) {
SherpaOnnxDestroyAudioTagging(at);
});
}
static Napi::External<SherpaOnnxOfflineStream>
AudioTaggingCreateOfflineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an audio tagging pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxAudioTagging *at =
info[0].As<Napi::External<SherpaOnnxAudioTagging>>().Data();
const SherpaOnnxOfflineStream *stream =
SherpaOnnxAudioTaggingCreateOfflineStream(at);
return Napi::External<SherpaOnnxOfflineStream>::New(
env, const_cast<SherpaOnnxOfflineStream *>(stream),
[](Napi::Env env, SherpaOnnxOfflineStream *stream) {
SherpaOnnxDestroyOfflineStream(stream);
});
}
static Napi::Object AudioTaggingComputeWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 3) {
std::ostringstream os;
os << "Expect only 3 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an audio tagging pointer as the first argument")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(
env, "You should pass an offline stream pointer as the second argument")
.ThrowAsJavaScriptException();
return {};
}
if (!info[2].IsNumber()) {
Napi::TypeError::New(env,
"You should pass an integer as the third argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxAudioTagging *at =
info[0].As<Napi::External<SherpaOnnxAudioTagging>>().Data();
SherpaOnnxOfflineStream *stream =
info[1].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
int32_t top_k = info[2].As<Napi::Number>().Int32Value();
const SherpaOnnxAudioEvent *const *events =
SherpaOnnxAudioTaggingCompute(at, stream, top_k);
auto p = events;
int32_t k = 0;
while (p && *p) {
++k;
++p;
}
Napi::Array ans = Napi::Array::New(env, k);
for (uint32_t i = 0; i != k; ++i) {
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "name"),
Napi::String::New(env, events[i]->name));
obj.Set(Napi::String::New(env, "index"),
Napi::Number::New(env, events[i]->index));
obj.Set(Napi::String::New(env, "prob"),
Napi::Number::New(env, events[i]->prob));
ans[i] = obj;
}
SherpaOnnxAudioTaggingFreeResults(events);
return ans;
}
void InitAudioTagging(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createAudioTagging"),
Napi::Function::New(env, CreateAudioTaggingWrapper));
exports.Set(Napi::String::New(env, "audioTaggingCreateOfflineStream"),
Napi::Function::New(env, AudioTaggingCreateOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "audioTaggingCompute"),
Napi::Function::New(env, AudioTaggingComputeWrapper));
}

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/audio-tagging.cc

View File

@@ -1,266 +0,0 @@
// scripts/node-addon-api/src/keyword-spotting.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// defined ./streaming-asr.cc
SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj);
// defined ./streaming-asr.cc
SherpaOnnxOnlineModelConfig GetOnlineModelConfig(Napi::Object obj);
static Napi::External<SherpaOnnxKeywordSpotter> CreateKeywordSpotterWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxKeywordSpotterConfig c;
memset(&c, 0, sizeof(c));
c.feat_config = GetFeatureConfig(o);
c.model_config = GetOnlineModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_trailing_blanks, numTrailingBlanks);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_score, keywordsScore);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(keywords_threshold, keywordsThreshold);
SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_file, keywordsFile);
SHERPA_ONNX_ASSIGN_ATTR_STR(keywords_buf, keywordsBuf);
SHERPA_ONNX_ASSIGN_ATTR_INT32(keywords_buf_size, keywordsBufSize);
SherpaOnnxKeywordSpotter *kws = SherpaOnnxCreateKeywordSpotter(&c);
if (c.model_config.transducer.encoder) {
delete[] c.model_config.transducer.encoder;
}
if (c.model_config.transducer.decoder) {
delete[] c.model_config.transducer.decoder;
}
if (c.model_config.transducer.joiner) {
delete[] c.model_config.transducer.joiner;
}
if (c.model_config.paraformer.encoder) {
delete[] c.model_config.paraformer.encoder;
}
if (c.model_config.paraformer.decoder) {
delete[] c.model_config.paraformer.decoder;
}
if (c.model_config.zipformer2_ctc.model) {
delete[] c.model_config.zipformer2_ctc.model;
}
if (c.model_config.tokens) {
delete[] c.model_config.tokens;
}
if (c.model_config.provider) {
delete[] c.model_config.provider;
}
if (c.model_config.model_type) {
delete[] c.model_config.model_type;
}
if (c.keywords_file) {
delete[] c.keywords_file;
}
if (c.keywords_buf) {
delete[] c.keywords_buf;
}
if (!kws) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxKeywordSpotter>::New(
env, kws, [](Napi::Env env, SherpaOnnxKeywordSpotter *kws) {
SherpaOnnxDestroyKeywordSpotter(kws);
});
}
static Napi::External<SherpaOnnxOnlineStream> CreateKeywordStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "You should pass a keyword spotter pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream = SherpaOnnxCreateKeywordStream(kws);
return Napi::External<SherpaOnnxOnlineStream>::New(
env, stream, [](Napi::Env env, SherpaOnnxOnlineStream *stream) {
SherpaOnnxDestroyOnlineStream(stream);
});
}
static Napi::Boolean IsKeywordStreamReadyWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
int32_t is_ready = SherpaOnnxIsKeywordStreamReady(kws, stream);
return Napi::Boolean::New(env, is_ready);
}
static void DecodeKeywordStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
SherpaOnnxDecodeKeywordStream(kws, stream);
}
static Napi::String GetKeywordResultAsJsonWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be a keyword spotter pointer.")
.ThrowAsJavaScriptException();
return {};
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxKeywordSpotter *kws =
info[0].As<Napi::External<SherpaOnnxKeywordSpotter>>().Data();
SherpaOnnxOnlineStream *stream =
info[1].As<Napi::External<SherpaOnnxOnlineStream>>().Data();
const char *json = SherpaOnnxGetKeywordResultAsJson(kws, stream);
Napi::String s = Napi::String::New(env, json);
SherpaOnnxFreeKeywordResultJson(json);
return s;
}
void InitKeywordSpotting(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createKeywordSpotter"),
Napi::Function::New(env, CreateKeywordSpotterWrapper));
exports.Set(Napi::String::New(env, "createKeywordStream"),
Napi::Function::New(env, CreateKeywordStreamWrapper));
exports.Set(Napi::String::New(env, "isKeywordStreamReady"),
Napi::Function::New(env, IsKeywordStreamReadyWrapper));
exports.Set(Napi::String::New(env, "decodeKeywordStream"),
Napi::Function::New(env, DecodeKeywordStreamWrapper));
exports.Set(Napi::String::New(env, "getKeywordResultAsJson"),
Napi::Function::New(env, GetKeywordResultAsJsonWrapper));
}

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/keyword-spotting.cc

View File

@@ -1,51 +0,0 @@
// scripts/node-addon-api/src/macros.h
//
// Copyright (c) 2024 Xiaomi Corporation
#ifndef SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_
#define SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_
#include <algorithm>
#include <string>
#define SHERPA_ONNX_ASSIGN_ATTR_STR(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsString()) { \
Napi::String _str = o.Get(#js_name).As<Napi::String>(); \
std::string s = _str.Utf8Value(); \
char *p = new char[s.size() + 1]; \
std::copy(s.begin(), s.end(), p); \
p[s.size()] = 0; \
\
c.c_name = p; \
} else if (o.Has(#js_name) && o.Get(#js_name).IsTypedArray()) { \
Napi::Uint8Array _array = o.Get(#js_name).As<Napi::Uint8Array>(); \
char *p = new char[_array.ElementLength() + 1]; \
std::copy(_array.Data(), _array.Data() + _array.ElementLength(), p); \
p[_array.ElementLength()] = '\0'; \
\
c.c_name = p; \
} \
} while (0)
#define SHERPA_ONNX_ASSIGN_ATTR_INT32(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \
c.c_name = o.Get(#js_name).As<Napi::Number>().Int32Value(); \
} \
} while (0)
#define SHERPA_ONNX_ASSIGN_ATTR_FLOAT(c_name, js_name) \
do { \
if (o.Has(#js_name) && o.Get(#js_name).IsNumber()) { \
c.c_name = o.Get(#js_name).As<Napi::Number>().FloatValue(); \
} \
} while (0)
#define SHERPA_ONNX_DELETE_C_STR(p) \
do { \
if (p) { \
delete[] p; \
} \
} while (0)
#endif // SCRIPTS_NODE_ADDON_API_SRC_MACROS_H_

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/macros.h

View File

@@ -1,461 +0,0 @@
// scripts/node-addon-api/src/non-streaming-asr.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
// defined in ./streaming-asr.cc
SherpaOnnxFeatureConfig GetFeatureConfig(Napi::Object obj);
static SherpaOnnxOfflineTransducerModelConfig GetOfflineTransducerModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTransducerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("transducer") || !obj.Get("transducer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("transducer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(joiner, joiner);
return c;
}
static SherpaOnnxOfflineParaformerModelConfig GetOfflineParaformerModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineParaformerModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("paraformer") || !obj.Get("paraformer").IsObject()) {
return c;
}
Napi::Object o = obj.Get("paraformer").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineNemoEncDecCtcModelConfig GetOfflineNeMoCtcModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineNemoEncDecCtcModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("nemoCtc") || !obj.Get("nemoCtc").IsObject()) {
return c;
}
Napi::Object o = obj.Get("nemoCtc").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineWhisperModelConfig GetOfflineWhisperModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineWhisperModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("whisper") || !obj.Get("whisper").IsObject()) {
return c;
}
Napi::Object o = obj.Get("whisper").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoder, decoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(language, language);
SHERPA_ONNX_ASSIGN_ATTR_STR(task, task);
SHERPA_ONNX_ASSIGN_ATTR_INT32(tail_paddings, tailPaddings);
return c;
}
static SherpaOnnxOfflineMoonshineModelConfig GetOfflineMoonshineModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineMoonshineModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("moonshine") || !obj.Get("moonshine").IsObject()) {
return c;
}
Napi::Object o = obj.Get("moonshine").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(preprocessor, preprocessor);
SHERPA_ONNX_ASSIGN_ATTR_STR(encoder, encoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(uncached_decoder, uncachedDecoder);
SHERPA_ONNX_ASSIGN_ATTR_STR(cached_decoder, cachedDecoder);
return c;
}
static SherpaOnnxOfflineTdnnModelConfig GetOfflineTdnnModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTdnnModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("tdnn") || !obj.Get("tdnn").IsObject()) {
return c;
}
Napi::Object o = obj.Get("tdnn").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineSenseVoiceModelConfig GetOfflineSenseVoiceModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineSenseVoiceModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("senseVoice") || !obj.Get("senseVoice").IsObject()) {
return c;
}
Napi::Object o = obj.Get("senseVoice").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(language, language);
SHERPA_ONNX_ASSIGN_ATTR_INT32(use_itn, useInverseTextNormalization);
return c;
}
static SherpaOnnxOfflineModelConfig GetOfflineModelConfig(Napi::Object obj) {
SherpaOnnxOfflineModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("modelConfig") || !obj.Get("modelConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("modelConfig").As<Napi::Object>();
c.transducer = GetOfflineTransducerModelConfig(o);
c.paraformer = GetOfflineParaformerModelConfig(o);
c.nemo_ctc = GetOfflineNeMoCtcModelConfig(o);
c.whisper = GetOfflineWhisperModelConfig(o);
c.tdnn = GetOfflineTdnnModelConfig(o);
c.sense_voice = GetOfflineSenseVoiceModelConfig(o);
c.moonshine = GetOfflineMoonshineModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
SHERPA_ONNX_ASSIGN_ATTR_STR(model_type, modelType);
SHERPA_ONNX_ASSIGN_ATTR_STR(modeling_unit, modelingUnit);
SHERPA_ONNX_ASSIGN_ATTR_STR(bpe_vocab, bpeVocab);
SHERPA_ONNX_ASSIGN_ATTR_STR(telespeech_ctc, teleSpeechCtc);
return c;
}
static SherpaOnnxOfflineLMConfig GetOfflineLMConfig(Napi::Object obj) {
SherpaOnnxOfflineLMConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("lmConfig") || !obj.Get("lmConfig").IsObject()) {
return c;
}
Napi::Object o = obj.Get("lmConfig").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(scale, scale);
return c;
}
static Napi::External<SherpaOnnxOfflineRecognizer>
CreateOfflineRecognizerWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineRecognizerConfig c;
memset(&c, 0, sizeof(c));
c.feat_config = GetFeatureConfig(o);
c.model_config = GetOfflineModelConfig(o);
c.lm_config = GetOfflineLMConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(decoding_method, decodingMethod);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_active_paths, maxActivePaths);
SHERPA_ONNX_ASSIGN_ATTR_STR(hotwords_file, hotwordsFile);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(hotwords_score, hotwordsScore);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(blank_penalty, blankPenalty);
const SherpaOnnxOfflineRecognizer *recognizer =
SherpaOnnxCreateOfflineRecognizer(&c);
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.transducer.joiner);
SHERPA_ONNX_DELETE_C_STR(c.model_config.paraformer.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.nemo_ctc.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.language);
SHERPA_ONNX_DELETE_C_STR(c.model_config.whisper.task);
SHERPA_ONNX_DELETE_C_STR(c.model_config.tdnn.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.model);
SHERPA_ONNX_DELETE_C_STR(c.model_config.sense_voice.language);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.preprocessor);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.encoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.uncached_decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.moonshine.cached_decoder);
SHERPA_ONNX_DELETE_C_STR(c.model_config.tokens);
SHERPA_ONNX_DELETE_C_STR(c.model_config.provider);
SHERPA_ONNX_DELETE_C_STR(c.model_config.model_type);
SHERPA_ONNX_DELETE_C_STR(c.model_config.modeling_unit);
SHERPA_ONNX_DELETE_C_STR(c.model_config.bpe_vocab);
SHERPA_ONNX_DELETE_C_STR(c.model_config.telespeech_ctc);
SHERPA_ONNX_DELETE_C_STR(c.lm_config.model);
SHERPA_ONNX_DELETE_C_STR(c.decoding_method);
SHERPA_ONNX_DELETE_C_STR(c.hotwords_file);
SHERPA_ONNX_DELETE_C_STR(c.rule_fsts);
SHERPA_ONNX_DELETE_C_STR(c.rule_fars);
if (!recognizer) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineRecognizer>::New(
env, const_cast<SherpaOnnxOfflineRecognizer *>(recognizer),
[](Napi::Env env, SherpaOnnxOfflineRecognizer *recognizer) {
SherpaOnnxDestroyOfflineRecognizer(recognizer);
});
}
static Napi::External<SherpaOnnxOfflineStream> CreateOfflineStreamWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env,
"You should pass an offline recognizer pointer as the only argument")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOfflineRecognizer>>().Data();
const SherpaOnnxOfflineStream *stream =
SherpaOnnxCreateOfflineStream(recognizer);
return Napi::External<SherpaOnnxOfflineStream>::New(
env, const_cast<SherpaOnnxOfflineStream *>(stream),
[](Napi::Env env, SherpaOnnxOfflineStream *stream) {
SherpaOnnxDestroyOfflineStream(stream);
});
}
static void AcceptWaveformOfflineWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOfflineStream *stream =
info[0].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return;
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("samples")) {
Napi::TypeError::New(env, "The argument object should have a field samples")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("samples").IsTypedArray()) {
Napi::TypeError::New(env, "The object['samples'] should be a typed array")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Has("sampleRate")) {
Napi::TypeError::New(env,
"The argument object should have a field sampleRate")
.ThrowAsJavaScriptException();
return;
}
if (!obj.Get("sampleRate").IsNumber()) {
Napi::TypeError::New(env, "The object['samples'] should be a number")
.ThrowAsJavaScriptException();
return;
}
Napi::Float32Array samples = obj.Get("samples").As<Napi::Float32Array>();
int32_t sample_rate = obj.Get("sampleRate").As<Napi::Number>().Int32Value();
SherpaOnnxAcceptWaveformOffline(stream, sample_rate, samples.Data(),
samples.ElementLength());
}
static void DecodeOfflineStreamWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env,
"Argument 0 should be an offline recognizer pointer.")
.ThrowAsJavaScriptException();
return;
}
if (!info[1].IsExternal()) {
Napi::TypeError::New(env, "Argument 1 should be an offline stream pointer.")
.ThrowAsJavaScriptException();
return;
}
SherpaOnnxOfflineRecognizer *recognizer =
info[0].As<Napi::External<SherpaOnnxOfflineRecognizer>>().Data();
SherpaOnnxOfflineStream *stream =
info[1].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
SherpaOnnxDecodeOfflineStream(recognizer, stream);
}
static Napi::String GetOfflineStreamResultAsJsonWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an online stream pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineStream *stream =
info[0].As<Napi::External<SherpaOnnxOfflineStream>>().Data();
const char *json = SherpaOnnxGetOfflineStreamResultAsJson(stream);
Napi::String s = Napi::String::New(env, json);
SherpaOnnxDestroyOfflineStreamResultJson(json);
return s;
}
void InitNonStreamingAsr(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineRecognizer"),
Napi::Function::New(env, CreateOfflineRecognizerWrapper));
exports.Set(Napi::String::New(env, "createOfflineStream"),
Napi::Function::New(env, CreateOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "acceptWaveformOffline"),
Napi::Function::New(env, AcceptWaveformOfflineWrapper));
exports.Set(Napi::String::New(env, "decodeOfflineStream"),
Napi::Function::New(env, DecodeOfflineStreamWrapper));
exports.Set(Napi::String::New(env, "getOfflineStreamResultAsJson"),
Napi::Function::New(env, GetOfflineStreamResultAsJsonWrapper));
}

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-asr.cc

View File

@@ -1,310 +0,0 @@
// scripts/node-addon-api/src/non-streaming-speaker-diarization.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig
GetOfflineSpeakerSegmentationPyannoteModelConfig(Napi::Object obj) {
SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("pyannote") || !obj.Get("pyannote").IsObject()) {
return c;
}
Napi::Object o = obj.Get("pyannote").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
return c;
}
static SherpaOnnxOfflineSpeakerSegmentationModelConfig
GetOfflineSpeakerSegmentationModelConfig(Napi::Object obj) {
SherpaOnnxOfflineSpeakerSegmentationModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("segmentation") || !obj.Get("segmentation").IsObject()) {
return c;
}
Napi::Object o = obj.Get("segmentation").As<Napi::Object>();
c.pyannote = GetOfflineSpeakerSegmentationPyannoteModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static SherpaOnnxSpeakerEmbeddingExtractorConfig
GetSpeakerEmbeddingExtractorConfig(Napi::Object obj) {
SherpaOnnxSpeakerEmbeddingExtractorConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("embedding") || !obj.Get("embedding").IsObject()) {
return c;
}
Napi::Object o = obj.Get("embedding").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static SherpaOnnxFastClusteringConfig GetFastClusteringConfig(
Napi::Object obj) {
SherpaOnnxFastClusteringConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("clustering") || !obj.Get("clustering").IsObject()) {
return c;
}
Napi::Object o = obj.Get("clustering").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_clusters, numClusters);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(threshold, threshold);
return c;
}
static Napi::External<SherpaOnnxOfflineSpeakerDiarization>
CreateOfflineSpeakerDiarizationWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineSpeakerDiarizationConfig c;
memset(&c, 0, sizeof(c));
c.segmentation = GetOfflineSpeakerSegmentationModelConfig(o);
c.embedding = GetSpeakerEmbeddingExtractorConfig(o);
c.clustering = GetFastClusteringConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_on, minDurationOn);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(min_duration_off, minDurationOff);
const SherpaOnnxOfflineSpeakerDiarization *sd =
SherpaOnnxCreateOfflineSpeakerDiarization(&c);
if (c.segmentation.pyannote.model) {
delete[] c.segmentation.pyannote.model;
}
if (c.segmentation.provider) {
delete[] c.segmentation.provider;
}
if (c.embedding.model) {
delete[] c.embedding.model;
}
if (c.embedding.provider) {
delete[] c.embedding.provider;
}
if (!sd) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineSpeakerDiarization>::New(
env, const_cast<SherpaOnnxOfflineSpeakerDiarization *>(sd),
[](Napi::Env env, SherpaOnnxOfflineSpeakerDiarization *sd) {
SherpaOnnxDestroyOfflineSpeakerDiarization(sd);
});
}
static Napi::Number OfflineSpeakerDiarizationGetSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return {};
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
int32_t sample_rate = SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(sd);
return Napi::Number::New(env, sample_rate);
}
static Napi::Array OfflineSpeakerDiarizationProcessWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return {};
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
if (!info[1].IsTypedArray()) {
Napi::TypeError::New(env, "Argument 1 should be a typed array")
.ThrowAsJavaScriptException();
return {};
}
Napi::Float32Array samples = info[1].As<Napi::Float32Array>();
const SherpaOnnxOfflineSpeakerDiarizationResult *r =
SherpaOnnxOfflineSpeakerDiarizationProcess(sd, samples.Data(),
samples.ElementLength());
int32_t num_segments =
SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(r);
const SherpaOnnxOfflineSpeakerDiarizationSegment *segments =
SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(r);
Napi::Array ans = Napi::Array::New(env, num_segments);
for (int32_t i = 0; i != num_segments; ++i) {
Napi::Object obj = Napi::Object::New(env);
obj.Set(Napi::String::New(env, "start"), segments[i].start);
obj.Set(Napi::String::New(env, "end"), segments[i].end);
obj.Set(Napi::String::New(env, "speaker"), segments[i].speaker);
ans.Set(i, obj);
}
SherpaOnnxOfflineSpeakerDiarizationDestroySegment(segments);
SherpaOnnxOfflineSpeakerDiarizationDestroyResult(r);
return ans;
}
static void OfflineSpeakerDiarizationSetConfigWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 2 arguments. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return;
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(
env, "Argument 0 should be an offline speaker diarization pointer.")
.ThrowAsJavaScriptException();
return;
}
const SherpaOnnxOfflineSpeakerDiarization *sd =
info[0].As<Napi::External<SherpaOnnxOfflineSpeakerDiarization>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return;
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineSpeakerDiarizationConfig c;
memset(&c, 0, sizeof(c));
c.clustering = GetFastClusteringConfig(o);
SherpaOnnxOfflineSpeakerDiarizationSetConfig(sd, &c);
}
void InitNonStreamingSpeakerDiarization(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineSpeakerDiarization"),
Napi::Function::New(env, CreateOfflineSpeakerDiarizationWrapper));
exports.Set(
Napi::String::New(env, "getOfflineSpeakerDiarizationSampleRate"),
Napi::Function::New(env, OfflineSpeakerDiarizationGetSampleRateWrapper));
exports.Set(
Napi::String::New(env, "offlineSpeakerDiarizationProcess"),
Napi::Function::New(env, OfflineSpeakerDiarizationProcessWrapper));
exports.Set(
Napi::String::New(env, "offlineSpeakerDiarizationSetConfig"),
Napi::Function::New(env, OfflineSpeakerDiarizationSetConfigWrapper));
}

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-speaker-diarization.cc

View File

@@ -1,329 +0,0 @@
// scripts/node-addon-api/src/non-streaming-tts.cc
//
// Copyright (c) 2024 Xiaomi Corporation
#include <algorithm>
#include <sstream>
#include "macros.h" // NOLINT
#include "napi.h" // NOLINT
#include "sherpa-onnx/c-api/c-api.h"
static SherpaOnnxOfflineTtsVitsModelConfig GetOfflineTtsVitsModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTtsVitsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("vits") || !obj.Get("vits").IsObject()) {
return c;
}
Napi::Object o = obj.Get("vits").As<Napi::Object>();
SHERPA_ONNX_ASSIGN_ATTR_STR(model, model);
SHERPA_ONNX_ASSIGN_ATTR_STR(lexicon, lexicon);
SHERPA_ONNX_ASSIGN_ATTR_STR(tokens, tokens);
SHERPA_ONNX_ASSIGN_ATTR_STR(data_dir, dataDir);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale, noiseScale);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(noise_scale_w, noiseScaleW);
SHERPA_ONNX_ASSIGN_ATTR_FLOAT(length_scale, lengthScale);
SHERPA_ONNX_ASSIGN_ATTR_STR(dict_dir, dictDir);
return c;
}
static SherpaOnnxOfflineTtsModelConfig GetOfflineTtsModelConfig(
Napi::Object obj) {
SherpaOnnxOfflineTtsModelConfig c;
memset(&c, 0, sizeof(c));
if (!obj.Has("model") || !obj.Get("model").IsObject()) {
return c;
}
Napi::Object o = obj.Get("model").As<Napi::Object>();
c.vits = GetOfflineTtsVitsModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_INT32(num_threads, numThreads);
if (o.Has("debug") &&
(o.Get("debug").IsNumber() || o.Get("debug").IsBoolean())) {
if (o.Get("debug").IsBoolean()) {
c.debug = o.Get("debug").As<Napi::Boolean>().Value();
} else {
c.debug = o.Get("debug").As<Napi::Number>().Int32Value();
}
}
SHERPA_ONNX_ASSIGN_ATTR_STR(provider, provider);
return c;
}
static Napi::External<SherpaOnnxOfflineTts> CreateOfflineTtsWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsObject()) {
Napi::TypeError::New(env, "Expect an object as the argument")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object o = info[0].As<Napi::Object>();
SherpaOnnxOfflineTtsConfig c;
memset(&c, 0, sizeof(c));
c.model = GetOfflineTtsModelConfig(o);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fsts, ruleFsts);
SHERPA_ONNX_ASSIGN_ATTR_INT32(max_num_sentences, maxNumSentences);
SHERPA_ONNX_ASSIGN_ATTR_STR(rule_fars, ruleFars);
SherpaOnnxOfflineTts *tts = SherpaOnnxCreateOfflineTts(&c);
if (c.model.vits.model) {
delete[] c.model.vits.model;
}
if (c.model.vits.lexicon) {
delete[] c.model.vits.lexicon;
}
if (c.model.vits.tokens) {
delete[] c.model.vits.tokens;
}
if (c.model.vits.data_dir) {
delete[] c.model.vits.data_dir;
}
if (c.model.vits.dict_dir) {
delete[] c.model.vits.dict_dir;
}
if (c.model.provider) {
delete[] c.model.provider;
}
if (c.rule_fsts) {
delete[] c.rule_fsts;
}
if (c.rule_fars) {
delete[] c.rule_fars;
}
if (!tts) {
Napi::TypeError::New(env, "Please check your config!")
.ThrowAsJavaScriptException();
return {};
}
return Napi::External<SherpaOnnxOfflineTts>::New(
env, tts, [](Napi::Env env, SherpaOnnxOfflineTts *tts) {
SherpaOnnxDestroyOfflineTts(tts);
});
}
static Napi::Number OfflineTtsSampleRateWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
int32_t sample_rate = SherpaOnnxOfflineTtsSampleRate(tts);
return Napi::Number::New(env, sample_rate);
}
static Napi::Number OfflineTtsNumSpeakersWrapper(
const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 1) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
int32_t num_speakers = SherpaOnnxOfflineTtsNumSpeakers(tts);
return Napi::Number::New(env, num_speakers);
}
static Napi::Object OfflineTtsGenerateWrapper(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
if (info.Length() != 2) {
std::ostringstream os;
os << "Expect only 1 argument. Given: " << info.Length();
Napi::TypeError::New(env, os.str()).ThrowAsJavaScriptException();
return {};
}
if (!info[0].IsExternal()) {
Napi::TypeError::New(env, "Argument 0 should be an offline tts pointer.")
.ThrowAsJavaScriptException();
return {};
}
SherpaOnnxOfflineTts *tts =
info[0].As<Napi::External<SherpaOnnxOfflineTts>>().Data();
if (!info[1].IsObject()) {
Napi::TypeError::New(env, "Argument 1 should be an object")
.ThrowAsJavaScriptException();
return {};
}
Napi::Object obj = info[1].As<Napi::Object>();
if (!obj.Has("text")) {
Napi::TypeError::New(env, "The argument object should have a field text")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("text").IsString()) {
Napi::TypeError::New(env, "The object['text'] should be a string")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("sid")) {
Napi::TypeError::New(env, "The argument object should have a field sid")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("sid").IsNumber()) {
Napi::TypeError::New(env, "The object['sid'] should be a number")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Has("speed")) {
Napi::TypeError::New(env, "The argument object should have a field speed")
.ThrowAsJavaScriptException();
return {};
}
if (!obj.Get("speed").IsNumber()) {
Napi::TypeError::New(env, "The object['speed'] should be a number")
.ThrowAsJavaScriptException();
return {};
}
bool enable_external_buffer = true;
if (obj.Has("enableExternalBuffer") &&
obj.Get("enableExternalBuffer").IsBoolean()) {
enable_external_buffer =
obj.Get("enableExternalBuffer").As<Napi::Boolean>().Value();
}
Napi::String _text = obj.Get("text").As<Napi::String>();
std::string text = _text.Utf8Value();
int32_t sid = obj.Get("sid").As<Napi::Number>().Int32Value();
float speed = obj.Get("speed").As<Napi::Number>().FloatValue();
const SherpaOnnxGeneratedAudio *audio =
SherpaOnnxOfflineTtsGenerate(tts, text.c_str(), sid, speed);
if (enable_external_buffer) {
Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(
env, const_cast<float *>(audio->samples), sizeof(float) * audio->n,
[](Napi::Env /*env*/, void * /*data*/,
const SherpaOnnxGeneratedAudio *hint) {
SherpaOnnxDestroyOfflineTtsGeneratedAudio(hint);
},
audio);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
Napi::Object ans = Napi::Object::New(env);
ans.Set(Napi::String::New(env, "samples"), float32Array);
ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
return ans;
} else {
// don't use external buffer
Napi::ArrayBuffer arrayBuffer =
Napi::ArrayBuffer::New(env, sizeof(float) * audio->n);
Napi::Float32Array float32Array =
Napi::Float32Array::New(env, audio->n, arrayBuffer, 0);
std::copy(audio->samples, audio->samples + audio->n, float32Array.Data());
Napi::Object ans = Napi::Object::New(env);
ans.Set(Napi::String::New(env, "samples"), float32Array);
ans.Set(Napi::String::New(env, "sampleRate"), audio->sample_rate);
SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio);
return ans;
}
}
void InitNonStreamingTts(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "createOfflineTts"),
Napi::Function::New(env, CreateOfflineTtsWrapper));
exports.Set(Napi::String::New(env, "getOfflineTtsSampleRate"),
Napi::Function::New(env, OfflineTtsSampleRateWrapper));
exports.Set(Napi::String::New(env, "getOfflineTtsNumSpeakers"),
Napi::Function::New(env, OfflineTtsNumSpeakersWrapper));
exports.Set(Napi::String::New(env, "offlineTtsGenerate"),
Napi::Function::New(env, OfflineTtsGenerateWrapper));
}

View File

@@ -0,0 +1 @@
../../../harmony-os/SherpaOnnxHar/sherpa_onnx/src/main/cpp/non-streaming-tts.cc

Some files were not shown because too many files have changed in this diff Show More