@bendyline/squisq-editor-react 2.11.18 → 2.11.20

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.
Files changed (33) hide show
  1. package/dist/chunk-2MP7QE7O.js +15 -0
  2. package/dist/{chunk-H7YTL6SL.js → chunk-DJS5I5NO.js} +1 -1
  3. package/dist/{chunk-DPAMJV6A.js → chunk-HSWWEWT4.js} +31 -2
  4. package/dist/{chunk-45NQUR62.js → chunk-JI74KA6C.js} +197 -126
  5. package/dist/chunk-K56KR27T.js +141 -0
  6. package/dist/{chunk-WKZLBGPX.js → chunk-LR6Q2MK4.js} +1 -1
  7. package/dist/{chunk-GIXXD2O3.js → chunk-NL4JP55H.js} +3453 -2606
  8. package/dist/{chunk-2XXBDECJ.js → chunk-OCQEWWHI.js} +1 -1
  9. package/dist/{chunk-U6NVT3BY.js → chunk-UFK47ZAI.js} +42 -22
  10. package/dist/{chunk-UBFNLWCN.js → chunk-V2OLZOYJ.js} +2 -2
  11. package/dist/{chunk-CGENKHWY.js → chunk-ZS6G2P57.js} +27 -3
  12. package/dist/deviceSettings-BSkIQncC.d.ts +83 -0
  13. package/dist/{dictationSession-PN3OT5LJ.js → dictationSession-SLDNYIZ2.js} +3 -133
  14. package/dist/index.d.ts +200 -18
  15. package/dist/index.js +33 -10
  16. package/dist/json-editor/index.js +3 -3
  17. package/dist/recorder/index.d.ts +4 -2
  18. package/dist/recorder/index.js +3 -3
  19. package/dist/{recorder-CRsw0eC7.d.ts → recorder-Civyq5SU.d.ts} +4 -82
  20. package/dist/review/index.js +2 -2
  21. package/dist/screenRegion-Ci_RsCzG.d.ts +8 -0
  22. package/dist/shell/index.d.ts +2 -2
  23. package/dist/shell/index.js +11 -9
  24. package/dist/{shell-Cb0P_ln8.d.ts → shell-DTefmCBG.d.ts} +40 -3
  25. package/dist/speech/index.d.ts +35 -118
  26. package/dist/speech/index.js +7 -1
  27. package/dist/speechAudioWav-WKIQWGIJ.js +13 -0
  28. package/dist/styles/index.css +113 -3
  29. package/dist/teleprompter/index.d.ts +3 -2
  30. package/dist/teleprompter/index.js +2 -2
  31. package/dist/types-Bi4ZVKvN.d.ts +126 -0
  32. package/dist/{useNarrationStage-vKtk6H-V.d.ts → useNarrationStage-DlighI1z.d.ts} +17 -9
  33. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MARKDOWN_ESCAPE_ATTR
3
- } from "./chunk-DPAMJV6A.js";
3
+ } from "./chunk-HSWWEWT4.js";
4
4
 
5
5
  // src/MarkdownEscape.ts
6
6
  import { Mark, mergeAttributes } from "@tiptap/core";
