@absolutejs/voice 0.0.22-beta.597 → 0.0.22-beta.599

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.
@@ -1227,146 +1227,25 @@ var resolveAudioConditioningConfig = (config) => {
1227
1227
  };
1228
1228
  };
1229
1229
 
1230
- // src/core/turnDetection.ts
1231
- var DEFAULT_SILENCE_MS = 700;
1232
- var DEFAULT_SPEECH_THRESHOLD = 0.015;
1233
- var DEFAULT_SEMANTIC_VETO_RECHECK_MS = 1200;
1234
- var toUint8Array = (audio) => {
1235
- if (audio instanceof ArrayBuffer) {
1236
- return new Uint8Array(audio);
1237
- }
1238
- return new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
1239
- };
1240
- var measureAudioLevel = (audio) => {
1241
- const bytes = toUint8Array(audio);
1242
- if (bytes.byteLength < 2) {
1243
- return 0;
1244
- }
1245
- const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
1246
- if (samples.length === 0) {
1247
- return 0;
1248
- }
1249
- let sumSquares = 0;
1250
- for (const sample of samples) {
1251
- const normalized = sample / 32768;
1252
- sumSquares += normalized * normalized;
1253
- }
1254
- return Math.sqrt(sumSquares / samples.length);
1255
- };
1256
- var normalizeText = (value) => value.trim().replace(/\s+/g, " ");
1257
- var countWords = (value) => value.length > 0 ? value.split(" ").length : 0;
1258
- var selectPreferredTranscriptText = (currentText, nextText) => {
1259
- const current = normalizeText(currentText);
1260
- const next = normalizeText(nextText);
1261
- if (!current) {
1262
- return next;
1263
- }
1264
- if (!next) {
1265
- return current;
1266
- }
1267
- if (current === next || current.includes(next)) {
1268
- return current;
1269
- }
1270
- if (next.includes(current)) {
1271
- return next;
1272
- }
1273
- if (countWords(next) > countWords(current)) {
1274
- return next;
1275
- }
1276
- if (countWords(next) === countWords(current) && next.length > current.length) {
1277
- return next;
1278
- }
1279
- return current;
1280
- };
1281
- var mergeSequentialTranscriptText = (currentText, nextText) => {
1282
- const current = normalizeText(currentText);
1283
- const next = normalizeText(nextText);
1284
- if (!current) {
1285
- return next;
1286
- }
1287
- if (!next) {
1288
- return current;
1289
- }
1290
- const currentWords = current.split(" ");
1291
- const nextWords = next.split(" ");
1292
- const maxOverlap = Math.min(currentWords.length, nextWords.length);
1293
- for (let overlap = maxOverlap;overlap > 0; overlap -= 1) {
1294
- const currentSuffix = currentWords.slice(-overlap).join(" ");
1295
- const nextPrefix = nextWords.slice(0, overlap).join(" ");
1296
- if (currentSuffix === nextPrefix) {
1297
- return [...currentWords, ...nextWords.slice(overlap)].join(" ");
1298
- }
1299
- }
1300
- return `${current} ${next}`.trim();
1301
- };
1302
- var countCommonPrefixWords = (currentText, nextText) => {
1303
- const currentWords = normalizeText(currentText).split(" ").filter(Boolean);
1304
- const nextWords = normalizeText(nextText).split(" ").filter(Boolean);
1305
- const maxWords = Math.min(currentWords.length, nextWords.length);
1306
- let count = 0;
1307
- for (let index = 0;index < maxWords; index += 1) {
1308
- if (currentWords[index] !== nextWords[index]) {
1309
- break;
1310
- }
1311
- count += 1;
1312
- }
1313
- return count;
1314
- };
1315
- var mergeTranscriptTexts = (transcripts) => {
1316
- const merged = [];
1317
- for (const transcript of transcripts) {
1318
- const nextText = normalizeText(transcript.text);
1319
- if (!nextText) {
1320
- continue;
1321
- }
1322
- const previous = merged.at(-1);
1323
- if (!previous) {
1324
- merged.push(nextText);
1325
- continue;
1326
- }
1327
- if (nextText === previous || previous.includes(nextText)) {
1328
- continue;
1329
- }
1330
- if (nextText.includes(previous)) {
1331
- merged[merged.length - 1] = nextText;
1332
- continue;
1333
- }
1334
- merged.push(nextText);
1335
- }
1336
- return merged.join(" ").trim();
1337
- };
1338
- var buildTurnText = (transcripts, partialText, options = {}) => {
1339
- const finalText = mergeTranscriptTexts(transcripts);
1340
- const nextPartial = normalizeText(partialText);
1341
- const lastFinalEndedAtMs = [...transcripts].reverse().find((transcript) => typeof transcript.endedAtMs === "number")?.endedAtMs;
1342
- if (finalText && nextPartial && typeof lastFinalEndedAtMs === "number" && typeof options.partialStartedAtMs === "number" && options.partialStartedAtMs - lastFinalEndedAtMs >= 250 && countCommonPrefixWords(finalText, nextPartial) === 0) {
1343
- return mergeSequentialTranscriptText(finalText, nextPartial);
1344
- }
1345
- return selectPreferredTranscriptText(finalText, nextPartial);
1346
- };
1347
-
1348
1230
  // src/core/turnProfiles.ts
