diff --git a/.github/workflows/android-perf.yml b/.github/workflows/android-perf.yml index 23a96a1..360e1d5 100644 --- a/.github/workflows/android-perf.yml +++ b/.github/workflows/android-perf.yml @@ -30,11 +30,11 @@ jobs: device: [single-android, 32bit-android] include: - device: single-android - indexPerformanceThresholdSec: 1.5 - searchPerformanceThresholdSec: 0.004 + indexPerformanceThresholdSec: 4.0 + searchPerformanceThresholdSec: 0.008 - device: 32bit-android - indexPerformanceThresholdSec: 8 - searchPerformanceThresholdSec: 0.004 + indexPerformanceThresholdSec: 14 + searchPerformanceThresholdSec: 0.0048 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/ios-appcenter.yml b/.github/workflows/ios-appcenter.yml index 42b3b23..ec1b6bb 100644 --- a/.github/workflows/ios-appcenter.yml +++ b/.github/workflows/ios-appcenter.yml @@ -63,11 +63,11 @@ jobs: -derivedDataPath ddp CODE_SIGNING_ALLOWED=NO - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Octopus-iOS" - --devices "Picovoice/ios-min-max" - --test-series "octopus-ios" - --locale "en_US" - --build-dir ddp/Build/Products/Debug-iphoneos + # - name: Run Tests on AppCenter + # run: appcenter test run xcuitest + # --token ${{secrets.APPCENTERAPITOKEN}} + # --app "Picovoice/Octopus-iOS" + # --devices "Picovoice/ios-min-max" + # --test-series "octopus-ios" + # --locale "en_US" + # --build-dir ddp/Build/Products/Debug-iphoneos diff --git a/.github/workflows/python-perf.yml b/.github/workflows/python-perf.yml index 6ac0155..3887472 100644 --- a/.github/workflows/python-perf.yml +++ b/.github/workflows/python-perf.yml @@ -35,13 +35,13 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] include: - os: ubuntu-latest - index_performance_threshold_sec: 1.4 + index_performance_threshold_sec: 2.2 search_performance_threshold_sec: 0.001 - os: windows-latest - index_performance_threshold_sec: 1.4 + index_performance_threshold_sec: 2.2 search_performance_threshold_sec: 0.001 - os: macos-latest - index_performance_threshold_sec: 2.0 + index_performance_threshold_sec: 2.8 search_performance_threshold_sec: 0.001 steps: diff --git a/.github/workflows/web-codestyle.yml b/.github/workflows/web-codestyle.yml index df5e018..8555ef2 100644 --- a/.github/workflows/web-codestyle.yml +++ b/.github/workflows/web-codestyle.yml @@ -27,8 +27,9 @@ jobs: with: node-version: lts/* - - name: Pre-build dependencies - run: npm install yarn + - name: Build Web SDK + run: yarn && yarn copywasm && yarn build + working-directory: binding/web - name: Run Binding Linter run: yarn && yarn lint diff --git a/.github/workflows/web-demos.yml b/.github/workflows/web-demos.yml index bacfb52..9f46a06 100644 --- a/.github/workflows/web-demos.yml +++ b/.github/workflows/web-demos.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x, 18.x, 20.x] + node-version: [16.x, 18.x, 20.x] steps: - uses: actions/checkout@v3 @@ -35,8 +35,5 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Pre-build dependencies - run: npm install yarn - - name: Install dependencies run: yarn install diff --git a/.github/workflows/web-perf.yml b/.github/workflows/web-perf.yml index cf0df8d..6642b97 100644 --- a/.github/workflows/web-perf.yml +++ b/.github/workflows/web-perf.yml @@ -27,7 +27,7 @@ jobs: matrix: node-version: [lts/*] include: - - indexPerformanceThresholdSec: 1.8 + - indexPerformanceThresholdSec: 2.4 searchPerformanceThresholdSec: 0.015 steps: @@ -38,9 +38,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Pre-build dependencies - run: npm install yarn - - name: Install dependencies run: yarn install diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index a7ab971..af05917 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -30,7 +30,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x, 18.x, 20.x] + node-version: [16.x, 18.x, 20.x] steps: - uses: actions/checkout@v3 @@ -42,9 +42,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Pre-build dependencies - run: npm install yarn - - name: Install dependencies run: yarn install diff --git a/README.md b/README.md index c61bc09..681dc9e 100644 --- a/README.md +++ b/README.md @@ -335,15 +335,23 @@ Replace `${ACCESS_KEY}` with yours obtained from [Picovoice Console](https://con ## Releases -### v1.2.0 August 11th, 2022 +### v2.0.0 - November 24th, 2023 -* added language support for French, German, Spanish, Japanese, Korean, Italian, and Portuguese -* improved testing infrastructure +- Improvements to error reporting +- Upgrades to authorization and authentication system +- Various bug fixes and improvements +- Web min support bumped to Node 16 +- iOS support bumped to iOS 13 + +### v1.2.0 - August 11th, 2022 + +- Added language support for French, German, Spanish, Japanese, Korean, Italian, and Portuguese. +- Improved testing infrastructure ### v1.1.0 May 12th, 2022 -* various bug fixes and improvements +- Various bug fixes and improvements ### v1.0.0 October 8th, 2021 -* Initial release. +- Initial release diff --git a/binding/android/Octopus/octopus/build.gradle b/binding/android/Octopus/octopus/build.gradle index b5c01ea..34ef80f 100644 --- a/binding/android/Octopus/octopus/build.gradle +++ b/binding/android/Octopus/octopus/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'ai.picovoice' - PUBLISH_VERSION = '1.2.2' + PUBLISH_VERSION = '2.0.0' PUBLISH_ARTIFACT_ID = 'octopus-android' } @@ -39,12 +39,12 @@ dependencies { } task copyLibs(type: Copy) { - from("${rootDir}/../../lib/android") + from("${rootDir}/../../../lib/android") into("${rootDir}/octopus/src/main/jniLibs") } task copyParams(type: Copy) { - from("${rootDir}/../../lib/common/param") + from("${rootDir}/../../../lib/common/param") include('octopus_params.pv') into("${rootDir}/octopus/src/main/res/raw") } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/Octopus.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/Octopus.java index f24ada6..8ce025c 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/Octopus.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/Octopus.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -30,12 +30,17 @@ public class Octopus { private static String defaultModelPath; + private static String _sdk = "android"; + + private long handle; static { System.loadLibrary("pv_octopus"); } - private long handle; + public static void setSdk(String sdk) { + Octopus._sdk = sdk; + } /** * Constructor. @@ -45,6 +50,7 @@ public class Octopus { * @throws OctopusException if there is an error while initializing Octopus. */ private Octopus(String accessKey, String modelPath) throws OctopusException { + OctopusNative.setSdk(Octopus._sdk); handle = OctopusNative.init(accessKey, modelPath); } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/OctopusNative.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/OctopusNative.java index 182365c..1b08222 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/OctopusNative.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/OctopusNative.java @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -14,9 +14,11 @@ class OctopusNative { + static native String getVersion(); + static native int getPcmDataSampleRate(); - static native String getVersion(); + static native void setSdk(String sdk); static native long init( String accessKey, diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationException.java index 76aa910..bbfcea7 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusActivationException(Throwable cause) { public OctopusActivationException(String message) { super(message); } + + public OctopusActivationException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationLimitException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationLimitException.java index d4f9c86..f425b2d 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationLimitException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationLimitException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusActivationLimitException(Throwable cause) { public OctopusActivationLimitException(String message) { super(message); } + + public OctopusActivationLimitException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationRefusedException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationRefusedException.java index 30cc1c2..6544686 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationRefusedException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationRefusedException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusActivationRefusedException(Throwable cause) { public OctopusActivationRefusedException(String message) { super(message); } + + public OctopusActivationRefusedException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationThrottledException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationThrottledException.java index 9648c64..5d88289 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationThrottledException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusActivationThrottledException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusActivationThrottledException(Throwable cause) { public OctopusActivationThrottledException(String message) { super(message); } + + public OctopusActivationThrottledException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusException.java index 1a7c6b7..c30a31d 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -11,12 +11,43 @@ package ai.picovoice.octopus; public class OctopusException extends Exception { + private final String message; + private final String[] messageStack; + public OctopusException(Throwable cause) { super(cause); + this.message = cause.getMessage(); + this.messageStack = null; } public OctopusException(String message) { super(message); + this.message = message; + this.messageStack = null; + } + + public OctopusException(String message, String[] messageStack) { + super(message); + this.message = message; + this.messageStack = messageStack; + } + + public String[] getMessageStack() { + return this.messageStack; + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(message); + if (messageStack != null) { + if (messageStack.length > 0) { + sb.append(":"); + for (int i = 0; i < messageStack.length; i++) { + sb.append(String.format("\n [%d] %s", i, messageStack[i])); + } + } + } + return sb.toString(); } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusIOException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusIOException.java index 9f8c756..0c5c8c7 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusIOException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusIOException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusIOException(Throwable cause) { public OctopusIOException(String message) { super(message); } + + public OctopusIOException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidArgumentException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidArgumentException.java index 1bf2e24..0ae0860 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidArgumentException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidArgumentException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusInvalidArgumentException(Throwable cause) { public OctopusInvalidArgumentException(String message) { super(message); } + + public OctopusInvalidArgumentException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidStateException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidStateException.java index 69a2b31..56e1506 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidStateException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusInvalidStateException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusInvalidStateException(Throwable cause) { public OctopusInvalidStateException(String message) { super(message); } + + public OctopusInvalidStateException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusKeyException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusKeyException.java index cd40539..a1c6708 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusKeyException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusKeyException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusKeyException(Throwable cause) { public OctopusKeyException(String message) { super(message); } + + public OctopusKeyException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusMemoryException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusMemoryException.java index 327ef6b..6a92f46 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusMemoryException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusMemoryException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusMemoryException(Throwable cause) { public OctopusMemoryException(String message) { super(message); } + + public OctopusMemoryException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusRuntimeException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusRuntimeException.java index b529faa..635d702 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusRuntimeException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusRuntimeException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusRuntimeException(Throwable cause) { public OctopusRuntimeException(String message) { super(message); } + + public OctopusRuntimeException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusStopIterationException.java b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusStopIterationException.java index 1b388a9..7e7eff6 100644 --- a/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusStopIterationException.java +++ b/binding/android/Octopus/octopus/src/main/java/ai/picovoice/octopus/exception/OctopusStopIterationException.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -18,5 +18,9 @@ public OctopusStopIterationException(Throwable cause) { public OctopusStopIterationException(String message) { super(message); } + + public OctopusStopIterationException(String message, String[] messageStack) { + super(message, messageStack); + } } diff --git a/binding/android/OctopusTestApp/octopus-test-app/build.gradle b/binding/android/OctopusTestApp/octopus-test-app/build.gradle index be75423..b68b338 100644 --- a/binding/android/OctopusTestApp/octopus-test-app/build.gradle +++ b/binding/android/OctopusTestApp/octopus-test-app/build.gradle @@ -112,7 +112,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' - implementation 'ai.picovoice:octopus-android:1.2.2' + implementation 'ai.picovoice:octopus-android:2.0.0' // Espresso UI Testing androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/LanguageTests.java b/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/LanguageTests.java index 353aa57..e1934fa 100644 --- a/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/LanguageTests.java +++ b/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/LanguageTests.java @@ -59,7 +59,7 @@ public static Collection initParameters() { { "ja", new HashMap() {{ - put("りんご", new double [][]{{0.960, 1.664, 1}}); + put("りんご", new double [][]{{0.992, 1.632, 1}}); }}, }, { diff --git a/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/OctopusTest.java b/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/OctopusTest.java index cd9532c..8e54433 100644 --- a/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/OctopusTest.java +++ b/binding/android/OctopusTestApp/octopus-test-app/src/androidTest/java/ai/picovoice/octopus/testapp/OctopusTest.java @@ -1,5 +1,5 @@ /* - Copyright 2021 Picovoice Inc. + Copyright 2021-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. Unless required by applicable law or agreed to in writing, software distributed under the @@ -249,4 +249,29 @@ public void testSpacesInSearchPhrase() throws Exception { metadata.delete(); octopus.delete(); } + + @Test + public void testErrorStack() { + String[] error = {}; + try { + new Octopus.Builder() + .setAccessKey("invalid") + .build(appContext); + } catch (OctopusException e) { + error = e.getMessageStack(); + } + + assertTrue(0 < error.length); + assertTrue(error.length <= 8); + + try { + new Octopus.Builder() + .setAccessKey("invalid") + .build(appContext); + } catch (OctopusException e) { + for (int i = 0; i < error.length; i++) { + assertEquals(e.getMessageStack()[i], error[i]); + } + } + } } diff --git a/binding/android/README.md b/binding/android/README.md index 0563c21..c13a074 100644 --- a/binding/android/README.md +++ b/binding/android/README.md @@ -1,4 +1,6 @@ -# Octopus +# Octopus Binding for Android + +## Octopus Speech-to-Index Engine Made in Vancouver, Canada by [Picovoice](https://picovoice.ai) diff --git a/binding/ios/Octopus-iOS.podspec b/binding/ios/Octopus-iOS.podspec index 828bc43..31ab27f 100644 --- a/binding/ios/Octopus-iOS.podspec +++ b/binding/ios/Octopus-iOS.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Octopus-iOS' s.module_name = 'Octopus' - s.version = '1.2.0' + s.version = '2.0.0' s.license = {:type => 'Apache 2.0'} s.summary = 'iOS binding for Picovoice\'s Octopus Speech-to-Index engine' s.description = @@ -12,8 +12,8 @@ Pod::Spec.new do |s| DESC s.homepage = 'https://github.com/Picovoice/octopus/tree/master/binding/ios' s.author = { 'Picovoice' => 'hello@picovoice.ai' } - s.source = { :git => "https://github.com/Picovoice/octopus.git", :tag => "Octopus-iOS-v1.2.0" } - s.ios.deployment_target = '9.0' + s.source = { :git => "https://github.com/Picovoice/octopus.git", :tag => "Octopus-iOS-v2.0.0" } + s.ios.deployment_target = '13.0' s.swift_version = '5.0' s.vendored_frameworks = 'lib/ios/PvOctopus.xcframework' s.resources = 'lib/common/param/octopus_params.pv' diff --git a/binding/ios/Octopus.swift b/binding/ios/Octopus.swift index 8f67962..6019e1e 100644 --- a/binding/ios/Octopus.swift +++ b/binding/ios/Octopus.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 Picovoice Inc. +// Copyright 2021-2023 Picovoice Inc. // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" // file accompanying this source. // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on @@ -33,6 +33,12 @@ public class Octopus { private var handle: OpaquePointer? + private static var sdk = "ios" + + public static func setSdk(sdk: String) { + self.sdk = sdk + } + /// Constructor. /// /// - Parameters: @@ -56,12 +62,15 @@ public class Octopus { modelPathArg = try getResourcePath(modelPathArg!) } + pv_set_sdk(Octopus.sdk) + let status = pv_octopus_init( accessKey, modelPathArg, &handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToOctopusError(status, "Octopus init failed") + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus init failed", messageStack) } } @@ -90,20 +99,34 @@ public class Octopus { throw OctopusInvalidStateError("Octopus must be initialized before indexing") } - var cMetadata: UnsafeMutableRawPointer? var cNumMetadataBytes: Int32 = -1 - let status = pv_octopus_index( + var status = pv_octopus_index_size( handle, - pcm, Int32(pcm.count), - &cMetadata, &cNumMetadataBytes) if status != PV_STATUS_SUCCESS { - throw pvStatusToOctopusError(status, "Octopus index failed") + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus failed to determine index buffer size", messageStack) } let numMetadataBytes = Int(cNumMetadataBytes) + let cMetadata: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate( + byteCount: numMetadataBytes, + alignment: MemoryLayout.alignment) + cMetadata?.initializeMemory(as: UInt8.self, repeating: 0, count: numMetadataBytes) + + status = pv_octopus_index( + handle, + pcm, + Int32(pcm.count), + cMetadata) + + if status != PV_STATUS_SUCCESS { + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus index failed", messageStack) + } + let metadata = cMetadata!.bindMemory(to: UInt8.self, capacity: numMetadataBytes) return OctopusMetadata(handle: metadata, numBytes: numMetadataBytes) @@ -125,19 +148,33 @@ public class Octopus { pathArg = try getResourcePath(pathArg) } - var cMetadata: UnsafeMutableRawPointer? var cNumMetadataBytes: Int32 = -1 - let status = pv_octopus_index_file( + var status = pv_octopus_index_file_size( handle, pathArg, - &cMetadata, &cNumMetadataBytes) if status != PV_STATUS_SUCCESS { - throw pvStatusToOctopusError(status, "Octopus index failed") + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus failed to determine index buffer size", messageStack) } let numMetadataBytes = Int(cNumMetadataBytes) + let cMetadata: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate( + byteCount: numMetadataBytes, + alignment: MemoryLayout.alignment) + cMetadata?.initializeMemory(as: UInt8.self, repeating: 0, count: numMetadataBytes) + + status = pv_octopus_index_file( + handle, + pathArg, + cMetadata) + + if status != PV_STATUS_SUCCESS { + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus index failed", messageStack) + } + let metadata = cMetadata!.bindMemory(to: UInt8.self, capacity: numMetadataBytes) return OctopusMetadata(handle: metadata, numBytes: numMetadataBytes) @@ -173,14 +210,16 @@ public class Octopus { var cMatches: UnsafeMutablePointer? var cNumMatches: Int32 = -1 - let status = pv_octopus_search(handle, - metadata.handle, - Int32(metadata.numBytes), - formattedPhrase, - &cMatches, - &cNumMatches) + let status = pv_octopus_search( + handle, + metadata.handle, + Int32(metadata.numBytes), + formattedPhrase, + &cMatches, + &cNumMatches) if status != PV_STATUS_SUCCESS { - throw pvStatusToOctopusError(status, "Octopus search failed") + let messageStack = try getMessageStack() + throw pvStatusToOctopusError(status, "Octopus search failed", messageStack) } let numPhraseMatches = Int(cNumMatches) @@ -192,6 +231,7 @@ public class Octopus { probability: Float(cMatch.probability)) phraseMatches.append(phraseMatch) } + pv_octopus_matches_delete(cMatches) matches[formattedPhrase] = phraseMatches } @@ -217,33 +257,54 @@ public class Octopus { "If this is a packaged asset, ensure you have added it to your xcode project.") } - private func pvStatusToOctopusError(_ status: pv_status_t, _ message: String) -> OctopusError { + private func pvStatusToOctopusError( + _ status: pv_status_t, + _ message: String, + _ messageStack: [String] = []) -> OctopusError { switch status { case PV_STATUS_OUT_OF_MEMORY: - return OctopusMemoryError(message) + return OctopusMemoryError(message, messageStack) case PV_STATUS_IO_ERROR: - return OctopusIOError(message) + return OctopusIOError(message, messageStack) case PV_STATUS_INVALID_ARGUMENT: - return OctopusInvalidArgumentError(message) + return OctopusInvalidArgumentError(message, messageStack) case PV_STATUS_STOP_ITERATION: - return OctopusStopIterationError(message) + return OctopusStopIterationError(message, messageStack) case PV_STATUS_KEY_ERROR: - return OctopusKeyError(message) + return OctopusKeyError(message, messageStack) case PV_STATUS_INVALID_STATE: - return OctopusInvalidStateError(message) + return OctopusInvalidStateError(message, messageStack) case PV_STATUS_RUNTIME_ERROR: - return OctopusRuntimeError(message) + return OctopusRuntimeError(message, messageStack) case PV_STATUS_ACTIVATION_ERROR: - return OctopusActivationError(message) + return OctopusActivationError(message, messageStack) case PV_STATUS_ACTIVATION_LIMIT_REACHED: - return OctopusActivationLimitError(message) + return OctopusActivationLimitError(message, messageStack) case PV_STATUS_ACTIVATION_THROTTLED: - return OctopusActivationThrottledError(message) + return OctopusActivationThrottledError(message, messageStack) case PV_STATUS_ACTIVATION_REFUSED: - return OctopusActivationRefusedError(message) + return OctopusActivationRefusedError(message, messageStack) default: let pvStatusString = String(cString: pv_status_to_string(status)) - return OctopusError("\(pvStatusString): \(message)") + return OctopusError("\(pvStatusString): \(message)", messageStack) } } + + private func getMessageStack() throws -> [String] { + var messageStackRef: UnsafeMutablePointer?>? + var messageStackDepth: Int32 = 0 + let status = pv_get_error_stack(&messageStackRef, &messageStackDepth) + if status != PV_STATUS_SUCCESS { + throw pvStatusToOctopusError(status, "Unable to get Octopus error state") + } + + var messageStack: [String] = [] + for i in 0.. 1.2.0' + pod 'Octopus-iOS', '~> 2.0.0' end target 'OctopusAppTestUITests' do - pod 'Octopus-iOS', '~> 1.2.0' + pod 'Octopus-iOS', '~> 2.0.0' end target 'PerformanceTest' do - pod 'Octopus-iOS', '~> 1.2.0' + pod 'Octopus-iOS', '~> 2.0.0' end diff --git a/binding/ios/OctopusAppTest/Podfile.lock b/binding/ios/OctopusAppTest/Podfile.lock index 382a350..c889449 100644 --- a/binding/ios/OctopusAppTest/Podfile.lock +++ b/binding/ios/OctopusAppTest/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - Octopus-iOS (1.2.0) + - Octopus-iOS (2.0.0) DEPENDENCIES: - - Octopus-iOS (~> 1.2.0) + - Octopus-iOS (~> 2.0.0) SPEC REPOS: trunk: - Octopus-iOS SPEC CHECKSUMS: - Octopus-iOS: c741f75aefc3ee701559ff8f5eb8ae537ef48f82 + Octopus-iOS: b080b854eabe191e1bc90ff95248249123d98548 -PODFILE CHECKSUM: 69284c2a1c2bc932189d48e8e5da4a9c219de6b5 +PODFILE CHECKSUM: 69ef501829b3c0bd33a90dca86df4aca3748b08b -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/binding/ios/OctopusErrors.swift b/binding/ios/OctopusErrors.swift index f51301d..1c67ad5 100644 --- a/binding/ios/OctopusErrors.swift +++ b/binding/ios/OctopusErrors.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 Picovoice Inc. +// Copyright 2022-2023 Picovoice Inc. // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" // file accompanying this source. // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on @@ -9,13 +9,22 @@ public class OctopusError: LocalizedError { private let message: String + private let messageStack: [String] - public init (_ message: String) { + public init (_ message: String, _ messageStack: [String] = []) { self.message = message + self.messageStack = messageStack } public var errorDescription: String? { - return message + var messageString = message + if messageStack.count > 0 { + messageString += ":" + for i in 0.. Octopus: - """ - Factory method for Octopus Speech-to-Index engine. - - :param access_key: AccessKey provided by Picovoice Console (https://console.picovoice.ai/) - :param model_path: Absolute path to the file containing model parameters. If not set it will be set to the default - location for English model. - :param library_path: Absolute path to Octopus' dynamic library. If not set it will be set to the default - location. - :return An instance of Octopus Speech-to-Index engine. - """ - - if model_path is None: - model_path = MODEL_PATH - - if library_path is None: - library_path = LIBRARY_PATH - - return Octopus(access_key=access_key, model_path=model_path, library_path=library_path) +from ._factory import * +from ._octopus import * +from ._util import * diff --git a/binding/python/_factory.py b/binding/python/_factory.py new file mode 100644 index 0000000..c000654 --- /dev/null +++ b/binding/python/_factory.py @@ -0,0 +1,42 @@ +# +# Copyright 2021-2023 Picovoice Inc. +# +# You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" +# file accompanying this source. +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +from typing import Optional + +from ._octopus import Octopus +from ._util import default_library_path, default_model_path + + +def create(access_key: str, model_path: Optional[str] = None, library_path: Optional[str] = None) -> Octopus: + """ + Factory method for Octopus Speech-to-Index engine. + + :param access_key: AccessKey provided by Picovoice Console (https://console.picovoice.ai/) + :param model_path: Absolute path to the file containing model parameters. If not set it will be set to the default + location for English model. + :param library_path: Absolute path to Octopus' dynamic library. If not set it will be set to the default + location. + :return An instance of Octopus Speech-to-Index engine. + """ + + if model_path is None: + model_path = default_model_path() + + if library_path is None: + library_path = default_library_path() + + return Octopus( + access_key=access_key, + model_path=model_path, + library_path=library_path) + + +__all__ = ['create'] diff --git a/binding/python/octopus.py b/binding/python/_octopus.py similarity index 64% rename from binding/python/octopus.py rename to binding/python/_octopus.py index 51f1ff2..ef3e380 100644 --- a/binding/python/octopus.py +++ b/binding/python/_octopus.py @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 Picovoice Inc. +# Copyright 2021-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. # A copy of the license is located in the "LICENSE" file accompanying this @@ -20,7 +20,27 @@ class OctopusError(Exception): - pass + def __init__(self, message: str = '', message_stack: Sequence[str] = None): + super().__init__(message) + + self._message = message + self._message_stack = list() if message_stack is None else message_stack + + def __str__(self): + message = self._message + if len(self._message_stack) > 0: + message += ':' + for i in range(len(self._message_stack)): + message += '\n [%d] %s' % (i, self._message_stack[i]) + return message + + @property + def message(self) -> str: + return self._message + + @property + def message_stack(self) -> Sequence[str]: + return self._message_stack class OctopusMemoryError(OctopusError): @@ -87,10 +107,6 @@ def size(self) -> int: def to_bytes(self) -> bytes: return self._to_bytes(self.handle, self.size) - @classmethod - def create_owned(cls, handle: c_void_p, size: int) -> 'OctopusMetadata': - return cls.from_bytes(cls._to_bytes(handle, size)) - @classmethod def from_bytes(cls, metadata_bytes: bytes) -> 'OctopusMetadata': byte_ptr = (c_byte * len(metadata_bytes)).from_buffer_copy(metadata_bytes) @@ -139,7 +155,11 @@ class PicovoiceStatuses(Enum): class COctopus(Structure): pass - def __init__(self, access_key: str, model_path: str, library_path: str) -> None: + def __init__( + self, + access_key: str, + model_path: str, + library_path: str) -> None: """ Constructor. @@ -148,43 +168,77 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None: :param library_path: Absolute path to Octopus' dynamic library. """ + if not isinstance(access_key, str) or len(access_key) == 0: + raise OctopusInvalidArgumentError("`access_key` should be a non-empty string.") + if not os.path.exists(model_path): - raise IOError("Couldn't find model file at `%s`." % model_path) + raise OctopusIOError("Couldn't find model file at `%s`." % model_path) if not os.path.exists(library_path): - raise IOError("Couldn't find dynamic library at '%s'." % library_path) + raise OctopusIOError("Couldn't find dynamic library at '%s'." % library_path) library = cdll.LoadLibrary(library_path) + set_sdk_func = library.pv_set_sdk + set_sdk_func.argtypes = [c_char_p] + set_sdk_func.restype = None + + set_sdk_func('python'.encode('utf-8')) + + self._get_error_stack_func = library.pv_get_error_stack + self._get_error_stack_func.argtypes = [POINTER(POINTER(c_char_p)), POINTER(c_int)] + self._get_error_stack_func.restype = self.PicovoiceStatuses + + self._free_error_stack_func = library.pv_free_error_stack + self._free_error_stack_func.argtypes = [POINTER(c_char_p)] + self._free_error_stack_func.restype = None + init_func = library.pv_octopus_init init_func.argtypes = [c_char_p, c_char_p, POINTER(POINTER(self.COctopus))] init_func.restype = self.PicovoiceStatuses self._handle = POINTER(self.COctopus)() - status = init_func(access_key.encode('utf-8'), model_path.encode('utf-8'), byref(self._handle)) + status = init_func( + access_key.encode('utf-8'), + model_path.encode('utf-8'), + byref(self._handle)) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Initialization failed', + message_stack=self._get_error_stack()) self._delete_func = library.pv_octopus_delete self._delete_func.argtypes = [POINTER(self.COctopus)] self._delete_func.restype = None + self._index_size_func = library.pv_octopus_index_size + self._index_size_func.argtypes = [ + POINTER(self.COctopus), + c_int32, + POINTER(c_int32)] + self._index_size_func.restype = self.PicovoiceStatuses + self._index_func = library.pv_octopus_index self._index_func.argtypes = [ POINTER(self.COctopus), POINTER(c_short), c_int32, - POINTER(c_void_p), - POINTER(c_int32)] + c_void_p] self._index_func.restype = self.PicovoiceStatuses + self._index_file_size_func = library.pv_octopus_index_file_size + self._index_file_size_func.argtypes = [ + POINTER(self.COctopus), + c_char_p, + POINTER(c_int32)] + self._index_file_size_func.restype = self.PicovoiceStatuses + self._index_file_func = library.pv_octopus_index_file self._index_file_func.argtypes = [ POINTER(self.COctopus), c_char_p, - POINTER(c_void_p), - POINTER(c_int32)] + c_void_p] self._index_file_func.restype = self.PicovoiceStatuses self._search_func = library.pv_octopus_search @@ -197,6 +251,10 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None: POINTER(c_int32)] self._search_func.restype = self.PicovoiceStatuses + self._matches_delete_func = library.pv_octopus_matches_delete + self._matches_delete_func.argtypes = [POINTER(self.CMatch)] + self._matches_delete_func.restype = None + version_func = library.pv_octopus_version version_func.argtypes = [] version_func.restype = c_char_p @@ -204,10 +262,6 @@ def __init__(self, access_key: str, model_path: str, library_path: str) -> None: self._sample_rate = library.pv_sample_rate() - self._pv_free = library.pv_free - self._pv_free.argtypes = [c_void_p] - self._pv_free.restype = None - def delete(self) -> None: """Releases resources acquired by Octopus.""" @@ -222,22 +276,29 @@ def index_audio_data(self, pcm: Sequence[int]) -> OctopusMetadata: :return metadata: An immutable metadata object. """ - c_metadata = c_void_p() metadata_size = c_int32() + status = self._index_size_func( + self._handle, + c_int32(len(pcm)), + byref(metadata_size)) + if status is not self.PicovoiceStatuses.SUCCESS: + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Index size failed', + message_stack=self._get_error_stack()) + metadata_bytes = create_string_buffer(metadata_size.value) + metadata_bytes_ptr = cast(metadata_bytes, c_void_p) status = self._index_func( self._handle, (c_short * len(pcm))(*pcm), c_int32(len(pcm)), - byref(c_metadata), - byref(metadata_size)) + metadata_bytes_ptr) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Index failed', + message_stack=self._get_error_stack()) - metadata = OctopusMetadata.create_owned(c_metadata, metadata_size.value) - self._pv_free(c_metadata) - - return metadata + return OctopusMetadata(metadata_bytes_ptr, metadata_size.value) def index_audio_file(self, path: str) -> OctopusMetadata: """ @@ -248,23 +309,30 @@ def index_audio_file(self, path: str) -> OctopusMetadata: """ if not os.path.exists(path): - raise IOError("Couldn't find input file at `%s`." % path) + raise OctopusIOError("Couldn't find input file at `%s`." % path) - c_metadata = c_void_p() metadata_size = c_int32() - - status = self._index_file_func( + status = self._index_file_size_func( self._handle, path.encode('utf-8'), - byref(c_metadata), byref(metadata_size)) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status](status.name) + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Index file size failed', + message_stack=self._get_error_stack()) - metadata = OctopusMetadata.create_owned(c_metadata, metadata_size.value) - self._pv_free(c_metadata) + metadata_bytes = create_string_buffer(metadata_size.value) + metadata_bytes_ptr = cast(metadata_bytes, c_void_p) + status = self._index_file_func( + self._handle, + path.encode('utf-8'), + metadata_bytes) + if status is not self.PicovoiceStatuses.SUCCESS: + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Index file failed', + message_stack=self._get_error_stack()) - return metadata + return OctopusMetadata(metadata_bytes_ptr, metadata_size.value) Match = namedtuple('Match', ['start_sec', 'end_sec', 'probability']) @@ -301,7 +369,9 @@ def search(self, metadata: OctopusMetadata, phrases: Iterable[str]) -> Dict[str, byref(c_phrase_matches), byref(num_phrase_matches)) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Search failed', + message_stack=self._get_error_stack()) if num_phrase_matches.value > 0: phrase_matches = list() for i in range(num_phrase_matches.value): @@ -311,6 +381,7 @@ def search(self, metadata: OctopusMetadata, phrases: Iterable[str]) -> Dict[str, probability=c_phrase_matches[i].probability) phrase_matches.append(match) matches[phrase] = phrase_matches + self._matches_delete_func(c_phrase_matches) return matches @@ -326,6 +397,21 @@ def sample_rate(self) -> int: return self._sample_rate + def _get_error_stack(self) -> Sequence[str]: + message_stack_ref = POINTER(c_char_p)() + message_stack_depth = c_int() + status = self._get_error_stack_func(byref(message_stack_ref), byref(message_stack_depth)) + if status is not self.PicovoiceStatuses.SUCCESS: + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status](message='Unable to get Porcupine error state') + + message_stack = list() + for i in range(message_stack_depth.value): + message_stack.append(message_stack_ref[i].decode('utf-8')) + + self._free_error_stack_func(message_stack_ref) + + return message_stack + __all__ = [ 'OctopusError', diff --git a/binding/python/util.py b/binding/python/_util.py similarity index 81% rename from binding/python/util.py rename to binding/python/_util.py index e8e7e41..b0c8d1f 100644 --- a/binding/python/util.py +++ b/binding/python/_util.py @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 Picovoice Inc. +# Copyright 2021-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" # file accompanying this source. @@ -9,14 +9,10 @@ # specific language governing permissions and limitations under the License. # -import logging import os import platform from typing import Tuple -log = logging.getLogger('OCT') -log.setLevel(logging.WARNING) - def _pv_platform() -> Tuple[str, str]: pv_system = platform.system() @@ -31,7 +27,7 @@ def _pv_platform() -> Tuple[str, str]: _PV_SYSTEM, _PV_MACHINE = _pv_platform() -def pv_library_path(relative_path: str) -> str: +def default_library_path(relative_path: str = '') -> str: if _PV_SYSTEM == 'Darwin': if _PV_MACHINE == 'x86_64': return os.path.join(os.path.dirname(__file__), relative_path, 'lib/mac/x86_64/libpv_octopus.dylib') @@ -46,14 +42,14 @@ def pv_library_path(relative_path: str) -> str: raise NotImplementedError('Unsupported platform.') -def pv_model_path(relative_path: str, language: str) -> str: +def default_model_path(relative_path: str = '') -> str: return os.path.join( os.path.dirname(__file__), relative_path, - 'lib/common/param/octopus_params%s.pv' % ('' if language == 'en' else ('_' + language))) + 'lib/common/param/octopus_params.pv') __all__ = [ - 'pv_library_path', - 'pv_model_path', + 'default_library_path', + 'default_model_path', ] diff --git a/binding/python/setup.py b/binding/python/setup.py index 4559066..b3abeed 100644 --- a/binding/python/setup.py +++ b/binding/python/setup.py @@ -3,51 +3,42 @@ import setuptools +INCLUDE_FILES = ('../../LICENSE', '__init__.py', '_factory.py', '_octopus.py', '_util.py') +INCLUDE_LIBS = ('linux', 'mac', 'windows') + os.system('git clean -dfx') package_folder = os.path.join(os.path.dirname(__file__), 'pvoctopus') os.mkdir(package_folder) +manifest_in = "" -shutil.copy(os.path.join(os.path.dirname(__file__), '../../LICENSE'), package_folder) - -shutil.copy(os.path.join(os.path.dirname(__file__), '__init__.py'), os.path.join(package_folder, '__init__.py')) -shutil.copy(os.path.join(os.path.dirname(__file__), 'octopus.py'), os.path.join(package_folder, 'octopus.py')) -shutil.copy(os.path.join(os.path.dirname(__file__), 'util.py'), os.path.join(package_folder, 'util.py')) +for rel_path in INCLUDE_FILES: + shutil.copy(os.path.join(os.path.dirname(__file__), rel_path), package_folder) + manifest_in += "include pvoctopus/%s\n" % os.path.basename(rel_path) -platforms = ('linux', 'mac', 'windows') +model_subdir = 'lib/common/param' +model_file = 'octopus_params.pv' +os.makedirs(os.path.join(package_folder, model_subdir)) +shutil.copy( + os.path.join(os.path.dirname(__file__), '../..', model_subdir, model_file), + os.path.join(package_folder, model_subdir, model_file)) +manifest_in += "include pvoctopus/%s/%s\n" % (model_subdir, model_file) -os.mkdir(os.path.join(package_folder, 'lib')) -for platform in platforms: +for platform in INCLUDE_LIBS: shutil.copytree( os.path.join(os.path.dirname(__file__), '../../lib', platform), os.path.join(package_folder, 'lib', platform)) - -os.makedirs(os.path.join(package_folder, 'lib/common/param')) -shutil.copy( - os.path.join(os.path.dirname(__file__), '../../lib/common/param/octopus_params.pv'), - os.path.join(package_folder, 'lib/common/param/octopus_params.pv')) - -MANIFEST_IN = """ -include pvoctopus/LICENSE -include pvoctopus/__init__.py -include pvoctopus/octopus.py -include pvoctopus/util.py -include pvoctopus/lib/common/param/octopus_params.pv -include pvoctopus/lib/linux/x86_64/libpv_octopus.so -include pvoctopus/lib/mac/x86_64/libpv_octopus.dylib -include pvoctopus/lib/mac/arm64/libpv_octopus.dylib -include pvoctopus/lib/windows/amd64/libpv_octopus.dll -""" + manifest_in += "recursive-include pvoctopus/lib/%s *\n" % platform with open(os.path.join(os.path.dirname(__file__), 'MANIFEST.in'), 'w') as f: - f.write(MANIFEST_IN.strip('\n ')) + f.write(manifest_in) with open(os.path.join(os.path.dirname(__file__), 'README.md'), 'r') as f: long_description = f.read() setuptools.setup( name="pvoctopus", - version="1.2.1", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Octopus Speech-to-Index engine.", diff --git a/binding/python/test_octopus.py b/binding/python/test_octopus.py index 1ffd078..ffb2c3a 100644 --- a/binding/python/test_octopus.py +++ b/binding/python/test_octopus.py @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 Picovoice Inc. +# Copyright 2021-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. # A copy of the license is located in the "LICENSE" file accompanying this @@ -19,9 +19,9 @@ from parameterized import parameterized -from octopus import * +from _octopus import * +from _util import * from test_util import * -from util import * TEST_PARAMS = [ ["en", {"alexa": [(7.648, 8.352, 1)], "porcupine": [(5.728, 6.752, 1), (35.360, 36.416, 1)]}], @@ -29,7 +29,7 @@ ["es", {"manzana": [(5.184, 5.984, 1)]}], ["fr", {"perroquet": [(4.352, 5.184, 0.952)]}], ["it", {"porcospino": [(0.480, 1.728, 1)]}], - ["ja", {"りんご": [(0.960, 1.664, 1)]}], + ["ja", {"りんご": [(0.990, 1.634, 1)]}], ["ko", {"아이스크림": [(6.592, 7.520, 0.961)]}], ["pt", {"porco espinho": [(0.480, 1.792, 1)]}], ] @@ -39,18 +39,13 @@ class OctopusTestCase(unittest.TestCase): @classmethod def setUpClass(cls): cls._access_key = sys.argv[1] + cls._relative = '../..' - def _create_octopus(self, language: str) -> Octopus: + def _create_octopus(self, language: str = 'en') -> Octopus: return Octopus( access_key=self._access_key, - library_path=pv_library_path('../..'), - model_path=pv_model_path('../..', language)) - - @staticmethod - def _audio_path(language: str) -> str: - return os.path.join( - os.path.dirname(__file__), - '../../res/audio/multiple_keywords%s.wav' % ('' if language == 'en' else ('_' + language))) + library_path=default_library_path(self._relative), + model_path=get_model_path_by_language(self._relative, language)) def _check_matches( self, phrase_matches: Dict[str, Sequence[Octopus.Match]], @@ -69,7 +64,8 @@ def test_index(self, language: str, phrase_occurrences: Dict[str, Sequence[Tuple try: octopus = self._create_octopus(language) - metadata = octopus.index_audio_data(read_wav_file(self._audio_path(language), octopus.sample_rate)) + audio_data = read_wav_file(get_audio_path_by_language(self._relative, language), octopus.sample_rate) + metadata = octopus.index_audio_data(audio_data) phrase_matches = octopus.search(metadata, list(phrase_occurrences.keys())) self._check_matches(phrase_matches, phrase_occurrences) finally: @@ -82,7 +78,7 @@ def _test_index_file(self, language: str, phrase_occurrences: Dict[str, Sequence try: octopus = self._create_octopus(language) - metadata = octopus.index_audio_file(self._audio_path(language)) + metadata = octopus.index_audio_file(get_audio_path_by_language(self._relative, language)) phrase_matches = octopus.search(metadata, list(phrase_occurrences.keys())) self._check_matches(phrase_matches, phrase_occurrences) finally: @@ -93,8 +89,8 @@ def _test_invalid(self, phrase: str) -> None: octopus = None try: - octopus = self._create_octopus('en') - metadata = octopus.index_audio_file(self._audio_path('en')) + octopus = self._create_octopus() + metadata = octopus.index_audio_file(get_audio_path_by_language(self._relative)) with self.assertRaises(OctopusInvalidArgumentError): octopus.search(metadata, [phrase]) finally: @@ -120,8 +116,8 @@ def test_index_with_spaces(self): octopus = None try: - octopus = self._create_octopus('en') - metadata = octopus.index_audio_file(self._audio_path('en')) + octopus = self._create_octopus() + metadata = octopus.index_audio_file(get_audio_path_by_language(self._relative)) search_term = ' americano avocado ' normalized_search_term = 'americano avocado' matches = octopus.search(metadata, [search_term]) @@ -143,7 +139,7 @@ def test_to_from_bytes(self, language: str, phrase_occurrences: Dict[str, Sequen try: octopus = self._create_octopus(language) - original_metadata = octopus.index_audio_file(self._audio_path(language)) + original_metadata = octopus.index_audio_file(get_audio_path_by_language(self._relative, language)) metadata_bytes = original_metadata.to_bytes() metadata = OctopusMetadata.from_bytes(metadata_bytes) @@ -164,7 +160,7 @@ def test_to_from_bytes_file( try: octopus = self._create_octopus(language) - original_metadata = octopus.index_audio_file(self._audio_path(language)) + original_metadata = octopus.index_audio_file(get_audio_path_by_language(self._relative, language)) with open(cache_path, 'wb') as f: f.write(original_metadata.to_bytes()) @@ -183,12 +179,63 @@ def test_version(self): octopus = None try: - octopus = self._create_octopus('en') + octopus = self._create_octopus() self.assertIsInstance(octopus.version, str) finally: if octopus is not None: octopus.delete() + def test_message_stack(self): + error = None + try: + o = Octopus( + access_key='invalid', + library_path=default_library_path(self._relative), + model_path=get_model_path_by_language(self._relative)) + self.assertIsNone(o) + except OctopusError as e: + error = e.message_stack + + self.assertIsNotNone(error) + self.assertGreater(len(error), 0) + + try: + o = Octopus( + access_key='invalid', + library_path=default_library_path(self._relative), + model_path=get_model_path_by_language(self._relative)) + self.assertIsNone(o) + except OctopusError as e: + self.assertEqual(len(error), len(e.message_stack)) + self.assertListEqual(list(error), list(e.message_stack)) + + def test_index_search_message_stack(self): + o = Octopus( + access_key=self._access_key, + library_path=default_library_path(self._relative), + model_path=get_model_path_by_language(self._relative)) + test_pcm = [0] * 512 + test_metadata = o.index_audio_file(get_audio_path_by_language(self._relative, "en")) + + address = o._handle + o._handle = None + + try: + res = o.index_audio_data(test_pcm) + self.assertIsNone(res) + except OctopusError as e: + self.assertGreater(len(e.message_stack), 0) + self.assertLess(len(e.message_stack), 8) + + try: + res = o.search(test_metadata, ["test"]) + self.assertIsNone(res) + except OctopusError as e: + self.assertGreater(len(e.message_stack), 0) + self.assertLess(len(e.message_stack), 8) + + o._handle = address + if __name__ == '__main__': if len(sys.argv) != 2: diff --git a/binding/python/test_octopus_perf.py b/binding/python/test_octopus_perf.py index 36cb006..0d3f312 100644 --- a/binding/python/test_octopus_perf.py +++ b/binding/python/test_octopus_perf.py @@ -1,5 +1,5 @@ # -# Copyright 2022 Picovoice Inc. +# Copyright 2022-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. # A copy of the license is located in the "LICENSE" file accompanying this @@ -12,14 +12,13 @@ # limitations under the License. # -import os import sys import time import unittest -from octopus import * +from _octopus import * +from _util import * from test_util import * -from util import * class OctopusTestCase(unittest.TestCase): @@ -28,17 +27,19 @@ class OctopusTestCase(unittest.TestCase): @classmethod def setUpClass(cls): access_key = sys.argv[1] + relative = '../..' + cls.num_test_iterations = int(sys.argv[2]) cls.index_performance_threshold_sec = float(sys.argv[3]) cls.search_performance_threshold_sec = float(sys.argv[4]) cls.octopus = Octopus( access_key=access_key, - library_path=pv_library_path('../..'), - model_path=pv_model_path('../..', 'en')) + library_path=default_library_path(relative), + model_path=get_model_path_by_language(relative)) cls.audio = read_wav_file( - os.path.join(os.path.dirname(__file__), '../../res/audio/multiple_keywords.wav'), + get_audio_path_by_language(relative), cls.octopus.sample_rate) @classmethod diff --git a/binding/python/test_util.py b/binding/python/test_util.py index a17d2ed..ead81e2 100644 --- a/binding/python/test_util.py +++ b/binding/python/test_util.py @@ -1,5 +1,5 @@ # -# Copyright 2022 Picovoice Inc. +# Copyright 2022-2023 Picovoice Inc. # # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" # file accompanying this source. @@ -9,6 +9,7 @@ # specific language governing permissions and limitations under the License. # +import os import struct import wave from typing import Sequence @@ -34,6 +35,24 @@ def read_wav_file(file_name: str, sample_rate: int) -> Sequence[int]: return frames[::channels] +def get_audio_path_by_language(relative: str, language: str = 'en') -> str: + audio_file_path = 'res/audio/multiple_keywords%s.wav' % ('' if language == 'en' else ('_' + language)) + return os.path.join( + os.path.dirname(__file__), + relative, + audio_file_path) + + +def get_model_path_by_language(relative: str, language: str = 'en'): + model_file_path = 'lib/common/param/octopus_params%s.pv' % ('' if language == 'en' else ('_%s' % language)) + return os.path.join( + os.path.dirname(__file__), + relative, + model_file_path) + + __all__ = [ + 'get_audio_path_by_language', + 'get_model_path_by_language', 'read_wav_file' ] diff --git a/binding/web/.eslintignore b/binding/web/.eslintignore new file mode 100644 index 0000000..8af5238 --- /dev/null +++ b/binding/web/.eslintignore @@ -0,0 +1,5 @@ +node_modules +dist +lib +rollup.config.js +.eslintrc.js diff --git a/binding/web/.eslintrc.js b/binding/web/.eslintrc.js index 112ad3c..00f929e 100644 --- a/binding/web/.eslintrc.js +++ b/binding/web/.eslintrc.js @@ -34,7 +34,8 @@ module.exports = { ignoreParameters: true, ignoreProperties: true } - ] + ], + '@typescript-eslint/no-shadow': 2 } }, { @@ -257,7 +258,7 @@ module.exports = { // disallow shadowing of names such as arguments 'no-shadow-restricted-names': 2, // disallow declaration of variables already declared in the outer scope - 'no-shadow': 2, + 'no-shadow': 0, // disallow use of undefined when initializing variables 'no-undef-init': 0, // disallow use of undeclared variables unless mentioned in a /*global */ block diff --git a/binding/web/.npmignore b/binding/web/.npmignore index a8e94ff..06f7b18 100644 --- a/binding/web/.npmignore +++ b/binding/web/.npmignore @@ -1,5 +1,8 @@ .idea node_modules .DS_Store -test lib +cypress +cypress.config.ts +scripts +test diff --git a/binding/web/package.json b/binding/web/package.json index 7f30a53..60a3482 100644 --- a/binding/web/package.json +++ b/binding/web/package.json @@ -3,7 +3,7 @@ "description": "Octopus library for web browsers (via WebAssembly)", "author": "Picovoice Inc", "license": "Apache-2.0", - "version": "1.2.15", + "version": "2.0.0", "keywords": [ "octopus", "web", @@ -33,7 +33,7 @@ "test-perf": "cypress run --spec test/octopus_perf.test.ts" }, "dependencies": { - "@picovoice/web-utils": "=1.3.1" + "@picovoice/web-utils": "=1.3.2" }, "devDependencies": { "@babel/core": "^7.21.3", @@ -62,6 +62,6 @@ "wasm-feature-detect": "^1.5.0" }, "engines": { - "node": ">=14" + "node": ">=16" } } diff --git a/binding/web/src/index.ts b/binding/web/src/index.ts index e6c621d..45a2564 100644 --- a/binding/web/src/index.ts +++ b/binding/web/src/index.ts @@ -1,5 +1,17 @@ -import { Octopus } from "./octopus"; -import { OctopusWorker } from "./octopus_worker"; +/* + Copyright 2022-2023 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" + file accompanying this source. + + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. +*/ + +import { Octopus } from './octopus'; +import { OctopusWorker } from './octopus_worker'; +import * as OctopusErrors from './octopus_errors'; import { OctopusMetadata, @@ -16,11 +28,11 @@ import { OctopusWorkerSearchResponse, OctopusWorkerReleaseResponse, OctopusWorkerFailureResponse, - OctopusWorkerResponse -} from "./types"; + OctopusWorkerResponse, +} from './types'; -import octopusWasm from "../lib/pv_octopus.wasm"; -import octopusWasmSimd from "../lib/pv_octopus_simd.wasm"; +import octopusWasm from '../lib/pv_octopus.wasm'; +import octopusWasmSimd from '../lib/pv_octopus_simd.wasm'; Octopus.setWasm(octopusWasm); Octopus.setWasmSimd(octopusWasmSimd); @@ -29,6 +41,7 @@ OctopusWorker.setWasmSimd(octopusWasmSimd); export { Octopus, + OctopusErrors, OctopusModel, OctopusOptions, OctopusMetadata, @@ -44,5 +57,5 @@ export { OctopusWorkerSearchResponse, OctopusWorkerReleaseResponse, OctopusWorkerFailureResponse, - OctopusWorkerResponse + OctopusWorkerResponse, }; diff --git a/binding/web/src/octopus.ts b/binding/web/src/octopus.ts index fddb925..04082b9 100644 --- a/binding/web/src/octopus.ts +++ b/binding/web/src/octopus.ts @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -11,64 +11,109 @@ /* eslint camelcase: 0 */ -import { Mutex } from "async-mutex"; +import { Mutex } from 'async-mutex'; -import { simd } from "wasm-feature-detect"; +import { simd } from 'wasm-feature-detect'; import { aligned_alloc_type, - pv_free_type, - buildWasm, arrayBufferToStringAtIndex, + buildWasm, isAccessKeyValid, loadModel, - PvError -} from "@picovoice/web-utils"; + pv_free_type, + PvError, +} from '@picovoice/web-utils'; -import { OctopusMetadata, OctopusMatch, OctopusModel, OctopusOptions } from "./types"; +import { + OctopusMatch, + OctopusMetadata, + OctopusModel, + OctopusOptions, + PvStatus, +} from './types'; + +import * as OctopusErrors from './octopus_errors'; +import { pvStatusToException } from './octopus_errors'; /** * WebAssembly function types */ -type pv_octopus_init_type = (accessKey: number, modelPath: number, object: number) => Promise; -type pv_octopus_index_type = (object: number, pcm: number, numSamples: number, indices: number, numIndicesBytes: number) => Promise; -type pv_octopus_search_type = (object: number, indices: number, numIndicesBytes: number, phrase: number, matches: number, numMatches: number) => Promise; +type pv_octopus_init_type = ( + accessKey: number, + modelPath: number, + object: number +) => Promise; +type pv_octopus_index_size_type = ( + object: number, + numSamples: number, + numIndicesBytes: number +) => Promise; +type pv_octopus_index_type = ( + object: number, + pcm: number, + numSamples: number, + indices: number +) => Promise; +type pv_octopus_search_type = ( + object: number, + indices: number, + numIndicesBytes: number, + phrase: number, + matches: number, + numMatches: number +) => Promise; type pv_octopus_delete_type = (object: number) => Promise; -type pv_status_to_string_type = (status: number) => Promise +type pv_octopus_matches_delete_type = (matches: number) => Promise; +type pv_status_to_string_type = (status: number) => Promise; type pv_sample_rate_type = () => Promise; type pv_octopus_version_type = () => Promise; +type pv_set_sdk_type = (sdk: number) => Promise; +type pv_get_error_stack_type = ( + messageStack: number, + messageStackDepth: number +) => Promise; +type pv_free_error_stack_type = (messageStack: number) => Promise; /** -* JavaScript/WebAssembly Binding for Octopus -*/ + * JavaScript/WebAssembly Binding for Octopus + */ type OctopusWasmOutput = { memory: WebAssembly.Memory; alignedAlloc: aligned_alloc_type; pvFree: pv_free_type; - objectAddress: number; pvOctopusDelete: pv_octopus_delete_type; + pvOctopusMatchesDelete: pv_octopus_matches_delete_type; + pvOctopusIndexSize: pv_octopus_index_size_type; pvOctopusIndex: pv_octopus_index_type; pvOctopusSearch: pv_octopus_search_type; pvStatusToString: pv_status_to_string_type; + pvGetErrorStack: pv_get_error_stack_type; + pvFreeErrorStack: pv_free_error_stack_type; sampleRate: number; version: string; - metadataAddressAddress: number; + objectAddress: number; metadataLengthAddress: number; octopusMatchAddressAddress: number; octopusMatchLengthAddress: number; + messageStackAddressAddressAddress: number; + messageStackDepthAddress: number; pvError: PvError; }; -const PV_STATUS_SUCCESS = 10000; const MAX_PCM_LENGTH_SEC = 60 * 15; export class Octopus { private readonly _pvOctopusDelete: pv_octopus_delete_type; + private readonly _pvOctopusMatchesDelete: pv_octopus_matches_delete_type; + private readonly _pvOctopusIndexSize: pv_octopus_index_size_type; private readonly _pvOctopusIndex: pv_octopus_index_type; private readonly _pvOctopusSearch: pv_octopus_search_type; private readonly _pvStatusToString: pv_status_to_string_type; + private readonly _pvGetErrorStack: pv_get_error_stack_type; + private readonly _pvFreeErrorStack: pv_free_error_stack_type; private _wasmMemory?: WebAssembly.Memory; private _pvFree: pv_free_type; @@ -76,15 +121,17 @@ export class Octopus { private readonly _objectAddress: number; private readonly _alignedAlloc: CallableFunction; - private _metadataAddressAddress: number; - private _metadataLengthAddress: number; - private _octopusMatchAddressAddress: number; - private _octopusMatchLengthAddress: number; + private readonly _messageStackAddressAddressAddress: number; + private readonly _messageStackDepthAddress: number; + private readonly _metadataLengthAddress: number; + private readonly _octopusMatchAddressAddress: number; + private readonly _octopusMatchLengthAddress: number; private static _sampleRate: number; private static _version: string; private static _wasm: string; private static _wasmSimd: string; + private static _sdk: string = 'web'; private static _octopusMutex = new Mutex(); @@ -98,13 +145,19 @@ export class Octopus { this._pvFree = handleWasm.pvFree; this._pvOctopusDelete = handleWasm.pvOctopusDelete; + this._pvOctopusMatchesDelete = handleWasm.pvOctopusMatchesDelete; + this._pvOctopusIndexSize = handleWasm.pvOctopusIndexSize; this._pvOctopusIndex = handleWasm.pvOctopusIndex; this._pvOctopusSearch = handleWasm.pvOctopusSearch; this._pvStatusToString = handleWasm.pvStatusToString; + this._pvGetErrorStack = handleWasm.pvGetErrorStack; + this._pvFreeErrorStack = handleWasm.pvFreeErrorStack; this._wasmMemory = handleWasm.memory; this._objectAddress = handleWasm.objectAddress; - this._metadataAddressAddress = handleWasm.metadataAddressAddress; + this._messageStackAddressAddressAddress = + handleWasm.messageStackAddressAddressAddress; + this._messageStackDepthAddress = handleWasm.messageStackDepthAddress; this._metadataLengthAddress = handleWasm.metadataLengthAddress; this._octopusMatchAddressAddress = handleWasm.octopusMatchAddressAddress; this._octopusMatchLengthAddress = handleWasm.octopusMatchLengthAddress; @@ -114,6 +167,10 @@ export class Octopus { this._pvError = handleWasm.pvError; } + public static setSdk(sdk: string): void { + Octopus._sdk = sdk; + } + /** * Get Octopus engine version. */ @@ -163,27 +220,37 @@ export class Octopus { * * @returns An instance of the Octopus engine. */ - public static async create(accessKey: string, model: OctopusModel, options: OctopusOptions = {}): Promise { - const customWritePath = (model.customWritePath) ? model.customWritePath : 'octopus_model'; + public static async create( + accessKey: string, + model: OctopusModel, + options: OctopusOptions = {} + ): Promise { + const customWritePath = model.customWritePath + ? model.customWritePath + : 'octopus_model'; const modelPath = await loadModel({ ...model, customWritePath }); - return await this._init(accessKey, modelPath, options); + return Octopus._init(accessKey, modelPath, options); } public static async _init( accessKey: string, modelPath: string, - options: OctopusOptions = {}, + options: OctopusOptions = {} ): Promise { if (!isAccessKeyValid(accessKey)) { - throw new Error('Invalid AccessKey'); + throw new OctopusErrors.OctopusInvalidArgumentError('Invalid AccessKey'); } - return new Promise((resolve, reject) => { Octopus._octopusMutex .runExclusive(async () => { const isSimd = await simd(); - const wasmOutput = await Octopus.initWasm(accessKey.trim(), (isSimd) ? this._wasmSimd : this._wasm, modelPath, options); + const wasmOutput = await Octopus.initWasm( + accessKey.trim(), + isSimd ? this._wasmSimd : this._wasm, + modelPath, + options + ); return new Octopus(wasmOutput); }) .then((result: Octopus) => { @@ -204,73 +271,112 @@ export class Octopus { */ public async index(pcm: Int16Array): Promise { if (!(pcm instanceof Int16Array)) { - throw new Error("The argument 'pcm' must be provided as an Int16Array"); + throw new OctopusErrors.OctopusInvalidArgumentError( + "The argument 'pcm' must be provided as an Int16Array" + ); } const maxSize = MAX_PCM_LENGTH_SEC * Octopus._sampleRate * 2; if (pcm.length > maxSize) { - throw new Error(`'pcm' size must be smaller than ${maxSize}`); + throw new OctopusErrors.OctopusInvalidArgumentError( + `'pcm' size must be smaller than ${maxSize}` + ); } - const returnPromise = new Promise((resolve, reject) => { - this._processMutex.runExclusive(async () => { - if (this._wasmMemory === undefined) { - throw new Error("Attempted to call Octopus index after release."); - } - - const pcmAddress = await this._alignedAlloc( - Int16Array.BYTES_PER_ELEMENT, - (pcm.length) * Int16Array.BYTES_PER_ELEMENT - ); - - const memoryBufferInt16 = new Int16Array(this._wasmMemory.buffer); - memoryBufferInt16.set(pcm, pcmAddress / Int16Array.BYTES_PER_ELEMENT); - - const status = await this._pvOctopusIndex( - this._objectAddress, - pcmAddress, - pcm.length, - this._metadataAddressAddress, - this._metadataLengthAddress - ); - await this._pvFree(pcmAddress); - if (status !== PV_STATUS_SUCCESS) { + return new Promise((resolve, reject) => { + this._processMutex + .runExclusive(async () => { + if (this._wasmMemory === undefined) { + throw new OctopusErrors.OctopusInvalidStateError( + 'Attempted to call Octopus index after release.' + ); + } + + const memoryBufferView = new DataView(this._wasmMemory.buffer); const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); - const msg = `process failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await this._pvStatusToString(status), - )}`; - throw new Error( - `${msg}\nDetails: ${this._pvError.getErrorString()}` + let status = await this._pvOctopusIndexSize( + this._objectAddress, + pcm.length, + this._metadataLengthAddress + ); + if (status !== PvStatus.SUCCESS) { + const messageStack = await Octopus.getMessageStack( + this._pvGetErrorStack, + this._pvFreeErrorStack, + this._messageStackAddressAddressAddress, + this._messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); + + throw pvStatusToException( + status, + 'Index size failed', + messageStack + ); + } + + const metadataLength = memoryBufferView.getInt32( + this._metadataLengthAddress, + true + ); + + const pcmAddress = await this._alignedAlloc( + Int16Array.BYTES_PER_ELEMENT, + pcm.length * Int16Array.BYTES_PER_ELEMENT + ); + if (pcmAddress === 0) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + const memoryBufferInt16 = new Int16Array(this._wasmMemory.buffer); + memoryBufferInt16.set(pcm, pcmAddress / Int16Array.BYTES_PER_ELEMENT); + + const metadataAddress = await this._alignedAlloc( + Uint8Array.BYTES_PER_ELEMENT, + Uint8Array.BYTES_PER_ELEMENT * metadataLength + ); + if (metadataAddress === 0) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + + status = await this._pvOctopusIndex( + this._objectAddress, + pcmAddress, + pcm.length, + metadataAddress ); - } - - const memoryBufferView = new DataView(this._wasmMemory.buffer); - - const metadataAddress = memoryBufferView.getInt32( - this._metadataAddressAddress, - true - ); - - const metadataLength = memoryBufferView.getInt32( - this._metadataLengthAddress, - true - ); - - const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer, metadataAddress, metadataLength); - const buffer = new Uint8Array(metadataLength); - buffer.set(memoryBufferUint8, 0); - - await this._pvFree(metadataAddress); - - return { buffer }; - }).then((result: OctopusMetadata) => { - resolve(result); - }).catch((error: any) => { - reject(error); - }); + await this._pvFree(pcmAddress); + if (status !== PvStatus.SUCCESS) { + const messageStack = await Octopus.getMessageStack( + this._pvGetErrorStack, + this._pvFreeErrorStack, + this._messageStackAddressAddressAddress, + this._messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); + + throw pvStatusToException(status, 'Index failed', messageStack); + } + + const buffer = memoryBufferUint8.slice( + metadataAddress / Uint8Array.BYTES_PER_ELEMENT, + metadataAddress / Uint8Array.BYTES_PER_ELEMENT + metadataLength + ); + await this._pvFree(metadataAddress); + return { buffer }; + }) + .then((result: OctopusMetadata) => { + resolve(result); + }) + .catch((error: any) => { + reject(error); + }); }); - return returnPromise; } /** @@ -280,93 +386,120 @@ export class Octopus { * @param searchPhrase - The text phrase to search the metadata (indexed audio) for. * @return An array of OctopusMatch objects. */ - public async search(octopusMetadata: OctopusMetadata, searchPhrase: string): Promise { - const returnPromise = new Promise((resolve, reject) => { - this._processMutex.runExclusive(async () => { - if (this._wasmMemory === undefined) { - throw new Error("Attempted to call Octopus search after release."); - } - - const searchPhraseCleaned = searchPhrase.trim(); - if (searchPhraseCleaned === '') { - throw new Error('The search phrase cannot be empty'); - } - - const encoded = new TextEncoder().encode(searchPhraseCleaned); - - const phraseAddress = await this._alignedAlloc( - Uint8Array.BYTES_PER_ELEMENT, - (encoded.length + 1) * Uint8Array.BYTES_PER_ELEMENT - ); - - if (phraseAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); - } - - const metadataAddress = await this._alignedAlloc( - Uint8Array.BYTES_PER_ELEMENT, - octopusMetadata.buffer.length * Uint8Array.BYTES_PER_ELEMENT - ); - - let memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); - memoryBufferUint8.set(encoded, phraseAddress); - memoryBufferUint8[phraseAddress + encoded.length] = 0; - - memoryBufferUint8.set(octopusMetadata.buffer, metadataAddress); - - const status = await this._pvOctopusSearch( - this._objectAddress, - metadataAddress, - octopusMetadata.buffer.length, - phraseAddress, - this._octopusMatchAddressAddress, - this._octopusMatchLengthAddress - ); - await this._pvFree(phraseAddress); - if (status !== PV_STATUS_SUCCESS) { - memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); - const msg = `process failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await this._pvStatusToString(status), - )}`; - - throw new Error( - `${msg}\nDetails: ${this._pvError.getErrorString()}` - ); - } - - const memoryBufferView = new DataView(this._wasmMemory.buffer); - - const matches: OctopusMatch[] = []; - const octopusMatchAddress = memoryBufferView.getInt32(this._octopusMatchAddressAddress, true); - const octopusMatchLength = memoryBufferView.getInt32(this._octopusMatchLengthAddress, true); + public async search( + octopusMetadata: OctopusMetadata, + searchPhrase: string + ): Promise { + const searchPhraseCleaned = searchPhrase.trim(); + if (searchPhraseCleaned === '') { + throw new OctopusErrors.OctopusInvalidArgumentError( + 'The search phrase cannot be empty' + ); + } - for (let i = 0; i < octopusMatchLength; i++) { - const octopusMatch = octopusMatchAddress + i * (3 * Number(Float32Array.BYTES_PER_ELEMENT)); + return new Promise((resolve, reject) => { + this._processMutex + .runExclusive(async () => { + if (this._wasmMemory === undefined) { + throw new OctopusErrors.OctopusInvalidStateError( + 'Attempted to call Octopus search after release.' + ); + } - const startSec = memoryBufferView.getFloat32(octopusMatch, true); - const endSec = memoryBufferView.getFloat32(octopusMatch + Number(Float32Array.BYTES_PER_ELEMENT), true); - const probability = memoryBufferView.getFloat32(octopusMatch + 2 * Number(Float32Array.BYTES_PER_ELEMENT), true); + const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); + const memoryBufferView = new DataView(this._wasmMemory.buffer); - matches.push({ - startSec, - endSec, - probability - }); - } + const encoded = new TextEncoder().encode(searchPhraseCleaned); - await this._pvFree(octopusMatchAddress); - await this._pvFree(metadataAddress); + const phraseAddress = await this._alignedAlloc( + Uint8Array.BYTES_PER_ELEMENT, + (encoded.length + 1) * Uint8Array.BYTES_PER_ELEMENT + ); + if (phraseAddress === 0) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + memoryBufferUint8.set(encoded, phraseAddress); + memoryBufferUint8[phraseAddress + encoded.length] = 0; + + const metadataAddress = await this._alignedAlloc( + Uint8Array.BYTES_PER_ELEMENT, + octopusMetadata.buffer.length * Uint8Array.BYTES_PER_ELEMENT + ); + if (metadataAddress === 0) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + memoryBufferUint8.set(octopusMetadata.buffer, metadataAddress); + + const status = await this._pvOctopusSearch( + this._objectAddress, + metadataAddress, + octopusMetadata.buffer.length, + phraseAddress, + this._octopusMatchAddressAddress, + this._octopusMatchLengthAddress + ); + await this._pvFree(phraseAddress); + if (status !== PvStatus.SUCCESS) { + const messageStack = await Octopus.getMessageStack( + this._pvGetErrorStack, + this._pvFreeErrorStack, + this._messageStackAddressAddressAddress, + this._messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); + + throw pvStatusToException(status, 'Search failed', messageStack); + } + + const matches: OctopusMatch[] = []; + const octopusMatchAddress = memoryBufferView.getInt32( + this._octopusMatchAddressAddress, + true + ); + const octopusMatchLength = memoryBufferView.getInt32( + this._octopusMatchLengthAddress, + true + ); - return matches; - }).then((result: OctopusMatch[]) => { - resolve(result); - }).catch((error: any) => { - reject(error); - }); + for (let i = 0; i < octopusMatchLength; i++) { + const octopusMatch = + octopusMatchAddress + + i * (3 * Number(Float32Array.BYTES_PER_ELEMENT)); + + const startSec = memoryBufferView.getFloat32(octopusMatch, true); + const endSec = memoryBufferView.getFloat32( + octopusMatch + Number(Float32Array.BYTES_PER_ELEMENT), + true + ); + const probability = memoryBufferView.getFloat32( + octopusMatch + 2 * Number(Float32Array.BYTES_PER_ELEMENT), + true + ); + + matches.push({ + startSec, + endSec, + probability, + }); + } + + await this._pvOctopusMatchesDelete(octopusMatchAddress); + await this._pvFree(metadataAddress); + + return matches; + }) + .then((result: OctopusMatch[]) => { + resolve(result); + }) + .catch((error: any) => { + reject(error); + }); }); - - return returnPromise; } /** @@ -374,14 +507,25 @@ export class Octopus { */ public async release(): Promise { await this._pvOctopusDelete(this._objectAddress); + await this._pvFree(this._messageStackAddressAddressAddress); + await this._pvFree(this._messageStackDepthAddress); + await this._pvFree(this._metadataLengthAddress); + await this._pvFree(this._octopusMatchAddressAddress); + await this._pvFree(this._octopusMatchLengthAddress); delete this._wasmMemory; this._wasmMemory = undefined; } - private static async initWasm(accessKey: string, wasmBase64: string, modelPath: string, options: OctopusOptions): Promise { + private static async initWasm( + accessKey: string, + wasmBase64: string, + modelPath: string, + _: OctopusOptions + ): Promise { // A WebAssembly page has a constant size of 64KiB. -> 1MiB ~= 16 pages - const memory = new WebAssembly.Memory({ initial: 1024 }); + const memory = new WebAssembly.Memory({ initial: 10240 }); + const memoryBufferView = new DataView(memory.buffer); const memoryBufferUint8 = new Uint8Array(memory.buffer); const pvError = new PvError(); @@ -391,28 +535,35 @@ export class Octopus { const aligned_alloc = exports.aligned_alloc as aligned_alloc_type; const pv_free = exports.pv_free as pv_free_type; - const pv_octopus_version = exports.pv_octopus_version as pv_octopus_version_type; + const pv_octopus_version = + exports.pv_octopus_version as pv_octopus_version_type; + const pv_octopus_index_size = + exports.pv_octopus_index_size as pv_octopus_index_size_type; const pv_octopus_index = exports.pv_octopus_index as pv_octopus_index_type; - const pv_octopus_search = exports.pv_octopus_search as pv_octopus_search_type; - const pv_octopus_delete = exports.pv_octopus_delete as pv_octopus_delete_type; + const pv_octopus_search = + exports.pv_octopus_search as pv_octopus_search_type; + const pv_octopus_delete = + exports.pv_octopus_delete as pv_octopus_delete_type; + const pv_octopus_matches_delete = + exports.pv_octopus_matches_delete as pv_octopus_matches_delete_type; const pv_octopus_init = exports.pv_octopus_init as pv_octopus_init_type; - const pv_status_to_string = exports.pv_status_to_string as pv_status_to_string_type; + const pv_status_to_string = + exports.pv_status_to_string as pv_status_to_string_type; const pv_sample_rate = exports.pv_sample_rate as pv_sample_rate_type; - - const metadataAddressAddress = await aligned_alloc( - Int32Array.BYTES_PER_ELEMENT, - Int32Array.BYTES_PER_ELEMENT - ); - if (metadataAddressAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); - } + const pv_set_sdk = exports.pv_set_sdk as pv_set_sdk_type; + const pv_get_error_stack = + exports.pv_get_error_stack as pv_get_error_stack_type; + const pv_free_error_stack = + exports.pv_free_error_stack as pv_free_error_stack_type; const metadataLengthAddress = await aligned_alloc( Int32Array.BYTES_PER_ELEMENT, Int32Array.BYTES_PER_ELEMENT ); if (metadataLengthAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const octopusMatchAddressAddress = await aligned_alloc( @@ -420,7 +571,9 @@ export class Octopus { Int32Array.BYTES_PER_ELEMENT ); if (octopusMatchAddressAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const octopusMatchLengthAddress = await aligned_alloc( @@ -428,7 +581,9 @@ export class Octopus { Int32Array.BYTES_PER_ELEMENT ); if (octopusMatchLengthAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const objectAddressAddress = await aligned_alloc( @@ -436,7 +591,9 @@ export class Octopus { Int32Array.BYTES_PER_ELEMENT ); if (objectAddressAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const accessKeyAddress = await aligned_alloc( @@ -444,7 +601,9 @@ export class Octopus { (accessKey.length + 1) * Uint8Array.BYTES_PER_ELEMENT ); if (accessKeyAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } for (let i = 0; i < accessKey.length; i++) { memoryBufferUint8[accessKeyAddress + i] = accessKey.charCodeAt(i); @@ -457,58 +616,146 @@ export class Octopus { (encodedModelPath.length + 1) * Uint8Array.BYTES_PER_ELEMENT ); if (modelPathAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } memoryBufferUint8.set(encodedModelPath, modelPathAddress); memoryBufferUint8[modelPathAddress + encodedModelPath.length] = 0; - const status = await pv_octopus_init(accessKeyAddress, modelPathAddress, objectAddressAddress); - await pv_free(accessKeyAddress); - if (status !== PV_STATUS_SUCCESS) { - const msg = `'pv_octopus_init' failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await pv_status_to_string(status) - )}`; + const sdkEncoded = new TextEncoder().encode(this._sdk); + const sdkAddress = await aligned_alloc( + Uint8Array.BYTES_PER_ELEMENT, + (sdkEncoded.length + 1) * Uint8Array.BYTES_PER_ELEMENT + ); + if (!sdkAddress) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + memoryBufferUint8.set(sdkEncoded, sdkAddress); + memoryBufferUint8[sdkAddress + sdkEncoded.length] = 0; + await pv_set_sdk(sdkAddress); - throw new Error( - `${msg}\nDetails: ${pvError.getErrorString()}` + const messageStackDepthAddress = await aligned_alloc( + Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT + ); + if (!messageStackDepthAddress) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' ); } - const memoryBuffer = new Uint8Array(memory.buffer); - const memoryBufferView = new DataView(memory.buffer); - const objectAddress = memoryBufferView.getInt32( - objectAddressAddress, - true + const messageStackAddressAddressAddress = await aligned_alloc( + Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT ); + if (!messageStackAddressAddressAddress) { + throw new OctopusErrors.OctopusOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + + const status = await pv_octopus_init( + accessKeyAddress, + modelPathAddress, + objectAddressAddress + ); + await pv_free(accessKeyAddress); + await pv_free(modelPathAddress); + if (status !== PvStatus.SUCCESS) { + const messageStack = await Octopus.getMessageStack( + pv_get_error_stack, + pv_free_error_stack, + messageStackAddressAddressAddress, + messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); + + throw pvStatusToException( + status, + 'Initialization failed', + messageStack, + pvError + ); + } + + const objectAddress = memoryBufferView.getInt32(objectAddressAddress, true); + await pv_free(objectAddressAddress); const sampleRate = await pv_sample_rate(); const versionAddress = await pv_octopus_version(); const version = arrayBufferToStringAtIndex( - memoryBuffer, - versionAddress, + memoryBufferUint8, + versionAddress ); - await pv_free(objectAddressAddress); - await pv_free(accessKeyAddress); - await pv_free(modelPathAddress); - return { memory: memory, alignedAlloc: aligned_alloc, pvFree: pv_free, pvOctopusDelete: pv_octopus_delete, + pvOctopusMatchesDelete: pv_octopus_matches_delete, + pvOctopusIndexSize: pv_octopus_index_size, pvOctopusIndex: pv_octopus_index, pvOctopusSearch: pv_octopus_search, pvStatusToString: pv_status_to_string, + pvGetErrorStack: pv_get_error_stack, + pvFreeErrorStack: pv_free_error_stack, sampleRate: sampleRate, version: version, objectAddress: objectAddress, - metadataAddressAddress: metadataAddressAddress, metadataLengthAddress: metadataLengthAddress, octopusMatchAddressAddress: octopusMatchAddressAddress, octopusMatchLengthAddress: octopusMatchLengthAddress, - pvError: pvError + messageStackAddressAddressAddress: messageStackAddressAddressAddress, + messageStackDepthAddress: messageStackDepthAddress, + pvError: pvError, }; } + + private static async getMessageStack( + pv_get_error_stack: pv_get_error_stack_type, + pv_free_error_stack: pv_free_error_stack_type, + messageStackAddressAddressAddress: number, + messageStackDepthAddress: number, + memoryBufferView: DataView, + memoryBufferUint8: Uint8Array + ): Promise { + const status = await pv_get_error_stack( + messageStackAddressAddressAddress, + messageStackDepthAddress + ); + if (status !== PvStatus.SUCCESS) { + throw pvStatusToException(status, 'Unable to get Octopus error state'); + } + + const messageStackAddressAddress = memoryBufferView.getInt32( + messageStackAddressAddressAddress, + true + ); + + const messageStackDepth = memoryBufferView.getInt32( + messageStackDepthAddress, + true + ); + const messageStack: string[] = []; + for (let i = 0; i < messageStackDepth; i++) { + const messageStackAddress = memoryBufferView.getInt32( + messageStackAddressAddress + i * Int32Array.BYTES_PER_ELEMENT, + true + ); + const message = arrayBufferToStringAtIndex( + memoryBufferUint8, + messageStackAddress + ); + messageStack.push(message); + } + + await pv_free_error_stack(messageStackAddressAddress); + + return messageStack; + } } diff --git a/binding/web/src/octopus_errors.ts b/binding/web/src/octopus_errors.ts new file mode 100644 index 0000000..2c7c612 --- /dev/null +++ b/binding/web/src/octopus_errors.ts @@ -0,0 +1,256 @@ +// +// Copyright 2023 Picovoice Inc. +// +// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" +// file accompanying this source. +// +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// + +import { PvError } from '@picovoice/web-utils'; +import { PvStatus } from './types'; + +class OctopusError extends Error { + private readonly _status: PvStatus; + private readonly _shortMessage: string; + private readonly _messageStack: string[]; + + constructor( + status: PvStatus, + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(OctopusError.errorToString(message, messageStack, pvError)); + this._status = status; + this.name = 'OctopusError'; + this._shortMessage = message; + this._messageStack = messageStack; + } + + get status(): PvStatus { + return this._status; + } + + get shortMessage(): string { + return this._shortMessage; + } + + get messageStack(): string[] { + return this._messageStack; + } + + private static errorToString( + initial: string, + messageStack: string[], + pvError: PvError | null = null + ): string { + let msg = initial; + + if (pvError) { + const pvErrorMessage = pvError.getErrorString(); + if (pvErrorMessage.length > 0) { + msg += `\nDetails: ${pvErrorMessage}`; + } + } + + if (messageStack.length > 0) { + msg += `: ${messageStack.reduce( + (acc, value, index) => acc + '\n [' + index + '] ' + value, + '' + )}`; + } + + return msg; + } +} + +class OctopusOutOfMemoryError extends OctopusError { + constructor( + message: string, + messageStack?: string[], + pvError: PvError | null = null + ) { + super(PvStatus.OUT_OF_MEMORY, message, messageStack, pvError); + this.name = 'OctopusOutOfMemoryError'; + } +} + +class OctopusIOError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.IO_ERROR, message, messageStack, pvError); + this.name = 'OctopusIOError'; + } +} + +class OctopusInvalidArgumentError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.INVALID_ARGUMENT, message, messageStack, pvError); + this.name = 'OctopusInvalidArgumentError'; + } +} + +class OctopusStopIterationError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.STOP_ITERATION, message, messageStack, pvError); + this.name = 'OctopusStopIterationError'; + } +} + +class OctopusKeyError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.KEY_ERROR, message, messageStack, pvError); + this.name = 'OctopusKeyError'; + } +} + +class OctopusInvalidStateError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.INVALID_STATE, message, messageStack, pvError); + this.name = 'OctopusInvalidStateError'; + } +} + +class OctopusRuntimeError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.RUNTIME_ERROR, message, messageStack, pvError); + this.name = 'OctopusRuntimeError'; + } +} + +class OctopusActivationError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_ERROR, message, messageStack, pvError); + this.name = 'OctopusActivationError'; + } +} + +class OctopusActivationLimitReachedError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_LIMIT_REACHED, message, messageStack, pvError); + this.name = 'OctopusActivationLimitReachedError'; + } +} + +class OctopusActivationThrottledError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_THROTTLED, message, messageStack, pvError); + this.name = 'OctopusActivationThrottledError'; + } +} + +class OctopusActivationRefusedError extends OctopusError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_REFUSED, message, messageStack, pvError); + this.name = 'OctopusActivationRefusedError'; + } +} + +export { + OctopusError, + OctopusOutOfMemoryError, + OctopusIOError, + OctopusInvalidArgumentError, + OctopusStopIterationError, + OctopusKeyError, + OctopusInvalidStateError, + OctopusRuntimeError, + OctopusActivationError, + OctopusActivationLimitReachedError, + OctopusActivationThrottledError, + OctopusActivationRefusedError, +}; + +export function pvStatusToException( + pvStatus: PvStatus, + errorMessage: string, + messageStack: string[] = [], + pvError: PvError | null = null +): OctopusError { + switch (pvStatus) { + case PvStatus.OUT_OF_MEMORY: + return new OctopusOutOfMemoryError(errorMessage, messageStack, pvError); + case PvStatus.IO_ERROR: + return new OctopusIOError(errorMessage, messageStack, pvError); + case PvStatus.INVALID_ARGUMENT: + return new OctopusInvalidArgumentError( + errorMessage, + messageStack, + pvError + ); + case PvStatus.STOP_ITERATION: + return new OctopusStopIterationError(errorMessage, messageStack, pvError); + case PvStatus.KEY_ERROR: + return new OctopusKeyError(errorMessage, messageStack, pvError); + case PvStatus.INVALID_STATE: + return new OctopusInvalidStateError(errorMessage, messageStack, pvError); + case PvStatus.RUNTIME_ERROR: + return new OctopusRuntimeError(errorMessage, messageStack, pvError); + case PvStatus.ACTIVATION_ERROR: + return new OctopusActivationError(errorMessage, messageStack, pvError); + case PvStatus.ACTIVATION_LIMIT_REACHED: + return new OctopusActivationLimitReachedError( + errorMessage, + messageStack, + pvError + ); + case PvStatus.ACTIVATION_THROTTLED: + return new OctopusActivationThrottledError( + errorMessage, + messageStack, + pvError + ); + case PvStatus.ACTIVATION_REFUSED: + return new OctopusActivationRefusedError( + errorMessage, + messageStack, + pvError + ); + default: + // eslint-disable-next-line no-console + console.warn(`Unmapped error code: ${pvStatus}`); + return new OctopusError(pvStatus, errorMessage); + } +} diff --git a/binding/web/src/octopus_worker.ts b/binding/web/src/octopus_worker.ts index 22e4e33..3b63d61 100644 --- a/binding/web/src/octopus_worker.ts +++ b/binding/web/src/octopus_worker.ts @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -9,7 +9,7 @@ specific language governing permissions and limitations under the License. */ -import PvWorker from "web-worker:./octopus_worker_handler.ts"; +import PvWorker from 'web-worker:./octopus_worker_handler.ts'; import { OctopusMatch, @@ -20,13 +20,16 @@ import { OctopusWorkerIndexResponse, OctopusWorkerSearchResponse, OctopusWorkerReleaseResponse, -} from "./types"; -import {loadModel} from "@picovoice/web-utils"; + PvStatus, +} from './types'; +import { loadModel } from '@picovoice/web-utils'; +import { pvStatusToException } from './octopus_errors'; export class OctopusWorker { private readonly _worker: Worker; private readonly _version: string; private readonly _sampleRate: number; + private static _sdk: string = 'web'; private static _wasm: string; private static _wasmSimd: string; @@ -78,6 +81,10 @@ export class OctopusWorker { } } + public static setSdk(sdk: string): void { + OctopusWorker._sdk = sdk; + } + /** * Creates a worker instance of the Picovoice Octopus Speech-to-Text engine. * Behind the scenes, it requires the WebAssembly code to load and initialize before @@ -93,37 +100,65 @@ export class OctopusWorker { * * @returns An instance of OctopusWorker. */ - public static async create(accessKey: string, model: OctopusModel, options: OctopusOptions = {}): Promise { - const customWritePath = (model.customWritePath) ? model.customWritePath : 'octopus_model'; + public static async create( + accessKey: string, + model: OctopusModel, + options: OctopusOptions = {} + ): Promise { + const customWritePath = model.customWritePath + ? model.customWritePath + : 'octopus_model'; const modelPath = await loadModel({ ...model, customWritePath }); const worker = new PvWorker(); - const returnPromise: Promise = new Promise((resolve, reject) => { - // @ts-ignore - block from GC - this.worker = worker; - worker.onmessage = (event: MessageEvent): void => { - switch (event.data.command) { - case "ok": - resolve(new OctopusWorker(worker, event.data.version, event.data.sampleRate)); - break; - case "failed": - case "error": - reject(event.data.message); - break; - default: - // @ts-ignore - reject(`Unrecognized command: ${event.data.command}`); - } - }; - }); + const returnPromise: Promise = new Promise( + (resolve, reject) => { + // @ts-ignore - block from GC + this.worker = worker; + worker.onmessage = ( + event: MessageEvent + ): void => { + switch (event.data.command) { + case 'ok': + resolve( + new OctopusWorker( + worker, + event.data.version, + event.data.sampleRate + ) + ); + break; + case 'failed': + case 'error': + reject( + pvStatusToException( + event.data.status, + event.data.shortMessage, + event.data.messageStack + ) + ); + break; + default: + reject( + pvStatusToException( + PvStatus.RUNTIME_ERROR, + // @ts-ignore + `Unrecognized command: ${event.data.command}` + ) + ); + } + }; + } + ); worker.postMessage({ - command: "init", + command: 'init', accessKey: accessKey, modelPath: modelPath, options: options, wasm: this._wasm, wasmSimd: this._wasmSimd, + sdk: this._sdk, }); return returnPromise; @@ -143,37 +178,58 @@ export class OctopusWorker { */ public index( pcm: Int16Array, - options : { transfer?: boolean, transferCallback?: (data: Int16Array) => void } = {} + options: { + transfer?: boolean; + transferCallback?: (data: Int16Array) => void; + } = {} ): Promise { const { transfer = false, transferCallback } = options; - const returnPromise: Promise = new Promise((resolve, reject) => { - this._worker.onmessage = (event: MessageEvent): void => { - if (transfer && transferCallback && event.data.inputFrame) { - transferCallback(new Int16Array(event.data.inputFrame.buffer)); - } - switch (event.data.command) { - case "ok": - resolve(event.data.result); - break; - case "failed": - case "error": - reject(event.data.message); - break; - default: - // @ts-ignore - reject(`Unrecognized command: ${event.data.command}`); - } - }; - }); + const returnPromise: Promise = new Promise( + (resolve, reject) => { + this._worker.onmessage = ( + event: MessageEvent + ): void => { + if (transfer && transferCallback && event.data.inputFrame) { + transferCallback(new Int16Array(event.data.inputFrame.buffer)); + } + switch (event.data.command) { + case 'ok': + resolve(event.data.result); + break; + case 'failed': + case 'error': + reject( + pvStatusToException( + event.data.status, + event.data.shortMessage, + event.data.messageStack + ) + ); + break; + default: + reject( + pvStatusToException( + PvStatus.RUNTIME_ERROR, + // @ts-ignore + `Unrecognized command: ${event.data.command}` + ) + ); + } + }; + } + ); - const transferable = (transfer) ? [pcm.buffer] : []; + const transferable = transfer ? [pcm.buffer] : []; - this._worker.postMessage({ - command: "index", - inputFrame: pcm, - transfer: transfer - }, transferable); + this._worker.postMessage( + { + command: 'index', + inputFrame: pcm, + transfer: transfer, + }, + transferable + ); return returnPromise; } @@ -185,28 +241,46 @@ export class OctopusWorker { * @param searchPhrase - The text phrase to search the metadata (indexed audio) for. * @return An array of OctopusMatch objects. */ - public search(octopusMetadata: OctopusMetadata, searchPhrase: string): Promise { - const returnPromise: Promise = new Promise((resolve, reject) => { - this._worker.onmessage = (event: MessageEvent): void => { - switch (event.data.command) { - case "ok": - resolve(event.data.result); - break; - case "failed": - case "error": - reject(event.data.message); - break; - default: - // @ts-ignore - reject(`Unrecognized command: ${event.data.command}`); - } - }; - }); + public search( + octopusMetadata: OctopusMetadata, + searchPhrase: string + ): Promise { + const returnPromise: Promise = new Promise( + (resolve, reject) => { + this._worker.onmessage = ( + event: MessageEvent + ): void => { + switch (event.data.command) { + case 'ok': + resolve(event.data.result); + break; + case 'failed': + case 'error': + reject( + pvStatusToException( + event.data.status, + event.data.shortMessage, + event.data.messageStack + ) + ); + break; + default: + reject( + pvStatusToException( + PvStatus.RUNTIME_ERROR, + // @ts-ignore + `Unrecognized command: ${event.data.command}` + ) + ); + } + }; + } + ); this._worker.postMessage({ - command: "search", + command: 'search', metadata: octopusMetadata, - searchPhrase: searchPhrase + searchPhrase: searchPhrase, }); return returnPromise; @@ -217,24 +291,37 @@ export class OctopusWorker { */ public release(): Promise { const returnPromise: Promise = new Promise((resolve, reject) => { - this._worker.onmessage = (event: MessageEvent): void => { + this._worker.onmessage = ( + event: MessageEvent + ): void => { switch (event.data.command) { - case "ok": + case 'ok': resolve(); break; - case "failed": - case "error": - reject(event.data.message); + case 'failed': + case 'error': + reject( + pvStatusToException( + event.data.status, + event.data.shortMessage, + event.data.messageStack + ) + ); break; default: - // @ts-ignore - reject(`Unrecognized command: ${event.data.command}`); + reject( + pvStatusToException( + PvStatus.RUNTIME_ERROR, + // @ts-ignore + `Unrecognized command: ${event.data.command}` + ) + ); } }; }); this._worker.postMessage({ - command: "release" + command: 'release', }); return returnPromise; diff --git a/binding/web/src/octopus_worker_handler.ts b/binding/web/src/octopus_worker_handler.ts index eef3285..5505fb8 100644 --- a/binding/web/src/octopus_worker_handler.ts +++ b/binding/web/src/octopus_worker_handler.ts @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -12,101 +12,162 @@ /// /// -import { Octopus } from "./octopus"; -import { OctopusWorkerRequest } from "./types"; +import { Octopus } from './octopus'; +import { + OctopusWorkerIndexRequest, + OctopusWorkerInitRequest, + OctopusWorkerRequest, + OctopusWorkerSearchRequest, + PvStatus, +} from './types'; +import { OctopusError } from './octopus_errors'; + +let octopus: Octopus | null = null; + +const initRequest = async (request: OctopusWorkerInitRequest): Promise => { + if (octopus !== null) { + return { + command: 'error', + status: PvStatus.INVALID_STATE, + shortMessage: 'Octopus already initialized', + }; + } + try { + Octopus.setWasm(request.wasm); + Octopus.setWasmSimd(request.wasmSimd); + Octopus.setSdk(request.sdk); + octopus = await Octopus._init( + request.accessKey, + request.modelPath, + request.options + ); + return { + command: 'ok', + version: octopus.version, + sampleRate: octopus.sampleRate, + }; + } catch (e: any) { + if (e instanceof OctopusError) { + return { + command: 'error', + status: e.status, + shortMessage: e.shortMessage, + messageStack: e.messageStack, + }; + } + return { + command: 'error', + status: PvStatus.RUNTIME_ERROR, + shortMessage: e.message, + }; + } +}; + +const indexRequest = async ( + request: OctopusWorkerIndexRequest +): Promise => { + if (octopus === null) { + return { + command: 'error', + status: PvStatus.INVALID_STATE, + shortMessage: 'Octopus already initialized', + inputFrame: request.inputFrame, + }; + } + try { + return { + command: 'ok', + result: await octopus.index(request.inputFrame), + inputFrame: request.transfer ? request.inputFrame : undefined, + }; + } catch (e: any) { + if (e instanceof OctopusError) { + return { + command: 'error', + status: e.status, + shortMessage: e.shortMessage, + messageStack: e.messageStack, + }; + } + return { + command: 'error', + status: PvStatus.RUNTIME_ERROR, + shortMessage: e.message, + }; + } +}; + +const searchRequest = async ( + request: OctopusWorkerSearchRequest +): Promise => { + if (octopus === null) { + return { + command: 'error', + status: PvStatus.INVALID_STATE, + shortMessage: 'Octopus already initialized', + }; + } + try { + return { + command: 'ok', + result: await octopus.search(request.metadata, request.searchPhrase), + }; + } catch (e: any) { + if (e instanceof OctopusError) { + return { + command: 'error', + status: e.status, + shortMessage: e.shortMessage, + messageStack: e.messageStack, + }; + } + return { + command: 'error', + status: PvStatus.RUNTIME_ERROR, + shortMessage: e.message, + }; + } +}; + +const releaseRequest = async (): Promise => { + if (octopus !== null) { + await octopus.release(); + octopus = null; + close(); + } + return { + command: 'ok', + }; +}; /** * Octopus worker handler. */ -let octopus: Octopus | null = null; self.onmessage = async function ( event: MessageEvent ): Promise { switch (event.data.command) { case 'init': - if (octopus !== null) { - self.postMessage({ - command: "error", - message: "Octopus already initialized" - }); - return; - } - try { - Octopus.setWasm(event.data.wasm); - Octopus.setWasmSimd(event.data.wasmSimd); - octopus = await Octopus._init(event.data.accessKey, event.data.modelPath, event.data.options); - self.postMessage({ - command: "ok", - version: octopus.version, - sampleRate: octopus.sampleRate - }); - } catch (e: any) { - self.postMessage({ - command: "error", - message: e.message - }); - } + self.postMessage(await initRequest(event.data)); break; case 'index': - // eslint-disable-next-line no-case-declarations - const transferable = (event.data.transfer) ? [event.data.inputFrame.buffer] : []; - if (octopus === null) { - self.postMessage({ - command: "error", - message: "Octopus not initialized", - inputFrame: event.data.inputFrame - }, transferable); - return; - } - try { - self.postMessage({ - command: "ok", - result: await octopus.index(event.data.inputFrame), - inputFrame: (event.data.transfer) ? event.data.inputFrame : undefined - }, transferable); - } catch (e: any) { - self.postMessage({ - command: "error", - message: e.message, - inputFrame: event.data.inputFrame - }, transferable); - } + self.postMessage( + await indexRequest(event.data), + event.data.transfer ? [event.data.inputFrame.buffer] : [] + ); break; case 'search': - if (octopus === null) { - self.postMessage({ - command: "error", - message: "Octopus not initialized", - }); - return; - } - try { - self.postMessage({ - command: "ok", - result: await octopus.search(event.data.metadata, event.data.searchPhrase) - }); - } catch (e: any) { - self.postMessage({ - command: "error", - message: e.message - }); - } + self.postMessage(await searchRequest(event.data)); break; case 'release': - if (octopus !== null) { - await octopus.release(); - octopus = null; - close(); - } - self.postMessage({ - command: "ok" - }); + self.postMessage(await releaseRequest()); break; default: self.postMessage({ - command: "failed", + command: 'failed', + status: PvStatus.RUNTIME_ERROR, // @ts-ignore - message: `Unrecognized command: ${event.data.command}` + shortMessage: `Unrecognized command: ${event.data.command}`, }); } }; diff --git a/binding/web/src/types.ts b/binding/web/src/types.ts index e12528a..449b284 100644 --- a/binding/web/src/types.ts +++ b/binding/web/src/types.ts @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -11,6 +11,21 @@ import { PvModel } from '@picovoice/web-utils'; +export enum PvStatus { + SUCCESS = 10000, + OUT_OF_MEMORY, + IO_ERROR, + INVALID_ARGUMENT, + STOP_ITERATION, + KEY_ERROR, + INVALID_STATE, + RUNTIME_ERROR, + ACTIVATION_ERROR, + ACTIVATION_LIMIT_REACHED, + ACTIVATION_THROTTLED, + ACTIVATION_REFUSED, +} + /** * OctopusModel types */ @@ -39,6 +54,7 @@ export type OctopusWorkerInitRequest = { options: OctopusOptions; wasm: string; wasmSimd: string; + sdk: string; }; export type OctopusWorkerIndexRequest = { @@ -59,40 +75,50 @@ export type OctopusWorkerReleaseRequest = { }; export type OctopusWorkerRequest = - OctopusWorkerInitRequest | - OctopusWorkerIndexRequest | - OctopusWorkerSearchRequest | - OctopusWorkerReleaseRequest; + | OctopusWorkerInitRequest + | OctopusWorkerIndexRequest + | OctopusWorkerSearchRequest + | OctopusWorkerReleaseRequest; export type OctopusWorkerFailureResponse = { command: 'failed' | 'error'; - message: string; + status: PvStatus; + shortMessage: string; + messageStack: string[]; inputFrame?: Int16Array; }; -export type OctopusWorkerInitResponse = OctopusWorkerFailureResponse | { - command: 'ok'; - sampleRate: number; - version: string; -}; - -export type OctopusWorkerIndexResponse = OctopusWorkerFailureResponse | { - command: 'ok'; - result: OctopusMetadata; - inputFrame?: Int16Array; -}; - -export type OctopusWorkerSearchResponse = OctopusWorkerFailureResponse | { - command: 'ok'; - result: OctopusMatch[]; -}; - -export type OctopusWorkerReleaseResponse = OctopusWorkerFailureResponse | { - command: 'ok'; -}; +export type OctopusWorkerInitResponse = + | OctopusWorkerFailureResponse + | { + command: 'ok'; + sampleRate: number; + version: string; + }; + +export type OctopusWorkerIndexResponse = + | OctopusWorkerFailureResponse + | { + command: 'ok'; + result: OctopusMetadata; + inputFrame?: Int16Array; + }; + +export type OctopusWorkerSearchResponse = + | OctopusWorkerFailureResponse + | { + command: 'ok'; + result: OctopusMatch[]; + }; + +export type OctopusWorkerReleaseResponse = + | OctopusWorkerFailureResponse + | { + command: 'ok'; + }; export type OctopusWorkerResponse = - OctopusWorkerInitResponse | - OctopusWorkerIndexResponse | - OctopusWorkerSearchResponse | - OctopusWorkerReleaseResponse; + | OctopusWorkerInitResponse + | OctopusWorkerIndexResponse + | OctopusWorkerSearchResponse + | OctopusWorkerReleaseResponse; diff --git a/binding/web/test/octopus.test.ts b/binding/web/test/octopus.test.ts index fd473f9..6ea9ac2 100644 --- a/binding/web/test/octopus.test.ts +++ b/binding/web/test/octopus.test.ts @@ -1,12 +1,12 @@ -import { Octopus, OctopusWorker } from "../"; +import { Octopus, OctopusWorker } from '../'; // @ts-ignore -import octopusParams from "./octopus_params"; +import octopusParams from './octopus_params'; import { PvModel } from '@picovoice/web-utils'; -import { testResults, languages } from "./test_results"; +import { testResults, languages } from './test_results'; -const ACCESS_KEY: string = Cypress.env("ACCESS_KEY"); +const ACCESS_KEY: string = Cypress.env('ACCESS_KEY'); const assertInBetween = (value: number, expected: number, epsilon = 0.01) => { expect(Math.abs(value - expected)).to.be.lte(epsilon); @@ -15,9 +15,9 @@ const assertInBetween = (value: number, expected: number, epsilon = 0.01) => { const runInitTest = async ( instance: typeof Octopus | typeof OctopusWorker, params: { - accessKey?: string, - model?: PvModel, - expectFailure?: boolean, + accessKey?: string; + model?: PvModel; + expectFailure?: boolean; } = {} ) => { const { @@ -57,8 +57,8 @@ const runProcTest = async ( inputPcm: Int16Array, results: any[], params: { - accessKey?: string, - model?: PvModel, + accessKey?: string; + model?: PvModel; } = {} ) => { const { @@ -70,7 +70,6 @@ const runProcTest = async ( const octopus = await instance.create(accessKey, model); const metadata = await octopus.index(inputPcm); - for (const { phrase, expected } of results) { const res = await octopus.search(metadata, phrase); expect(res.length).to.be.gt(0); @@ -91,9 +90,9 @@ const runProcTest = async ( } }; -describe("Octopus Binding", function () { +describe('Octopus Binding', function () { for (const instance of [Octopus, OctopusWorker]) { - const instanceString = (instance === OctopusWorker) ? 'worker' : 'main'; + const instanceString = instance === OctopusWorker ? 'worker' : 'main'; it(`should be able to init with public path (${instanceString})`, () => { cy.wrap(null).then(async () => { @@ -104,7 +103,7 @@ describe("Octopus Binding", function () { it(`should be able to init with base64 (${instanceString})`, () => { cy.wrap(null).then(async () => { await runInitTest(instance, { - model: { base64: octopusParams, forceWrite: true } + model: { base64: octopusParams, forceWrite: true }, }); }); }); @@ -112,7 +111,11 @@ describe("Octopus Binding", function () { it(`should be able to handle UTF-8 public path (${instanceString})`, () => { cy.wrap(null).then(async () => { await runInitTest(instance, { - model: { publicPath: '/test/octopus_params.pv', forceWrite: true, customWritePath: '테스트' } + model: { + publicPath: '/test/octopus_params.pv', + forceWrite: true, + customWritePath: '테스트', + }, }); }); }); @@ -121,7 +124,7 @@ describe("Octopus Binding", function () { cy.wrap(null).then(async () => { await runInitTest(instance, { model: { publicPath: 'invalid', forceWrite: true }, - expectFailure: true + expectFailure: true, }); }); }); @@ -130,7 +133,7 @@ describe("Octopus Binding", function () { cy.wrap(null).then(async () => { await runInitTest(instance, { model: { base64: 'invalid', forceWrite: true }, - expectFailure: true + expectFailure: true, }); }); }); @@ -139,23 +142,50 @@ describe("Octopus Binding", function () { cy.wrap(null).then(async () => { await runInitTest(instance, { accessKey: 'invalid', - expectFailure: true + expectFailure: true, }); }); }); + it(`should return correct error message stack (${instanceString})`, async () => { + let messageStack = []; + try { + const octopus = await instance.create('invalidAccessKey', { + publicPath: '/test/octopus_params.pv', + forceWrite: true, + }); + expect(octopus).to.be.undefined; + } catch (e: any) { + messageStack = e.messageStack; + } + + expect(messageStack.length).to.be.gt(0); + expect(messageStack.length).to.be.lte(8); + + try { + const octopus = await instance.create('invalidAccessKey', { + publicPath: '/test/octopus_params.pv', + forceWrite: true, + }); + expect(octopus).to.be.undefined; + } catch (e: any) { + expect(messageStack.length).to.be.eq(e.messageStack.length); + } + }); + for (const language of languages) { it(`should be able to process (${language}) (${instanceString})`, () => { try { - const suffix = (language === 'en') ? '' : `_${language}`; - cy.getFramesFromFile(`audio_samples/multiple_keywords${suffix}.wav`).then( async pcm => { - await runProcTest( - instance, - pcm, - testResults[language], - { - model: { publicPath: `/test/octopus_params${suffix}.pv`, forceWrite: true }, - }); + const suffix = language === 'en' ? '' : `_${language}`; + cy.getFramesFromFile( + `audio_samples/multiple_keywords${suffix}.wav` + ).then(async pcm => { + await runProcTest(Octopus, pcm, testResults[language], { + model: { + publicPath: `/test/octopus_params${suffix}.pv`, + forceWrite: true, + }, + }); }); } catch (e) { expect(e).to.be.undefined; @@ -166,19 +196,26 @@ describe("Octopus Binding", function () { it(`should be able to transfer buffer`, () => { try { - cy.getFramesFromFile(`audio_samples/multiple_keywords.wav`).then( async pcm => { - const octopus = await OctopusWorker.create( - ACCESS_KEY, - { publicPath: '/test/octopus_params.pv', forceWrite: true } - ); - - let copy = new Int16Array(pcm.length); - copy.set(pcm); - await octopus.index(copy, {transfer: true, transferCallback: data => { copy = data; } }); - octopus.terminate(); - - expect(copy).to.deep.eq(pcm); - }); + cy.getFramesFromFile(`audio_samples/multiple_keywords.wav`).then( + async pcm => { + const octopus = await OctopusWorker.create(ACCESS_KEY, { + publicPath: '/test/octopus_params.pv', + forceWrite: true, + }); + + let copy = new Int16Array(pcm.length); + copy.set(pcm); + await octopus.index(copy, { + transfer: true, + transferCallback: data => { + copy = data; + }, + }); + octopus.terminate(); + + expect(copy).to.deep.eq(pcm); + } + ); } catch (e) { expect(e).to.be.undefined; } diff --git a/binding/web/test/octopus_perf.test.ts b/binding/web/test/octopus_perf.test.ts index 2b8e76e..8b70f34 100644 --- a/binding/web/test/octopus_perf.test.ts +++ b/binding/web/test/octopus_perf.test.ts @@ -1,9 +1,13 @@ -import { Octopus, OctopusWorker } from "../"; +import { Octopus, OctopusWorker } from '../'; const ACCESS_KEY = Cypress.env('ACCESS_KEY'); const NUM_TEST_ITERATIONS = Number(Cypress.env('NUM_TEST_ITERATIONS')); -const INDEX_PERFORMANCE_THRESHOLD_SEC = Number(Cypress.env('INDEX_PERFORMANCE_THRESHOLD_SEC')); -const SEARCH_PERFORMANCE_THRESHOLD_SEC = Number(Cypress.env('SEARCH_PERFORMANCE_THRESHOLD_SEC')); +const INDEX_PERFORMANCE_THRESHOLD_SEC = Number( + Cypress.env('INDEX_PERFORMANCE_THRESHOLD_SEC') +); +const SEARCH_PERFORMANCE_THRESHOLD_SEC = Number( + Cypress.env('SEARCH_PERFORMANCE_THRESHOLD_SEC') +); async function testPerformance( instance: typeof Octopus | typeof OctopusWorker, @@ -13,10 +17,10 @@ async function testPerformance( const searchPerfResults: number[] = []; for (let j = 0; j < NUM_TEST_ITERATIONS; j++) { - const octopus = await instance.create( - ACCESS_KEY, - { publicPath: '/test/octopus_params.pv', forceWrite: true } - ); + const octopus = await instance.create(ACCESS_KEY, { + publicPath: '/test/octopus_params.pv', + forceWrite: true, + }); let start = Date.now(); const metadata = await octopus.index(inputPcm); @@ -24,7 +28,7 @@ async function testPerformance( indexPerfResults.push((end - start) / 1000); start = Date.now(); - await octopus.search(metadata, "porcupine"); + await octopus.search(metadata, 'porcupine'); end = Date.now(); searchPerfResults.push((end - start) / 1000); @@ -35,8 +39,10 @@ async function testPerformance( } } - const indexAvgPerf = indexPerfResults.reduce((a, b) => a + b) / NUM_TEST_ITERATIONS; - const searchAvgPerf = searchPerfResults.reduce((a, b) => a + b) / NUM_TEST_ITERATIONS; + const indexAvgPerf = + indexPerfResults.reduce((a, b) => a + b) / NUM_TEST_ITERATIONS; + const searchAvgPerf = + searchPerfResults.reduce((a, b) => a + b) / NUM_TEST_ITERATIONS; // eslint-disable-next-line no-console console.log(`Average index performance: ${indexAvgPerf} seconds`); @@ -51,12 +57,14 @@ describe('Octopus binding performance test', () => { Cypress.config('defaultCommandTimeout', 120000); for (const instance of [Octopus, OctopusWorker]) { - const instanceString = (instance === OctopusWorker) ? 'worker' : 'main'; + const instanceString = instance === OctopusWorker ? 'worker' : 'main'; it(`should be lower than performance threshold (${instanceString})`, () => { - cy.getFramesFromFile('audio_samples/multiple_keywords.wav').then( async inputPcm => { - await testPerformance(instance, inputPcm); - }); + cy.getFramesFromFile('audio_samples/multiple_keywords.wav').then( + async inputPcm => { + await testPerformance(instance, inputPcm); + } + ); }); } }); diff --git a/binding/web/test/test_results.js b/binding/web/test/test_results.js index 85664c3..31adbc6 100644 --- a/binding/web/test/test_results.js +++ b/binding/web/test/test_results.js @@ -1,77 +1,61 @@ -const languages = ["en", "de", "es", "fr", "it", "ja", "ko", "pt"] +const languages = ['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'pt']; const testResults = { - "en": [ + en: [ { - phrase: "alexa", - expected: [ - {startSec: 7.648, endSec: 8.352, probability: 1} - ] + phrase: 'alexa', + expected: [{ startSec: 7.648, endSec: 8.352, probability: 1 }], }, { - phrase: "porcupine", + phrase: 'porcupine', expected: [ - {startSec: 5.728, endSec: 6.752, probability: 1}, - {startSec: 35.360, endSec: 36.416, probability: 1}, - ] + { startSec: 5.728, endSec: 6.752, probability: 1 }, + { startSec: 35.36, endSec: 36.416, probability: 1 }, + ], }, ], - "de": [ + de: [ { - phrase: "ananas", - expected: [ - {startSec: 0.000, endSec: 0.704, probability: 0.954} - ] + phrase: 'ananas', + expected: [{ startSec: 0.0, endSec: 0.704, probability: 0.954 }], }, ], - "es": [ + es: [ { - phrase: "manzana", - expected: [ - {startSec: 5.184, endSec: 5.984, probability: 1} - ] + phrase: 'manzana', + expected: [{ startSec: 5.184, endSec: 5.984, probability: 1 }], }, ], - "fr": [ + fr: [ { - phrase: "perroquet", - expected: [ - {startSec: 4.352, endSec: 5.184, probability: 0.952} - ] + phrase: 'perroquet', + expected: [{ startSec: 4.352, endSec: 5.184, probability: 0.952 }], }, ], - "it": [ + it: [ { - phrase: "porcospino", - expected: [ - {startSec: 0.480, endSec: 1.728, probability: 1} - ] + phrase: 'porcospino', + expected: [{ startSec: 0.48, endSec: 1.728, probability: 1 }], }, ], - "ja": [ + ja: [ { - phrase: "りんご", - expected: [ - {startSec: 0.960, endSec: 1.664, probability: 1} - ] + phrase: 'りんご', + expected: [{ startSec: 0.99, endSec: 1.634, probability: 1 }], }, ], - "ko": [ + ko: [ { - phrase: "아이스크림", - expected: [ - {startSec: 6.592, endSec: 7.520, probability: 0.961} - ] + phrase: '아이스크림', + expected: [{ startSec: 6.592, endSec: 7.52, probability: 0.961 }], }, ], - "pt": [ + pt: [ { - phrase: "porco espinho", - expected: [ - {startSec: 0.480, endSec: 1.792, probability: 1} - ] + phrase: 'porco espinho', + expected: [{ startSec: 0.48, endSec: 1.792, probability: 1 }], }, ], -} +}; export { testResults, languages }; diff --git a/binding/web/yarn.lock b/binding/web/yarn.lock index 85e7623..5093574 100644 --- a/binding/web/yarn.lock +++ b/binding/web/yarn.lock @@ -1283,12 +1283,12 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@picovoice/web-utils@=1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@picovoice/web-utils/-/web-utils-1.3.1.tgz#d417e98604a650b54a8e03669015ecf98c2383ec" - integrity sha512-jcDqdULtTm+yJrnHDjg64hARup+Z4wNkYuXHNx6EM8+qZkweBq9UA6XJrHAlUkPnlkso4JWjaIKhz3x8vZcd3g== +"@picovoice/web-utils@=1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@picovoice/web-utils/-/web-utils-1.3.2.tgz#9fe0a798a1f016fa8d8c28e48537466aa2854ca3" + integrity sha512-jDZn+kxVUyzM84kGVmq9hU6N60K2VH3sDevpZSw4CNDx3ppSz9Biz7aPHs+i16ssYJCxJweiJ44XhI+5Lo5ZjQ== dependencies: - commander "^9.2.0" + commander "^10.0.1" "@rollup/plugin-babel@^6.0.3": version "6.0.3" @@ -1843,6 +1843,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -1853,11 +1858,6 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^9.2.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - common-tags@^1.8.0: version "1.8.2" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" diff --git a/demo/android/OctopusDemo/octopus-demo-app/build.gradle b/demo/android/OctopusDemo/octopus-demo-app/build.gradle index 2344597..254e528 100644 --- a/demo/android/OctopusDemo/octopus-demo-app/build.gradle +++ b/demo/android/OctopusDemo/octopus-demo-app/build.gradle @@ -27,6 +27,6 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' - implementation 'ai.picovoice:octopus-android:1.2.2' + implementation 'ai.picovoice:octopus-android:2.0.0' implementation 'ai.picovoice:android-voice-processor:1.0.2' } diff --git a/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java b/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java index 61e5dec..e7a0f0f 100644 --- a/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java +++ b/demo/android/OctopusDemo/octopus-demo-app/src/main/java/ai/picovoice/octopusdemo/MainActivity.java @@ -82,7 +82,7 @@ protected void onCreate(Bundle savedInstanceState) { octopus = new Octopus.Builder().setAccessKey(ACCESS_KEY).build(getApplicationContext()); setUIInteractivity(true); } catch (OctopusInvalidArgumentException e) { - displayFatalError(String.format("AccessKey '%s' is invalid", ACCESS_KEY)); + displayFatalError(e.getMessage()); } catch (OctopusActivationException e) { displayFatalError("AccessKey activation error"); } catch (OctopusActivationLimitException e) { diff --git a/demo/c/octopus_index_demo.c b/demo/c/octopus_index_demo.c index 77521a0..33ed1ad 100644 --- a/demo/c/octopus_index_demo.c +++ b/demo/c/octopus_index_demo.c @@ -1,7 +1,6 @@ #include #include #include -#include #if defined(_WIN32) || defined(_WIN64) @@ -75,13 +74,19 @@ static struct option long_options[] = { {"index_path", required_argument, NULL, 'i'}, }; -void print_usage(const char *program_name) { +static void print_usage(const char *program_name) { fprintf( stderr, "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -w AUDIO_PATH -i INDEX_PATH\n", program_name); } +static void print_error_message(char **message_stack, int32_t message_stack_depth) { + for (int32_t i = 0; i < message_stack_depth; i++) { + fprintf(stderr, " [%d] %s\n", i, message_stack[i]); + } +} + int picovoice_main(int argc, char *argv[]) { const char *library_path = NULL; const char *model_path = NULL; @@ -108,91 +113,163 @@ int picovoice_main(int argc, char *argv[]) { index_path = optarg; break; default: - exit(1); + exit(EXIT_FAILURE); } } if (!library_path || !model_path || !audio_path || !access_key || !index_path) { print_usage(argv[0]); - exit(1); + exit(EXIT_FAILURE); } void *dl = pv_open_dl(library_path); if (!dl) { - fprintf(stderr, "failed to open library.\n"); - exit(1); + fprintf(stderr, "Failed to open library.\n"); + exit(EXIT_FAILURE); } const char *(*pv_status_to_string_func)(pv_status_t) = pv_load_sym(dl, "pv_status_to_string"); if (!pv_status_to_string_func) { - print_dl_error("failed to load 'pv_status_to_string'"); - exit(1); + print_dl_error("Failed to load 'pv_status_to_string'"); + exit(EXIT_FAILURE); } - pv_status_t (*pv_octopus_init_func)(const char *, const char *, pv_octopus_t **) = pv_load_sym(dl, "pv_octopus_init"); + pv_status_t + (*pv_octopus_init_func)(const char *, const char *, pv_octopus_t **) = pv_load_sym(dl, "pv_octopus_init"); if (!pv_octopus_init_func) { - print_dl_error("failed to load 'pv_octopus_init()'."); - exit(1); + print_dl_error("Failed to load 'pv_octopus_init'."); + exit(EXIT_FAILURE); } void (*pv_octopus_delete_func)(pv_octopus_t *) = pv_load_sym(dl, "pv_octopus_delete"); if (!pv_octopus_delete_func) { - print_dl_error("failed to load 'pv_octopus_delete()'"); - exit(1); + print_dl_error("Failed to load 'pv_octopus_delete'"); + exit(EXIT_FAILURE); } - pv_status_t (*pv_octopus_index_file_func)(pv_octopus_t *, const char *, void **, int32_t *) = NULL; + pv_status_t (*pv_octopus_index_file_size_func)(pv_octopus_t *, const char *, int32_t *) = NULL; + pv_octopus_index_file_size_func = pv_load_sym(dl, "pv_octopus_index_file_size"); + if (!pv_octopus_index_file_size_func) { + print_dl_error("Failed to load 'pv_octopus_index_file_size'"); + exit(EXIT_FAILURE); + } + + pv_status_t (*pv_octopus_index_file_func)(pv_octopus_t *, const char *, void *) = NULL; pv_octopus_index_file_func = pv_load_sym(dl, "pv_octopus_index_file"); if (!pv_octopus_index_file_func) { - print_dl_error("failed to load 'pv_octopus_index_file()'"); - exit(1); + print_dl_error("Failed to load 'pv_octopus_index_file'"); + exit(EXIT_FAILURE); } const char (*pv_octopus_version_func)(const pv_octopus_t *) = pv_load_sym(dl, "pv_octopus_version"); if (!pv_octopus_version_func) { - print_dl_error("failed to load 'pv_octopus_version()'"); - exit(1); + print_dl_error("Failed to load 'pv_octopus_version'"); + exit(EXIT_FAILURE); + } + + + pv_status_t (*pv_get_error_stack_func)(char ***, int32_t *) = pv_load_sym(dl, "pv_get_error_stack"); + if (!pv_get_error_stack_func) { + print_dl_error("Failed to load 'pv_get_error_stack'"); + exit(EXIT_FAILURE); + } + + void (*pv_free_error_stack_func)(char **) = pv_load_sym(dl, "pv_free_error_stack"); + if (!pv_free_error_stack_func) { + print_dl_error("Failed to load 'pv_free_error_stack'"); + exit(EXIT_FAILURE); } pv_octopus_t *o = NULL; pv_status_t status = pv_octopus_init_func(access_key, model_path, &o); if (status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to init with '%s'.\n", pv_status_to_string_func(status)); - exit(1); - } + fprintf(stderr, "Failed to init with '%s'", pv_status_to_string_func(status)); + + char **message_stack = NULL; + int32_t message_stack_depth = 0; + pv_status_t error_status = pv_get_error_stack_func(&message_stack, &message_stack_depth); + if (error_status != PV_STATUS_SUCCESS) { + fprintf( + stderr, + ".\nUnable to get Octopus error state with '%s'.\n", + pv_status_to_string_func(error_status)); + exit(EXIT_FAILURE); + } - double total_cpu_time_usec = 0; + if (message_stack_depth > 0) { + fprintf(stderr, ":\n"); + print_error_message(message_stack, message_stack_depth); + pv_free_error_stack_func(message_stack); + } + + exit(EXIT_FAILURE); + } - void *indices = NULL; int32_t num_indices_byte = 0; + status = pv_octopus_index_file_size_func(o, audio_path, &num_indices_byte); + if (status != PV_STATUS_SUCCESS) { + fprintf(stderr, "Failed to get index size with '%s'", pv_status_to_string_func(status)); + + char **message_stack = NULL; + int32_t message_stack_depth = 0; + pv_status_t error_status = pv_get_error_stack_func(&message_stack, &message_stack_depth); + if (error_status != PV_STATUS_SUCCESS) { + fprintf( + stderr, + ".\nUnable to get Octopus error state with '%s'.\n", + pv_status_to_string_func(error_status)); + exit(EXIT_FAILURE); + } - struct timeval before; - gettimeofday(&before, NULL); + if (message_stack_depth > 0) { + fprintf(stderr, ":\n"); + print_error_message(message_stack, message_stack_depth); + pv_free_error_stack_func(message_stack); + } + exit(EXIT_FAILURE); + } - status = pv_octopus_index_file_func(o, audio_path, &indices, &num_indices_byte); - if (status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to index with '%s'.\n", pv_status_to_string_func(status)); - exit(1); + void *indices = calloc(num_indices_byte, sizeof(char)); + if (!indices) { + fprintf(stderr, "Failed to allocate '%d' bytes of memory for Octopus indices.\n", num_indices_byte); + exit(EXIT_FAILURE); } - struct timeval after; - gettimeofday(&after, NULL); + status = pv_octopus_index_file_func(o, audio_path, indices); + if (status != PV_STATUS_SUCCESS) { + fprintf(stderr, "Failed to index file with '%s'", pv_status_to_string_func(status)); + + char **message_stack = NULL; + int32_t message_stack_depth = 0; + pv_status_t error_status = pv_get_error_stack_func(&message_stack, &message_stack_depth); + if (error_status != PV_STATUS_SUCCESS) { + fprintf( + stderr, + ".\nUnable to get Octopus error state with '%s'.\n", + pv_status_to_string_func(error_status)); + exit(EXIT_FAILURE); + } - total_cpu_time_usec += - (double) (after.tv_sec - before.tv_sec) * 1e6 + (double) (after.tv_usec - before.tv_usec); + if (message_stack_depth > 0) { + fprintf(stderr, ":\n"); + print_error_message(message_stack, message_stack_depth); + pv_free_error_stack_func(message_stack); + } + exit(EXIT_FAILURE); + } pv_octopus_delete_func(o); pv_close_dl(dl); FILE *f = fopen(index_path, "wb"); if (!f) { - fprintf(stderr, "failed to create index file at '%s'.\n", index_path); - exit(1); + fprintf(stderr, "Failed to create index file at '%s'.\n", index_path); + exit(EXIT_FAILURE); } if (fwrite(indices, 1, num_indices_byte, f) != (size_t) num_indices_byte) { - fprintf(stderr, "failed to write into index file at '%s'.\n", index_path); - exit(1); + fprintf(stderr, "Failed to write into index file at '%s'.\n", index_path); + exit(EXIT_FAILURE); } fclose(f); @@ -211,7 +288,7 @@ int main(int argc, char *argv[]) { LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargv == NULL) { fprintf(stderr, "CommandLineToArgvW failed\n"); - exit(1); + exit(EXIT_FAILURE); } char *utf8_argv[argc]; @@ -221,7 +298,7 @@ int main(int argc, char *argv[]) { int arg_chars_num = WideCharToMultiByte(CP_UTF8, UTF8_COMPOSITION_FLAG, wargv[i], NULL_TERMINATED, NULL, 0, NULL, NULL); utf8_argv[i] = (char *) malloc(arg_chars_num * sizeof(char)); if (!utf8_argv[i]) { - fprintf(stderr, "failed to to allocate memory for converting args"); + fprintf(stderr, "Failed to to allocate memory for converting args"); } WideCharToMultiByte(CP_UTF8, UTF8_COMPOSITION_FLAG, wargv[i], NULL_TERMINATED, utf8_argv[i], arg_chars_num, NULL, NULL); } diff --git a/demo/c/octopus_search_demo.c b/demo/c/octopus_search_demo.c index f55bd8c..1c00515 100644 --- a/demo/c/octopus_search_demo.c +++ b/demo/c/octopus_search_demo.c @@ -1,7 +1,6 @@ #include #include #include -#include #if defined(_WIN32) || defined(_WIN64) @@ -75,13 +74,19 @@ static struct option long_options[] = { {"search_phrase", required_argument, NULL, 's'}, }; -void print_usage(const char *program_name) { +static void print_usage(const char *program_name) { fprintf( stderr, "usage : %s -l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -i INDEX_PATH -s SEARCH_PHRASE\n", program_name); } +static void print_error_message(char **message_stack, int32_t message_stack_depth) { + for (int32_t i = 0; i < message_stack_depth; i++) { + fprintf(stderr, " [%d] %s\n", i, message_stack[i]); + } +} + int picovoice_main(int argc, char *argv[]) { const char *library_path = NULL; const char *model_path = NULL; @@ -108,37 +113,44 @@ int picovoice_main(int argc, char *argv[]) { search_phrase = optarg; break; default: - exit(1); + exit(EXIT_FAILURE); } } if (!library_path || !model_path || !search_phrase || !access_key || !index_path) { print_usage(argv[0]); - exit(1); + exit(EXIT_FAILURE); } void *dl = pv_open_dl(library_path); if (!dl) { print_dl_error("Failed to open library"); - exit(1); + exit(EXIT_FAILURE); } const char *(*pv_status_to_string_func)(pv_status_t) = pv_load_sym(dl, "pv_status_to_string"); if (!pv_status_to_string_func) { print_dl_error("Failed to load symbol 'pv_status_to_string'"); - exit(1); + exit(EXIT_FAILURE); } - pv_status_t (*pv_octopus_init_func)(const char *, const char *, pv_octopus_t **) = pv_load_sym(dl, "pv_octopus_init"); + pv_status_t + (*pv_octopus_init_func)(const char *, const char *, pv_octopus_t **) = pv_load_sym(dl, "pv_octopus_init"); if (!pv_octopus_init_func) { print_dl_error("Failed to load symbol 'pv_octopus_init'"); - exit(1); + exit(EXIT_FAILURE); } void (*pv_octopus_delete_func)(pv_octopus_t *) = pv_load_sym(dl, "pv_octopus_delete"); if (!pv_octopus_delete_func) { print_dl_error("Failed to load symbol 'pv_octopus_delete'"); - exit(1); + exit(EXIT_FAILURE); + } + + void (*pv_octopus_matches_delete_func)(pv_octopus_match_t *) = pv_load_sym(dl, "pv_octopus_matches_delete"); + if (!pv_octopus_matches_delete_func) { + print_dl_error("Failed to load symbol 'pv_octopus_matches_delete'"); + exit(EXIT_FAILURE); } pv_status_t (*pv_octopus_search_func)( @@ -150,68 +162,103 @@ int picovoice_main(int argc, char *argv[]) { int32_t *) = pv_load_sym(dl, "pv_octopus_search"); if (!pv_octopus_search_func) { print_dl_error("Failed to load symbol 'pv_octopus_search'"); - exit(1); + exit(EXIT_FAILURE); } const char (*pv_octopus_version_func)(const pv_octopus_t *) = pv_load_sym(dl, "pv_octopus_version"); if (!pv_octopus_version_func) { print_dl_error("Failed to load symbol 'pv_octopus_version'"); - exit(1); + exit(EXIT_FAILURE); } - void (*pv_free_func)(void *) = pv_load_sym(dl, "pv_free"); - if (!pv_free_func) { - print_dl_error("failed to load `pv_free`"); - exit(1); + pv_status_t (*pv_get_error_stack_func)(char ***, int32_t *) = pv_load_sym(dl, "pv_get_error_stack"); + if (!pv_get_error_stack_func) { + print_dl_error("Failed to load 'pv_get_error_stack'"); + exit(EXIT_FAILURE); + } + + void (*pv_free_error_stack_func)(char **) = pv_load_sym(dl, "pv_free_error_stack"); + if (!pv_free_error_stack_func) { + print_dl_error("Failed to load 'pv_free_error_stack'"); + exit(EXIT_FAILURE); } pv_octopus_t *o = NULL; pv_status_t status = pv_octopus_init_func(access_key, model_path, &o); if (status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to init with '%s'.\n", pv_status_to_string_func(status)); - exit(1); + fprintf(stderr, "Failed to init with '%s'", pv_status_to_string_func(status)); + char **message_stack = NULL; + int32_t message_stack_depth = 0; + pv_status_t error_status = pv_get_error_stack_func(&message_stack, &message_stack_depth); + if (error_status != PV_STATUS_SUCCESS) { + fprintf( + stderr, + ".\nUnable to get Octopus error state with '%s'.\n", + pv_status_to_string_func(error_status)); + exit(EXIT_FAILURE); + } + + if (message_stack_depth > 0) { + fprintf(stderr, ":\n"); + print_error_message(message_stack, message_stack_depth); + pv_free_error_stack_func(message_stack); + } + + exit(EXIT_FAILURE); } FILE *f = fopen(index_path, "rb"); if (!f) { - fprintf(stderr, "failed to open index file at '%s'.\n", index_path); - exit(1); + fprintf(stderr, "Failed to open index file at '%s'.\n", index_path); + exit(EXIT_FAILURE); } fseek(f, 0, SEEK_END); const int32_t num_indices_byte = (int32_t) ftell(f); fseek(f, 0, SEEK_SET); - void *indices = malloc(num_indices_byte); + + void *indices = calloc(num_indices_byte, sizeof(char)); if (!indices) { - fprintf(stderr, "failed to allocate '%d' bytes of memory for indices.\n", num_indices_byte); - exit(1); + fprintf(stderr, "Failed to allocate '%d' bytes of memory for indices.\n", num_indices_byte); + exit(EXIT_FAILURE); } if (fread(indices, 1, num_indices_byte, f) != (size_t) num_indices_byte) { - fprintf(stderr, "failed to read indices from '%s'.\n", index_path); - exit(1); + fprintf(stderr, "Failed to read indices from '%s'.\n", index_path); + exit(EXIT_FAILURE); } fclose(f); - double total_cpu_time_usec = 0; - pv_octopus_match_t *matches = NULL; int32_t num_matches = 0; - struct timeval before; - gettimeofday(&before, NULL); - - status = pv_octopus_search_func(o, indices, num_indices_byte, search_phrase, &matches, &num_matches); + status = pv_octopus_search_func( + o, + indices, + num_indices_byte, + search_phrase, + &matches, + &num_matches); if (status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to search with '%s'.\n", pv_status_to_string_func(status)); - exit(1); - } - - struct timeval after; - gettimeofday(&after, NULL); + fprintf(stderr, "Failed to search with '%s'", pv_status_to_string_func(status)); + char **message_stack = NULL; + int32_t message_stack_depth = 0; + pv_status_t error_status = pv_get_error_stack_func(&message_stack, &message_stack_depth); + if (error_status != PV_STATUS_SUCCESS) { + fprintf( + stderr, + ".\nUnable to get Octopus error state with '%s'.\n", + pv_status_to_string_func(error_status)); + exit(EXIT_FAILURE); + } - total_cpu_time_usec += - (double) (after.tv_sec - before.tv_sec) * 1e6 + (double) (after.tv_usec - before.tv_usec); + if (message_stack_depth > 0) { + fprintf(stderr, ":\n"); + print_error_message(message_stack, message_stack_depth); + pv_free_error_stack_func(message_stack); + } + exit(EXIT_FAILURE); + } free(indices); pv_octopus_delete_func(o); @@ -227,7 +274,7 @@ int picovoice_main(int argc, char *argv[]) { matches[i].probability); } - pv_free_func(matches); + pv_octopus_matches_delete_func(matches); pv_close_dl(dl); return 0; @@ -243,7 +290,7 @@ int main(int argc, char *argv[]) { LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &argc); if (wargv == NULL) { fprintf(stderr, "CommandLineToArgvW failed\n"); - exit(1); + exit(EXIT_FAILURE); } char *utf8_argv[argc]; @@ -253,7 +300,7 @@ int main(int argc, char *argv[]) { int arg_chars_num = WideCharToMultiByte(CP_UTF8, UTF8_COMPOSITION_FLAG, wargv[i], NULL_TERMINATED, NULL, 0, NULL, NULL); utf8_argv[i] = (char *) malloc(arg_chars_num * sizeof(char)); if (!utf8_argv[i]) { - fprintf(stderr, "failed to to allocate memory for converting args"); + fprintf(stderr, "Failed to to allocate memory for converting args"); } WideCharToMultiByte(CP_UTF8, UTF8_COMPOSITION_FLAG, wargv[i], NULL_TERMINATED, utf8_argv[i], arg_chars_num, NULL, NULL); } diff --git a/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift b/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift index 13defe0..1b6d3e4 100644 --- a/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift +++ b/demo/ios/OctopusDemo/OctopusDemo/ViewModel.swift @@ -51,8 +51,8 @@ class ViewModel: ObservableObject { try octopus = Octopus(accessKey: ACCESS_KEY) statusText = "Start by recording some audio" isBusy = false - } catch is OctopusInvalidArgumentError { - onOctopusInitFail("ACCESS_KEY '\(ACCESS_KEY)' is invalid") + } catch let error as OctopusInvalidArgumentError { + onOctopusInitFail("\(error.localizedDescription)") } catch is OctopusActivationError { errorMessage = "ACCESS_KEY activation error" } catch is OctopusActivationRefusedError { diff --git a/demo/ios/OctopusDemo/Podfile b/demo/ios/OctopusDemo/Podfile index 6a2c1fe..4e2041b 100644 --- a/demo/ios/OctopusDemo/Podfile +++ b/demo/ios/OctopusDemo/Podfile @@ -1,6 +1,6 @@ source 'https://cdn.cocoapods.org/' -platform :ios, '9.0' +platform :ios, '13.0' target 'OctopusDemo' do - pod 'Octopus-iOS', '~> 1.2.0' + pod 'Octopus-iOS', '~> 2.0.0' end diff --git a/demo/ios/OctopusDemo/Podfile.lock b/demo/ios/OctopusDemo/Podfile.lock index 5707c36..45062cf 100644 --- a/demo/ios/OctopusDemo/Podfile.lock +++ b/demo/ios/OctopusDemo/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - Octopus-iOS (1.2.0) + - Octopus-iOS (2.0.0) DEPENDENCIES: - - Octopus-iOS (~> 1.2.0) + - Octopus-iOS (~> 2.0.0) SPEC REPOS: trunk: - Octopus-iOS SPEC CHECKSUMS: - Octopus-iOS: c741f75aefc3ee701559ff8f5eb8ae537ef48f82 + Octopus-iOS: b080b854eabe191e1bc90ff95248249123d98548 -PODFILE CHECKSUM: 46c66a375b591b3da119ecbb2bcfa7ea00d33654 +PODFILE CHECKSUM: 550f7da05d7c9f47c9fc4eefcda36ca8ea96651f COCOAPODS: 1.11.3 diff --git a/demo/python/octopus_demo.py b/demo/python/octopus_demo.py index 800578a..96b16f6 100644 --- a/demo/python/octopus_demo.py +++ b/demo/python/octopus_demo.py @@ -49,25 +49,20 @@ def stop(self): def main(): parser = argparse.ArgumentParser() - parser.add_argument('--audio_paths', nargs='+', help='Absolute paths to input audio files', required=True) - - parser.add_argument('--library_path', help='Absolute path to dynamic library', default=pvoctopus.LIBRARY_PATH) - - parser.add_argument( - '--model_path', - help='Absolute path to the file containing model parameters', - default=pvoctopus.MODEL_PATH) - parser.add_argument( '--access_key', help='AccessKey provided by Picovoice Console (https://console.picovoice.ai/)', required=True) + parser.add_argument('--audio_paths', nargs='+', help='Absolute paths to input audio files', required=True) + + parser.add_argument('--library_path', help='Absolute path to dynamic library') + + parser.add_argument('--model_path', help='Absolute path to the file containing model parameters') + parser.add_argument( '--search_phrase', - help='Phrase to search in the provided audio paths', - default=None - ) + help='Phrase to search in the provided audio paths') args = parser.parse_args() @@ -77,7 +72,7 @@ def main(): library_path=args.library_path, model_path=args.model_path) print("Octopus version: %s" % octopus.version) - except (MemoryError, ValueError, RuntimeError, PermissionError) as e: + except pvoctopus.OctopusError as e: print(e) sys.exit(1) @@ -88,7 +83,7 @@ def main(): try: print("\rindexing '%s'" % os.path.basename(audio_file)) metadata_list.append(octopus.index_audio_file(os.path.abspath(audio_file))) - except (MemoryError, ValueError, RuntimeError, PermissionError, IOError) as e: + except pvoctopus.OctopusError as e: print("Failed to process '%s' with '%s'" % (os.path.basename(audio_file), e)) octopus.delete() sys.exit(1) @@ -104,7 +99,7 @@ def main(): for i, metadata in enumerate(metadata_list): try: matches = octopus.search(metadata, [str(search_phrase)]) - except pvoctopus.OctopusInvalidArgumentError as e: + except pvoctopus.OctopusError as e: print(e) continue if len(matches) != 0: diff --git a/demo/python/requirements.txt b/demo/python/requirements.txt index d16245e..c035c47 100644 --- a/demo/python/requirements.txt +++ b/demo/python/requirements.txt @@ -1,2 +1,2 @@ -pvoctopus==1.2.0 +pvoctopus==2.0.0 tabulate diff --git a/demo/python/setup.py b/demo/python/setup.py index f6c8ba6..25c286f 100644 --- a/demo/python/setup.py +++ b/demo/python/setup.py @@ -23,7 +23,7 @@ setuptools.setup( name="pvoctopusdemo", - version="1.2.1", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Octopus Speech-to-Index engine demo.", @@ -31,7 +31,7 @@ long_description_content_type="text/markdown", url="https://github.com/Picovoice/octopus", packages=["pvoctopusdemo"], - install_requires=["pvoctopus==1.2.1", "tabulate"], + install_requires=["pvoctopus==2.0.0", "tabulate"], include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/demo/web/package.json b/demo/web/package.json index 3e75b8e..b3c5574 100644 --- a/demo/web/package.json +++ b/demo/web/package.json @@ -1,6 +1,6 @@ { "name": "octopus-web-demo", - "version": "1.0.0", + "version": "2.0.0", "description": "A basic demo to show how to use Octopus for web browsers, using the IIFE version of the library", "main": "index.js", "private": true, @@ -18,7 +18,7 @@ "author": "Picovoice Inc", "license": "Apache-2.0", "dependencies": { - "@picovoice/octopus-web": "~1.2.15" + "@picovoice/octopus-web": "=2.0.0" }, "devDependencies": { "http-server": "^14.0.0" diff --git a/demo/web/yarn.lock b/demo/web/yarn.lock index b460985..37dca11 100644 --- a/demo/web/yarn.lock +++ b/demo/web/yarn.lock @@ -2,19 +2,19 @@ # yarn lockfile v1 -"@picovoice/octopus-web@~1.2.15": - version "1.2.15" - resolved "https://registry.yarnpkg.com/@picovoice/octopus-web/-/octopus-web-1.2.15.tgz#072e6bf6b860eb4fc16e619d1f6713ab1af4e4cc" - integrity sha512-MJ/Dxmu9kRBYM1loYO1TWKgoBzHqKdrH9AijMoa1RvKmWFKSrY2IkL2LEjjbJdPry67ViNWaTtHDyH7Dr1xkdg== +"@picovoice/octopus-web@=2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@picovoice/octopus-web/-/octopus-web-2.0.0.tgz#2dd4ba06f1921504e3d8226925af387c5dc8cfaa" + integrity sha512-tVb7RaX6Y7ejmyETE7FPeuybZMLhPUJiRJKL2NgH0NnxnaVspeBXRyehgOarqFmoT8wlyfTX6ITD7nOblKYEhA== dependencies: - "@picovoice/web-utils" "=1.3.1" + "@picovoice/web-utils" "=1.3.2" -"@picovoice/web-utils@=1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@picovoice/web-utils/-/web-utils-1.3.1.tgz#d417e98604a650b54a8e03669015ecf98c2383ec" - integrity sha512-jcDqdULtTm+yJrnHDjg64hARup+Z4wNkYuXHNx6EM8+qZkweBq9UA6XJrHAlUkPnlkso4JWjaIKhz3x8vZcd3g== +"@picovoice/web-utils@=1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@picovoice/web-utils/-/web-utils-1.3.2.tgz#9fe0a798a1f016fa8d8c28e48537466aa2854ca3" + integrity sha512-jDZn+kxVUyzM84kGVmq9hU6N60K2VH3sDevpZSw4CNDx3ppSz9Biz7aPHs+i16ssYJCxJweiJ44XhI+5Lo5ZjQ== dependencies: - commander "^9.2.0" + commander "^10.0.1" ansi-styles@^4.1.0: version "4.3.0" @@ -65,10 +65,10 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -commander@^9.2.0: - version "9.4.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" - integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== corser@^2.0.1: version "2.0.1" diff --git a/include/picovoice.h b/include/picovoice.h index 3631832..7fb3b43 100644 --- a/include/picovoice.h +++ b/include/picovoice.h @@ -1,5 +1,5 @@ /* - Copyright 2018-2021 Picovoice Inc. + Copyright 2018-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -53,7 +53,32 @@ typedef enum { */ PV_API const char *pv_status_to_string(pv_status_t status); +/** + * If a function returns a failure (any pv_status_t other than PV_STATUS_SUCCESS), this function can be called + * to get a series of error messages related to the failure. This function can only be called only once per + * failure status on another function. The memory for `message_stack` must be freed using `pv_free_error_stack`. + * + * Regardless of the return status of this function, if `message_stack` is not `NULL`, then `message_stack` + * contains valid memory. However, a failure status on this function indicates that future error messages + * may not be reported. + * + * @param[out] message_stack Array of messages relating to the failure. Messages are NULL terminated strings. + * The array and messages must be freed using `pv_free_error_stack`. + * @param[out] message_stack_depth The number of messages in the `message_stack` array. + */ +PV_API pv_status_t pv_get_error_stack( + char ***message_stack, + int32_t *message_stack_depth); + +/** + * This function frees the memory used by error messages allocated by `pv_get_error_stack`. + * + * @param message_stack Array of messages relating to the failure, allocated from `pv_get_error_stack`. + */ +PV_API void pv_free_error_stack(char **message_stack); + #ifdef __cplusplus + } #endif diff --git a/include/pv_octopus.h b/include/pv_octopus.h index 8c7ed35..5f6eac7 100644 --- a/include/pv_octopus.h +++ b/include/pv_octopus.h @@ -1,5 +1,5 @@ /* - Copyright 2020-2022 Picovoice Inc. + Copyright 2020-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -49,6 +49,19 @@ PV_API pv_status_t pv_octopus_init( */ PV_API void pv_octopus_delete(pv_octopus_t *object); +/** + * Determines size required for indices buffer when indexing audio data. + * + * @param object Octopus object. + * @param num_samples Number of audio samples to index. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_size( + pv_octopus_t *object, + int32_t num_samples, + int32_t *num_indices_bytes); + /** * Indexes audio data. * @@ -56,8 +69,7 @@ PV_API void pv_octopus_delete(pv_octopus_t *object); * @param pcm Audio data. The audio needs to have a sample rate equal to 'pv_sample_rate()' and be 16-bit * linearly-encoded. Octopus operates on single-channel audio. * @param num_samples Number of audio samples to index. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT', 'PV_STATUS_OUT_OF_MEMORY', 'PV_STATUS_RUNTIME_ERROR', * 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', 'PV_STATUS_ACTIVATION_THROTTLED', or * 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -66,16 +78,31 @@ PV_API pv_status_t pv_octopus_index( pv_octopus_t *object, const int16_t *pcm, int32_t num_samples, - void **indices, + void *indices); + +/** + * Determines size required for indices buffer when indexing an audio file. + * + * @param object Octopus object. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_file_size( + pv_octopus_t *object, + const char *path, int32_t *num_indices_bytes); /** * Indexes an audio file. * * @param object Octopus object. - * @param path Absolute path to the audio file. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_file_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' or 'PV_STATUS_OUT_OF_MEMORY', * 'PV_STATUS_RUNTIME_ERROR', 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', * 'PV_STATUS_ACTIVATION_THROTTLED', or 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -83,8 +110,7 @@ PV_API pv_status_t pv_octopus_index( PV_API pv_status_t pv_octopus_index_file( pv_octopus_t *object, const char *path, - void **indices, - int32_t *num_indices_bytes); + void *indices); /** * Container representing a matched utterance. @@ -114,6 +140,13 @@ PV_API pv_status_t pv_octopus_search( pv_octopus_match_t **matches, int32_t *num_matches); +/** + * Deletes matches returned from `pv_octopus_search()` + * + * @param matches matched utterances returned from `pv_octopus_search()` + */ +PV_API void pv_octopus_matches_delete(pv_octopus_match_t *matches); + /** * Getter for version. * diff --git a/lib/android/arm64-v8a/libpv_octopus.so b/lib/android/arm64-v8a/libpv_octopus.so index 6ed0745..b6c9fe6 100755 Binary files a/lib/android/arm64-v8a/libpv_octopus.so and b/lib/android/arm64-v8a/libpv_octopus.so differ diff --git a/lib/android/armeabi-v7a/libpv_octopus.so b/lib/android/armeabi-v7a/libpv_octopus.so index 7ce26a4..4ed1176 100755 Binary files a/lib/android/armeabi-v7a/libpv_octopus.so and b/lib/android/armeabi-v7a/libpv_octopus.so differ diff --git a/lib/android/x86/libpv_octopus.so b/lib/android/x86/libpv_octopus.so index 5c2c5ff..963e218 100755 Binary files a/lib/android/x86/libpv_octopus.so and b/lib/android/x86/libpv_octopus.so differ diff --git a/lib/android/x86_64/libpv_octopus.so b/lib/android/x86_64/libpv_octopus.so index de89b90..36f90ed 100755 Binary files a/lib/android/x86_64/libpv_octopus.so and b/lib/android/x86_64/libpv_octopus.so differ diff --git a/lib/common/light/octopus_params.pv b/lib/common/light/octopus_params.pv index 5f35c53..3d48d87 100644 Binary files a/lib/common/light/octopus_params.pv and b/lib/common/light/octopus_params.pv differ diff --git a/lib/common/light/octopus_params_de.pv b/lib/common/light/octopus_params_de.pv index 67457c5..44f996c 100644 Binary files a/lib/common/light/octopus_params_de.pv and b/lib/common/light/octopus_params_de.pv differ diff --git a/lib/common/light/octopus_params_es.pv b/lib/common/light/octopus_params_es.pv index 020af78..7361e6a 100644 Binary files a/lib/common/light/octopus_params_es.pv and b/lib/common/light/octopus_params_es.pv differ diff --git a/lib/common/light/octopus_params_fr.pv b/lib/common/light/octopus_params_fr.pv index 0136033..1465dff 100644 Binary files a/lib/common/light/octopus_params_fr.pv and b/lib/common/light/octopus_params_fr.pv differ diff --git a/lib/common/light/octopus_params_it.pv b/lib/common/light/octopus_params_it.pv index 136ec16..8191d5b 100644 Binary files a/lib/common/light/octopus_params_it.pv and b/lib/common/light/octopus_params_it.pv differ diff --git a/lib/common/light/octopus_params_ja.pv b/lib/common/light/octopus_params_ja.pv index 690e923..4c738d7 100644 Binary files a/lib/common/light/octopus_params_ja.pv and b/lib/common/light/octopus_params_ja.pv differ diff --git a/lib/common/light/octopus_params_ko.pv b/lib/common/light/octopus_params_ko.pv index fb73ecc..c13300c 100644 Binary files a/lib/common/light/octopus_params_ko.pv and b/lib/common/light/octopus_params_ko.pv differ diff --git a/lib/common/light/octopus_params_pt.pv b/lib/common/light/octopus_params_pt.pv index 6389731..47c98c8 100644 Binary files a/lib/common/light/octopus_params_pt.pv and b/lib/common/light/octopus_params_pt.pv differ diff --git a/lib/common/param/octopus_params.pv b/lib/common/param/octopus_params.pv index 700f267..dc12205 100644 Binary files a/lib/common/param/octopus_params.pv and b/lib/common/param/octopus_params.pv differ diff --git a/lib/common/param/octopus_params_de.pv b/lib/common/param/octopus_params_de.pv index 9c575ca..765497b 100644 Binary files a/lib/common/param/octopus_params_de.pv and b/lib/common/param/octopus_params_de.pv differ diff --git a/lib/common/param/octopus_params_es.pv b/lib/common/param/octopus_params_es.pv index 41ef036..57d7655 100644 Binary files a/lib/common/param/octopus_params_es.pv and b/lib/common/param/octopus_params_es.pv differ diff --git a/lib/common/param/octopus_params_fr.pv b/lib/common/param/octopus_params_fr.pv index 55b2917..647cf8e 100644 Binary files a/lib/common/param/octopus_params_fr.pv and b/lib/common/param/octopus_params_fr.pv differ diff --git a/lib/common/param/octopus_params_it.pv b/lib/common/param/octopus_params_it.pv index f7b5f86..c78fa2d 100644 Binary files a/lib/common/param/octopus_params_it.pv and b/lib/common/param/octopus_params_it.pv differ diff --git a/lib/common/param/octopus_params_ja.pv b/lib/common/param/octopus_params_ja.pv index 26b5425..6c2e06c 100644 Binary files a/lib/common/param/octopus_params_ja.pv and b/lib/common/param/octopus_params_ja.pv differ diff --git a/lib/common/param/octopus_params_ko.pv b/lib/common/param/octopus_params_ko.pv index f50714b..5adde69 100644 Binary files a/lib/common/param/octopus_params_ko.pv and b/lib/common/param/octopus_params_ko.pv differ diff --git a/lib/common/param/octopus_params_pt.pv b/lib/common/param/octopus_params_pt.pv index 3ceeb92..6d84a74 100644 Binary files a/lib/common/param/octopus_params_pt.pv and b/lib/common/param/octopus_params_pt.pv differ diff --git a/lib/ios/PvOctopus.xcframework/Info.plist b/lib/ios/PvOctopus.xcframework/Info.plist index 1b42142..64dca83 100644 --- a/lib/ios/PvOctopus.xcframework/Info.plist +++ b/lib/ios/PvOctopus.xcframework/Info.plist @@ -6,32 +6,30 @@ LibraryIdentifier - ios-arm64_i386_x86_64-simulator + ios-arm64 LibraryPath PvOctopus.framework SupportedArchitectures arm64 - i386 - x86_64 SupportedPlatform ios - SupportedPlatformVariant - simulator LibraryIdentifier - ios-arm64_armv7 + ios-arm64_x86_64-simulator LibraryPath PvOctopus.framework SupportedArchitectures arm64 - armv7 + x86_64 SupportedPlatform ios + SupportedPlatformVariant + simulator CFBundlePackageType diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/PvOctopus.h b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/PvOctopus.h similarity index 100% rename from lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/PvOctopus.h rename to lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/PvOctopus.h diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/picovoice.h similarity index 51% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h rename to lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/picovoice.h index 485e23c..3f13d70 100644 --- a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h +++ b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/picovoice.h @@ -1,5 +1,5 @@ /* - Copyright 2018-2021 Picovoice Inc. + Copyright 2018-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -31,17 +31,7 @@ PV_API int32_t pv_sample_rate(void); * Status codes. */ typedef enum { - -#ifdef __PV_PLATFORM_WASM__ - - PV_STATUS_SUCCESS = 10000, - -#else - PV_STATUS_SUCCESS = 0, - -#endif - PV_STATUS_OUT_OF_MEMORY, PV_STATUS_IO_ERROR, PV_STATUS_INVALID_ARGUMENT, @@ -63,6 +53,32 @@ typedef enum { */ PV_API const char *pv_status_to_string(pv_status_t status); +/** + * If a function returns a failure (any pv_status_t other than PV_STATUS_SUCCESS), this function can be called + * to get a series of error messages related to the failure. This function can only be called only once per + * failure status on another function. The memory for `message_stack` must be freed using `pv_free_error_stack`. + * + * Regardless of the return status of this function, if `message_stack` is not `NULL`, then `message_stack` + * contains valid memory. However, a failure status on this function indicates that future error messages + * may not be reported. + * + * @param[out] message_stack Array of messages relating to the failure. Messages are NULL terminated strings. + * The array and messages must be freed using `pv_free_error_stack`. + * @param[out] message_stack_depth The number of messages in the `message_stack` array. + */ +PV_API pv_status_t pv_get_error_stack( + char ***message_stack, + int32_t *message_stack_depth); + +/** + * This function frees the memory used by error messages allocated by `pv_get_error_stack`. + * + * @param message_stack Array of messages relating to the failure, allocated from `pv_get_error_stack`. + */ +PV_API void pv_free_error_stack(char **message_stack); + +PV_API void pv_set_sdk(const char *sdk); + #ifdef __cplusplus } diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/pv_octopus.h similarity index 68% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h rename to lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/pv_octopus.h index a93abe9..98047fc 100644 --- a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h +++ b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Headers/pv_octopus.h @@ -1,5 +1,5 @@ /* - Copyright 2020-2022 Picovoice Inc. + Copyright 2020-2021 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -49,6 +49,19 @@ PV_API pv_status_t pv_octopus_init( */ PV_API void pv_octopus_delete(pv_octopus_t *object); +/** + * Determines size required for indices buffer when indexing audio data. + * + * @param object Octopus object. + * @param num_samples Number of audio samples to index. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_size( + pv_octopus_t *object, + int32_t num_samples, + int32_t *num_indices_bytes); + /** * Indexes audio data. * @@ -56,8 +69,7 @@ PV_API void pv_octopus_delete(pv_octopus_t *object); * @param pcm Audio data. The audio needs to have a sample rate equal to 'pv_sample_rate()' and be 16-bit * linearly-encoded. Octopus operates on single-channel audio. * @param num_samples Number of audio samples to index. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT', 'PV_STATUS_OUT_OF_MEMORY', 'PV_STATUS_RUNTIME_ERROR', * 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', 'PV_STATUS_ACTIVATION_THROTTLED', or * 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -66,16 +78,31 @@ PV_API pv_status_t pv_octopus_index( pv_octopus_t *object, const int16_t *pcm, int32_t num_samples, - void **indices, + void *indices); + +/** + * Determines size required for indices buffer when indexing an audio file. + * + * @param object Octopus object. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_file_size( + pv_octopus_t *object, + const char *path, int32_t *num_indices_bytes); /** * Indexes an audio file. * * @param object Octopus object. - * @param path Absolute path to the audio file. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_file_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' or 'PV_STATUS_OUT_OF_MEMORY', * 'PV_STATUS_RUNTIME_ERROR', 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', * 'PV_STATUS_ACTIVATION_THROTTLED', or 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -83,8 +110,7 @@ PV_API pv_status_t pv_octopus_index( PV_API pv_status_t pv_octopus_index_file( pv_octopus_t *object, const char *path, - void **indices, - int32_t *num_indices_bytes); + void *indices); /** * Container representing a matched utterance. @@ -114,6 +140,13 @@ PV_API pv_status_t pv_octopus_search( pv_octopus_match_t **matches, int32_t *num_matches); +/** + * Deletes matches returned from `pv_octopus_search()` + * + * @param matches matched utterances returned from `pv_octopus_search()` + */ +PV_API void pv_octopus_matches_delete(pv_octopus_match_t *matches); + /** * Getter for version. * diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Info.plist b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Info.plist new file mode 100644 index 0000000..2d7dc57 Binary files /dev/null and b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Info.plist differ diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Modules/module.modulemap b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Modules/module.modulemap similarity index 100% rename from lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Modules/module.modulemap rename to lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/Modules/module.modulemap diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/PvOctopus b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/PvOctopus new file mode 100755 index 0000000..e0c2f1f Binary files /dev/null and b/lib/ios/PvOctopus.xcframework/ios-arm64/PvOctopus.framework/PvOctopus differ diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Info.plist b/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Info.plist deleted file mode 100644 index c84af91..0000000 Binary files a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Info.plist and /dev/null differ diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/PvOctopus b/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/PvOctopus deleted file mode 100755 index dbe7b51..0000000 Binary files a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/PvOctopus and /dev/null differ diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/PvOctopus.h b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/PvOctopus.h similarity index 100% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Headers/PvOctopus.h rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/PvOctopus.h diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/picovoice.h b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h similarity index 51% rename from lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/picovoice.h rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h index 485e23c..3f13d70 100644 --- a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/picovoice.h +++ b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/picovoice.h @@ -1,5 +1,5 @@ /* - Copyright 2018-2021 Picovoice Inc. + Copyright 2018-2023 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -31,17 +31,7 @@ PV_API int32_t pv_sample_rate(void); * Status codes. */ typedef enum { - -#ifdef __PV_PLATFORM_WASM__ - - PV_STATUS_SUCCESS = 10000, - -#else - PV_STATUS_SUCCESS = 0, - -#endif - PV_STATUS_OUT_OF_MEMORY, PV_STATUS_IO_ERROR, PV_STATUS_INVALID_ARGUMENT, @@ -63,6 +53,32 @@ typedef enum { */ PV_API const char *pv_status_to_string(pv_status_t status); +/** + * If a function returns a failure (any pv_status_t other than PV_STATUS_SUCCESS), this function can be called + * to get a series of error messages related to the failure. This function can only be called only once per + * failure status on another function. The memory for `message_stack` must be freed using `pv_free_error_stack`. + * + * Regardless of the return status of this function, if `message_stack` is not `NULL`, then `message_stack` + * contains valid memory. However, a failure status on this function indicates that future error messages + * may not be reported. + * + * @param[out] message_stack Array of messages relating to the failure. Messages are NULL terminated strings. + * The array and messages must be freed using `pv_free_error_stack`. + * @param[out] message_stack_depth The number of messages in the `message_stack` array. + */ +PV_API pv_status_t pv_get_error_stack( + char ***message_stack, + int32_t *message_stack_depth); + +/** + * This function frees the memory used by error messages allocated by `pv_get_error_stack`. + * + * @param message_stack Array of messages relating to the failure, allocated from `pv_get_error_stack`. + */ +PV_API void pv_free_error_stack(char **message_stack); + +PV_API void pv_set_sdk(const char *sdk); + #ifdef __cplusplus } diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/pv_octopus.h b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h similarity index 68% rename from lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/pv_octopus.h rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h index a93abe9..98047fc 100644 --- a/lib/ios/PvOctopus.xcframework/ios-arm64_armv7/PvOctopus.framework/Headers/pv_octopus.h +++ b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Headers/pv_octopus.h @@ -1,5 +1,5 @@ /* - Copyright 2020-2022 Picovoice Inc. + Copyright 2020-2021 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -49,6 +49,19 @@ PV_API pv_status_t pv_octopus_init( */ PV_API void pv_octopus_delete(pv_octopus_t *object); +/** + * Determines size required for indices buffer when indexing audio data. + * + * @param object Octopus object. + * @param num_samples Number of audio samples to index. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_size( + pv_octopus_t *object, + int32_t num_samples, + int32_t *num_indices_bytes); + /** * Indexes audio data. * @@ -56,8 +69,7 @@ PV_API void pv_octopus_delete(pv_octopus_t *object); * @param pcm Audio data. The audio needs to have a sample rate equal to 'pv_sample_rate()' and be 16-bit * linearly-encoded. Octopus operates on single-channel audio. * @param num_samples Number of audio samples to index. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT', 'PV_STATUS_OUT_OF_MEMORY', 'PV_STATUS_RUNTIME_ERROR', * 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', 'PV_STATUS_ACTIVATION_THROTTLED', or * 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -66,16 +78,31 @@ PV_API pv_status_t pv_octopus_index( pv_octopus_t *object, const int16_t *pcm, int32_t num_samples, - void **indices, + void *indices); + +/** + * Determines size required for indices buffer when indexing an audio file. + * + * @param object Octopus object. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param num_indices_bytes Size of index metadata in bytes. + * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' on failure + */ +PV_API pv_status_t pv_octopus_index_file_size( + pv_octopus_t *object, + const char *path, int32_t *num_indices_bytes); /** * Indexes an audio file. * * @param object Octopus object. - * @param path Absolute path to the audio file. - * @param indices Index metadata. - * @param num_indices_bytes Size of index metadata in bytes. + * @param path Absolute path to the audio file. The file needs to have a sample rate equal to or greater than + * `pv_sample_rate()`. The supported formats are: `3gp (AMR)`, `FLAC`, `MP3`, `MP4/m4a (AAC)`, `Ogg`, `WAV`, `WebM`. + * Files with stereo audio are mixed into a single mono channel and then processed. + * @param indices Buffer to store index metadata. Must be pre-allocated with result of `pv_octopus_index_file_size()`. * @return Status code. Returns 'PV_STATUS_INVALID_ARGUMENT' or 'PV_STATUS_OUT_OF_MEMORY', * 'PV_STATUS_RUNTIME_ERROR', 'PV_STATUS_ACTIVATION_ERROR', 'PV_STATUS_ACTIVATION_LIMIT_REACHED', * 'PV_STATUS_ACTIVATION_THROTTLED', or 'PV_STATUS_ACTIVATION_REFUSED' on failure @@ -83,8 +110,7 @@ PV_API pv_status_t pv_octopus_index( PV_API pv_status_t pv_octopus_index_file( pv_octopus_t *object, const char *path, - void **indices, - int32_t *num_indices_bytes); + void *indices); /** * Container representing a matched utterance. @@ -114,6 +140,13 @@ PV_API pv_status_t pv_octopus_search( pv_octopus_match_t **matches, int32_t *num_matches); +/** + * Deletes matches returned from `pv_octopus_search()` + * + * @param matches matched utterances returned from `pv_octopus_search()` + */ +PV_API void pv_octopus_matches_delete(pv_octopus_match_t *matches); + /** * Getter for version. * diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Info.plist b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Info.plist similarity index 68% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Info.plist rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Info.plist index 98cfd82..e447ff1 100644 Binary files a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Info.plist and b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Info.plist differ diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Modules/module.modulemap b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Modules/module.modulemap similarity index 100% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/Modules/module.modulemap rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/Modules/module.modulemap diff --git a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/PvOctopus b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/PvOctopus similarity index 57% rename from lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/PvOctopus rename to lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/PvOctopus index 3e908ab..51d202f 100755 Binary files a/lib/ios/PvOctopus.xcframework/ios-arm64_i386_x86_64-simulator/PvOctopus.framework/PvOctopus and b/lib/ios/PvOctopus.xcframework/ios-arm64_x86_64-simulator/PvOctopus.framework/PvOctopus differ diff --git a/lib/linux/x86_64/libpv_octopus.so b/lib/linux/x86_64/libpv_octopus.so index 8efaa26..6eb994f 100755 Binary files a/lib/linux/x86_64/libpv_octopus.so and b/lib/linux/x86_64/libpv_octopus.so differ diff --git a/lib/mac/arm64/libpv_octopus.dylib b/lib/mac/arm64/libpv_octopus.dylib index b0a05ac..3d23b09 100755 Binary files a/lib/mac/arm64/libpv_octopus.dylib and b/lib/mac/arm64/libpv_octopus.dylib differ diff --git a/lib/mac/x86_64/libpv_octopus.dylib b/lib/mac/x86_64/libpv_octopus.dylib index 1170ab1..f54e6ff 100755 Binary files a/lib/mac/x86_64/libpv_octopus.dylib and b/lib/mac/x86_64/libpv_octopus.dylib differ diff --git a/lib/wasm/pv_octopus.wasm b/lib/wasm/pv_octopus.wasm index fcbef89..75412e0 100755 Binary files a/lib/wasm/pv_octopus.wasm and b/lib/wasm/pv_octopus.wasm differ diff --git a/lib/wasm/pv_octopus_simd.wasm b/lib/wasm/pv_octopus_simd.wasm index a097386..dc5f589 100755 Binary files a/lib/wasm/pv_octopus_simd.wasm and b/lib/wasm/pv_octopus_simd.wasm differ diff --git a/lib/windows/amd64/libpv_octopus.dll b/lib/windows/amd64/libpv_octopus.dll index e9eaf0b..09d7218 100644 Binary files a/lib/windows/amd64/libpv_octopus.dll and b/lib/windows/amd64/libpv_octopus.dll differ diff --git a/res/.lint/java/suppress.xml b/res/.lint/java/suppress.xml index f120f7e..e53583d 100644 --- a/res/.lint/java/suppress.xml +++ b/res/.lint/java/suppress.xml @@ -9,7 +9,7 @@ - +