Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
albho committed Nov 9, 2023
1 parent a22dd64 commit 0f803f9
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 72 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/react.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ on:
- '!binding/react/README.md'
- 'lib/common/**'
- 'lib/wasm/**'
- 'resources/**'
- '!resources/.lint/**'
- 'resources/.test/**'
- 'resources/audio_samples/**'
- '.github/workflows/react.yml'
pull_request:
branches: [ master, 'v[0-9]+.[0-9]+' ]
Expand All @@ -19,8 +19,8 @@ on:
- '!binding/react/README.md'
- 'lib/common/**'
- 'lib/wasm/**'
- 'resources/**'
- '!resources/.lint/**'
- 'resources/.test/**'
- 'resources/audio_samples/**'
- '.github/workflows/react.yml'

defaults:
Expand Down
1 change: 0 additions & 1 deletion binding/react/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ cypress/**/*.wav
test/*.pv
test/*.js
test/**/*.json
test/**/*.ppn
26 changes: 8 additions & 18 deletions binding/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,48 +106,38 @@ const options = {
}
```

For processing, you may also consider transferring the buffer for performance:

```typescript
const processOptions = {
transfer: true,
transferCallback: (data) => { pcm = data }
}
```

### Initialize Leopard

Use `useLeopard` and `init` to initialize `Leopard`:

```typescript
const {
init,
result,
isLoaded,
error,
init,
processFile,
startRecording,
stopRecording,
isRecording,
recordingElapsedSec,
result,
release,
error,
} = useLeopard();

