@bendyline/squisq-editor-react 2.11.19 → 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.
@@ -0,0 +1,141 @@
1
+ import {
2
+ SPEECH_INPUT_SAMPLE_RATE
3
+ } from "./chunk-DNEVUOBZ.js";
4
+
5
+ // src/speech/speechAudioWav.ts
6
+ async function microphoneTakeAsWav(blob) {
7
+ const encoded = await blobBytes(blob);
8
+ let audio;
9
+ try {
10
+ audio = await decodeAt16k(encoded);
11
+ } catch (caught) {
12
+ const detail = caught instanceof Error && caught.message ? ` (${caught.message})` : "";
13
+ throw new Error(`This recording could not be prepared for speech recognition${detail}`);
14
+ }
15
+ const channels = Array.from(
16
+ { length: audio.numberOfChannels },
17
+ (_, index) => audio.getChannelData(index)
18
+ );
19
+ const mono = downmixToMono(channels);
20
+ const samples = audio.sampleRate === SPEECH_INPUT_SAMPLE_RATE ? mono : resampleLinear(mono, audio.sampleRate, SPEECH_INPUT_SAMPLE_RATE);
21
+ return encodeMonoPcm16Wav([samples], SPEECH_INPUT_SAMPLE_RATE).buffer;
22
+ }
23
+ function blobBytes(blob) {
24
+ if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
25
+ return new Promise((resolve, reject) => {
26
+ const reader = new FileReader();
27
+ reader.onload = () => {
28
+ const result = reader.result;
29
+ if (result instanceof ArrayBuffer) resolve(result);
30
+ else reject(new Error("The recording could not be read."));
31
+ };
32
+ reader.onerror = () => reject(reader.error ?? new Error("The recording could not be read."));
33
+ reader.readAsArrayBuffer(blob);
34
+ });
35
+ }
36
+ async function decodeAt16k(encoded) {
37
+ if (typeof OfflineAudioContext !== "undefined") {
38
+ let offline = null;
39
+ try {
40
+ offline = new OfflineAudioContext(1, 1, SPEECH_INPUT_SAMPLE_RATE);
41
+ } catch {
42
+ offline = null;
43
+ }
44
+ if (offline) return offline.decodeAudioData(encoded);
45
+ }
46
+ if (typeof AudioContext === "undefined") {
47
+ throw new Error("Web Audio is unavailable");
48
+ }
49
+ const context = new AudioContext();
50
+ try {
51
+ return await context.decodeAudioData(encoded);
52
+ } finally {
53
+ await context.close().catch(() => void 0);
54
+ }
55
+ }
56
+ function downmixToMono(channels) {
57
+ const sampleCount = channels[0]?.length ?? 0;
58
+ if (channels.length === 0 || sampleCount === 0) {
59
+ throw new Error("The microphone recording was empty.");
60
+ }
61
+ if (channels.length === 1) return channels[0];
62
+ if (channels.some((channel) => channel.length !== sampleCount)) {
63
+ throw new Error("The microphone recording channels have different lengths.");
64
+ }
65
+ const mono = new Float32Array(sampleCount);
66
+ for (let index = 0; index < sampleCount; index += 1) {
67
+ let sum = 0;
68
+ for (const channel of channels) sum += channel[index] ?? 0;
69
+ mono[index] = sum / channels.length;
70
+ }
71
+ return mono;
72
+ }
73
+ function resampleLinear(samples, fromRate, toRate) {
74
+ if (!Number.isFinite(fromRate) || fromRate <= 0 || !Number.isFinite(toRate) || toRate <= 0) {
75
+ throw new Error("The microphone recording has an invalid sample rate.");
76
+ }
77
+ if (fromRate === toRate) return samples;
78
+ const outputLength = Math.max(1, Math.round(samples.length * toRate / fromRate));
79
+ const output = new Float32Array(outputLength);
80
+ const step = fromRate / toRate;
81
+ for (let index = 0; index < outputLength; index += 1) {
82
+ const position = index * step;
83
+ const left = Math.floor(position);
84
+ const right = Math.min(left + 1, samples.length - 1);
85
+ const fraction = position - left;
86
+ const a = samples[Math.min(left, samples.length - 1)] ?? 0;
87
+ const b = samples[right] ?? 0;
88
+ output[index] = a + (b - a) * fraction;
89
+ }
90
+ return output;
91
+ }
92
+ function encodeMonoPcm16Wav(channels, sampleRate) {
93
+ const sampleCount = channels[0]?.length ?? 0;
94
+ if (channels.length === 0 || sampleCount === 0) {
95
+ throw new Error("The microphone recording was empty.");
96
+ }
97
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
98
+ throw new Error("The microphone recording has an invalid sample rate.");
99
+ }
100
+ if (channels.some((channel) => channel.length !== sampleCount)) {
101
+ throw new Error("The microphone recording channels have different lengths.");
102
+ }
103
+ const rate = Math.round(sampleRate);
104
+ const bytesPerSample = 2;
105
+ const dataBytes = sampleCount * bytesPerSample;
106
+ const buffer = new ArrayBuffer(44 + dataBytes);
107
+ const view = new DataView(buffer);
108
+ writeAscii(view, 0, "RIFF");
109
+ view.setUint32(4, 36 + dataBytes, true);
110
+ writeAscii(view, 8, "WAVE");
111
+ writeAscii(view, 12, "fmt ");
112
+ view.setUint32(16, 16, true);
113
+ view.setUint16(20, 1, true);
114
+ view.setUint16(22, 1, true);
115
+ view.setUint32(24, rate, true);
116
+ view.setUint32(28, rate * bytesPerSample, true);
117
+ view.setUint16(32, bytesPerSample, true);
118
+ view.setUint16(34, 16, true);
119
+ writeAscii(view, 36, "data");
120
+ view.setUint32(40, dataBytes, true);
121
+ for (let index = 0; index < sampleCount; index += 1) {
122
+ let mono = 0;
123
+ for (const channel of channels) mono += channel[index] ?? 0;
124
+ mono = Math.max(-1, Math.min(1, mono / channels.length));
125
+ const pcm = mono < 0 ? Math.round(mono * 32768) : Math.round(mono * 32767);
126
+ view.setInt16(44 + index * bytesPerSample, pcm, true);
127
+ }
128
+ return new Uint8Array(buffer);
129
+ }
130
+ function writeAscii(view, offset, value) {
131
+ for (let index = 0; index < value.length; index += 1) {
132
+ view.setUint8(offset + index, value.charCodeAt(index));
133
+ }
134
+ }
135
+
136
+ export {
137
+ microphoneTakeAsWav,
138
+ downmixToMono,
139
+ resampleLinear,
140
+ encodeMonoPcm16Wav
141
+ };