diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml index ffb82e8..ca92b2a 100644 --- a/.github/workflows/link-check.yml +++ b/.github/workflows/link-check.yml @@ -3,9 +3,9 @@ name: Check Markdown links on: workflow_dispatch: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] jobs: markdown-link-check: diff --git a/.github/workflows/python-codestyle.yml b/.github/workflows/python-codestyle.yml index 8aa9848..e4116ee 100644 --- a/.github/workflows/python-codestyle.yml +++ b/.github/workflows/python-codestyle.yml @@ -3,11 +3,11 @@ name: Python Codestyle on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '**/*.py' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '**/*.py' diff --git a/.github/workflows/python-demos.yml b/.github/workflows/python-demos.yml index 5cb82bc..768adf6 100644 --- a/.github/workflows/python-demos.yml +++ b/.github/workflows/python-demos.yml @@ -3,14 +3,14 @@ name: Python Demos on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python-demos.yml' - 'demo/python/**' - '!demo/python/README.md' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python-demos.yml' - 'demo/python/**' diff --git a/.github/workflows/python-perf.yml b/.github/workflows/python-perf.yml index 4a5d234..c3bba29 100644 --- a/.github/workflows/python-perf.yml +++ b/.github/workflows/python-perf.yml @@ -3,7 +3,7 @@ name: Python Performance on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python-perf.yml' - 'binding/python/__init__.py' @@ -18,7 +18,7 @@ on: - 'lib/windows/**' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python-perf.yml' - 'binding/python/__init__.py' diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 1cca42e..9d6098c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -3,7 +3,7 @@ name: Python on: workflow_dispatch: push: - branches: [main] + branches: [ main ] paths: - '.github/workflows/python.yml' - 'binding/python/**' @@ -17,7 +17,7 @@ on: - 'resources/audio_samples/noise.wav' - 'resources/audio_samples/test.wav' pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - '.github/workflows/python.yml' - 'binding/python/**' diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 55c1afc..df60996 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -3,9 +3,9 @@ name: SpellCheck on: workflow_dispatch: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main, 'v[0-9]+.[0-9]+'] + branches: [ main, 'v[0-9]+.[0-9]+' ] jobs: spellcheck: diff --git a/.github/workflows/web-demos.yml b/.github/workflows/web-demos.yml index 6e4fa73..bf31629 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,6 +35,10 @@ jobs: with: node-version: ${{ matrix.node-version }} + - name: Build Web SDK + run: yarn && yarn copywasm && yarn build + working-directory: binding/web + - name: Pre-build dependencies run: npm install yarn diff --git a/.github/workflows/web-perf.yml b/.github/workflows/web-perf.yml index 40a3d82..a17307d 100644 --- a/.github/workflows/web-perf.yml +++ b/.github/workflows/web-perf.yml @@ -58,4 +58,4 @@ jobs: run: yarn setup-test - name: Test - run: yarn test-perf --env ACCESS_KEY=${{secrets.PV_VALID_ACCESS_KEY}},NUM_TEST_ITERATIONS=20,PROC_PERFORMANCE_THRESHOLD_SEC=0.8 + run: yarn test-perf --env ACCESS_KEY=${{secrets.PV_VALID_ACCESS_KEY}},NUM_TEST_ITERATIONS=15,PROC_PERFORMANCE_THRESHOLD_SEC=${{ matrix.procPerformanceThresholdSec }} diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index ddecc08..4ae734d 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -31,7 +31,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 diff --git a/README.md b/README.md index 43d0833..5d38cf1 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,14 @@ Replace `${ACCESS_KEY}` with yours obtained from [Picovoice Console](https://con ## Releases -### v1.0.0 February 7th, 2023 +### v2.0.0 - November 24th, 2023 -- Initial release. +- 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.0.0 - February 7th, 2023 + +- Initial release \ No newline at end of file diff --git a/binding/android/Koala/koala/build.gradle b/binding/android/Koala/koala/build.gradle index 0f58d1d..38613e6 100644 --- a/binding/android/Koala/koala/build.gradle +++ b/binding/android/Koala/koala/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' ext { PUBLISH_GROUP_ID = 'ai.picovoice' - PUBLISH_VERSION = '1.0.0' + PUBLISH_VERSION = '2.0.0' PUBLISH_ARTIFACT_ID = 'koala-android' } @@ -40,12 +40,12 @@ dependencies { } task copyLibs(type: Copy) { - from("${rootDir}/../../lib/android") + from("${rootDir}/../../../lib/android") into("${rootDir}/koala/src/main/jniLibs") } task copyParams(type: Copy) { - from("${rootDir}/../../lib/common") + from("${rootDir}/../../../lib/common") into("${rootDir}/koala/src/main/res/raw") } diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/Koala.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/Koala.java index c604fa0..c1fccea 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/Koala.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/Koala.java @@ -27,6 +27,7 @@ public class Koala { private static String defaultModelPath; + private static String _sdk = "android"; static { System.loadLibrary("pv_koala"); @@ -34,6 +35,10 @@ public class Koala { private long handle; + public static void setSdk(String sdk) { + Koala._sdk = sdk; + } + /** * Constructor. * @@ -43,6 +48,7 @@ public class Koala { * @throws KoalaException if there is an error while initializing Koala. */ private Koala(String accessKey, String modelPath) throws KoalaException { + KoalaNative.setSdk(Koala._sdk); handle = KoalaNative.init(accessKey, modelPath); } diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/KoalaNative.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/KoalaNative.java index c954efa..1984f6b 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/KoalaNative.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/KoalaNative.java @@ -14,12 +14,14 @@ class KoalaNative { - static native int getSampleRate(); - static native String getVersion(); static native int getFrameLength(); + static native int getSampleRate(); + + static native void setSdk(String sdk); + static native long init(String accessKey, String modelPath) throws KoalaException; static native void delete(long object); diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java index 1b3138d..ea00b96 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaActivationException(Throwable cause) { public KoalaActivationException(String message) { super(message); } -} + public KoalaActivationException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java index c04c005..be4fc75 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationLimitException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaActivationLimitException(Throwable cause) { public KoalaActivationLimitException(String message) { super(message); } -} + public KoalaActivationLimitException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java index d9ff09e..050ebdf 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationRefusedException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaActivationRefusedException(Throwable cause) { public KoalaActivationRefusedException(String message) { super(message); } -} + public KoalaActivationRefusedException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java index b50d1a5..75dc5bb 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaActivationThrottledException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaActivationThrottledException(Throwable cause) { public KoalaActivationThrottledException(String message) { super(message); } -} + public KoalaActivationThrottledException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java index 6042dd4..48acd6a 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaException.java @@ -1,7 +1,9 @@ /* 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 @@ -11,12 +13,42 @@ package ai.picovoice.koala; public class KoalaException extends Exception { + private final String message; + private final String[] messageStack; + public KoalaException(Throwable cause) { super(cause); + this.message = cause.getMessage(); + this.messageStack = null; } public KoalaException(String message) { super(message); + this.message = message; + this.messageStack = null; } -} + public KoalaException(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/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java index ba2b3e0..82f6de5 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaIOException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaIOException(Throwable cause) { public KoalaIOException(String message) { super(message); } -} + public KoalaIOException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java index 4abc938..e894805 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidArgumentException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaInvalidArgumentException(Throwable cause) { public KoalaInvalidArgumentException(String message) { super(message); } -} + public KoalaInvalidArgumentException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java index 9d80896..66b9b7c 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaInvalidStateException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaInvalidStateException(Throwable cause) { public KoalaInvalidStateException(String message) { super(message); } -} + public KoalaInvalidStateException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java index 5215661..bc07601 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaKeyException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaKeyException(Throwable cause) { public KoalaKeyException(String message) { super(message); } -} + public KoalaKeyException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java index b5d7a63..80ea292 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaMemoryException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaMemoryException(Throwable cause) { public KoalaMemoryException(String message) { super(message); } -} + public KoalaMemoryException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java index d1dba66..8ee1652 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaRuntimeException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaRuntimeException(Throwable cause) { public KoalaRuntimeException(String message) { super(message); } -} + public KoalaRuntimeException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java index 595d14d..e6bc0ac 100644 --- a/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java +++ b/binding/android/Koala/koala/src/main/java/ai/picovoice/koala/exception/KoalaStopIterationException.java @@ -1,7 +1,9 @@ /* 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 @@ -18,5 +20,8 @@ public KoalaStopIterationException(Throwable cause) { public KoalaStopIterationException(String message) { super(message); } -} + public KoalaStopIterationException(String message, String[] messageStack) { + super(message, messageStack); + } +} diff --git a/binding/android/KoalaTestApp/koala-test-app/build.gradle b/binding/android/KoalaTestApp/koala-test-app/build.gradle index fbe2c9a..f41fde6 100644 --- a/binding/android/KoalaTestApp/koala-test-app/build.gradle +++ b/binding/android/KoalaTestApp/koala-test-app/build.gradle @@ -112,7 +112,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.1' implementation 'androidx.navigation:navigation-fragment:2.3.5' implementation 'androidx.navigation:navigation-ui:2.3.5' - implementation 'ai.picovoice:koala-android:1.0.0' + implementation 'ai.picovoice:koala-android:2.0.0' // Espresso UI Testing androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/binding/android/KoalaTestApp/koala-test-app/src/androidTest/java/ai/picovoice/koala/testapp/KoalaTest.java b/binding/android/KoalaTestApp/koala-test-app/src/androidTest/java/ai/picovoice/koala/testapp/KoalaTest.java index fa10921..b2be5df 100644 --- a/binding/android/KoalaTestApp/koala-test-app/src/androidTest/java/ai/picovoice/koala/testapp/KoalaTest.java +++ b/binding/android/KoalaTestApp/koala-test-app/src/androidTest/java/ai/picovoice/koala/testapp/KoalaTest.java @@ -206,6 +206,31 @@ private void extractAssetsRecursively(String path) throws IOException { } } + @Test + public void testErrorStack() { + String[] error = {}; + try { + new Koala.Builder() + .setAccessKey("invalid") + .build(getApplicationContext()); + } catch (KoalaException e) { + error = e.getMessageStack(); + } + + assertTrue(0 < error.length); + assertTrue(error.length <= 8); + + try { + new Koala.Builder() + .setAccessKey("invalid") + .build(getApplicationContext()); + } catch (KoalaException e) { + for (int i = 0; i < error.length; i++) { + assertEquals(e.getMessageStack()[i], error[i]); + } + } + } + private void extractTestFile(String filepath) throws IOException { InputStream is = new BufferedInputStream(assetManager.open(filepath), 256); diff --git a/binding/ios/Koala-iOS.podspec b/binding/ios/Koala-iOS.podspec index b07a122..2c9e161 100644 --- a/binding/ios/Koala-iOS.podspec +++ b/binding/ios/Koala-iOS.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Koala-iOS' s.module_name = 'Koala' - s.version = '1.0.0' + s.version = '2.0.0' s.license = {:type => 'Apache 2.0'} s.summary = 'iOS SDK for Picovoice\'s Koala Noise Suppression Engine' s.description = @@ -18,8 +18,8 @@ Pod::Spec.new do |s| DESC s.homepage = 'https://github.com/Picovoice/koala/tree/main/binding/ios' s.author = { 'Picovoice' => 'hello@picovoice.ai' } - s.source = { :git => "https://github.com/Picovoice/koala.git", :tag => "Koala-iOS-v1.0.0" } - s.ios.deployment_target = '11.0' + s.source = { :git => "https://github.com/Picovoice/koala.git", :tag => "Koala-iOS-v2.0.0" } + s.ios.deployment_target = '13.0' s.swift_version = '5.0' s.vendored_frameworks = 'lib/ios/PvKoala.xcframework' s.resource_bundles = { diff --git a/binding/ios/Koala.swift b/binding/ios/Koala.swift index 0f9a03e..27cad5e 100644 --- a/binding/ios/Koala.swift +++ b/binding/ios/Koala.swift @@ -44,6 +44,12 @@ public class Koala { /// streams of audio data, this delay specifies the time shift between the input and output stream. public var delaySample: UInt32 = 0 + private static var sdk = "ios" + + public static func setSdk(sdk: String) { + self.sdk = sdk + } + /// Constructor. /// /// - Parameters: @@ -70,18 +76,22 @@ public class Koala { modelPathArg = try getResourcePath(modelPathArg!) } + pv_set_sdk(Koala.sdk) + var status = pv_koala_init( accessKey, modelPathArg, &self.handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToKoalaError(status, "Koala init failed") + let messageStack = try getMessageStack() + throw pvStatusToKoalaError(status, "Koala init failed", messageStack) } var cDelaySample: Int32 = 0 status = pv_koala_delay_sample(self.handle, &cDelaySample) if status != PV_STATUS_SUCCESS { - throw pvStatusToKoalaError(status, "Failed to get Koala delay sample") + let messageStack = try getMessageStack() + throw pvStatusToKoalaError(status, "Failed to get Koala delay sample", messageStack) } self.delaySample = UInt32(cDelaySample) } @@ -141,7 +151,8 @@ public class Koala { var enhancedPcm = [Int16](repeating: 0, count: Int(Koala.frameLength)) let status = pv_koala_process(self.handle, pcm, &enhancedPcm[0]) if status != PV_STATUS_SUCCESS { - throw pvStatusToKoalaError(status, "Koala process failed") + let messageStack = try getMessageStack() + throw pvStatusToKoalaError(status, "Koala process failed", messageStack) } return enhancedPcm @@ -158,7 +169,8 @@ public class Koala { let status = pv_koala_reset(self.handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToKoalaError(status, "Koala process failed") + let messageStack = try getMessageStack() + throw pvStatusToKoalaError(status, "Koala process failed", messageStack) } } @@ -181,33 +193,54 @@ public class Koala { """) } - private func pvStatusToKoalaError(_ status: pv_status_t, _ message: String) -> KoalaError { + private func pvStatusToKoalaError( + _ status: pv_status_t, + _ message: String, + _ messageStack: [String] = []) -> KoalaError { switch status { case PV_STATUS_OUT_OF_MEMORY: - return KoalaMemoryError(message) + return KoalaMemoryError(message, messageStack) case PV_STATUS_IO_ERROR: - return KoalaIOError(message) + return KoalaIOError(message, messageStack) case PV_STATUS_INVALID_ARGUMENT: - return KoalaInvalidArgumentError(message) + return KoalaInvalidArgumentError(message, messageStack) case PV_STATUS_STOP_ITERATION: - return KoalaStopIterationError(message) + return KoalaStopIterationError(message, messageStack) case PV_STATUS_KEY_ERROR: - return KoalaKeyError(message) + return KoalaKeyError(message, messageStack) case PV_STATUS_INVALID_STATE: - return KoalaInvalidStateError(message) + return KoalaInvalidStateError(message, messageStack) case PV_STATUS_RUNTIME_ERROR: - return KoalaRuntimeError(message) + return KoalaRuntimeError(message, messageStack) case PV_STATUS_ACTIVATION_ERROR: - return KoalaActivationError(message) + return KoalaActivationError(message, messageStack) case PV_STATUS_ACTIVATION_LIMIT_REACHED: - return KoalaActivationLimitError(message) + return KoalaActivationLimitError(message, messageStack) case PV_STATUS_ACTIVATION_THROTTLED: - return KoalaActivationThrottledError(message) + return KoalaActivationThrottledError(message, messageStack) case PV_STATUS_ACTIVATION_REFUSED: - return KoalaActivationRefusedError(message) + return KoalaActivationRefusedError(message, messageStack) default: let pvStatusString = String(cString: pv_status_to_string(status)) - return KoalaError("\(pvStatusString): \(message)") + return KoalaError("\(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 pvStatusToKoalaError(status, "Unable to get Koala error state") + } + + var messageStack: [String] = [] + for i in 0.. 0) + } + } } diff --git a/binding/ios/KoalaAppTest/Podfile b/binding/ios/KoalaAppTest/Podfile index 20aec20..41b52bc 100644 --- a/binding/ios/KoalaAppTest/Podfile +++ b/binding/ios/KoalaAppTest/Podfile @@ -1,14 +1,14 @@ source 'https://cdn.cocoapods.org/' -platform :ios, '11.0' +platform :ios, '13.0' target 'KoalaAppTest' do - pod 'Koala-iOS', '~> 1.0.0' + pod 'Koala-iOS', '~> 2.0.0' end target 'KoalaAppTestUITests' do - pod 'Koala-iOS', '~> 1.0.0' + pod 'Koala-iOS', '~> 2.0.0' end target 'PerformanceTest' do - pod 'Koala-iOS', '~> 1.0.0' + pod 'Koala-iOS', '~> 2.0.0' end diff --git a/binding/ios/KoalaAppTest/Podfile.lock b/binding/ios/KoalaAppTest/Podfile.lock index d899a00..f11ba0d 100644 --- a/binding/ios/KoalaAppTest/Podfile.lock +++ b/binding/ios/KoalaAppTest/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - Koala-iOS (1.0.0) + - Koala-iOS (2.0.0) DEPENDENCIES: - - Koala-iOS (~> 1.0.0) + - Koala-iOS (~> 2.0.0) SPEC REPOS: trunk: - Koala-iOS SPEC CHECKSUMS: - Koala-iOS: 98ccd64b2531c57a0380dae47ea4105c4c5ae641 + Koala-iOS: b0e48400203d615520e6539f9994826183e5bb5a -PODFILE CHECKSUM: 1973a21901d505bae8a4a3f3777bd9df4b434bda +PODFILE CHECKSUM: 96e97bfb2db18a96bd2a8170036d04a3667e55fb -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/binding/ios/KoalaErrors.swift b/binding/ios/KoalaErrors.swift index 1aba76b..e71b9b8 100644 --- a/binding/ios/KoalaErrors.swift +++ b/binding/ios/KoalaErrors.swift @@ -9,13 +9,22 @@ public class KoalaError: 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.. 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 KoalaMemoryError(KoalaError): @@ -123,15 +143,34 @@ def __init__( 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_koala_init init_func.argtypes = [c_char_p, c_char_p, POINTER(POINTER(self.CKoala))] init_func.restype = self.PicovoiceStatuses self._handle = POINTER(self.CKoala)() - status = init_func(access_key.encode(), model_path.encode(), byref(self._handle)) + status = init_func( + access_key.encode(), + model_path.encode(), + 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_koala_delete self._delete_func.argtypes = [POINTER(self.CKoala)] @@ -144,7 +183,10 @@ def __init__( status = delay_sample_func(self._handle, delay_sample) if status is not self.PicovoiceStatuses.SUCCESS: self.delete() - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Failed to get delay samples', + message_stack=self._get_error_stack()) + self._delay_sample = delay_sample.value self._process_func = library.pv_koala_process @@ -193,7 +235,9 @@ def process(self, pcm: Sequence[int]) -> Sequence[int]: status = self._process_func(self._handle, pcm, enhanced_pcm) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Processing failed', + message_stack=self._get_error_stack()) # noinspection PyTypeChecker return list(enhanced_pcm) @@ -206,7 +250,9 @@ def reset(self) -> None: status = self._reset_func(self._handle) if status is not self.PicovoiceStatuses.SUCCESS: - raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]() + raise self._PICOVOICE_STATUS_TO_EXCEPTION[status]( + message='Reset failed', + message_stack=self._get_error_stack()) def delete(self) -> None: """Releases resources acquired by Koala.""" @@ -239,6 +285,21 @@ def version(self) -> str: return self._version + 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 Koala 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__ = [ 'Koala', diff --git a/binding/python/setup.py b/binding/python/setup.py index 480989e..9e5cbcd 100644 --- a/binding/python/setup.py +++ b/binding/python/setup.py @@ -32,7 +32,7 @@ shutil.copytree( os.path.join(os.path.dirname(__file__), '../../lib', platform), os.path.join(package_folder, 'lib', platform)) -manifest_in += "recursive-include pvkoala/lib/ *\n" +manifest_in += "recursive-include pvkoala/lib *\n" with open(os.path.join(os.path.dirname(__file__), 'MANIFEST.in'), 'w') as f: f.write(manifest_in) @@ -42,7 +42,7 @@ setuptools.setup( name="pvkoala", - version="1.0.1", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Koala Noise Suppression Engine.", diff --git a/binding/python/test_koala.py b/binding/python/test_koala.py index 4a11881..2c2224d 100644 --- a/binding/python/test_koala.py +++ b/binding/python/test_koala.py @@ -17,7 +17,7 @@ import wave from typing import Optional, Sequence -from _koala import Koala +from _koala import Koala, KoalaError from _util import default_library_path, default_model_path @@ -111,6 +111,54 @@ def test_version(self) -> None: self.assertIsInstance(version, str) self.assertGreater(len(version), 0) + def test_message_stack(self): + relative_path = '../..' + + error = None + try: + k = Koala( + access_key='invalid', + model_path=default_model_path(relative_path), + library_path=default_library_path(relative_path)) + self.assertIsNone(k) + except KoalaError as e: + error = e.message_stack + + self.assertIsNotNone(error) + self.assertGreater(len(error), 0) + + try: + k = Koala( + access_key='invalid', + model_path=default_model_path(relative_path), + library_path=default_library_path(relative_path)) + self.assertIsNone(k) + except KoalaError as e: + self.assertEqual(len(error), len(e.message_stack)) + self.assertListEqual(list(error), list(e.message_stack)) + + def test_process_message_stack(self): + relative_path = '../..' + + k = Koala( + access_key=self.access_key, + model_path=default_model_path(relative_path), + library_path=default_library_path(relative_path)) + + test_pcm = [0] * k.frame_length + + address = k._handle + k._handle = None + + try: + res = k.process(test_pcm) + self.assertEqual(len(res), 0) + except KoalaError as e: + self.assertGreater(len(e.message_stack), 0) + self.assertLess(len(e.message_stack), 8) + + k._handle = address + if __name__ == '__main__': parser = argparse.ArgumentParser() diff --git a/binding/web/.eslintignore b/binding/web/.eslintignore new file mode 100644 index 0000000..2a3f354 --- /dev/null +++ b/binding/web/.eslintignore @@ -0,0 +1,5 @@ +node_modules +dist +lib +rollup.config.js +.eslintrc.js \ No newline at end of file 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/cypress.config.ts b/binding/web/cypress.config.ts index bc17521..01bb065 100644 --- a/binding/web/cypress.config.ts +++ b/binding/web/cypress.config.ts @@ -1,14 +1,15 @@ -import { defineConfig } from "cypress"; +import { defineConfig } from 'cypress'; export default defineConfig({ env: { - "NUM_TEST_ITERATIONS": 20, - "PROC_PERFORMANCE_THRESHOLD_SEC": 0.8 + NUM_TEST_ITERATIONS: 20, + PROC_PERFORMANCE_THRESHOLD_SEC: 0.8, }, e2e: { - supportFile: "cypress/support/index.ts", - specPattern: "test/*.test.{js,jsx,ts,tsx}", + defaultCommandTimeout: 30000, + supportFile: 'cypress/support/index.ts', + specPattern: 'test/*.test.{js,jsx,ts,tsx}', video: false, - screenshotOnRunFailure: false + screenshotOnRunFailure: false, }, }); diff --git a/binding/web/package.json b/binding/web/package.json index 246ad7e..b039294 100644 --- a/binding/web/package.json +++ b/binding/web/package.json @@ -3,7 +3,7 @@ "description": "Koala Noise Suppression engine for web browsers (via WebAssembly)", "author": "Picovoice Inc", "license": "Apache-2.0", - "version": "1.0.3", + "version": "2.0.0", "keywords": [ "koala", "web", @@ -37,7 +37,7 @@ "test-perf": "cypress run --spec test/koala_perf.test.ts" }, "dependencies": { - "@picovoice/web-utils": "=1.3.1" + "@picovoice/web-utils": "=1.3.2" }, "devDependencies": { "@babel/core": "^7.20.12", @@ -67,6 +67,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 d759296..666a409 100644 --- a/binding/web/src/index.ts +++ b/binding/web/src/index.ts @@ -1,5 +1,6 @@ import { Koala } from './koala'; import { KoalaWorker } from './koala_worker'; +import * as KoalaErrors from './koala_errors'; import { KoalaModel, @@ -25,6 +26,7 @@ KoalaWorker.setWasmSimd(koalaWasmSimd); export { Koala, + KoalaErrors, KoalaModel, KoalaOptions, KoalaWorker, diff --git a/binding/web/src/koala.ts b/binding/web/src/koala.ts index 718bf45..568a1be 100644 --- a/binding/web/src/koala.ts +++ b/binding/web/src/koala.ts @@ -15,30 +15,50 @@ import { Mutex } from 'async-mutex'; import { aligned_alloc_type, - pv_free_type, - buildWasm, arrayBufferToStringAtIndex, + buildWasm, isAccessKeyValid, loadModel, - PvError + pv_free_type, + PvError, } from '@picovoice/web-utils'; import { simd } from 'wasm-feature-detect'; -import { KoalaModel, KoalaOptions } from './types'; +import { KoalaModel, KoalaOptions, PvStatus } from './types'; + +import * as KoalaErrors from './koala_errors'; +import { pvStatusToException } from './koala_errors'; /** * WebAssembly function types */ -type pv_koala_init_type = (accessKey: number, modelPath: number, object: number) => Promise; -type pv_koala_delay_sample_type = (object: number, delaySample: number) => Promise; -type pv_koala_process_type = (object: number, pcm: number, enhancedPcm: number) => Promise; +type pv_koala_init_type = ( + accessKey: number, + modelPath: number, + object: number +) => Promise; +type pv_koala_delay_sample_type = ( + object: number, + delaySample: number +) => Promise; +type pv_koala_process_type = ( + object: number, + pcm: number, + enhancedPcm: number +) => Promise; type pv_koala_reset_type = (object: number) => Promise; type pv_koala_delete_type = (object: number) => Promise; -type pv_status_to_string_type = (status: number) => Promise +type pv_status_to_string_type = (status: number) => Promise; type pv_koala_frame_length_type = () => Promise; type pv_sample_rate_type = () => Promise; type pv_koala_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 Koala @@ -48,12 +68,16 @@ type KoalaWasmOutput = { memory: WebAssembly.Memory; pvFree: pv_free_type; objectAddress: number; + messageStackAddressAddressAddress: number; + messageStackDepthAddress: number; pvKoalaDelete: pv_koala_delete_type; pvKoalaProcess: pv_koala_process_type; pvKoalaReset: pv_koala_reset_type; pvStatusToString: pv_status_to_string_type; + pvGetErrorStack: pv_get_error_stack_type; + pvFreeErrorStack: pv_free_error_stack_type; delaySample: number; - frameLength: number + frameLength: number; sampleRate: number; version: string; inputBufferAddress: number; @@ -68,6 +92,8 @@ export class Koala { private readonly _pvKoalaProcess: pv_koala_process_type; private readonly _pvKoalaReset: pv_koala_reset_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 | undefined; private readonly _pvFree: pv_free_type; @@ -76,6 +102,8 @@ export class Koala { private readonly _objectAddress: number; private readonly _inputBufferAddress: number; private readonly _outputBufferAddress: number; + private readonly _messageStackAddressAddressAddress: number; + private readonly _messageStackDepthAddress: number; private static _delaySample: number; private static _frameLength: number; @@ -83,18 +111,21 @@ export class Koala { private static _version: string; private static _wasm: string; private static _wasmSimd: string; + private static _sdk: string = 'web'; private static _koalaMutex = new Mutex(); private readonly _processCallback: (enhancedPcm: Int16Array) => void; - private readonly _processErrorCallback?: (error: string) => void; + private readonly _processErrorCallback?: ( + error: KoalaErrors.KoalaError + ) => void; private readonly _pvError: PvError; private constructor( handleWasm: KoalaWasmOutput, processCallback: (enhancedPcm: Int16Array) => void, - processErrorCallback?: (error: string) => void, + processErrorCallback?: (error: KoalaErrors.KoalaError) => void ) { Koala._delaySample = handleWasm.delaySample; Koala._frameLength = handleWasm.frameLength; @@ -105,12 +136,17 @@ export class Koala { this._pvKoalaProcess = handleWasm.pvKoalaProcess; this._pvKoalaReset = handleWasm.pvKoalaReset; this._pvStatusToString = handleWasm.pvStatusToString; + this._pvGetErrorStack = handleWasm.pvGetErrorStack; + this._pvFreeErrorStack = handleWasm.pvFreeErrorStack; this._wasmMemory = handleWasm.memory; this._pvFree = handleWasm.pvFree; this._objectAddress = handleWasm.objectAddress; this._inputBufferAddress = handleWasm.inputBufferAddress; this._outputBufferAddress = handleWasm.outputBufferAddress; + this._messageStackAddressAddressAddress = + handleWasm.messageStackAddressAddressAddress; + this._messageStackDepthAddress = handleWasm.messageStackDepthAddress; this._pvError = handleWasm.pvError; this._processMutex = new Mutex(); @@ -119,6 +155,10 @@ export class Koala { this._processErrorCallback = processErrorCallback; } + public static setSdk(sdk: string): void { + Koala._sdk = sdk; + } + /** * Delay in samples. If the input and output of consecutive calls to `.process()` are viewed as two contiguous * streams of audio data, this delay specifies the time shift between the input and output stream. @@ -195,36 +235,37 @@ export class Koala { accessKey: string, processCallback: (enhancedPcm: Int16Array) => void, model: KoalaModel, - options: KoalaOptions = {}, + options: KoalaOptions = {} ): Promise { - const customWritePath = (model.customWritePath) ? model.customWritePath : 'koala_model'; + const customWritePath = model.customWritePath + ? model.customWritePath + : 'koala_model'; const modelPath = await loadModel({ ...model, customWritePath }); - return Koala._init( - accessKey, - processCallback, - modelPath, - options - ); + return Koala._init(accessKey, processCallback, modelPath, options); } public static async _init( accessKey: string, processCallback: (enhancedPcm: Int16Array) => void, modelPath: string, - options: KoalaOptions = {}, + options: KoalaOptions = {} ): Promise { const { processErrorCallback } = options; if (!isAccessKeyValid(accessKey)) { - throw new Error('Invalid AccessKey'); + throw new KoalaErrors.KoalaInvalidArgumentError('Invalid AccessKey'); } return new Promise((resolve, reject) => { Koala._koalaMutex .runExclusive(async () => { const isSimd = await simd(); - const wasmOutput = await Koala.initWasm(accessKey.trim(), (isSimd) ? this._wasmSimd : this._wasm, modelPath, options); + const wasmOutput = await Koala.initWasm( + accessKey.trim(), + isSimd ? this._wasmSimd : this._wasm, + modelPath + ); return new Koala(wasmOutput, processCallback, processErrorCallback); }) .then((result: Koala) => { @@ -247,9 +288,11 @@ export class Koala { */ public async process(pcm: Int16Array): Promise { if (!(pcm instanceof Int16Array)) { - const error = new Error('The argument \'pcm\' must be provided as an Int16Array'); + const error = new KoalaErrors.KoalaInvalidArgumentError( + "The argument 'pcm' must be provided as an Int16Array" + ); if (this._processErrorCallback) { - this._processErrorCallback(error.toString()); + this._processErrorCallback(error); } else { // eslint-disable-next-line no-console console.error(error); @@ -257,10 +300,11 @@ export class Koala { } if (pcm.length !== this.frameLength) { - const error = new Error(`Koala process requires frames of length ${this.frameLength}. + const error = + new KoalaErrors.KoalaInvalidArgumentError(`Koala process requires frames of length ${this.frameLength}. Received frame of size ${pcm.length}.`); if (this._processErrorCallback) { - this._processErrorCallback(error.toString()); + this._processErrorCallback(error); } else { // eslint-disable-next-line no-console console.error(error); @@ -270,13 +314,15 @@ export class Koala { this._processMutex .runExclusive(async () => { if (this._wasmMemory === undefined) { - throw new Error('Attempted to call Koala process after release.'); + throw new KoalaErrors.KoalaInvalidStateError( + 'Attempted to call Koala process after release.' + ); } const memoryBuffer = new Int16Array(this._wasmMemory.buffer); memoryBuffer.set( pcm, - this._inputBufferAddress / Int16Array.BYTES_PER_ELEMENT, + this._inputBufferAddress / Int16Array.BYTES_PER_ELEMENT ); const status = await this._pvKoalaProcess( @@ -285,29 +331,44 @@ export class Koala { this._outputBufferAddress ); - const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); - - if (status !== PV_STATUS_SUCCESS) { - const msg = `process failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await this._pvStatusToString(status), - )}`; + if (status !== PvStatus.SUCCESS) { + const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); + const memoryBufferView = new DataView(this._wasmMemory.buffer); + const messageStack = await Koala.getMessageStack( + this._pvGetErrorStack, + this._pvFreeErrorStack, + this._messageStackAddressAddressAddress, + this._messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); - throw new Error( - `${msg}\nDetails: ${this._pvError.getErrorString()}` + const error = pvStatusToException( + status, + 'Process failed', + messageStack ); + if (this._processErrorCallback) { + this._processErrorCallback(error); + } else { + // eslint-disable-next-line no-console + console.error(error); + } } const output = memoryBuffer.slice( this._outputBufferAddress / Int16Array.BYTES_PER_ELEMENT, - (this._outputBufferAddress / Int16Array.BYTES_PER_ELEMENT) + this.frameLength + this._outputBufferAddress / Int16Array.BYTES_PER_ELEMENT + + this.frameLength ); this._processCallback(output); }) .catch((error: any) => { if (this._processErrorCallback) { - this._processErrorCallback(error.toString()); + this._processErrorCallback( + pvStatusToException(PvStatus.RUNTIME_ERROR, error.toString()) + ); } else { // eslint-disable-next-line no-console console.error(error); @@ -324,23 +385,28 @@ export class Koala { this._processMutex .runExclusive(async () => { if (this._wasmMemory === undefined) { - throw new Error('Attempted to call Koala reset after release.'); + throw new KoalaErrors.KoalaInvalidStateError( + 'Attempted to call Koala reset after release.' + ); } const status = await this._pvKoalaReset(this._objectAddress); - - if (status !== PV_STATUS_SUCCESS) { - const memoryBuffer = new Uint8Array(this._wasmMemory.buffer); - const msg = `process failed with status ${arrayBufferToStringAtIndex( - memoryBuffer, - await this._pvStatusToString(status), - )}`; - - throw new Error( - `${msg}\nDetails: ${this._pvError.getErrorString()}` + if (status !== PvStatus.SUCCESS) { + const memoryBufferUint8 = new Uint8Array(this._wasmMemory.buffer); + const memoryBufferView = new DataView(this._wasmMemory.buffer); + const messageStack = await Koala.getMessageStack( + this._pvGetErrorStack, + this._pvFreeErrorStack, + this._messageStackAddressAddressAddress, + this._messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 ); - } + throw pvStatusToException(status, 'Reset failed', messageStack); + } + }) + .then(() => { resolve(); }) .catch((error: any) => { @@ -356,6 +422,8 @@ export class Koala { await this._pvKoalaDelete(this._objectAddress); await this._pvFree(this._inputBufferAddress); await this._pvFree(this._outputBufferAddress); + await this._pvFree(this._messageStackAddressAddressAddress); + await this._pvFree(this._messageStackDepthAddress); delete this._wasmMemory; this._wasmMemory = undefined; } @@ -371,11 +439,16 @@ export class Koala { } } - private static async initWasm(accessKey: string, wasmBase64: string, modelPath: string, _: KoalaOptions): Promise { + private static async initWasm( + accessKey: string, + wasmBase64: string, + modelPath: string + ): Promise { // A WebAssembly page has a constant size of 64KiB. -> 1MiB ~= 16 pages const memory = new WebAssembly.Memory({ initial: 300 }); const memoryBufferUint8 = new Uint8Array(memory.buffer); + const memoryBufferView = new DataView(memory.buffer); const pvError = new PvError(); @@ -384,30 +457,42 @@ export class Koala { const aligned_alloc = exports.aligned_alloc as aligned_alloc_type; const pv_free = exports.pv_free as pv_free_type; const pv_koala_version = exports.pv_koala_version as pv_koala_version_type; - const pv_koala_delay_sample = exports.pv_koala_delay_sample as pv_koala_delay_sample_type; + const pv_koala_delay_sample = + exports.pv_koala_delay_sample as pv_koala_delay_sample_type; const pv_koala_process = exports.pv_koala_process as pv_koala_process_type; const pv_koala_reset = exports.pv_koala_reset as pv_koala_reset_type; const pv_koala_delete = exports.pv_koala_delete as pv_koala_delete_type; const pv_koala_init = exports.pv_koala_init as pv_koala_init_type; - const pv_status_to_string = exports.pv_status_to_string as pv_status_to_string_type; - const pv_koala_frame_length = exports.pv_koala_frame_length as pv_koala_frame_length_type; + const pv_status_to_string = + exports.pv_status_to_string as pv_status_to_string_type; + const pv_koala_frame_length = + exports.pv_koala_frame_length as pv_koala_frame_length_type; const pv_sample_rate = exports.pv_sample_rate as pv_sample_rate_type; + 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 objectAddressAddress = await aligned_alloc( Int32Array.BYTES_PER_ELEMENT, - Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT ); if (objectAddressAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const accessKeyAddress = await aligned_alloc( Uint8Array.BYTES_PER_ELEMENT, - (accessKey.length + 1) * Uint8Array.BYTES_PER_ELEMENT, + (accessKey.length + 1) * Uint8Array.BYTES_PER_ELEMENT ); if (accessKeyAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } for (let i = 0; i < accessKey.length; i++) { @@ -418,82 +503,140 @@ export class Koala { const modelPathEncoded = new TextEncoder().encode(modelPath); const modelPathAddress = await aligned_alloc( Uint8Array.BYTES_PER_ELEMENT, - (modelPathEncoded.length + 1) * Uint8Array.BYTES_PER_ELEMENT, + (modelPathEncoded.length + 1) * Uint8Array.BYTES_PER_ELEMENT ); if (modelPathAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } memoryBufferUint8.set(modelPathEncoded, modelPathAddress); memoryBufferUint8[modelPathAddress + modelPathEncoded.length] = 0; + 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 KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + memoryBufferUint8.set(sdkEncoded, sdkAddress); + memoryBufferUint8[sdkAddress + sdkEncoded.length] = 0; + await pv_set_sdk(sdkAddress); + + const messageStackDepthAddress = await aligned_alloc( + Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT + ); + if (!messageStackDepthAddress) { + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + + const messageStackAddressAddressAddress = await aligned_alloc( + Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT + ); + if (!messageStackAddressAddressAddress) { + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); + } + let status = await pv_koala_init( accessKeyAddress, modelPathAddress, - objectAddressAddress); - if (status !== PV_STATUS_SUCCESS) { - const msg = `'pv_koala_init' failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await pv_status_to_string(status), - )}`; + objectAddressAddress + ); - throw new Error( - `${msg}\nDetails: ${pvError.getErrorString()}` + await pv_free(accessKeyAddress); + await pv_free(modelPathAddress); + + if (status !== PvStatus.SUCCESS) { + const messageStack = await Koala.getMessageStack( + pv_get_error_stack, + pv_free_error_stack, + messageStackAddressAddressAddress, + messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); + + throw pvStatusToException( + status, + 'Initialization failed', + messageStack, + pvError ); } - const memoryBufferView = new DataView(memory.buffer); + const objectAddress = memoryBufferView.getInt32(objectAddressAddress, true); const delaySampleAddress = await aligned_alloc( Int32Array.BYTES_PER_ELEMENT, - Int32Array.BYTES_PER_ELEMENT, + Int32Array.BYTES_PER_ELEMENT ); if (delaySampleAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } status = await pv_koala_delay_sample(objectAddress, delaySampleAddress); if (status !== PV_STATUS_SUCCESS) { - const msg = `'pv_koala_delay_sample' failed with status ${arrayBufferToStringAtIndex( - memoryBufferUint8, - await pv_status_to_string(status), - )}`; + const messageStack = await Koala.getMessageStack( + pv_get_error_stack, + pv_free_error_stack, + messageStackAddressAddressAddress, + messageStackDepthAddress, + memoryBufferView, + memoryBufferUint8 + ); - throw new Error( - `${msg}\nDetails: ${pvError.getErrorString()}` + throw pvStatusToException( + status, + 'Get Koala delay samples failed', + messageStack, + pvError ); } const delaySample = memoryBufferView.getInt32(delaySampleAddress, true); + await pv_free(delaySample); const frameLength = await pv_koala_frame_length(); const sampleRate = await pv_sample_rate(); const versionAddress = await pv_koala_version(); const version = arrayBufferToStringAtIndex( memoryBufferUint8, - versionAddress, + versionAddress ); - await pv_free(accessKeyAddress); - await pv_free(modelPathAddress); - await pv_free(delaySample); - const inputBufferAddress = await aligned_alloc( Int16Array.BYTES_PER_ELEMENT, - frameLength * Int16Array.BYTES_PER_ELEMENT, + frameLength * Int16Array.BYTES_PER_ELEMENT ); if (inputBufferAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } const outputBufferAddress = await aligned_alloc( Int16Array.BYTES_PER_ELEMENT, - frameLength * Int16Array.BYTES_PER_ELEMENT, + frameLength * Int16Array.BYTES_PER_ELEMENT ); if (outputBufferAddress === 0) { - throw new Error('malloc failed: Cannot allocate memory'); + throw new KoalaErrors.KoalaOutOfMemoryError( + 'malloc failed: Cannot allocate memory' + ); } return { @@ -501,17 +644,64 @@ export class Koala { memory: memory, pvFree: pv_free, objectAddress: objectAddress, + messageStackAddressAddressAddress: messageStackAddressAddressAddress, + messageStackDepthAddress: messageStackDepthAddress, pvKoalaDelete: pv_koala_delete, pvKoalaProcess: pv_koala_process, pvKoalaReset: pv_koala_reset, pvStatusToString: pv_status_to_string, + pvGetErrorStack: pv_get_error_stack, + pvFreeErrorStack: pv_free_error_stack, delaySample: delaySample, frameLength: frameLength, sampleRate: sampleRate, version: version, inputBufferAddress: inputBufferAddress, outputBufferAddress: outputBufferAddress, - pvError: pvError + 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 Koala 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/koala_errors.ts b/binding/web/src/koala_errors.ts new file mode 100644 index 0000000..515f578 --- /dev/null +++ b/binding/web/src/koala_errors.ts @@ -0,0 +1,252 @@ +// +// 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 KoalaError 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(KoalaError.errorToString(message, messageStack, pvError)); + this._status = status; + this.name = 'KoalaError'; + 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 KoalaOutOfMemoryError extends KoalaError { + constructor( + message: string, + messageStack?: string[], + pvError: PvError | null = null + ) { + super(PvStatus.OUT_OF_MEMORY, message, messageStack, pvError); + this.name = 'KoalaOutOfMemoryError'; + } +} + +class KoalaIOError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.IO_ERROR, message, messageStack, pvError); + this.name = 'KoalaIOError'; + } +} + +class KoalaInvalidArgumentError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.INVALID_ARGUMENT, message, messageStack, pvError); + this.name = 'KoalaInvalidArgumentError'; + } +} + +class KoalaStopIterationError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.STOP_ITERATION, message, messageStack, pvError); + this.name = 'KoalaStopIterationError'; + } +} + +class KoalaKeyError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.KEY_ERROR, message, messageStack, pvError); + this.name = 'KoalaKeyError'; + } +} + +class KoalaInvalidStateError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.INVALID_STATE, message, messageStack, pvError); + this.name = 'KoalaInvalidStateError'; + } +} + +class KoalaRuntimeError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.RUNTIME_ERROR, message, messageStack, pvError); + this.name = 'KoalaRuntimeError'; + } +} + +class KoalaActivationError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_ERROR, message, messageStack, pvError); + this.name = 'KoalaActivationError'; + } +} + +class KoalaActivationLimitReachedError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_LIMIT_REACHED, message, messageStack, pvError); + this.name = 'KoalaActivationLimitReachedError'; + } +} + +class KoalaActivationThrottledError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_THROTTLED, message, messageStack, pvError); + this.name = 'KoalaActivationThrottledError'; + } +} + +class KoalaActivationRefusedError extends KoalaError { + constructor( + message: string, + messageStack: string[] = [], + pvError: PvError | null = null + ) { + super(PvStatus.ACTIVATION_REFUSED, message, messageStack, pvError); + this.name = 'KoalaActivationRefusedError'; + } +} + +export { + KoalaError, + KoalaOutOfMemoryError, + KoalaIOError, + KoalaInvalidArgumentError, + KoalaStopIterationError, + KoalaKeyError, + KoalaInvalidStateError, + KoalaRuntimeError, + KoalaActivationError, + KoalaActivationLimitReachedError, + KoalaActivationThrottledError, + KoalaActivationRefusedError, +}; + +export function pvStatusToException( + pvStatus: PvStatus, + errorMessage: string, + messageStack: string[] = [], + pvError: PvError | null = null +): KoalaError { + switch (pvStatus) { + case PvStatus.OUT_OF_MEMORY: + return new KoalaOutOfMemoryError(errorMessage, messageStack, pvError); + case PvStatus.IO_ERROR: + return new KoalaIOError(errorMessage, messageStack, pvError); + case PvStatus.INVALID_ARGUMENT: + return new KoalaInvalidArgumentError(errorMessage, messageStack, pvError); + case PvStatus.STOP_ITERATION: + return new KoalaStopIterationError(errorMessage, messageStack, pvError); + case PvStatus.KEY_ERROR: + return new KoalaKeyError(errorMessage, messageStack, pvError); + case PvStatus.INVALID_STATE: + return new KoalaInvalidStateError(errorMessage, messageStack, pvError); + case PvStatus.RUNTIME_ERROR: + return new KoalaRuntimeError(errorMessage, messageStack, pvError); + case PvStatus.ACTIVATION_ERROR: + return new KoalaActivationError(errorMessage, messageStack, pvError); + case PvStatus.ACTIVATION_LIMIT_REACHED: + return new KoalaActivationLimitReachedError( + errorMessage, + messageStack, + pvError + ); + case PvStatus.ACTIVATION_THROTTLED: + return new KoalaActivationThrottledError( + errorMessage, + messageStack, + pvError + ); + case PvStatus.ACTIVATION_REFUSED: + return new KoalaActivationRefusedError( + errorMessage, + messageStack, + pvError + ); + default: + // eslint-disable-next-line no-console + console.warn(`Unmapped error code: ${pvStatus}`); + return new KoalaError(pvStatus, errorMessage); + } +} diff --git a/binding/web/src/koala_worker.ts b/binding/web/src/koala_worker.ts index 1d62ff5..5f62765 100644 --- a/binding/web/src/koala_worker.ts +++ b/binding/web/src/koala_worker.ts @@ -17,8 +17,11 @@ import { KoalaWorkerInitResponse, KoalaWorkerProcessResponse, KoalaWorkerReleaseResponse, + KoalaWorkerResetResponse, + PvStatus, } from './types'; import { loadModel } from '@picovoice/web-utils'; +import { pvStatusToException } from './koala_errors'; export class KoalaWorker { private readonly _worker: Worker; @@ -26,11 +29,18 @@ export class KoalaWorker { private readonly _frameLength: number; private readonly _sampleRate: number; private readonly _delaySample: number; + private static _sdk: string = 'web'; private static _wasm: string; private static _wasmSimd: string; - private constructor(worker: Worker, version: string, frameLength: number, sampleRate: number, delaySample: number) { + private constructor( + worker: Worker, + version: string, + frameLength: number, + sampleRate: number, + delaySample: number + ) { this._worker = worker; this._version = version; this._frameLength = frameLength; @@ -94,6 +104,10 @@ export class KoalaWorker { } } + public static setSdk(sdk: string): void { + KoalaWorker._sdk = sdk; + } + /** * Creates an instance of the Picovoice Koala Noise Suppression Engine. * Behind the scenes, it requires the WebAssembly code to load and initialize before @@ -121,65 +135,103 @@ export class KoalaWorker { accessKey: string, processCallback: (enhancedPcm: Int16Array) => void, model: KoalaModel, - options: KoalaOptions = {}, + options: KoalaOptions = {} ): Promise { - const { processErrorCallback, ...rest } = options; + const { processErrorCallback, ...workerOptions } = options; - const customWritePath = (model.customWritePath) ? model.customWritePath : 'koala_model'; + const customWritePath = model.customWritePath + ? model.customWritePath + : 'koala_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': - worker.onmessage = (ev: MessageEvent): void => { - switch (ev.data.command) { - case 'ok': - processCallback(ev.data.enhancedPcm); - break; - case 'failed': - case 'error': - if (processErrorCallback) { - processErrorCallback(ev.data.message); - } else { - // eslint-disable-next-line no-console - console.error(ev.data.message); - } - break; - default: + 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': + worker.onmessage = ( + ev: MessageEvent< + KoalaWorkerProcessResponse | KoalaWorkerResetResponse + > + ): void => { + switch (ev.data.command) { + case 'ok-process': + processCallback(ev.data.enhancedPcm); + break; + case 'ok-reset': + break; + case 'failed': + case 'error': + { + const error = pvStatusToException( + ev.data.status, + ev.data.shortMessage, + ev.data.messageStack + ); + if (processErrorCallback) { + processErrorCallback(error); + } else { + // eslint-disable-next-line no-console + console.error(error); + } + } + break; + default: + // @ts-ignore + processErrorCallback( + pvStatusToException( + PvStatus.RUNTIME_ERROR, + `Unrecognized command: ${event.data.command}` + ) + ); + } + }; + resolve( + new KoalaWorker( + worker, + event.data.version, + event.data.frameLength, + event.data.sampleRate, + event.data.delaySample + ) + ); + 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 - processErrorCallback(`Unrecognized command: ${event.data.command}`); - } - }; - resolve( - new KoalaWorker( - worker, - event.data.version, - event.data.frameLength, - event.data.sampleRate, - event.data.delaySample)); - break; - case 'failed': - case 'error': - reject(event.data.message); - break; - default: - // @ts-ignore - reject(`Unrecognized command: ${event.data.command}`); - } - }; - }); + `Unrecognized command: ${event.data.command}` + ) + ); + } + }; + } + ); worker.postMessage({ command: 'init', accessKey: accessKey, modelPath: modelPath, - options: rest, + options: workerOptions, wasm: this._wasm, wasmSimd: this._wasmSimd, + sdk: this._sdk, }); return returnPromise; @@ -206,7 +258,7 @@ export class KoalaWorker { */ public async reset(): Promise { this._worker.postMessage({ - command: 'reset' + command: 'reset', }); } @@ -215,18 +267,31 @@ export class KoalaWorker { */ 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': resolve(); break; case 'failed': case 'error': - reject(event.data.message); + 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}` + ) + ); } }; }); diff --git a/binding/web/src/koala_worker_handler.ts b/binding/web/src/koala_worker_handler.ts index 2e2b19f..c0d566e 100644 --- a/binding/web/src/koala_worker_handler.ts +++ b/binding/web/src/koala_worker_handler.ts @@ -11,24 +11,110 @@ /// import { Koala } from './koala'; -import { KoalaWorkerRequest } from './types'; +import { KoalaWorkerRequest, KoalaWorkerInitRequest, PvStatus } from './types'; +import { KoalaError } from './koala_errors'; let koala: Koala | null = null; const processCallback = (enhancedPcm: Int16Array): void => { self.postMessage({ - command: 'ok', + command: 'ok-process', enhancedPcm: enhancedPcm, }); }; -const processErrorCallback = (error: string): void => { +const processErrorCallback = (error: KoalaError): void => { self.postMessage({ command: 'error', - message: error, + status: error.status, + shortMessage: error.shortMessage, + messageStack: error.messageStack, }); }; +const initRequest = async (request: KoalaWorkerInitRequest): Promise => { + if (koala !== null) { + return { + command: 'error', + status: PvStatus.INVALID_STATE, + shortMessage: 'Koala already initialized', + }; + } + try { + Koala.setWasm(request.wasm); + Koala.setWasmSimd(request.wasmSimd); + Koala.setSdk(request.sdk); + koala = await Koala._init( + request.accessKey, + processCallback, + request.modelPath, + { ...request.options, processErrorCallback } + ); + return { + command: 'ok', + version: koala.version, + frameLength: koala.frameLength, + sampleRate: koala.sampleRate, + delaySample: koala.delaySample, + }; + } catch (e: any) { + if (e instanceof KoalaError) { + return { + command: 'error', + status: e.status, + shortMessage: e.shortMessage, + messageStack: e.messageStack, + }; + } + return { + command: 'error', + status: PvStatus.RUNTIME_ERROR, + shortMessage: e.message, + }; + } +}; + +const resetRequest = async (): Promise => { + if (koala === null) { + return { + command: 'error', + status: PvStatus.INVALID_STATE, + shortMessage: 'Koala not initialized', + }; + } + try { + await koala.reset(); + return { + command: 'ok-reset', + }; + } catch (e: any) { + if (e instanceof KoalaError) { + 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 (koala !== null) { + await koala.release(); + koala = null; + close(); + } + return { + command: 'ok', + }; +}; + /** * Koala worker handler. */ @@ -37,71 +123,31 @@ self.onmessage = async function ( ): Promise { switch (event.data.command) { case 'init': - if (koala !== null) { - self.postMessage({ - command: 'error', - message: 'Koala already initialized', - }); - return; - } - try { - Koala.setWasm(event.data.wasm); - Koala.setWasmSimd(event.data.wasmSimd); - koala = await Koala._init( - event.data.accessKey, - processCallback, - event.data.modelPath, - { ...event.data.options, processErrorCallback } - ); - self.postMessage({ - command: 'ok', - version: koala.version, - frameLength: koala.frameLength, - sampleRate: koala.sampleRate, - delaySample: koala.delaySample - }); - } catch (e: any) { - self.postMessage({ - command: 'error', - message: e.message, - }); - } + self.postMessage(await initRequest(event.data)); break; case 'process': if (koala === null) { self.postMessage({ command: 'error', - message: 'Koala not initialized', + status: PvStatus.INVALID_STATE, + shortMessage: 'Koala not initialized', }); return; } await koala.process(event.data.inputFrame); break; case 'reset': - if (koala === null) { - self.postMessage({ - command: 'error', - message: 'Koala not initialized', - }); - return; - } - await koala.reset(); + self.postMessage(await resetRequest()); break; case 'release': - if (koala !== null) { - await koala.release(); - koala = null; - close(); - } - self.postMessage({ - command: 'ok', - }); + self.postMessage(await releaseRequest()); break; default: self.postMessage({ 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 cbc3591..f2faf3f 100644 --- a/binding/web/src/types.ts +++ b/binding/web/src/types.ts @@ -9,7 +9,24 @@ specific language governing permissions and limitations under the License. */ -import { PvModel } from "@picovoice/web-utils"; +import { PvModel } from '@picovoice/web-utils'; + +import { KoalaError } from './koala_errors'; + +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, +} /** * KoalaModel types @@ -18,7 +35,7 @@ export type KoalaModel = PvModel; export type KoalaOptions = { /** @defaultValue undefined */ - processErrorCallback?: (error: string) => void + processErrorCallback?: (error: KoalaError) => void; }; export type KoalaWorkerInitRequest = { @@ -28,6 +45,7 @@ export type KoalaWorkerInitRequest = { options: KoalaOptions; wasm: string; wasmSimd: string; + sdk: string; }; export type KoalaWorkerProcessRequest = { @@ -37,46 +55,56 @@ export type KoalaWorkerProcessRequest = { export type KoalaWorkerResetRequest = { command: 'reset'; -} +}; export type KoalaWorkerReleaseRequest = { command: 'release'; }; export type KoalaWorkerRequest = - KoalaWorkerInitRequest | - KoalaWorkerProcessRequest | - KoalaWorkerResetRequest | - KoalaWorkerReleaseRequest; + | KoalaWorkerInitRequest + | KoalaWorkerProcessRequest + | KoalaWorkerResetRequest + | KoalaWorkerReleaseRequest; export type KoalaWorkerFailureResponse = { command: 'failed' | 'error'; - message: string; -}; - -export type KoalaWorkerInitResponse = KoalaWorkerFailureResponse | { - command: 'ok'; - frameLength: number; - sampleRate: number; - version: string; - delaySample: number; + status: PvStatus; + shortMessage: string; + messageStack: string[]; }; -export type KoalaWorkerProcessResponse = KoalaWorkerFailureResponse | { - command: 'ok'; - enhancedPcm: Int16Array; -}; - -export type KoalaWorkerResetResponse = KoalaWorkerFailureResponse | { - command: 'ok'; -}; - -export type KoalaWorkerReleaseResponse = KoalaWorkerFailureResponse | { - command: 'ok'; -}; +export type KoalaWorkerInitResponse = + | KoalaWorkerFailureResponse + | { + command: 'ok'; + frameLength: number; + sampleRate: number; + version: string; + delaySample: number; + }; + +export type KoalaWorkerProcessResponse = + | KoalaWorkerFailureResponse + | { + command: 'ok-process'; + enhancedPcm: Int16Array; + }; + +export type KoalaWorkerResetResponse = + | KoalaWorkerFailureResponse + | { + command: 'ok-reset'; + }; + +export type KoalaWorkerReleaseResponse = + | KoalaWorkerFailureResponse + | { + command: 'ok'; + }; export type KoalaWorkerResponse = - KoalaWorkerInitResponse | - KoalaWorkerProcessResponse | - KoalaWorkerResetResponse | - KoalaWorkerReleaseResponse; + | KoalaWorkerInitResponse + | KoalaWorkerProcessResponse + | KoalaWorkerResetResponse + | KoalaWorkerReleaseResponse; diff --git a/binding/web/test/koala.test.ts b/binding/web/test/koala.test.ts index c6b4d3e..3621383 100644 --- a/binding/web/test/koala.test.ts +++ b/binding/web/test/koala.test.ts @@ -1,7 +1,8 @@ -import { Koala, KoalaWorker } from "../"; +import { Koala, KoalaWorker } from '../'; // @ts-ignore import koalaParams from './koala_params'; +import { KoalaError } from "../dist/types/koala_errors"; const ACCESS_KEY = Cypress.env('ACCESS_KEY'); @@ -13,6 +14,10 @@ function rootMeanSquare(pcm: Int16Array): number { return Math.sqrt(sumSquares / pcm.length); } +function delay(time: number) { + return new Promise(resolve => setTimeout(resolve, time)); +} + async function runTest( instance: typeof Koala | typeof KoalaWorker, inputPcm: Int16Array, @@ -21,59 +26,71 @@ async function runTest( ) { const errorFrames: number[] = []; - const runProcess = () => new Promise(async (resolve, reject) => { - let numFrames = 0; - let numProcessed = 0; - - const koala = await instance.create( - ACCESS_KEY, - enhancedPcm => { - const frameStart = numProcessed * koala.frameLength; - const frameEnergy = rootMeanSquare(enhancedPcm); + const runProcess = () => + new Promise(async (resolve, reject) => { + let numFrames = 0; + let numProcessed = 0; - let energyDeviation: number; - if (referencePcm === undefined || frameStart < koala.delaySample) { - energyDeviation = frameEnergy; - } else { - const referenceFrame = referencePcm.slice(frameStart - koala.delaySample, frameStart - koala.delaySample + koala.frameLength); - energyDeviation = Math.abs(frameEnergy - rootMeanSquare(referenceFrame)); - } - - try { - expect(energyDeviation).to.be.lessThan(tolerance); - } catch (e) { - errorFrames.push(numProcessed); - } + const koala = await instance.create( + ACCESS_KEY, + enhancedPcm => { + const frameStart = numProcessed * koala.frameLength; + const frameEnergy = rootMeanSquare(enhancedPcm); - numProcessed += 1; - if (numFrames === numProcessed) { - if (koala instanceof KoalaWorker) { - koala.terminate(); + let energyDeviation: number; + if (referencePcm === undefined || frameStart < koala.delaySample) { + energyDeviation = frameEnergy; } else { - koala.release(); + const referenceFrame = referencePcm.slice( + frameStart - koala.delaySample, + frameStart - koala.delaySample + koala.frameLength + ); + energyDeviation = Math.abs( + frameEnergy - rootMeanSquare(referenceFrame) + ); } - if (errorFrames.length !== 0) { - reject(`Failed comparison for frames: '${errorFrames.join(",")}'`); - } else { - resolve(); + try { + expect(energyDeviation).to.be.lessThan(tolerance); + } catch (e) { + errorFrames.push(numProcessed); } + + numProcessed += 1; + if (numFrames === numProcessed) { + if (koala instanceof KoalaWorker) { + koala.terminate(); + } else { + koala.release(); + } + + if (errorFrames.length !== 0) { + reject( + `Failed comparison for frames: '${errorFrames.join(',')}'` + ); + } else { + resolve(); + } + } + }, + { publicPath: '/test/koala_params.pv', forceWrite: true }, + { + processErrorCallback: (error: KoalaError) => { + reject(error); + }, } - }, - { publicPath: '/test/koala_params.pv', forceWrite: true }, - { - processErrorCallback: (error: string) => { - reject(error); - } - } - ); + ); - numFrames = Math.round(inputPcm.length / koala.frameLength) - 1; - await koala.reset(); - for (let i = 0; i < (inputPcm.length - koala.frameLength + 1); i += koala.frameLength) { - await koala.process(inputPcm.slice(i, i + koala.frameLength)); - } - }); + numFrames = Math.round(inputPcm.length / koala.frameLength) - 1; + await koala.reset(); + for ( + let i = 0; + i < inputPcm.length - koala.frameLength + 1; + i += koala.frameLength + ) { + await koala.process(inputPcm.slice(i, i + koala.frameLength)); + } + }); try { await runProcess(); @@ -99,24 +116,33 @@ async function testReset( numFrames = Math.round(inputPcm.length / koala.frameLength) - 1; await koala.reset(); - for (let i = 0; i < (inputPcm.length - koala.frameLength + 1); i += koala.frameLength) { + for ( + let i = 0; + i < inputPcm.length - koala.frameLength + 1; + i += koala.frameLength + ) { await koala.process(inputPcm.slice(i, i + koala.frameLength)); } - const waitUntil = (): Promise => new Promise(resolve => { - setInterval(() => { - if (numFrames === frames.length) { - resolve(); - } - }, 100); - }); + const waitUntil = (): Promise => + new Promise(resolve => { + setInterval(() => { + if (numFrames === frames.length) { + resolve(); + } + }, 100); + }); await waitUntil(); const originalFrames = [...frames]; frames = []; await koala.reset(); - for (let i = 0; i < (inputPcm.length - koala.frameLength + 1); i += koala.frameLength) { + for ( + let i = 0; + i < inputPcm.length - koala.frameLength + 1; + i += koala.frameLength + ) { await koala.process(inputPcm.slice(i, i + koala.frameLength)); } @@ -134,137 +160,148 @@ async function testReset( } describe('Koala Binding', function () { - it('should be able to init with public path', async () => { - try { + it(`should return process error message stack`, async () => { + let error: KoalaError | null = null; + + const runProcess = () => new Promise(async resolve => { const koala = await Koala.create( ACCESS_KEY, - _ => {}, - { publicPath: '/test/koala_params.pv', forceWrite: true } + () => { }, + { + publicPath: '/test/koala_params.pv', + forceWrite: true, + }, + { + processErrorCallback: (e: KoalaError) => { + error = e; + resolve(); + } + } ); - expect(koala).to.not.be.undefined; - expect(koala.frameLength).to.be.greaterThan(0); - expect(koala.delaySample).to.be.gte(0); - expect(typeof koala.version).to.eq('string'); - expect(koala.version).length.to.be.greaterThan(0); - await koala.release(); - } catch (e) { - expect(e).to.be.undefined; - } - }); + const testPcm = new Int16Array(koala.frameLength); + // @ts-ignore + const objectAddress = koala._objectAddress; - it('should be able to init with public path (worker)', async () => { - try { - const koala = await KoalaWorker.create( - ACCESS_KEY, - _ => {}, - { publicPath: '/test/koala_params.pv', forceWrite: true } - ); - expect(koala).to.not.be.undefined; - expect(koala.frameLength).to.be.greaterThan(0); - expect(koala.delaySample).to.be.gte(0); - expect(typeof koala.version).to.eq('string'); - expect(koala.version).length.to.be.greaterThan(0); - await koala.terminate(); - } catch (e) { - expect(e).to.be.undefined; - } - }); + // @ts-ignore + koala._objectAddress = 0; + await koala.process(testPcm); - it('should be able to init with base64', async () => { - try { - const koala = await Koala.create( - ACCESS_KEY, - _ => {}, - { base64: koalaParams, forceWrite: true } - ); - expect(koala).to.not.be.undefined; - expect(koala.frameLength).to.be.greaterThan(0); - expect(koala.delaySample).to.be.gte(0); - expect(typeof koala.version).to.eq('string'); - expect(koala.version).length.to.be.greaterThan(0); - await koala.release(); - } catch (e) { - expect(e).to.be.undefined; - } - }); + await delay(1000); - it('should be able to init with base64 (worker)', async () => { - try { - const koala = await KoalaWorker.create( - ACCESS_KEY, - _ => {}, - { base64: koalaParams, forceWrite: true } - ); - expect(koala).to.not.be.undefined; - expect(koala.frameLength).to.be.greaterThan(0); - expect(koala.delaySample).to.be.gte(0); - expect(typeof koala.version).to.eq('string'); - expect(koala.version).length.to.be.greaterThan(0); + // @ts-ignore + koala._objectAddress = objectAddress; await koala.release(); - } catch (e) { - expect(e).to.be.undefined; + }); + + await runProcess(); + expect(error).to.not.be.null; + if (error) { + expect((error as KoalaError).messageStack.length).to.be.gt(0); + expect((error as KoalaError).messageStack.length).to.be.lte(8); } }); - it('should be able to process pure speech', () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await runTest(Koala, inputPcm, inputPcm); + for (const instance of [Koala, KoalaWorker]) { + const instanceString = instance === KoalaWorker ? 'worker' : 'main'; + it(`should be able to init with public path (${instanceString})`, async () => { + try { + const koala = await instance.create(ACCESS_KEY, _ => {}, { + publicPath: '/test/koala_params.pv', + forceWrite: true, + }); + expect(koala).to.not.be.undefined; + expect(koala.frameLength).to.be.greaterThan(0); + expect(koala.delaySample).to.be.gte(0); + expect(typeof koala.version).to.eq('string'); + expect(koala.version).length.to.be.greaterThan(0); + if (koala instanceof KoalaWorker) { + koala.terminate(); + } else { + await koala.release(); + } + } catch (e) { + expect(e).to.be.undefined; + } }); - }); - it('should be able to process pure speech (worker)', () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await runTest(KoalaWorker, inputPcm, inputPcm); + it(`should be able to init with base64 (${instanceString})`, async () => { + try { + const koala = await Koala.create(ACCESS_KEY, _ => {}, { + base64: koalaParams, + forceWrite: true, + }); + expect(koala).to.not.be.undefined; + expect(koala.frameLength).to.be.greaterThan(0); + expect(koala.delaySample).to.be.gte(0); + expect(typeof koala.version).to.eq('string'); + expect(koala.version).length.to.be.greaterThan(0); + if (koala instanceof KoalaWorker) { + koala.terminate(); + } else { + await koala.release(); + } + } catch (e) { + expect(e).to.be.undefined; + } }); - }); - it('should be able to process noise speech', () => { - cy.getFramesFromFile('audio_samples/noise.wav').then( async inputPcm => { - await runTest(Koala, inputPcm); + it(`should be able to process pure speech (${instanceString})`, () => { + cy.getFramesFromFile('audio_samples/test.wav').then(async inputPcm => { + await runTest(instance, inputPcm, inputPcm); + }); }); - }); - it('should be able to process noise speech (worker)', () => { - cy.getFramesFromFile('audio_samples/noise.wav').then( async inputPcm => { - await runTest(KoalaWorker, inputPcm); + it(`should be able to process noise speech (${instanceString})`, () => { + cy.getFramesFromFile('audio_samples/noise.wav').then(async inputPcm => { + await runTest(instance, inputPcm); + }); }); - }); - it('should be able to process mixed speech', () => { - cy.getFramesFromFile('audio_samples/noise.wav').then( inputPcm => { - cy.getFramesFromFile('audio_samples/test.wav').then(async referencePcm => { - const noisyPcm = new Int16Array(inputPcm.length); - for (let i = 0; i < inputPcm.length; i++) { - noisyPcm[i] = inputPcm[i] + referencePcm[i]; - } + it(`should be able to process mixed speech (${instanceString})`, () => { + cy.getFramesFromFile('audio_samples/noise.wav').then(inputPcm => { + cy.getFramesFromFile('audio_samples/test.wav').then( + async referencePcm => { + const noisyPcm = new Int16Array(inputPcm.length); + for (let i = 0; i < inputPcm.length; i++) { + noisyPcm[i] = inputPcm[i] + referencePcm[i]; + } - await runTest(Koala, noisyPcm, referencePcm); + await runTest(instance, noisyPcm, referencePcm); + } + ); }); }); - }); - it('should be able to process mixed speech (worker)', () => { - cy.getFramesFromFile('audio_samples/noise.wav').then( inputPcm => { - cy.getFramesFromFile('audio_samples/test.wav').then(async referencePcm => { - const noisyPcm = new Int16Array(inputPcm.length); - for (let i = 0; i < inputPcm.length; i++) { - noisyPcm[i] = inputPcm[i] + referencePcm[i]; - } - - await runTest(KoalaWorker, noisyPcm, referencePcm); + it(`should be able to reset (${instanceString})`, () => { + cy.getFramesFromFile('audio_samples/test.wav').then(async inputPcm => { + await testReset(instance, inputPcm); }); }); - }); - it('should be able to reset', () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await testReset(Koala, inputPcm); - }); - }); + it(`should return correct error message stack (${instanceString})`, async () => { + let messageStack = []; + try { + const koala = await instance.create('invalidAccessKey', _ => {}, { + publicPath: '/test/koala_params.pv', + forceWrite: true, + }); + expect(koala).to.be.undefined; + } catch (e: any) { + messageStack = e.messageStack; + } - it('should be able to reset (worker)', () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await testReset(KoalaWorker, inputPcm); + expect(messageStack.length).to.be.gt(0); + expect(messageStack.length).to.be.lte(8); + + try { + const koala = await instance.create('invalidAccessKey', _ => {}, { + publicPath: '/test/koala_params.pv', + forceWrite: true, + }); + expect(koala).to.be.undefined; + } catch (e: any) { + expect(messageStack.length).to.be.eq(e.messageStack.length); + } }); - }); + } }); diff --git a/binding/web/test/koala_perf.test.ts b/binding/web/test/koala_perf.test.ts index 7990ed5..f55574a 100644 --- a/binding/web/test/koala_perf.test.ts +++ b/binding/web/test/koala_perf.test.ts @@ -1,8 +1,10 @@ -import { Koala, KoalaWorker } from "../"; +import { Koala, KoalaWorker } from '../'; const ACCESS_KEY = Cypress.env('ACCESS_KEY'); const NUM_TEST_ITERATIONS = Number(Cypress.env('NUM_TEST_ITERATIONS')); -const PROC_PERFORMANCE_THRESHOLD_SEC = Number(Cypress.env('PROC_PERFORMANCE_THRESHOLD_SEC')); +const PROC_PERFORMANCE_THRESHOLD_SEC = Number( + Cypress.env('PROC_PERFORMANCE_THRESHOLD_SEC') +); async function testPerformance( instance: typeof Koala | typeof KoalaWorker, @@ -23,17 +25,22 @@ async function testPerformance( numFrames = Math.round(inputPcm.length / koala.frameLength) - 1; - const waitUntil = (): Promise => new Promise(resolve => { - setInterval(() => { - if (numFrames === processedFrames) { - resolve(); - } - }, 100); - }); + const waitUntil = (): Promise => + new Promise(resolve => { + setInterval(() => { + if (numFrames === processedFrames) { + resolve(); + } + }, 100); + }); await koala.reset(); const start = Date.now(); - for (let i = 0; i < (inputPcm.length - koala.frameLength + 1); i += koala.frameLength) { + for ( + let i = 0; + i < inputPcm.length - koala.frameLength + 1; + i += koala.frameLength + ) { await koala.process(inputPcm.slice(i, i + koala.frameLength)); } @@ -49,22 +56,19 @@ async function testPerformance( } const avgPerf = perfResults.reduce((a, b) => a + b) / NUM_TEST_ITERATIONS; + // eslint-disable-next-line no-console console.log(`Average proc performance: ${avgPerf} seconds`); expect(avgPerf).to.be.lessThan(PROC_PERFORMANCE_THRESHOLD_SEC); } describe('Koala binding performance test', () => { - Cypress.config('defaultCommandTimeout', 60000); - - it(`should be lower than performance threshold (${PROC_PERFORMANCE_THRESHOLD_SEC}s)`, () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await testPerformance(Koala, inputPcm); - }); - }); + for (const instance of [Koala, KoalaWorker]) { + Cypress.config('defaultCommandTimeout', 90000); - it(`should be lower than performance threshold (${PROC_PERFORMANCE_THRESHOLD_SEC}s) (worker)`, () => { - cy.getFramesFromFile('audio_samples/test.wav').then( async inputPcm => { - await testPerformance(KoalaWorker, inputPcm); + it(`should be lower than performance threshold (${PROC_PERFORMANCE_THRESHOLD_SEC}s)`, () => { + cy.getFramesFromFile('audio_samples/test.wav').then(async inputPcm => { + await testPerformance(instance, inputPcm); + }); }); - }); + } }); diff --git a/binding/web/yarn.lock b/binding/web/yarn.lock index 45cd59c..ef9f20b 100644 --- a/binding/web/yarn.lock +++ b/binding/web/yarn.lock @@ -1161,12 +1161,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" @@ -1711,6 +1711,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" @@ -1721,11 +1726,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/Activity/koala-activity-demo-app/build.gradle b/demo/android/Activity/koala-activity-demo-app/build.gradle index 2e1efbb..1572f57 100644 --- a/demo/android/Activity/koala-activity-demo-app/build.gradle +++ b/demo/android/Activity/koala-activity-demo-app/build.gradle @@ -33,6 +33,6 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.1' implementation 'androidx.navigation:navigation-fragment:2.3.5' implementation 'androidx.navigation:navigation-ui:2.3.5' - implementation 'ai.picovoice:koala-android:1.0.0' + implementation 'ai.picovoice:koala-android:2.0.0' implementation 'ai.picovoice:android-voice-processor:1.0.2' } diff --git a/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java b/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java index 090c220..596c2ab 100644 --- a/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java +++ b/demo/android/Activity/koala-activity-demo-app/src/main/java/ai/picovoice/koalaactivitydemo/MainActivity.java @@ -89,7 +89,7 @@ protected void onCreate(Bundle savedInstanceState) { try { koala = new Koala.Builder().setAccessKey(ACCESS_KEY).build(getApplicationContext()); } catch (KoalaInvalidArgumentException e) { - onKoalaInitError(String.format("AccessKey '%s' is invalid", ACCESS_KEY)); + onKoalaInitError(e.getMessage()); } catch (KoalaActivationException e) { onKoalaInitError("AccessKey activation error"); } catch (KoalaActivationLimitException e) { diff --git a/demo/c/koala_demo_file.c b/demo/c/koala_demo_file.c index 6b1b30c..9865320 100644 --- a/demo/c/koala_demo_file.c +++ b/demo/c/koala_demo_file.c @@ -94,8 +94,17 @@ static struct option long_options[] = { {"output_path", required_argument, NULL, 'o'}, }; -void print_usage(const char *program_name) { - fprintf(stdout, "Usage: %s [-l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -i INPUT_PATH -o OUTPUT_PATH]\n", program_name); +static void print_usage(const char *program_name) { + fprintf( + stdout, + "Usage: %s [-l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -i INPUT_PATH -o OUTPUT_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]); + } } static void print_progress_bar(size_t num_total_samples, size_t num_processed_samples) { @@ -151,66 +160,94 @@ int picovoice_main(int argc, char *argv[]) { void *koala_library = open_dl(library_path); if (!koala_library) { - fprintf(stderr, "failed to open library at '%s'.\n", library_path); + fprintf(stderr, "Failed to open library at '%s'.\n", library_path); exit(EXIT_FAILURE); } const char *(*pv_status_to_string_func)(pv_status_t) = load_symbol(koala_library, "pv_status_to_string"); if (!pv_status_to_string_func) { - print_dl_error("failed to load 'pv_status_to_string'"); + print_dl_error("Failed to load 'pv_status_to_string'"); exit(EXIT_FAILURE); } int32_t (*pv_sample_rate_func)() = load_symbol(koala_library, "pv_sample_rate"); if (!pv_sample_rate_func) { - print_dl_error("failed to load 'pv_sample_rate'"); + print_dl_error("Failed to load 'pv_sample_rate'"); exit(EXIT_FAILURE); } pv_status_t (*pv_koala_init_func)(const char *, const char *, pv_koala_t **) = load_symbol(koala_library, "pv_koala_init"); if (!pv_koala_init_func) { - print_dl_error("failed to load 'pv_koala_init'"); + print_dl_error("Failed to load 'pv_koala_init'"); exit(EXIT_FAILURE); } void (*pv_koala_delete_func)(pv_koala_t *) = load_symbol(koala_library, "pv_koala_delete"); if (!pv_koala_delete_func) { - print_dl_error("failed to load 'pv_koala_delete'"); + print_dl_error("Failed to load 'pv_koala_delete'"); exit(EXIT_FAILURE); } pv_status_t (*pv_koala_process_func)(pv_koala_t *, const int16_t *, int16_t *) = load_symbol(koala_library, "pv_koala_process"); if (!pv_koala_process_func) { - print_dl_error("failed to load 'pv_koala_process'"); + print_dl_error("Failed to load 'pv_koala_process'"); exit(EXIT_FAILURE); } pv_status_t (*pv_koala_delay_sample_func)(pv_koala_t *, int32_t *) = load_symbol(koala_library, "pv_koala_delay_sample"); if (!pv_koala_delay_sample_func) { - print_dl_error("failed to load 'pv_koala_delay_sample'"); + print_dl_error("Failed to load 'pv_koala_delay_sample'"); exit(EXIT_FAILURE); } int32_t (*pv_koala_frame_length_func)() = load_symbol(koala_library, "pv_koala_frame_length"); if (!pv_koala_frame_length_func) { - print_dl_error("failed to load 'pv_koala_frame_length'"); + print_dl_error("Failed to load 'pv_koala_frame_length'"); exit(EXIT_FAILURE); } const char *(*pv_koala_version_func)() = load_symbol(koala_library, "pv_koala_version"); if (!pv_koala_version_func) { - print_dl_error("failed to load 'pv_koala_version'"); + print_dl_error("Failed to load 'pv_koala_version'"); + exit(EXIT_FAILURE); + } + + pv_status_t (*pv_get_error_stack_func)(char ***, int32_t *) = load_symbol(koala_library, "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 **) = load_symbol(koala_library, "pv_free_error_stack"); + if (!pv_free_error_stack_func) { + print_dl_error("Failed to load 'pv_free_error_stack'"); exit(EXIT_FAILURE); } pv_koala_t *koala = NULL; pv_status_t koala_status = pv_koala_init_func(access_key, model_path, &koala); if (koala_status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to init with '%s'", pv_status_to_string_func(koala_status)); + fprintf(stderr, "Failed to init with '%s'", pv_status_to_string_func(koala_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); } fprintf(stdout, "V%s\n\n", pv_koala_version_func()); @@ -231,7 +268,7 @@ int picovoice_main(int argc, char *argv[]) { #endif if (!drwav_init_file_status) { - fprintf(stderr, "failed to open wav file at '%s'.", input_path); + fprintf(stderr, "Failed to open wav file at '%s'.", input_path); exit(EXIT_FAILURE); } @@ -272,7 +309,7 @@ int picovoice_main(int argc, char *argv[]) { #endif if (!drwav_init_file_status) { - fprintf(stderr, "failed to open the output file at '%s'.", output_path); + fprintf(stderr, "Failed to open the output file at '%s'.", output_path); exit(EXIT_FAILURE); } @@ -280,7 +317,23 @@ int picovoice_main(int argc, char *argv[]) { int32_t delay_samples = 0; koala_status = pv_koala_delay_sample_func(koala, &delay_samples); if (koala_status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to get delay sample with '%s'", pv_status_to_string_func(koala_status)); + fprintf(stderr, "Failed to get delay sample with '%s'", pv_status_to_string_func(koala_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); } @@ -318,6 +371,22 @@ int picovoice_main(int argc, char *argv[]) { koala_status = pv_koala_process_func(koala, pcm, enhanced_pcm); if (koala_status != PV_STATUS_SUCCESS) { fprintf(stderr, "'pv_koala_process' failed with '%s'\n", pv_status_to_string_func(koala_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); } @@ -351,7 +420,7 @@ int picovoice_main(int argc, char *argv[]) { } const double real_time_factor = total_cpu_time_usec / total_processed_time_usec; - fprintf(stdout, "\nreal time factor : %.3f\n", real_time_factor); + fprintf(stdout, "\nReal time factor : %.3f\n", real_time_factor); fprintf(stdout, "\n"); @@ -382,7 +451,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/koala_demo_mic.c b/demo/c/koala_demo_mic.c index 730a1c6..a94e2da 100644 --- a/demo/c/koala_demo_mic.c +++ b/demo/c/koala_demo_mic.c @@ -99,18 +99,24 @@ static struct option long_options[] = { {"show_audio_devices", no_argument, NULL, 's'}, }; -void print_usage(const char *program_name) { +static void print_usage(const char *program_name) { fprintf(stdout, "Usage: %s [-s] [-l LIBRARY_PATH -m MODEL_PATH -a ACCESS_KEY -d AUDIO_DEVICE_INDEX -o WAV_OUTPUT_PATH -r WAV_REFERENCE_PATH]\n", program_name); } -void interrupt_handler(int _) { +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]); + } +} + +static void interrupt_handler(int _) { (void) _; is_interrupted = true; } -void show_audio_devices(void) { +static void show_audio_devices(void) { char **devices = NULL; int32_t count = 0; @@ -217,7 +223,7 @@ int picovoice_main(int argc, char *argv[]) { #endif if (!drwav_init_file_status) { - fprintf(stderr, "failed to open the output wav file at '%s'.", output_path); + fprintf(stderr, "Failed to open the output wav file at '%s'.", output_path); exit(EXIT_FAILURE); } @@ -239,26 +245,26 @@ int picovoice_main(int argc, char *argv[]) { #endif if (!drwav_init_file_status) { - fprintf(stderr, "failed to open the reference wav file at '%s'.", reference_path); + fprintf(stderr, "Failed to open the reference wav file at '%s'.", reference_path); exit(EXIT_FAILURE); } } void *koala_library = open_dl(library_path); if (!koala_library) { - fprintf(stderr, "failed to open library at '%s'.\n", library_path); + fprintf(stderr, "Failed to open library at '%s'.\n", library_path); exit(EXIT_FAILURE); } const char *(*pv_status_to_string_func)(pv_status_t) = load_symbol(koala_library, "pv_status_to_string"); if (!pv_status_to_string_func) { - print_dl_error("failed to load 'pv_status_to_string'"); + print_dl_error("Failed to load 'pv_status_to_string'"); exit(EXIT_FAILURE); } int32_t (*pv_sample_rate_func)() = load_symbol(koala_library, "pv_sample_rate"); if (!pv_sample_rate_func) { - print_dl_error("failed to load 'pv_sample_rate'"); + print_dl_error("Failed to load 'pv_sample_rate'"); exit(EXIT_FAILURE); } @@ -267,13 +273,13 @@ int picovoice_main(int argc, char *argv[]) { const char *, pv_koala_t **) = load_symbol(koala_library, "pv_koala_init"); if (!pv_koala_init_func) { - print_dl_error("failed to load 'pv_koala_init'"); + print_dl_error("Failed to load 'pv_koala_init'"); exit(EXIT_FAILURE); } void (*pv_koala_delete_func)(pv_koala_t *) = load_symbol(koala_library, "pv_koala_delete"); if (!pv_koala_delete_func) { - print_dl_error("failed to load 'pv_koala_delete'"); + print_dl_error("Failed to load 'pv_koala_delete'"); exit(EXIT_FAILURE); } @@ -282,26 +288,54 @@ int picovoice_main(int argc, char *argv[]) { int16_t *) = load_symbol(koala_library, "pv_koala_process"); if (!pv_koala_process_func) { - print_dl_error("failed to load 'pv_koala_process'"); + print_dl_error("Failed to load 'pv_koala_process'"); exit(EXIT_FAILURE); } int32_t (*pv_koala_frame_length_func)() = load_symbol(koala_library, "pv_koala_frame_length"); if (!pv_koala_frame_length_func) { - print_dl_error("failed to load 'pv_koala_frame_length'"); + print_dl_error("Failed to load 'pv_koala_frame_length'"); exit(EXIT_FAILURE); } const char *(*pv_koala_version_func)() = load_symbol(koala_library, "pv_koala_version"); if (!pv_koala_version_func) { - print_dl_error("failed to load 'pv_koala_version'"); + print_dl_error("Failed to load 'pv_koala_version'"); + exit(EXIT_FAILURE); + } + + pv_status_t (*pv_get_error_stack_func)(char ***, int32_t *) = load_symbol(koala_library, "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 **) = load_symbol(koala_library, "pv_free_error_stack"); + if (!pv_free_error_stack_func) { + print_dl_error("Failed to load 'pv_free_error_stack'"); exit(EXIT_FAILURE); } pv_koala_t *koala = NULL; pv_status_t koala_status = pv_koala_init_func(access_key, model_path, &koala); if (koala_status != PV_STATUS_SUCCESS) { - fprintf(stderr, "failed to init with '%s'", pv_status_to_string_func(koala_status)); + fprintf(stderr, "Failed to init with '%s'", pv_status_to_string_func(koala_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); } @@ -346,7 +380,23 @@ int picovoice_main(int argc, char *argv[]) { koala_status = pv_koala_process_func(koala, pcm, enhanced_pcm); if (koala_status != PV_STATUS_SUCCESS) { - fprintf(stderr, "'pv_koala_process' failed with '%s'\n", pv_status_to_string_func(koala_status)); + fprintf(stderr, "'pv_koala_process' failed with '%s'", pv_status_to_string_func(koala_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); } @@ -402,7 +452,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/test/test_koala_c.py b/demo/c/test/test_koala_c.py index 62dff1c..9477d76 100644 --- a/demo/c/test/test_koala_c.py +++ b/demo/c/test/test_koala_c.py @@ -61,7 +61,7 @@ def run_koala(self, audio_file_name): stdout, stderr = process.communicate() self.assertEqual(process.poll(), 0) self.assertEqual(stderr.decode('utf-8'), '') - self.assertTrue("real time factor" in stdout.decode('utf-8')) + self.assertTrue("Real time factor" in stdout.decode('utf-8')) def test_koala(self): self.run_koala("test.wav") diff --git a/demo/ios/KoalaDemo/Podfile b/demo/ios/KoalaDemo/Podfile index 0c033f8..61ab0f5 100644 --- a/demo/ios/KoalaDemo/Podfile +++ b/demo/ios/KoalaDemo/Podfile @@ -1,7 +1,7 @@ source 'https://cdn.cocoapods.org/' -platform :ios, '11.0' +platform :ios, '13.0' target 'KoalaDemo' do - pod 'Koala-iOS', '~> 1.0.0' + pod 'Koala-iOS', '~> 2.0.0' pod 'ios-voice-processor', '~> 1.1.0' end diff --git a/demo/ios/KoalaDemo/Podfile.lock b/demo/ios/KoalaDemo/Podfile.lock index d58ac25..b4d14e0 100644 --- a/demo/ios/KoalaDemo/Podfile.lock +++ b/demo/ios/KoalaDemo/Podfile.lock @@ -1,10 +1,10 @@ PODS: - ios-voice-processor (1.1.0) - - Koala-iOS (1.0.0) + - Koala-iOS (2.0.0) DEPENDENCIES: - ios-voice-processor (~> 1.1.0) - - Koala-iOS (~> 1.0.0) + - Koala-iOS (~> 2.0.0) SPEC REPOS: trunk: @@ -13,8 +13,8 @@ SPEC REPOS: SPEC CHECKSUMS: ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1 - Koala-iOS: 98ccd64b2531c57a0380dae47ea4105c4c5ae641 + Koala-iOS: b0e48400203d615520e6539f9994826183e5bb5a -PODFILE CHECKSUM: 0e642f89b04c02a356a72938e8737b9f102378c0 +PODFILE CHECKSUM: 83bf75f0e80df078aab1b58cc8d0c1f923297232 COCOAPODS: 1.11.3 diff --git a/demo/python/koala_demo_mic.py b/demo/python/koala_demo_mic.py index ad4157a..75b424a 100644 --- a/demo/python/koala_demo_mic.py +++ b/demo/python/koala_demo_mic.py @@ -13,7 +13,6 @@ import contextlib import math import struct -import sys import wave from pvkoala import create, KoalaActivationLimitError @@ -45,7 +44,7 @@ def main(): args = parser.parse_args() if args.show_audio_devices: - for index, name in enumerate(PvRecorder.get_audio_devices()): + for index, name in enumerate(PvRecorder.get_available_devices()): print('Device #%d: %s' % (index, name)) return diff --git a/demo/python/requirements.txt b/demo/python/requirements.txt index 5440243..e7508ed 100644 --- a/demo/python/requirements.txt +++ b/demo/python/requirements.txt @@ -1,2 +1,2 @@ -pvkoala==1.0.1 +pvkoala==2.0.0 pvrecorder==1.2.1 diff --git a/demo/python/setup.py b/demo/python/setup.py index 48ea6b4..71c482e 100644 --- a/demo/python/setup.py +++ b/demo/python/setup.py @@ -24,7 +24,7 @@ setuptools.setup( name="pvkoalademo", - version="1.0.3", + version="2.0.0", author="Picovoice", author_email="hello@picovoice.ai", description="Koala Noise Suppression Engine demos", @@ -32,7 +32,7 @@ long_description_content_type="text/markdown", url="https://github.com/Picovoice/koala", packages=["pvkoalademo"], - install_requires=["pvkoala==1.0.1", "pvrecorder==1.2.1"], + install_requires=["pvkoala==2.0.0", "pvrecorder==1.2.1"], include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/demo/web/package.json b/demo/web/package.json index 7b1f5d0..6f2ee91 100644 --- a/demo/web/package.json +++ b/demo/web/package.json @@ -1,6 +1,6 @@ { "name": "koala-web-demo", - "version": "1.0.0", + "version": "2.0.0", "description": "A basic demo to show how to use Koala 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/koala-web": "~1.0.3", + "@picovoice/koala-web": "=2.0.0", "@picovoice/web-voice-processor": "~4.0.8" }, "devDependencies": { diff --git a/demo/web/yarn.lock b/demo/web/yarn.lock index 01ac5c3..3bd3893 100644 --- a/demo/web/yarn.lock +++ b/demo/web/yarn.lock @@ -2,12 +2,12 @@ # yarn lockfile v1 -"@picovoice/koala-web@~1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@picovoice/koala-web/-/koala-web-1.0.3.tgz#95dd441f34f1697d7d1a03d5b61326ad3a1488ac" - integrity sha512-S1LFNn1CaWsBZ0jNo3IsfFfhAyjl7aZjCwO4eyFdJdHsybFNORUeKAWEfaPoyELmiHUooR4jtRVn51f+woGvyA== +"@picovoice/koala-web@=2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@picovoice/koala-web/-/koala-web-2.0.0.tgz#04941fcb424bfc33d185b35d74e415b630acaaf4" + integrity sha512-bqR+zzWAeY1EpeZkUguT+mXlTVa8KSwa6Av+P+YGI7sUMm9YGM01QdVgmutyMZzpU5HhFmeF3udM4VgDRQh0QA== dependencies: - "@picovoice/web-utils" "=1.3.1" + "@picovoice/web-utils" "=1.3.2" "@picovoice/web-utils@=1.3.1": version "1.3.1" @@ -16,6 +16,13 @@ dependencies: commander "^9.2.0" +"@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 "^10.0.1" + "@picovoice/web-voice-processor@~4.0.8": version "4.0.8" resolved "https://registry.yarnpkg.com/@picovoice/web-voice-processor/-/web-voice-processor-4.0.8.tgz#95247a5393cac4d16490a53feb0f413c902ee5fa" @@ -72,6 +79,11 @@ 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@^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@^9.2.0: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" diff --git a/include/picovoice.h b/include/picovoice.h index 76fae98..886558d 100644 --- a/include/picovoice.h +++ b/include/picovoice.h @@ -53,6 +53,30 @@ 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 } diff --git a/lib/android/arm64-v8a/libpv_koala.so b/lib/android/arm64-v8a/libpv_koala.so index bf43828..c9b6350 100755 Binary files a/lib/android/arm64-v8a/libpv_koala.so and b/lib/android/arm64-v8a/libpv_koala.so differ diff --git a/lib/android/armeabi-v7a/libpv_koala.so b/lib/android/armeabi-v7a/libpv_koala.so index 8db97e6..30e46a6 100755 Binary files a/lib/android/armeabi-v7a/libpv_koala.so and b/lib/android/armeabi-v7a/libpv_koala.so differ diff --git a/lib/android/x86/libpv_koala.so b/lib/android/x86/libpv_koala.so index d3b0670..5f31bfd 100755 Binary files a/lib/android/x86/libpv_koala.so and b/lib/android/x86/libpv_koala.so differ diff --git a/lib/android/x86_64/libpv_koala.so b/lib/android/x86_64/libpv_koala.so index 6de7777..804a7b4 100755 Binary files a/lib/android/x86_64/libpv_koala.so and b/lib/android/x86_64/libpv_koala.so differ diff --git a/lib/common/koala_params.pv b/lib/common/koala_params.pv index ec2f523..d768615 100644 Binary files a/lib/common/koala_params.pv and b/lib/common/koala_params.pv differ diff --git a/lib/ios/PvKoala.xcframework/Info.plist b/lib/ios/PvKoala.xcframework/Info.plist index d1e42d7..cd0a8c7 100644 --- a/lib/ios/PvKoala.xcframework/Info.plist +++ b/lib/ios/PvKoala.xcframework/Info.plist @@ -6,30 +6,30 @@ LibraryIdentifier - ios-arm64_x86_64-simulator + ios-arm64 LibraryPath PvKoala.framework SupportedArchitectures arm64 - x86_64 SupportedPlatform ios - SupportedPlatformVariant - simulator LibraryIdentifier - ios-arm64 + ios-arm64_x86_64-simulator LibraryPath PvKoala.framework SupportedArchitectures arm64 + x86_64 SupportedPlatform ios + SupportedPlatformVariant + simulator CFBundlePackageType diff --git a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Headers/picovoice.h b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Headers/picovoice.h index 147f6be..3f13d70 100644 --- a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Headers/picovoice.h +++ b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.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. @@ -53,8 +53,33 @@ typedef enum { */ PV_API const char *pv_status_to_string(pv_status_t status); -#ifdef __cplusplus +/** + * 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 } #endif diff --git a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Info.plist b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Info.plist index fa085fa..4ce8d88 100644 Binary files a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Info.plist and b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/Info.plist differ diff --git a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/PvKoala b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/PvKoala index f8906df..753ffae 100755 Binary files a/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/PvKoala and b/lib/ios/PvKoala.xcframework/ios-arm64/PvKoala.framework/PvKoala differ diff --git a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Headers/picovoice.h b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Headers/picovoice.h index 147f6be..3f13d70 100644 --- a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Headers/picovoice.h +++ b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.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. @@ -53,8 +53,33 @@ typedef enum { */ PV_API const char *pv_status_to_string(pv_status_t status); -#ifdef __cplusplus +/** + * 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 } #endif diff --git a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Info.plist b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Info.plist index f4109fd..5b47355 100644 Binary files a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Info.plist and b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/Info.plist differ diff --git a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/PvKoala b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/PvKoala index 36b19ec..53b0ce9 100755 Binary files a/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/PvKoala and b/lib/ios/PvKoala.xcframework/ios-arm64_x86_64-simulator/PvKoala.framework/PvKoala differ diff --git a/lib/jetson/cortex-a57-aarch64/libpv_koala.so b/lib/jetson/cortex-a57-aarch64/libpv_koala.so index deb3021..5b2b836 100755 Binary files a/lib/jetson/cortex-a57-aarch64/libpv_koala.so and b/lib/jetson/cortex-a57-aarch64/libpv_koala.so differ diff --git a/lib/linux/x86_64/libpv_koala.so b/lib/linux/x86_64/libpv_koala.so index 73d4269..928a4e9 100755 Binary files a/lib/linux/x86_64/libpv_koala.so and b/lib/linux/x86_64/libpv_koala.so differ diff --git a/lib/mac/arm64/libpv_koala.dylib b/lib/mac/arm64/libpv_koala.dylib index f664624..1e64032 100755 Binary files a/lib/mac/arm64/libpv_koala.dylib and b/lib/mac/arm64/libpv_koala.dylib differ diff --git a/lib/mac/x86_64/libpv_koala.dylib b/lib/mac/x86_64/libpv_koala.dylib index 1981bc2..f31f9e6 100755 Binary files a/lib/mac/x86_64/libpv_koala.dylib and b/lib/mac/x86_64/libpv_koala.dylib differ diff --git a/lib/raspberry-pi/cortex-a53-aarch64/libpv_koala.so b/lib/raspberry-pi/cortex-a53-aarch64/libpv_koala.so index a08079b..34c4d8c 100755 Binary files a/lib/raspberry-pi/cortex-a53-aarch64/libpv_koala.so and b/lib/raspberry-pi/cortex-a53-aarch64/libpv_koala.so differ diff --git a/lib/raspberry-pi/cortex-a53/libpv_koala.so b/lib/raspberry-pi/cortex-a53/libpv_koala.so index 29abcc2..e7c181b 100755 Binary files a/lib/raspberry-pi/cortex-a53/libpv_koala.so and b/lib/raspberry-pi/cortex-a53/libpv_koala.so differ diff --git a/lib/raspberry-pi/cortex-a72-aarch64/libpv_koala.so b/lib/raspberry-pi/cortex-a72-aarch64/libpv_koala.so index 61ded8b..b7d14ee 100755 Binary files a/lib/raspberry-pi/cortex-a72-aarch64/libpv_koala.so and b/lib/raspberry-pi/cortex-a72-aarch64/libpv_koala.so differ diff --git a/lib/raspberry-pi/cortex-a72/libpv_koala.so b/lib/raspberry-pi/cortex-a72/libpv_koala.so index dbbf9af..89665e3 100755 Binary files a/lib/raspberry-pi/cortex-a72/libpv_koala.so and b/lib/raspberry-pi/cortex-a72/libpv_koala.so differ diff --git a/lib/wasm/pv_koala.wasm b/lib/wasm/pv_koala.wasm index 7a63f12..b0ee33d 100755 Binary files a/lib/wasm/pv_koala.wasm and b/lib/wasm/pv_koala.wasm differ diff --git a/lib/wasm/pv_koala_simd.wasm b/lib/wasm/pv_koala_simd.wasm index 6be6324..4b8b5e1 100755 Binary files a/lib/wasm/pv_koala_simd.wasm and b/lib/wasm/pv_koala_simd.wasm differ diff --git a/lib/windows/amd64/libpv_koala.dll b/lib/windows/amd64/libpv_koala.dll index b0d5b6a..1d0373b 100644 Binary files a/lib/windows/amd64/libpv_koala.dll and b/lib/windows/amd64/libpv_koala.dll differ diff --git a/resources/.lint/java/suppress.xml b/resources/.lint/java/suppress.xml index 290f2c2..02787ef 100644 --- a/resources/.lint/java/suppress.xml +++ b/resources/.lint/java/suppress.xml @@ -10,7 +10,7 @@ - +