@fonoster/voice 0.25.1 → 0.27.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 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
 
@@ -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
  *
@@ -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 };
@@ -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;
@@ -32,3 +32,4 @@ export * from "./Unmute";
32
32
  export * from "./Verb";
33
33
  export * from "./validateRequest";
34
34
  export * from "./StopSay";
35
+ export * from "./SetAudioFilters";
@@ -48,3 +48,4 @@ __exportStar(require("./Unmute"), exports);
48
48
  __exportStar(require("./Verb"), exports);
49
49
  __exportStar(require("./validateRequest"), exports);
50
50
  __exportStar(require("./StopSay"), exports);
51
+ __exportStar(require("./SetAudioFilters"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/voice",
3
- "version": "0.25.1",
3
+ "version": "0.27.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.25.1",
26
- "@fonoster/identity": "^0.25.1",
27
- "@fonoster/logger": "^0.25.0",
25
+ "@fonoster/common": "^0.27.0",
26
+ "@fonoster/identity": "^0.27.0",
27
+ "@fonoster/logger": "^0.27.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": "7a3786a2704ae379211fbd350a3f6e8d913a5fd6"
48
+ "gitHead": "1191b34677b51a36000177887ff82303ef2693e1"
49
49
  }