@fonoster/voice 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/VoiceResponse.d.ts +22 -1
- package/dist/VoiceResponse.js +40 -0
- package/dist/filters.d.ts +44 -0
- package/dist/filters.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/verbs/SetAudioFilters.d.ts +25 -0
- package/dist/verbs/SetAudioFilters.js +16 -0
- package/dist/verbs/index.d.ts +1 -0
- package/dist/verbs/index.js +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -33,6 +33,37 @@ A Voice Application is a server that controls a call's flow. A Voice Application
|
|
|
33
33
|
- `Record` - It records the voice of the calling party and saves the audio on the Storage sub-system
|
|
34
34
|
- `Mute` - It tells the channel to stop sending media, effectively muting the channel
|
|
35
35
|
- `Unmute` - It tells the channel to allow media flow
|
|
36
|
+
- `SetAudioFilters` - Filters the caller's audio in the Media Server before it reaches speech recognition and your media stream
|
|
37
|
+
|
|
38
|
+
### Audio filters
|
|
39
|
+
|
|
40
|
+
Background voices — a television, a radio, someone else in the room — get transcribed too, and
|
|
41
|
+
your application ends up answering them. Ask the Media Server to filter the caller's audio before
|
|
42
|
+
it reaches speech recognition and your media stream:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const { aiCoustics } = require("@fonoster/voice");
|
|
46
|
+
|
|
47
|
+
new VoiceServer().listen(async (req: VoiceRequest, voice: VoiceResponse) => {
|
|
48
|
+
// Set filters before answering; they apply for the rest of the session
|
|
49
|
+
await voice.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
50
|
+
|
|
51
|
+
await voice.answer();
|
|
52
|
+
// ...
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
|
|
58
|
+
- The filter runs in the Media Server, which needs the optional
|
|
59
|
+
`@ai-coustics/aic-sdk` package and an `AIC_SDK_LICENSE` key
|
|
60
|
+
([developers.ai-coustics.com](https://developers.ai-coustics.com)). Credentials never leave the
|
|
61
|
+
Media Server, so your application never handles them.
|
|
62
|
+
- `enhancementLevel` goes from 0 to 1; higher values also suppress competing speech.
|
|
63
|
+
- If the filter cannot be applied, `setAudioFilters` throws and **the call continues with
|
|
64
|
+
unfiltered audio**. Catch it if a failure should not interrupt your flow.
|
|
65
|
+
- Filtering adds about 30 ms of delay to the audio your application hears.
|
|
66
|
+
- Sending a new list replaces the previous one, and an empty list removes all filters.
|
|
36
67
|
|
|
37
68
|
Voice Application Example:
|
|
38
69
|
|
package/dist/VoiceResponse.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* See the License for the specific language governing permissions and
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
|
-
import { DialOptions, GatherOptions, GatherResponse, MuteOptions, PlaybackControlAction, PlayOptions, PlayResponse, RecordOptions, RecordResponse, SayOptions, StreamEvent, StreamGatherOptions, StreamOptions, VerbResponse, VoiceRequest, VoiceSessionStreamServer } from "@fonoster/common";
|
|
19
|
+
import { AudioFilterConfig, DialOptions, GatherOptions, GatherResponse, MuteOptions, PlaybackControlAction, PlayOptions, PlayResponse, RecordOptions, RecordResponse, SayOptions, StreamEvent, StreamGatherOptions, StreamOptions, VerbResponse, VoiceRequest, VoiceSessionStreamServer } from "@fonoster/common";
|
|
20
20
|
import { DialStatusStream, Stream, StreamGatherStream } from "./verbs";
|
|
21
21
|
/**
|
|
22
22
|
* @classdesc Use the VoiceResponse object, to construct advance Interactive
|
|
@@ -235,6 +235,27 @@ declare class VoiceResponse {
|
|
|
235
235
|
* }
|
|
236
236
|
*/
|
|
237
237
|
sgather(options: StreamGatherOptions): Promise<StreamGatherStream>;
|
|
238
|
+
/**
|
|
239
|
+
* Sets the audio filters for this session. Filters run in the Media Server,
|
|
240
|
+
* on the caller's audio, before it reaches speech recognition and the
|
|
241
|
+
* application's media stream. Call it before answering.
|
|
242
|
+
*
|
|
243
|
+
* Sending a new list replaces the previous one; an empty list removes all
|
|
244
|
+
* filters. If a filter cannot be created — an unknown name, invalid options,
|
|
245
|
+
* a missing SDK or license — the verb fails and the call continues with
|
|
246
|
+
* unfiltered audio.
|
|
247
|
+
*
|
|
248
|
+
* @param {AudioFilterConfig[]} filters - The filters to run, in order
|
|
249
|
+
* @return {Promise<void>}
|
|
250
|
+
* @see aiCoustics
|
|
251
|
+
* @example
|
|
252
|
+
*
|
|
253
|
+
* async function handler (request, response) {
|
|
254
|
+
* await response.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
255
|
+
* await response.answer();
|
|
256
|
+
* }
|
|
257
|
+
*/
|
|
258
|
+
setAudioFilters(filters: AudioFilterConfig[]): Promise<void>;
|
|
238
259
|
/**
|
|
239
260
|
* Mutes a call.
|
|
240
261
|
*
|
package/dist/VoiceResponse.js
CHANGED
|
@@ -355,6 +355,46 @@ class VoiceResponse {
|
|
|
355
355
|
return stream;
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Sets the audio filters for this session. Filters run in the Media Server,
|
|
360
|
+
* on the caller's audio, before it reaches speech recognition and the
|
|
361
|
+
* application's media stream. Call it before answering.
|
|
362
|
+
*
|
|
363
|
+
* Sending a new list replaces the previous one; an empty list removes all
|
|
364
|
+
* filters. If a filter cannot be created — an unknown name, invalid options,
|
|
365
|
+
* a missing SDK or license — the verb fails and the call continues with
|
|
366
|
+
* unfiltered audio.
|
|
367
|
+
*
|
|
368
|
+
* @param {AudioFilterConfig[]} filters - The filters to run, in order
|
|
369
|
+
* @return {Promise<void>}
|
|
370
|
+
* @see aiCoustics
|
|
371
|
+
* @example
|
|
372
|
+
*
|
|
373
|
+
* async function handler (request, response) {
|
|
374
|
+
* await response.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
375
|
+
* await response.answer();
|
|
376
|
+
* }
|
|
377
|
+
*/
|
|
378
|
+
setAudioFilters(filters) {
|
|
379
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
380
|
+
var _a;
|
|
381
|
+
const result = yield new verbs_1.SetAudioFilters(this.request, this.voice).run({
|
|
382
|
+
mediaSessionRef: this.request.mediaSessionRef,
|
|
383
|
+
filters: filters.map((filter) => {
|
|
384
|
+
var _a;
|
|
385
|
+
return ({
|
|
386
|
+
name: filter.name,
|
|
387
|
+
options: pb_util_1.struct.encode(((_a = filter.options) !== null && _a !== void 0 ? _a : {}))
|
|
388
|
+
});
|
|
389
|
+
})
|
|
390
|
+
});
|
|
391
|
+
// The Media Server applies what it can and keeps the call alive; anything
|
|
392
|
+
// it refused comes back here so the application can log or react to it.
|
|
393
|
+
if ((_a = result.setAudioFiltersResponse) === null || _a === void 0 ? void 0 : _a.error) {
|
|
394
|
+
throw new Error(result.setAudioFiltersResponse.error);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
358
398
|
/**
|
|
359
399
|
* Mutes a call.
|
|
360
400
|
*
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { AudioFilterConfig } from "@fonoster/common";
|
|
20
|
+
/**
|
|
21
|
+
* Options for the ai-coustics filter. Tuning only: the license key is read
|
|
22
|
+
* from the Media Server's environment and never travels with the session.
|
|
23
|
+
*/
|
|
24
|
+
type AiCousticsOptions = {
|
|
25
|
+
/** Model to load. Defaults to Quail Voice Focus at 16 kHz. */
|
|
26
|
+
model?: string;
|
|
27
|
+
/** 0 to 1. Higher values also suppress competing speech. Defaults to 1. */
|
|
28
|
+
enhancementLevel?: number;
|
|
29
|
+
/** Keeps the processing delay but stops enhancing. Useful for A/B tests. */
|
|
30
|
+
bypass?: boolean;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* ai-coustics speech enhancement: keeps the primary speaker and suppresses
|
|
34
|
+
* noise and competing voices.
|
|
35
|
+
*
|
|
36
|
+
* @param {AiCousticsOptions} options - Tuning options for the filter
|
|
37
|
+
* @return {AudioFilterConfig} A filter to pass to `setAudioFilters`
|
|
38
|
+
* @example
|
|
39
|
+
*
|
|
40
|
+
* await voice.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
41
|
+
* await voice.answer();
|
|
42
|
+
*/
|
|
43
|
+
declare function aiCoustics(options?: AiCousticsOptions): AudioFilterConfig;
|
|
44
|
+
export { aiCoustics, AiCousticsOptions };
|
package/dist/filters.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.aiCoustics = aiCoustics;
|
|
4
|
+
/**
|
|
5
|
+
* ai-coustics speech enhancement: keeps the primary speaker and suppresses
|
|
6
|
+
* noise and competing voices.
|
|
7
|
+
*
|
|
8
|
+
* @param {AiCousticsOptions} options - Tuning options for the filter
|
|
9
|
+
* @return {AudioFilterConfig} A filter to pass to `setAudioFilters`
|
|
10
|
+
* @example
|
|
11
|
+
*
|
|
12
|
+
* await voice.setAudioFilters([aiCoustics({ enhancementLevel: 0.8 })]);
|
|
13
|
+
* await voice.answer();
|
|
14
|
+
*/
|
|
15
|
+
function aiCoustics(options = {}) {
|
|
16
|
+
return { name: "aiCoustics", options };
|
|
17
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,5 +20,6 @@ import { AzureVoice, AzureVoiceDetails, GatherSource, GoogleVoice, GoogleVoiceDe
|
|
|
20
20
|
import VoiceServer from "./VoiceServer";
|
|
21
21
|
export default VoiceServer;
|
|
22
22
|
export * from "./VoiceResponse";
|
|
23
|
+
export * from "./filters";
|
|
23
24
|
export * from "./types";
|
|
24
25
|
export { AzureVoice, AzureVoiceDetails, GatherSource, GoogleVoice, GoogleVoiceDetails, StreamGatherSource };
|
package/dist/index.js
CHANGED
|
@@ -46,4 +46,5 @@ Object.defineProperty(exports, "StreamGatherSource", { enumerable: true, get: fu
|
|
|
46
46
|
const VoiceServer_1 = __importDefault(require("./VoiceServer"));
|
|
47
47
|
exports.default = VoiceServer_1.default;
|
|
48
48
|
__exportStar(require("./VoiceResponse"), exports);
|
|
49
|
+
__exportStar(require("./filters"), exports);
|
|
49
50
|
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { SetAudioFiltersRequest } from "@fonoster/common";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { Verb } from "./Verb";
|
|
22
|
+
declare class SetAudioFilters extends Verb<SetAudioFiltersRequest> {
|
|
23
|
+
getValidationSchema(): z.Schema;
|
|
24
|
+
}
|
|
25
|
+
export { SetAudioFilters };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SetAudioFilters = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const Verb_1 = require("./Verb");
|
|
6
|
+
class SetAudioFilters extends Verb_1.Verb {
|
|
7
|
+
getValidationSchema() {
|
|
8
|
+
return zod_1.z.object({
|
|
9
|
+
filters: zod_1.z.array(zod_1.z.object({
|
|
10
|
+
name: zod_1.z.string().min(1, { message: "A filter name is required" }),
|
|
11
|
+
options: zod_1.z.record(zod_1.z.unknown()).optional()
|
|
12
|
+
}))
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.SetAudioFilters = SetAudioFilters;
|
package/dist/verbs/index.d.ts
CHANGED
package/dist/verbs/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/voice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Voice Server for Fonoster",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"fonoster": "./dist/index.js"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@fonoster/common": "^0.
|
|
26
|
-
"@fonoster/identity": "^0.
|
|
27
|
-
"@fonoster/logger": "^0.
|
|
25
|
+
"@fonoster/common": "^0.26.0",
|
|
26
|
+
"@fonoster/identity": "^0.26.0",
|
|
27
|
+
"@fonoster/logger": "^0.26.0",
|
|
28
28
|
"@grpc/grpc-js": "~1.10.6",
|
|
29
29
|
"deepmerge": "^4.3.1",
|
|
30
30
|
"grpc-health-check": "^2.0.2",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"bugs": {
|
|
46
46
|
"url": "https://github.com/fonoster/fonoster/issues"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "d14f414d791a33f92e8c4225f7e462abc440a142"
|
|
49
49
|
}
|