const initLeopard = async () => {
await init(
"${ACCESS_KEY}",
leopardModel,
options,
processOptions
)
);
}
```

In case of any errors, use the `error` state variable to check the error message. Use the `isLoaded` state variable to check if `Leopard` has loaded.

### Process Audio Frames
### Transcribe Audio

The audio that you want to transcribe can either be uploaded as a File object or recorded.
The audio that you want to transcribe can either be uploaded as a `File` object or recorded with a microphone.

#### File Object

Expand Down Expand Up @@ -177,7 +167,7 @@ await startRecording();

If `WebVoiceProcessor` has started correctly, `isRecording` will be set to true.

**Note**: By default, Leopard will only record for 2 minutes before stopping and processing the recording. This is to prevent buffer overflow. To increase this limit, call `startRecording` with the optional `maxRecordingSec` parameter:
**Note**: By default, Leopard will only record for 2 minutes before stopping and processing the buffered audio. This is to prevent unbounded memory usage. To increase this limit, call `startRecording` with the optional `maxRecordingSec` parameter:

```typescript
const maxRecordingSec = 60 * 10
Expand Down Expand Up @@ -216,7 +206,7 @@ While running in a component, you can call `release` to clean up all resources u
await release();
```

This will set `isLoaded` and `isRecording` to false and `error` to null.
This will set `isLoaded` and `isRecording` to false, `recordingElapsedSec` to 0, and `error` to null.

If any arguments require changes, call `release`, then `init` again to initialize Leopard with the new settings.

Expand Down
82 changes: 43 additions & 39 deletions binding/react/src/use_leopard.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// /*
// 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.
// */
//
/*
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 { useCallback, useEffect, useRef, useState } from 'react';
import { WebVoiceProcessor } from '@picovoice/web-voice-processor';
import {
Expand All @@ -27,11 +27,7 @@ export const useLeopard = (): {
init: (
accessKey: string,
model: LeopardModel,
options?: LeopardOptions,
processOptions?: {
transfer?: boolean;
transferCallback?: (data: Int16Array) => void;
}
options?: LeopardOptions
) => Promise<void>;
processFile: (file: File) => Promise<void>;
startRecording: (maxRecordingSec?: number) => Promise<void>;
Expand All @@ -48,8 +44,7 @@ export const useLeopard = (): {

const leopardRef = useRef<LeopardWorker | null>(null);
const audioDataRef = useRef<Int16Array[]>([]);
const processOptionsRef = useRef({});
const timerRef = useRef<null | ReturnType<typeof setTimeout>>(null);
const timerRef = useRef<null | ReturnType<typeof setInterval>>(null);
const recorderEngineRef = useRef({
onmessage: (event: any) => {
switch (event.data.command) {
Expand All @@ -66,11 +61,7 @@ export const useLeopard = (): {
async (
accessKey: string,
model: LeopardModel,
options: LeopardOptions = {},
processOptions: {
transfer?: boolean;
transferCallback?: (data: Int16Array) => void;
} = {}
options: LeopardOptions = {}
): Promise<void> => {
try {
if (!leopardRef.current) {
Expand All @@ -80,7 +71,6 @@ export const useLeopard = (): {
options
);

processOptionsRef.current = processOptions;
setIsLoaded(true);
setError(null);
}
Expand All @@ -100,10 +90,9 @@ export const useLeopard = (): {
return;
}

const processResult = await leopardRef.current.process(
pcm,
processOptionsRef.current
);
const processResult = await leopardRef.current.process(pcm, {
transfer: true,
});
setResult(processResult);
} catch (e: any) {
setError(e);
Expand Down Expand Up @@ -160,24 +149,29 @@ export const useLeopard = (): {

const stopRecording = useCallback(async (): Promise<void> => {
try {
if (!isRecording) {
return;
}

if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}

await WebVoiceProcessor.unsubscribe(recorderEngineRef.current);
setIsRecording(false);

const frames = new Int16Array(audioDataRef.current.length * 512);
for (let i = 0; i < audioDataRef.current.length; i++) {
frames.set(audioDataRef.current[i], i * 512);
}
await process(frames);
await WebVoiceProcessor.unsubscribe(recorderEngineRef.current);
setIsRecording(false);

audioDataRef.current = [];
const frames = new Int16Array(audioDataRef.current.length * 512);
for (let i = 0; i < audioDataRef.current.length; i++) {
frames.set(audioDataRef.current[i], i * 512);
}
await process(frames);

audioDataRef.current = [];
} catch (e: any) {
setError(e);
}
}, []);
}, [isRecording]);

const startRecording = useCallback(
async (maxRecordingSec = DEFAULT_MAX_RECORDING_SEC): Promise<void> => {
Expand All @@ -188,8 +182,13 @@ export const useLeopard = (): {
return;
}

if (isRecording) {
return;
}

setError(null);
audioDataRef.current = [];
setRecordingElapsedSec(0);

try {
await WebVoiceProcessor.subscribe(recorderEngineRef.current);
Expand All @@ -210,7 +209,7 @@ export const useLeopard = (): {
setError(e);
}
},
[]
[isRecording]
);

const release = useCallback(async (): Promise<void> => {
Expand All @@ -219,9 +218,14 @@ export const useLeopard = (): {
await WebVoiceProcessor.unsubscribe(recorderEngineRef.current);
leopardRef.current?.terminate();
leopardRef.current = null;
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
setError(null);
setIsLoaded(false);
setIsRecording(false);
setRecordingElapsedSec(0);
}
} catch (e: any) {
setError(e);
Expand Down
2 changes: 1 addition & 1 deletion demo/react/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ button {
.release-button,
input {
padding: 0;
margin: 0;
margin: 0 0.25rem;
font-size: medium;
}

Expand Down
24 changes: 15 additions & 9 deletions demo/react/src/VoiceWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
import React, { useCallback, useRef } from "react";
import React, { useCallback, useState, useRef } from "react";
import { useLeopard } from "@picovoice/leopard-react";

import leopardModel from "./lib/leopardModel";

export default function VoiceWidget() {
const accessKeyRef = useRef<string>("");
const [isBusy, setIsBusy] = useState(false);

const {
init,
result,
isLoaded,
error,
init,
processFile,
startRecording,
stopRecording,
isRecording,
recordingElapsedSec,
release,
error,
} = useLeopard();

const initEngine = useCallback(async () => {
if (accessKeyRef.current.length === 0) {
return;
}

setIsBusy(true);
await init(
accessKeyRef.current,
leopardModel,
{
enableAutomaticPunctuation: true,
},
{
transfer: true,
}
);
setIsBusy(false);
}, [init]);

const toggleRecord = async () => {
setIsBusy(true);
if (isRecording) {
await stopRecording();
} else {
await startRecording();
}
setIsBusy(false);
};

return (
Expand All @@ -54,21 +56,22 @@ export default function VoiceWidget() {
<input
type="text"
name="accessKey"
disabled={isLoaded || isBusy}
onChange={(e) => {
accessKeyRef.current = e.target.value;
}}
/>
<button
className="init-button"
onClick={initEngine}
disabled={isLoaded}
disabled={isLoaded || isBusy}
>
Init Leopard
</button>
<button
className="release-button"
onClick={release}
disabled={error !== null || !isLoaded}
disabled={!isLoaded || isBusy}
>
Release
</button>
Expand All @@ -87,9 +90,12 @@ export default function VoiceWidget() {
name="audio-file"
onChange={async (e) => {
if (!!e.target.files?.length) {
setIsBusy(true);
await processFile(e.target.files[0]);
setIsBusy(false);
}
}}
disabled={!isLoaded || isBusy}
/>
<p>
<b>OR</b>
Expand All @@ -100,7 +106,7 @@ export default function VoiceWidget() {
<span>{recordingElapsedSec}s</span>
<br />
<br />
<button id="record-audio" onClick={toggleRecord}>
<button id="record-audio" onClick={toggleRecord} disabled={!isLoaded || isBusy}>
{isRecording ? "Stop Recording" : "Start Recording"}
</button>
<h3>Transcript:</h3>
Expand Down

0 comments on commit 0f803f9

Please sign in to comment.