@@ -20,7 +20,7 @@ import {
20
20
  timingPathFor,
21
21
  useNarrationStage,
22
22
  useStreamPreview
23
- } from "./chunk-CGENKHWY.js";
23
+ } from "./chunk-ZS6G2P57.js";
24
24
  import {
25
25
  buildFilename,
26
26
  requestMicStream,
@@ -2612,6 +2612,7 @@ function RecorderModal({
2612
2612
  const narrationRecording = narrationOn && Boolean(narration?.recording);
2613
2613
  const expanded = isExpandedPanel(panelMode);
2614
2614
  const [narrationRequesting, setNarrationRequesting] = useState3(false);
2615
+ const [countingIn, setCountingIn] = useState3(false);
2615
2616
  const [narrationPreview, setNarrationPreview] = useState3(null);
2616
2617
  const [deviceSettings, setDeviceSettings] = useState3(() => ({
2617
2618
  ...DEFAULT_RECORDER_DEVICE_SETTINGS,
@@ -2927,17 +2928,27 @@ function RecorderModal({
2927
2928
  } catch {
2928
2929
  }
2929
2930
  }, [recorder]);
2930
- const handleStart = useCallback4(() => {
2931
+ const handleStart = useCallback4(async () => {
2931
2932
  setSaveError(null);
2932
2933
  dualSaveProgressRef.current = {};
2934
+ if (narrationOn) {
2935
+ setCountingIn(true);
2936
+ const rolled = await stageRef.current.controller.countIn();
2937
+ setCountingIn(false);
2938
+ if (!rolled) return;
2939
+ }
2933
2940
  recorder.start();
2934
2941
  const blockId = slideDeckRef.current[slideIndexRef.current]?.blockId;
2935
2942
  setAdvanceLog(blockId ? recordSlideShown(EMPTY_ADVANCE_LOG, blockId, 0) : EMPTY_ADVANCE_LOG);
2936
- }, [recorder]);
2943
+ }, [narrationOn, recorder]);
2937
2944
  const handleStop = useCallback4(async () => {
2938
2945
  setSaveError(null);
2946
+ if (countingIn) {
2947
+ stageRef.current.controller.pause();
2948
+ return;
2949
+ }
2939
2950
  await recorder.stop();
2940
- }, [recorder]);
2951
+ }, [countingIn, recorder]);
2941
2952
  const writeSlideTimingSidecar = useCallback4(
2942
2953
  async (mediaRelativePath, durationSec) => {
2943
2954
  const doc = slides?.doc;
@@ -3130,8 +3141,8 @@ function RecorderModal({
3130
3141
  );
3131
3142
  const isAudioOnly = recordedMediaKind(source, recorder.stream, recorder.mimeType) === "audio";
3132
3143
  const showPreview = recorder.state !== "idle" && recorder.state !== "error";
3133
- const canRecord = recorder.state === "ready";
3134
- const canStop = recorder.state === "recording";
3144
+ const canRecord = recorder.state === "ready" && !countingIn;
3145
+ const canStop = recorder.state === "recording" || countingIn;
3135
3146
  const canSave = recorder.state === "stopped" && recorder.blob !== null;
3136
3147
  const isBusy = recorder.state === "requesting" || recorder.state === "stopping" || isSaving;
3137
3148
  const slidesTimingCapture = slidesOn && slidesCaptureTimings;
@@ -3185,7 +3196,7 @@ function RecorderModal({
3185
3196
  sourceText
3186
3197
  ]);
3187
3198
  const slideCoverageWarning = slideCoverage ? unshownSlidesWarning(slideCoverage.total, slideCoverage.unshown) : null;
3188
- const togglesLocked = recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
3199
+ const togglesLocked = countingIn || recorder.state === "recording" || recorder.state === "requesting" || recorder.state === "stopping" || canSave;
3189
3200
  const toggleLockReason = canSave ? "Save or discard this recording before changing sources" : void 0;
3190
3201
  const systemAudioHasCompanion = micOn || cameraOn || screenOn;
3191
3202
  const simpleToggleProps = (key) => {
@@ -3680,24 +3691,33 @@ function RecorderModal({
3680
3691
  children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3681
3692
  }
3682
3693
  ),
3683
- canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3684
- /* @__PURE__ */ jsx6(
3685
- "span",
3686
- {
3687
- className: "squisq-recorder-record-dot",
3688
- style: recordDotFrameStyle,
3689
- "aria-hidden": "true",
3690
- children: /* @__PURE__ */ jsx6(
3694
+ canRecord && /* @__PURE__ */ jsxs5(
3695
+ "button",
3696
+ {
3697
+ type: "button",
3698
+ style: btnRecord,
3699
+ onClick: () => void handleStart(),
3700
+ disabled: isBusy,
3701
+ children: [
3702
+ /* @__PURE__ */ jsx6(
3691
3703
  "span",
3692
3704
  {
3693
- className: "squisq-recorder-record-dot-center",
3694
- style: recordDotStyle
3705
+ className: "squisq-recorder-record-dot",
3706
+ style: recordDotFrameStyle,
3707
+ "aria-hidden": "true",
3708
+ children: /* @__PURE__ */ jsx6(
3709
+ "span",
3710
+ {
3711
+ className: "squisq-recorder-record-dot-center",
3712
+ style: recordDotStyle
3713
+ }
3714
+ )
3695
3715
  }
3696
- )
3697
- }
3698
- ),
3699
- "Record"
3700
- ] }),
3716
+ ),
3717
+ "Record"
3718
+ ]
3719
+ }
3720
+ ),
3701
3721
  canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3702
3722
  canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3703
3723
  /* @__PURE__ */ jsx6(
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MarkdownEscape
3
- } from "./chunk-2XXBDECJ.js";
3
+ } from "./chunk-OCQEWWHI.js";
4
4
  import {
5
5
  markdownToTiptap,
6
6
  tiptapToMarkdown
7
- } from "./chunk-DPAMJV6A.js";
7
+ } from "./chunk-HSWWEWT4.js";
8
8
 
9
9
  // src/jsonEditor/JsonEditorContext.tsx
10
10
  import { createContext, useContext, useEffect, useMemo } from "react";
@@ -429,6 +429,7 @@ function useTeleprompter(opts) {
429
429
  const levelRef = useRef2(0);
430
430
  const voiceRef = useRef2(false);
431
431
  const countdownTimerRef = useRef2(null);
432
+ const countInWaitersRef = useRef2([]);
432
433
  scriptRef.current = script;
433
434
  prefsRef.current = prefs;
434
435
  transportRef.current = transport;
@@ -453,13 +454,19 @@ function useTeleprompter(opts) {
453
454
  const notifyTick = useCallback2(() => {
454
455
  for (const cb of tickSubsRef.current) cb(wordPosRef.current);
455
456
  }, []);
457
+ const settleCountIn = useCallback2((rolled) => {
458
+ const waiters = countInWaitersRef.current;
459
+ countInWaitersRef.current = [];
460
+ for (const resolve of waiters) resolve(rolled);
461
+ }, []);
456
462
  const clearCountdown = useCallback2(() => {
457
463
  if (countdownTimerRef.current !== null) {
458
464
  clearInterval(countdownTimerRef.current);
459
465
  countdownTimerRef.current = null;
460
466
  }
461
467
  setCountdownRemaining(null);
462
- }, []);
468
+ settleCountIn(false);
469
+ }, [settleCountIn]);
463
470
  const finish = useCallback2(() => {
464
471
  setTransport("finished");
465
472
  transportRef.current = "finished";
@@ -533,13 +540,14 @@ function useTeleprompter(opts) {
533
540
  return () => cancelAnimationFrame(raf);
534
541
  }, [transport, mic.status, finish, notifyTick, publish]);
535
542
  const beginRolling = useCallback2(() => {
543
+ settleCountIn(true);
536
544
  clearCountdown();
537
545
  if (sessionRef.current) {
538
546
  sessionRef.current = reanchorSession(sessionRef.current, wordPosRef.current);
539
547
  }
540
548
  setTransport("rolling");
541
549
  transportRef.current = "rolling";
542
- }, [clearCountdown]);
550
+ }, [clearCountdown, settleCountIn]);
543
551
  const play = useCallback2(() => {
544
552
  if (transportRef.current === "rolling" || transportRef.current === "countdown") return;
545
553
  if (transportRef.current === "finished") {
@@ -564,6 +572,14 @@ function useTeleprompter(opts) {
564
572
  beginRolling();
565
573
  }
566
574
  }, [beginRolling, mic, publish]);
575
+ const countIn = useCallback2(() => {
576
+ if (transportRef.current === "rolling") return Promise.resolve(true);
577
+ const rolled = new Promise((resolve) => {
578
+ countInWaitersRef.current.push(resolve);
579
+ });
580
+ play();
581
+ return rolled;
582
+ }, [play]);
567
583
  const pause = useCallback2(() => {
568
584
  if (transportRef.current === "countdown") {
569
585
  clearCountdown();
@@ -699,6 +715,7 @@ function useTeleprompter(opts) {
699
715
  prefs,
700
716
  setPrefs,
701
717
  play,
718
+ countIn,
702
719
  pause,
703
720
  restart,
704
721
  nudge,
@@ -1450,6 +1467,11 @@ function useNarrationRecorder(options) {
1450
1467
  cameraMime = videoFormat.mimeType;
1451
1468
  cameraExt = videoFormat.extension;
1452
1469
  }
1470
+ if (opts.countIn) {
1471
+ setCameraStream(camera);
1472
+ if (!await opts.countIn()) throw new StartAborted();
1473
+ if (superseded()) throw new StartAborted();
1474
+ }
1453
1475
  const capture = {
1454
1476
  audioRecorder,
1455
1477
  audioChunks: [],
@@ -1527,6 +1549,7 @@ function useNarrationRecorder(options) {
1527
1549
  if (!capture) {
1528
1550
  if (startingRef.current) {
1529
1551
  cancelPendingStart();
1552
+ optionsRef.current.onRecordingStop?.();
1530
1553
  setState("idle");
1531
1554
  }
1532
1555
  return;
@@ -1826,7 +1849,8 @@ function useNarrationStage(opts) {
1826
1849
  maxRecordingBytes: opts.maxRecordingBytes,
1827
1850
  audioRecorderOptions: opts.audioRecorderOptions,
1828
1851
  cameraRecorderOptions: opts.cameraRecorderOptions,
1829
- onRecordingStart: () => controllerRef.current.play(),
1852
+ // The take starts when the countdown ends, with the prompter already rolling.
1853
+ countIn: () => controllerRef.current.countIn(),
1830
1854
  onRecordingStop: () => controllerRef.current.pause()
1831
1855
  });
1832
1856
  const recorderRef = useRef5(recorder);
@@ -0,0 +1,83 @@
1
+ import { S as ScreenCaptureRegion } from './screenRegion-Ci_RsCzG.js';
2
+
3
+ /**
4
+ * User-configurable recorder device/encoding settings.
5
+ *
6
+ * Values stay serializable so the modal can include them in its capture key:
7
+ * changing a setting releases any prepared preview before a new stream is
8
+ * requested. Numeric and string track values are expressed as either `ideal`
9
+ * (best effort) or `exact` (fail when unavailable) constraints. Display
10
+ * capture is the exception: the screen-capture standard rejects exact
11
+ * constraints during its user picker, so those values always remain ideal.
12
+ */
13
+
14
+ type RecorderConstraintMode = 'ideal' | 'exact';
15
+ interface RecorderAudioDeviceSettings {
16
+ deviceId: string;
17
+ groupId?: string;
18
+ autoGainControl?: boolean;
19
+ channelCount?: number;
20
+ echoCancellation?: boolean;
21
+ latency?: number;
22
+ noiseSuppression?: boolean;
23
+ sampleRate?: number;
24
+ sampleSize?: number;
25
+ }
26
+ interface RecorderCameraDeviceSettings {
27
+ deviceId: string;
28
+ groupId?: string;
29
+ aspectRatio?: number;
30
+ backgroundBlur?: boolean;
31
+ facingMode?: string;
32
+ frameRate?: number;
33
+ height?: number;
34
+ resizeMode?: string;
35
+ width?: number;
36
+ }
37
+ interface RecorderScreenDeviceSettings {
38
+ /** Crop in captured pixels, relative to the chosen surface (not the desktop). */
39
+ crop?: ScreenCaptureRegion;
40
+ aspectRatio?: number;
41
+ cursor?: string;
42
+ displaySurface?: string;
43
+ frameRate?: number;
44
+ height?: number;
45
+ logicalSurface?: boolean;
46
+ resizeMode?: string;
47
+ width?: number;
48
+ }
49
+ interface RecorderScreenAudioSettings {
50
+ restrictOwnAudio?: boolean;
51
+ suppressLocalAudioPlayback?: boolean;
52
+ }
53
+ interface RecorderEncodingSettings {
54
+ audioMimeType: string;
55
+ videoMimeType: string;
56
+ bitsPerSecond?: number;
57
+ audioBitsPerSecond?: number;
58
+ videoBitsPerSecond?: number;
59
+ audioBitrateMode?: 'constant' | 'variable';
60
+ videoKeyFrameIntervalDuration?: number;
61
+ videoKeyFrameIntervalCount?: number;
62
+ }
63
+ interface RecorderDeviceSettings {
64
+ constraintMode: RecorderConstraintMode;
65
+ audio: RecorderAudioDeviceSettings;
66
+ camera: RecorderCameraDeviceSettings;
67
+ screen: RecorderScreenDeviceSettings;
68
+ screenAudio: RecorderScreenAudioSettings;
69
+ encoding: RecorderEncodingSettings;
70
+ }
71
+ declare const DEFAULT_RECORDER_DEVICE_SETTINGS: RecorderDeviceSettings;
72
+ /** Build microphone constraints from the advanced settings form. */
73
+ declare function buildRecorderAudioConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
74
+ /** Build camera constraints from the advanced settings form. */
75
+ declare function buildRecorderCameraConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
76
+ /** Build display-capture constraints independently from camera constraints. */
77
+ declare function buildRecorderScreenConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
78
+ /** Build the audio-track constraints specific to getDisplayMedia. */
79
+ declare function buildRecorderScreenAudioConstraints(settings: RecorderDeviceSettings): MediaTrackConstraints;
80
+ /** Convert the form's kbps values to the bps values MediaRecorder expects. */
81
+ declare function recorderBitsPerSecond(value: number | undefined): number | undefined;
82
+
83
+ export { DEFAULT_RECORDER_DEVICE_SETTINGS as D, type RecorderDeviceSettings as R, type RecorderAudioDeviceSettings as a, type RecorderCameraDeviceSettings as b, type RecorderConstraintMode as c, type RecorderEncodingSettings as d, type RecorderScreenAudioSettings as e, type RecorderScreenDeviceSettings as f, buildRecorderAudioConstraints as g, buildRecorderCameraConstraints as h, buildRecorderScreenAudioConstraints as i, buildRecorderScreenConstraints as j, recorderBitsPerSecond as r };
@@ -1,6 +1,7 @@
1
1
  import {
2
- SPEECH_INPUT_SAMPLE_RATE
3
- } from "./chunk-DNEVUOBZ.js";
2
+ microphoneTakeAsWav
3
+ } from "./chunk-K56KR27T.js";
4
+ import "./chunk-DNEVUOBZ.js";
4
5
  import {
5
6
  DEFAULT_RECORDER_DEVICE_SETTINGS,
6
7
  buildRecorderAudioConstraints
@@ -320,137 +321,6 @@ function extendRecognitionPrompt(previous, transcript) {
320
321
  return (firstWhitespace >= 0 ? tail.slice(firstWhitespace + 1) : tail).trim();
321
322
  }
322
323
 
323
- // src/speech/speechAudioWav.ts
324
- async function microphoneTakeAsWav(blob) {
325
- const encoded = await blobBytes(blob);
326
- let audio;
327
- try {
328
- audio = await decodeAt16k(encoded);
329
- } catch (caught) {
330
- const detail = caught instanceof Error && caught.message ? ` (${caught.message})` : "";
331
- throw new Error(`This recording could not be prepared for speech recognition${detail}`);
332
- }
333
- const channels = Array.from(
334
- { length: audio.numberOfChannels },
335
- (_, index) => audio.getChannelData(index)
336
- );
337
- const mono = downmixToMono(channels);
338
- const samples = audio.sampleRate === SPEECH_INPUT_SAMPLE_RATE ? mono : resampleLinear(mono, audio.sampleRate, SPEECH_INPUT_SAMPLE_RATE);
339
- return encodeMonoPcm16Wav([samples], SPEECH_INPUT_SAMPLE_RATE).buffer;
340
- }
341
- function blobBytes(blob) {
342
- if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
343
- return new Promise((resolve, reject) => {
344
- const reader = new FileReader();
345
- reader.onload = () => {
346
- const result = reader.result;
347
- if (result instanceof ArrayBuffer) resolve(result);
348
- else reject(new Error("The recording could not be read."));
349
- };
350
- reader.onerror = () => reject(reader.error ?? new Error("The recording could not be read."));
351
- reader.readAsArrayBuffer(blob);
352
- });
353
- }
354
- async function decodeAt16k(encoded) {
355
- if (typeof OfflineAudioContext !== "undefined") {
356
- let offline = null;
357
- try {
358
- offline = new OfflineAudioContext(1, 1, SPEECH_INPUT_SAMPLE_RATE);
359
- } catch {
360
- offline = null;
361
- }
362
- if (offline) return offline.decodeAudioData(encoded);
363
- }
364
- if (typeof AudioContext === "undefined") {
365
- throw new Error("Web Audio is unavailable");
366
- }
367
- const context = new AudioContext();
368
- try {
369
- return await context.decodeAudioData(encoded);
370
- } finally {
371
- await context.close().catch(() => void 0);
372
- }
373
- }
374
- function downmixToMono(channels) {
375
- const sampleCount = channels[0]?.length ?? 0;
376
- if (channels.length === 0 || sampleCount === 0) {
377
- throw new Error("The microphone recording was empty.");
378
- }
379
- if (channels.length === 1) return channels[0];
380
- if (channels.some((channel) => channel.length !== sampleCount)) {
381
- throw new Error("The microphone recording channels have different lengths.");
382
- }
383
- const mono = new Float32Array(sampleCount);
384
- for (let index = 0; index < sampleCount; index += 1) {
385
- let sum = 0;
386
- for (const channel of channels) sum += channel[index] ?? 0;
387
- mono[index] = sum / channels.length;
388
- }
389
- return mono;
390
- }
391
- function resampleLinear(samples, fromRate, toRate) {
392
- if (!Number.isFinite(fromRate) || fromRate <= 0 || !Number.isFinite(toRate) || toRate <= 0) {
393
- throw new Error("The microphone recording has an invalid sample rate.");
394
- }
395
- if (fromRate === toRate) return samples;
396
- const outputLength = Math.max(1, Math.round(samples.length * toRate / fromRate));
397
- const output = new Float32Array(outputLength);
398
- const step = fromRate / toRate;
399
- for (let index = 0; index < outputLength; index += 1) {
400
- const position = index * step;
401
- const left = Math.floor(position);
402
- const right = Math.min(left + 1, samples.length - 1);
403
- const fraction = position - left;
404
- const a = samples[Math.min(left, samples.length - 1)] ?? 0;
405
- const b = samples[right] ?? 0;
406
- output[index] = a + (b - a) * fraction;
407
- }
408
- return output;
409
- }
410
- function encodeMonoPcm16Wav(channels, sampleRate) {
411
- const sampleCount = channels[0]?.length ?? 0;
412
- if (channels.length === 0 || sampleCount === 0) {
413
- throw new Error("The microphone recording was empty.");
414
- }
415
- if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
416
- throw new Error("The microphone recording has an invalid sample rate.");
417
- }
418
- if (channels.some((channel) => channel.length !== sampleCount)) {
419
- throw new Error("The microphone recording channels have different lengths.");
420
- }
421
- const rate = Math.round(sampleRate);
422
- const bytesPerSample = 2;
423
- const dataBytes = sampleCount * bytesPerSample;
424
- const buffer = new ArrayBuffer(44 + dataBytes);
425
- const view = new DataView(buffer);
426
- writeAscii(view, 0, "RIFF");
427
- view.setUint32(4, 36 + dataBytes, true);
428
- writeAscii(view, 8, "WAVE");
429
- writeAscii(view, 12, "fmt ");
430
- view.setUint32(16, 16, true);
431
- view.setUint16(20, 1, true);
432
- view.setUint16(22, 1, true);
433
- view.setUint32(24, rate, true);
434
- view.setUint32(28, rate * bytesPerSample, true);
435
- view.setUint16(32, bytesPerSample, true);
436
- view.setUint16(34, 16, true);
437
- writeAscii(view, 36, "data");
438
- view.setUint32(40, dataBytes, true);
439
- for (let index = 0; index < sampleCount; index += 1) {
440
- let mono = 0;
441
- for (const channel of channels) mono += channel[index] ?? 0;
442
- mono = Math.max(-1, Math.min(1, mono / channels.length));
443
- const pcm = mono < 0 ? Math.round(mono * 32768) : Math.round(mono * 32767);
444
- view.setInt16(44 + index * bytesPerSample, pcm, true);
445
- }
446
- return new Uint8Array(buffer);
447
- }
448
- function writeAscii(view, offset, value) {
449
- for (let index = 0; index < value.length; index += 1) {
450
- view.setUint8(offset + index, value.charCodeAt(index));
451
- }
452
- }
453
-
454
324
  // src/speech/dictationSession.ts
455
325
  function dictationAudioConstraints(settings = DEFAULT_RECORDER_DEVICE_SETTINGS) {
456
326
  return {