1349
1231
  var TURN_PROFILE_DEFAULTS = {
1350
1232
  balanced: {
1351
1233
  qualityProfile: "general",
1352
- semanticVetoMaxMs: 0,
1353
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1234
+ minSilenceMs: 400,
1354
1235
  silenceMs: 1400,
1355
1236
  speechThreshold: 0.012,
1356
1237
  transcriptStabilityMs: 1000
1357
1238
  },
1358
1239
  fast: {
1359
1240
  qualityProfile: "general",
1360
- semanticVetoMaxMs: 0,
1361
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1241
+ minSilenceMs: 300,
1362
1242
  silenceMs: 700,
1363
1243
  speechThreshold: 0.015,
1364
1244
  transcriptStabilityMs: 450
1365
1245
  },
1366
1246
  "long-form": {
1367
1247
  qualityProfile: "general",
1368
- semanticVetoMaxMs: 0,
1369
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1248
+ minSilenceMs: 600,
1370
1249
  silenceMs: 2200,
1371
1250
  speechThreshold: 0.01,
1372
1251
  transcriptStabilityMs: 1500
@@ -1397,12 +1276,12 @@ var resolveTurnDetectionConfig = (config) => {
1397
1276
  const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
1398
1277
  const preset = TURN_PROFILE_DEFAULTS[profile];
1399
1278
  const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
1279
+ const silenceMs = config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs;
1400
1280
  return {
1401
1281
  profile,
1402
1282
  qualityProfile,
1403
- semanticVetoMaxMs: config?.semanticVetoMaxMs ?? preset.semanticVetoMaxMs,
1404
- semanticVetoRecheckMs: config?.semanticVetoRecheckMs ?? preset.semanticVetoRecheckMs,
1405
- silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
1283
+ minSilenceMs: Math.min(silenceMs, config?.minSilenceMs ?? quality.minSilenceMs ?? preset.minSilenceMs),
1284
+ silenceMs,
1406
1285
  speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
1407
1286
  transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
1408
1287
  };
@@ -1107,31 +1107,25 @@ var resolveAudioConditioningConfig = (config) => {
1107
1107
  };
1108
1108
  };
1109
1109
 
1110
- // src/core/turnDetection.ts
1111
- var DEFAULT_SEMANTIC_VETO_RECHECK_MS = 1200;
1112
-
1113
1110
  // src/core/turnProfiles.ts
1114
1111
  var TURN_PROFILE_DEFAULTS = {
1115
1112
  balanced: {
1116
1113
  qualityProfile: "general",
1117
- semanticVetoMaxMs: 0,
1118
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1114
+ minSilenceMs: 400,
1119
1115
  silenceMs: 1400,
1120
1116
  speechThreshold: 0.012,
1121
1117
  transcriptStabilityMs: 1000
1122
1118
  },
1123
1119
  fast: {
1124
1120
  qualityProfile: "general",
1125
- semanticVetoMaxMs: 0,
1126
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1121
+ minSilenceMs: 300,
1127
1122
  silenceMs: 700,
1128
1123
  speechThreshold: 0.015,
1129
1124
  transcriptStabilityMs: 450
1130
1125
  },
1131
1126
  "long-form": {
1132
1127
  qualityProfile: "general",
1133
- semanticVetoMaxMs: 0,
1134
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1128
+ minSilenceMs: 600,
1135
1129
  silenceMs: 2200,
1136
1130
  speechThreshold: 0.01,
1137
1131
  transcriptStabilityMs: 1500
@@ -1162,12 +1156,12 @@ var resolveTurnDetectionConfig = (config) => {
1162
1156
  const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
1163
1157
  const preset = TURN_PROFILE_DEFAULTS[profile];
1164
1158
  const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
1159
+ const silenceMs = config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs;
1165
1160
  return {
1166
1161
  profile,
1167
1162
  qualityProfile,
1168
- semanticVetoMaxMs: config?.semanticVetoMaxMs ?? preset.semanticVetoMaxMs,
1169
- semanticVetoRecheckMs: config?.semanticVetoRecheckMs ?? preset.semanticVetoRecheckMs,
1170
- silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
1163
+ minSilenceMs: Math.min(silenceMs, config?.minSilenceMs ?? quality.minSilenceMs ?? preset.minSilenceMs),
1164
+ silenceMs,
1171
1165
  speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
1172
1166
  transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
1173
1167
  };
@@ -1678,146 +1678,25 @@ var resolveAudioConditioningConfig = (config) => {
1678
1678
  };
1679
1679
  };
1680
1680
 
1681
- // src/core/turnDetection.ts
1682
- var DEFAULT_SILENCE_MS = 700;
1683
- var DEFAULT_SPEECH_THRESHOLD = 0.015;
1684
- var DEFAULT_SEMANTIC_VETO_RECHECK_MS = 1200;
1685
- var toUint8Array = (audio) => {
1686
- if (audio instanceof ArrayBuffer) {
1687
- return new Uint8Array(audio);
1688
- }
1689
- return new Uint8Array(audio.buffer, audio.byteOffset, audio.byteLength);
1690
- };
1691
- var measureAudioLevel = (audio) => {
1692
- const bytes = toUint8Array(audio);
1693
- if (bytes.byteLength < 2) {
1694
- return 0;
1695
- }
1696
- const samples = new Int16Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 2));
1697
- if (samples.length === 0) {
1698
- return 0;
1699
- }
1700
- let sumSquares = 0;
1701
- for (const sample of samples) {
1702
- const normalized = sample / 32768;
1703
- sumSquares += normalized * normalized;
1704
- }
1705
- return Math.sqrt(sumSquares / samples.length);
1706
- };
1707
- var normalizeText = (value) => value.trim().replace(/\s+/g, " ");
1708
- var countWords = (value) => value.length > 0 ? value.split(" ").length : 0;
1709
- var selectPreferredTranscriptText = (currentText, nextText) => {
1710
- const current = normalizeText(currentText);
1711
- const next = normalizeText(nextText);
1712
- if (!current) {
1713
- return next;
1714
- }
1715
- if (!next) {
1716
- return current;
1717
- }
1718
- if (current === next || current.includes(next)) {
1719
- return current;
1720
- }
1721
- if (next.includes(current)) {
1722
- return next;
1723
- }
1724
- if (countWords(next) > countWords(current)) {
1725
- return next;
1726
- }
1727
- if (countWords(next) === countWords(current) && next.length > current.length) {
1728
- return next;
1729
- }
1730
- return current;
1731
- };
1732
- var mergeSequentialTranscriptText = (currentText, nextText) => {
1733
- const current = normalizeText(currentText);
1734
- const next = normalizeText(nextText);
1735
- if (!current) {
1736
- return next;
1737
- }
1738
- if (!next) {
1739
- return current;
1740
- }
1741
- const currentWords = current.split(" ");
1742
- const nextWords = next.split(" ");
1743
- const maxOverlap = Math.min(currentWords.length, nextWords.length);
1744
- for (let overlap = maxOverlap;overlap > 0; overlap -= 1) {
1745
- const currentSuffix = currentWords.slice(-overlap).join(" ");
1746
- const nextPrefix = nextWords.slice(0, overlap).join(" ");
1747
- if (currentSuffix === nextPrefix) {
1748
- return [...currentWords, ...nextWords.slice(overlap)].join(" ");
1749
- }
1750
- }
1751
- return `${current} ${next}`.trim();
1752
- };
1753
- var countCommonPrefixWords = (currentText, nextText) => {
1754
- const currentWords = normalizeText(currentText).split(" ").filter(Boolean);
1755
- const nextWords = normalizeText(nextText).split(" ").filter(Boolean);
1756
- const maxWords = Math.min(currentWords.length, nextWords.length);
1757
- let count = 0;
1758
- for (let index = 0;index < maxWords; index += 1) {
1759
- if (currentWords[index] !== nextWords[index]) {
1760
- break;
1761
- }
1762
- count += 1;
1763
- }
1764
- return count;
1765
- };
1766
- var mergeTranscriptTexts = (transcripts) => {
1767
- const merged = [];
1768
- for (const transcript of transcripts) {
1769
- const nextText = normalizeText(transcript.text);
1770
- if (!nextText) {
1771
- continue;
1772
- }
1773
- const previous = merged.at(-1);
1774
- if (!previous) {
1775
- merged.push(nextText);
1776
- continue;
1777
- }
1778
- if (nextText === previous || previous.includes(nextText)) {
1779
- continue;
1780
- }
1781
- if (nextText.includes(previous)) {
1782
- merged[merged.length - 1] = nextText;
1783
- continue;
1784
- }
1785
- merged.push(nextText);
1786
- }
1787
- return merged.join(" ").trim();
1788
- };
1789
- var buildTurnText = (transcripts, partialText, options = {}) => {
1790
- const finalText = mergeTranscriptTexts(transcripts);
1791
- const nextPartial = normalizeText(partialText);
1792
- const lastFinalEndedAtMs = [...transcripts].reverse().find((transcript) => typeof transcript.endedAtMs === "number")?.endedAtMs;
1793
- if (finalText && nextPartial && typeof lastFinalEndedAtMs === "number" && typeof options.partialStartedAtMs === "number" && options.partialStartedAtMs - lastFinalEndedAtMs >= 250 && countCommonPrefixWords(finalText, nextPartial) === 0) {
1794
- return mergeSequentialTranscriptText(finalText, nextPartial);
1795
- }
1796
- return selectPreferredTranscriptText(finalText, nextPartial);
1797
- };
1798
-
1799
1681
  // src/core/turnProfiles.ts
1800
1682
  var TURN_PROFILE_DEFAULTS = {
1801
1683
  balanced: {
1802
1684
  qualityProfile: "general",
1803
- semanticVetoMaxMs: 0,
1804
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1685
+ minSilenceMs: 400,
1805
1686
  silenceMs: 1400,
1806
1687
  speechThreshold: 0.012,
1807
1688
  transcriptStabilityMs: 1000
1808
1689
  },
1809
1690
  fast: {
1810
1691
  qualityProfile: "general",
1811
- semanticVetoMaxMs: 0,
1812
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1692
+ minSilenceMs: 300,
1813
1693
  silenceMs: 700,
1814
1694
  speechThreshold: 0.015,
1815
1695
  transcriptStabilityMs: 450
1816
1696
  },
1817
1697
  "long-form": {
1818
1698
  qualityProfile: "general",
1819
- semanticVetoMaxMs: 0,
1820
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1699
+ minSilenceMs: 600,
1821
1700
  silenceMs: 2200,
1822
1701
  speechThreshold: 0.01,
1823
1702
  transcriptStabilityMs: 1500
@@ -1848,12 +1727,12 @@ var resolveTurnDetectionConfig = (config) => {
1848
1727
  const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
1849
1728
  const preset = TURN_PROFILE_DEFAULTS[profile];
1850
1729
  const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
1730
+ const silenceMs = config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs;
1851
1731
  return {
1852
1732
  profile,
1853
1733
  qualityProfile,
1854
- semanticVetoMaxMs: config?.semanticVetoMaxMs ?? preset.semanticVetoMaxMs,
1855
- semanticVetoRecheckMs: config?.semanticVetoRecheckMs ?? preset.semanticVetoRecheckMs,
1856
- silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
1734
+ minSilenceMs: Math.min(silenceMs, config?.minSilenceMs ?? quality.minSilenceMs ?? preset.minSilenceMs),
1735
+ silenceMs,
1857
1736
  speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
1858
1737
  transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
1859
1738
  };
@@ -1,10 +1,20 @@
1
- import type { Transcript } from "./types";
1
+ import type { AudioFormat, Transcript } from "./types";
2
2
  export type VoiceSemanticTurnInput = {
3
3
  audioLevel?: number;
4
4
  lastFinalTranscript?: Transcript;
5
5
  partialText: string;
6
6
  silenceMs: number;
7
7
  transcripts: Transcript[];
8
+ /**
9
+ * The current turn's buffered user audio (PCM chunks, oldest→newest) and its
10
+ * format. Lets an AUDIO-based end-of-turn detector (e.g. a smart-turn / Whisper
11
+ * EOT model) judge completion from prosody — pitch, pace, trailing intonation —
12
+ * which a transcript-only judge fundamentally cannot see. Undefined when no
13
+ * audio was buffered for the turn (the runtime only stores chunks above the
14
+ * speech threshold).
15
+ */
16
+ turnAudio?: ReadonlyArray<Uint8Array>;
17
+ turnAudioFormat?: AudioFormat;
8
18
  };
9
19
  export type VoiceSemanticTurnVerdict = {
10
20
  confidence?: number;
@@ -1,7 +1,7 @@
1
1
  import type { AudioChunk, Transcript } from "./types";
2
2
  export declare const DEFAULT_SILENCE_MS = 700;
3
3
  export declare const DEFAULT_SPEECH_THRESHOLD = 0.015;
4
- export declare const DEFAULT_SEMANTIC_VETO_RECHECK_MS = 1200;
4
+ export declare const DEFAULT_MIN_SILENCE_MS = 400;
5
5
  export declare const measureAudioLevel: (audio: AudioChunk) => number;
6
6
  export declare const selectPreferredTranscriptText: (currentText: string, nextText: string) => string;
7
7
  export declare const buildTurnText: (transcripts: Transcript[], partialText: string, options?: {
@@ -436,19 +436,17 @@ export type VoiceTurnDetectionConfig = {
436
436
  profile?: VoiceTurnProfile;
437
437
  qualityProfile?: VoiceTurnQualityProfile;
438
438
  silenceMs?: number;
439
+ minSilenceMs?: number;
439
440
  speechThreshold?: number;
440
441
  transcriptStabilityMs?: number;
441
- semanticVetoMaxMs?: number;
442
- semanticVetoRecheckMs?: number;
443
442
  };
444
443
  export type VoiceResolvedTurnDetectionConfig = {
445
444
  qualityProfile: VoiceTurnQualityProfile;
446
445
  profile: VoiceTurnProfile;
447
446
  silenceMs: number;
447
+ minSilenceMs: number;
448
448
  speechThreshold: number;
449
449
  transcriptStabilityMs: number;
450
- semanticVetoMaxMs: number;
451
- semanticVetoRecheckMs: number;
452
450
  };
453
451
  export type VoiceAudioConditioningConfig = {
454
452
  enabled?: boolean;
@@ -1104,31 +1104,25 @@ var resolveAudioConditioningConfig = (config) => {
1104
1104
  };
1105
1105
  };
1106
1106
 
1107
- // src/core/turnDetection.ts
1108
- var DEFAULT_SEMANTIC_VETO_RECHECK_MS = 1200;
1109
-
1110
1107
  // src/core/turnProfiles.ts
1111
1108
  var TURN_PROFILE_DEFAULTS = {
1112
1109
  balanced: {
1113
1110
  qualityProfile: "general",
1114
- semanticVetoMaxMs: 0,
1115
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1111
+ minSilenceMs: 400,
1116
1112
  silenceMs: 1400,
1117
1113
  speechThreshold: 0.012,
1118
1114
  transcriptStabilityMs: 1000
1119
1115
  },
1120
1116
  fast: {
1121
1117
  qualityProfile: "general",
1122
- semanticVetoMaxMs: 0,
1123
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1118
+ minSilenceMs: 300,
1124
1119
  silenceMs: 700,
1125
1120
  speechThreshold: 0.015,
1126
1121
  transcriptStabilityMs: 450
1127
1122
  },
1128
1123
  "long-form": {
1129
1124
  qualityProfile: "general",
1130
- semanticVetoMaxMs: 0,
1131
- semanticVetoRecheckMs: DEFAULT_SEMANTIC_VETO_RECHECK_MS,
1125
+ minSilenceMs: 600,
1132
1126
  silenceMs: 2200,
1133
1127
  speechThreshold: 0.01,
1134
1128
  transcriptStabilityMs: 1500
@@ -1159,12 +1153,12 @@ var resolveTurnDetectionConfig = (config) => {
1159
1153
  const qualityProfile = config?.qualityProfile ?? DEFAULT_QUALITY_PROFILE;
1160
1154
  const preset = TURN_PROFILE_DEFAULTS[profile];
1161
1155
  const quality = QUALITY_PROFILE_DEFAULTS[qualityProfile];
1156
+ const silenceMs = config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs;
1162
1157
  return {
1163
1158
  profile,
1164
1159
  qualityProfile,
1165
- semanticVetoMaxMs: config?.semanticVetoMaxMs ?? preset.semanticVetoMaxMs,
1166
- semanticVetoRecheckMs: config?.semanticVetoRecheckMs ?? preset.semanticVetoRecheckMs,
1167
- silenceMs: config?.silenceMs ?? quality.silenceMs ?? preset.silenceMs,
1160
+ minSilenceMs: Math.min(silenceMs, config?.minSilenceMs ?? quality.minSilenceMs ?? preset.minSilenceMs),
1161
+ silenceMs,
1168
1162
  speechThreshold: config?.speechThreshold ?? quality.speechThreshold ?? preset.speechThreshold,
1169
1163
  transcriptStabilityMs: config?.transcriptStabilityMs ?? quality.transcriptStabilityMs ?? preset.transcriptStabilityMs
1170
1164
  };