@skillhq/concierge 1.5.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 +91 -0
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +46 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/shared.d.ts +18 -0
- package/dist/cli/shared.d.ts.map +1 -0
- package/dist/cli/shared.js +2 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/call.d.ts +7 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +409 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +120 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/find-contact.d.ts +4 -0
- package/dist/commands/find-contact.d.ts.map +1 -0
- package/dist/commands/find-contact.js +57 -0
- package/dist/commands/find-contact.js.map +1 -0
- package/dist/commands/server.d.ts +7 -0
- package/dist/commands/server.d.ts.map +1 -0
- package/dist/commands/server.js +212 -0
- package/dist/commands/server.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/call/audio/mulaw.d.ts +35 -0
- package/dist/lib/call/audio/mulaw.d.ts.map +1 -0
- package/dist/lib/call/audio/mulaw.js +109 -0
- package/dist/lib/call/audio/mulaw.js.map +1 -0
- package/dist/lib/call/audio/pcm-utils.d.ts +62 -0
- package/dist/lib/call/audio/pcm-utils.d.ts.map +1 -0
- package/dist/lib/call/audio/pcm-utils.js +149 -0
- package/dist/lib/call/audio/pcm-utils.js.map +1 -0
- package/dist/lib/call/audio/resample.d.ts +34 -0
- package/dist/lib/call/audio/resample.d.ts.map +1 -0
- package/dist/lib/call/audio/resample.js +97 -0
- package/dist/lib/call/audio/resample.js.map +1 -0
- package/dist/lib/call/audio/streaming-decoder.d.ts +45 -0
- package/dist/lib/call/audio/streaming-decoder.d.ts.map +1 -0
- package/dist/lib/call/audio/streaming-decoder.js +110 -0
- package/dist/lib/call/audio/streaming-decoder.js.map +1 -0
- package/dist/lib/call/call-server.d.ts +110 -0
- package/dist/lib/call/call-server.d.ts.map +1 -0
- package/dist/lib/call/call-server.js +681 -0
- package/dist/lib/call/call-server.js.map +1 -0
- package/dist/lib/call/call-session.d.ts +133 -0
- package/dist/lib/call/call-session.d.ts.map +1 -0
- package/dist/lib/call/call-session.js +890 -0
- package/dist/lib/call/call-session.js.map +1 -0
- package/dist/lib/call/call-types.d.ts +133 -0
- package/dist/lib/call/call-types.d.ts.map +1 -0
- package/dist/lib/call/call-types.js +16 -0
- package/dist/lib/call/call-types.js.map +1 -0
- package/dist/lib/call/conversation-ai.d.ts +56 -0
- package/dist/lib/call/conversation-ai.d.ts.map +1 -0
- package/dist/lib/call/conversation-ai.js +276 -0
- package/dist/lib/call/conversation-ai.js.map +1 -0
- package/dist/lib/call/eval/codec-test.d.ts +45 -0
- package/dist/lib/call/eval/codec-test.d.ts.map +1 -0
- package/dist/lib/call/eval/codec-test.js +169 -0
- package/dist/lib/call/eval/codec-test.js.map +1 -0
- package/dist/lib/call/eval/conversation-scripts.d.ts +55 -0
- package/dist/lib/call/eval/conversation-scripts.d.ts.map +1 -0
- package/dist/lib/call/eval/conversation-scripts.js +359 -0
- package/dist/lib/call/eval/conversation-scripts.js.map +1 -0
- package/dist/lib/call/eval/eval-runner.d.ts +64 -0
- package/dist/lib/call/eval/eval-runner.d.ts.map +1 -0
- package/dist/lib/call/eval/eval-runner.js +369 -0
- package/dist/lib/call/eval/eval-runner.js.map +1 -0
- package/dist/lib/call/eval/index.d.ts +9 -0
- package/dist/lib/call/eval/index.d.ts.map +1 -0
- package/dist/lib/call/eval/index.js +9 -0
- package/dist/lib/call/eval/index.js.map +1 -0
- package/dist/lib/call/eval/integration-test-suite.d.ts +71 -0
- package/dist/lib/call/eval/integration-test-suite.d.ts.map +1 -0
- package/dist/lib/call/eval/integration-test-suite.js +519 -0
- package/dist/lib/call/eval/integration-test-suite.js.map +1 -0
- package/dist/lib/call/eval/turn-taking-test.d.ts +84 -0
- package/dist/lib/call/eval/turn-taking-test.d.ts.map +1 -0
- package/dist/lib/call/eval/turn-taking-test.js +260 -0
- package/dist/lib/call/eval/turn-taking-test.js.map +1 -0
- package/dist/lib/call/index.d.ts +12 -0
- package/dist/lib/call/index.d.ts.map +1 -0
- package/dist/lib/call/index.js +17 -0
- package/dist/lib/call/index.js.map +1 -0
- package/dist/lib/call/providers/deepgram.d.ts +81 -0
- package/dist/lib/call/providers/deepgram.d.ts.map +1 -0
- package/dist/lib/call/providers/deepgram.js +279 -0
- package/dist/lib/call/providers/deepgram.js.map +1 -0
- package/dist/lib/call/providers/elevenlabs.d.ts +78 -0
- package/dist/lib/call/providers/elevenlabs.d.ts.map +1 -0
- package/dist/lib/call/providers/elevenlabs.js +272 -0
- package/dist/lib/call/providers/elevenlabs.js.map +1 -0
- package/dist/lib/call/providers/local-deps.d.ts +18 -0
- package/dist/lib/call/providers/local-deps.d.ts.map +1 -0
- package/dist/lib/call/providers/local-deps.js +114 -0
- package/dist/lib/call/providers/local-deps.js.map +1 -0
- package/dist/lib/call/providers/twilio.d.ts +53 -0
- package/dist/lib/call/providers/twilio.d.ts.map +1 -0
- package/dist/lib/call/providers/twilio.js +173 -0
- package/dist/lib/call/providers/twilio.js.map +1 -0
- package/dist/lib/concierge-client-types.d.ts +68 -0
- package/dist/lib/concierge-client-types.d.ts.map +1 -0
- package/dist/lib/concierge-client-types.js +2 -0
- package/dist/lib/concierge-client-types.js.map +1 -0
- package/dist/lib/concierge-client.d.ts +29 -0
- package/dist/lib/concierge-client.d.ts.map +1 -0
- package/dist/lib/concierge-client.js +534 -0
- package/dist/lib/concierge-client.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +66 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/output.d.ts +7 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +114 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/utils/contact-extractor.d.ts +12 -0
- package/dist/lib/utils/contact-extractor.d.ts.map +1 -0
- package/dist/lib/utils/contact-extractor.js +159 -0
- package/dist/lib/utils/contact-extractor.js.map +1 -0
- package/dist/lib/utils/formatters.d.ts +15 -0
- package/dist/lib/utils/formatters.d.ts.map +1 -0
- package/dist/lib/utils/formatters.js +107 -0
- package/dist/lib/utils/formatters.js.map +1 -0
- package/dist/lib/utils/url-parser.d.ts +11 -0
- package/dist/lib/utils/url-parser.d.ts.map +1 -0
- package/dist/lib/utils/url-parser.js +103 -0
- package/dist/lib/utils/url-parser.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PCM audio buffer utilities
|
|
3
|
+
*/
|
|
4
|
+
export interface AudioFormat {
|
|
5
|
+
sampleRate: number;
|
|
6
|
+
channels: number;
|
|
7
|
+
bitDepth: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const TWILIO_FORMAT: AudioFormat;
|
|
10
|
+
export declare const DEEPGRAM_FORMAT: AudioFormat;
|
|
11
|
+
/**
|
|
12
|
+
* Resample 16-bit PCM audio using linear interpolation
|
|
13
|
+
* @param pcm - Input PCM buffer (16-bit little-endian)
|
|
14
|
+
* @param fromRate - Source sample rate
|
|
15
|
+
* @param toRate - Target sample rate
|
|
16
|
+
* @returns Resampled PCM buffer
|
|
17
|
+
*/
|
|
18
|
+
export declare function resamplePcm(pcm: Buffer, fromRate: number, toRate: number): Buffer;
|
|
19
|
+
/**
|
|
20
|
+
* Upsample 8kHz to 16kHz using linear interpolation
|
|
21
|
+
*/
|
|
22
|
+
export declare function upsample8kTo16k(pcm: Buffer): Buffer;
|
|
23
|
+
/**
|
|
24
|
+
* Downsample 16kHz to 8kHz
|
|
25
|
+
*/
|
|
26
|
+
export declare function downsample16kTo8k(pcm: Buffer): Buffer;
|
|
27
|
+
/**
|
|
28
|
+
* Downsample 24kHz to 8kHz
|
|
29
|
+
*/
|
|
30
|
+
export declare function downsample24kTo8k(pcm: Buffer): Buffer;
|
|
31
|
+
/**
|
|
32
|
+
* Downsample 44.1kHz to 8kHz
|
|
33
|
+
*/
|
|
34
|
+
export declare function downsample44kTo8k(pcm: Buffer): Buffer;
|
|
35
|
+
/**
|
|
36
|
+
* Convert stereo PCM to mono by averaging channels
|
|
37
|
+
*/
|
|
38
|
+
export declare function stereoToMono(pcm: Buffer): Buffer;
|
|
39
|
+
/**
|
|
40
|
+
* Normalize audio to a target peak level
|
|
41
|
+
* @param pcm - 16-bit PCM buffer
|
|
42
|
+
* @param targetPeak - Target peak (0-1, default 0.9)
|
|
43
|
+
*/
|
|
44
|
+
export declare function normalizePcm(pcm: Buffer, targetPeak?: number): Buffer;
|
|
45
|
+
/**
|
|
46
|
+
* Apply a simple low-pass filter to reduce aliasing before downsampling
|
|
47
|
+
*/
|
|
48
|
+
export declare function lowPassFilter(pcm: Buffer, cutoffRatio?: number): Buffer;
|
|
49
|
+
/**
|
|
50
|
+
* Calculate RMS (root mean square) level of audio
|
|
51
|
+
* Returns value from 0-1
|
|
52
|
+
*/
|
|
53
|
+
export declare function calculateRms(pcm: Buffer): number;
|
|
54
|
+
/**
|
|
55
|
+
* Check if audio buffer contains silence (below threshold)
|
|
56
|
+
*/
|
|
57
|
+
export declare function isSilence(pcm: Buffer, threshold?: number): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Concatenate multiple PCM buffers
|
|
60
|
+
*/
|
|
61
|
+
export declare function concatenatePcm(buffers: Buffer[]): Buffer;
|
|
62
|
+
//# sourceMappingURL=pcm-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcm-utils.d.ts","sourceRoot":"","sources":["../../../../src/lib/call/audio/pcm-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,eAAO,MAAM,aAAa,EAAE,WAI3B,CAAC;AAGF,eAAO,MAAM,eAAe,EAAE,WAI7B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAuBjF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,MAAM,CAyBlE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,SAAM,GAAG,MAAM,CAepE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUhD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAEhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PCM audio buffer utilities
|
|
3
|
+
*/
|
|
4
|
+
// Twilio's audio format: 8kHz mono µ-law
|
|
5
|
+
export const TWILIO_FORMAT = {
|
|
6
|
+
sampleRate: 8000,
|
|
7
|
+
channels: 1,
|
|
8
|
+
bitDepth: 8,
|
|
9
|
+
};
|
|
10
|
+
// Deepgram preferred format: 16kHz mono 16-bit PCM
|
|
11
|
+
export const DEEPGRAM_FORMAT = {
|
|
12
|
+
sampleRate: 16000,
|
|
13
|
+
channels: 1,
|
|
14
|
+
bitDepth: 16,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Resample 16-bit PCM audio using linear interpolation
|
|
18
|
+
* @param pcm - Input PCM buffer (16-bit little-endian)
|
|
19
|
+
* @param fromRate - Source sample rate
|
|
20
|
+
* @param toRate - Target sample rate
|
|
21
|
+
* @returns Resampled PCM buffer
|
|
22
|
+
*/
|
|
23
|
+
export function resamplePcm(pcm, fromRate, toRate) {
|
|
24
|
+
if (fromRate === toRate)
|
|
25
|
+
return pcm;
|
|
26
|
+
const ratio = fromRate / toRate;
|
|
27
|
+
const inputSamples = pcm.length / 2;
|
|
28
|
+
const outputSamples = Math.floor(inputSamples / ratio);
|
|
29
|
+
const output = Buffer.alloc(outputSamples * 2);
|
|
30
|
+
for (let i = 0; i < outputSamples; i++) {
|
|
31
|
+
const srcIndex = i * ratio;
|
|
32
|
+
const srcIndexFloor = Math.floor(srcIndex);
|
|
33
|
+
const fraction = srcIndex - srcIndexFloor;
|
|
34
|
+
const sample1 = pcm.readInt16LE(srcIndexFloor * 2);
|
|
35
|
+
const sample2Index = Math.min(srcIndexFloor + 1, inputSamples - 1) * 2;
|
|
36
|
+
const sample2 = pcm.readInt16LE(sample2Index);
|
|
37
|
+
// Linear interpolation
|
|
38
|
+
const interpolated = Math.round(sample1 + (sample2 - sample1) * fraction);
|
|
39
|
+
output.writeInt16LE(interpolated, i * 2);
|
|
40
|
+
}
|
|
41
|
+
return output;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Upsample 8kHz to 16kHz using linear interpolation
|
|
45
|
+
*/
|
|
46
|
+
export function upsample8kTo16k(pcm) {
|
|
47
|
+
return resamplePcm(pcm, 8000, 16000);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Downsample 16kHz to 8kHz
|
|
51
|
+
*/
|
|
52
|
+
export function downsample16kTo8k(pcm) {
|
|
53
|
+
return resamplePcm(pcm, 16000, 8000);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Downsample 24kHz to 8kHz
|
|
57
|
+
*/
|
|
58
|
+
export function downsample24kTo8k(pcm) {
|
|
59
|
+
return resamplePcm(pcm, 24000, 8000);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Downsample 44.1kHz to 8kHz
|
|
63
|
+
*/
|
|
64
|
+
export function downsample44kTo8k(pcm) {
|
|
65
|
+
return resamplePcm(pcm, 44100, 8000);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Convert stereo PCM to mono by averaging channels
|
|
69
|
+
*/
|
|
70
|
+
export function stereoToMono(pcm) {
|
|
71
|
+
const mono = Buffer.alloc(pcm.length / 2);
|
|
72
|
+
for (let i = 0; i < mono.length / 2; i++) {
|
|
73
|
+
const left = pcm.readInt16LE(i * 4);
|
|
74
|
+
const right = pcm.readInt16LE(i * 4 + 2);
|
|
75
|
+
mono.writeInt16LE(Math.round((left + right) / 2), i * 2);
|
|
76
|
+
}
|
|
77
|
+
return mono;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Normalize audio to a target peak level
|
|
81
|
+
* @param pcm - 16-bit PCM buffer
|
|
82
|
+
* @param targetPeak - Target peak (0-1, default 0.9)
|
|
83
|
+
*/
|
|
84
|
+
export function normalizePcm(pcm, targetPeak = 0.9) {
|
|
85
|
+
// Find current peak
|
|
86
|
+
let peak = 0;
|
|
87
|
+
for (let i = 0; i < pcm.length / 2; i++) {
|
|
88
|
+
const sample = Math.abs(pcm.readInt16LE(i * 2));
|
|
89
|
+
if (sample > peak)
|
|
90
|
+
peak = sample;
|
|
91
|
+
}
|
|
92
|
+
if (peak === 0)
|
|
93
|
+
return pcm;
|
|
94
|
+
// Calculate gain
|
|
95
|
+
const targetAmplitude = Math.round(32767 * targetPeak);
|
|
96
|
+
const gain = targetAmplitude / peak;
|
|
97
|
+
// Apply gain
|
|
98
|
+
const output = Buffer.alloc(pcm.length);
|
|
99
|
+
for (let i = 0; i < pcm.length / 2; i++) {
|
|
100
|
+
const sample = pcm.readInt16LE(i * 2);
|
|
101
|
+
const amplified = Math.round(sample * gain);
|
|
102
|
+
// Clamp to 16-bit range
|
|
103
|
+
const clamped = Math.max(-32768, Math.min(32767, amplified));
|
|
104
|
+
output.writeInt16LE(clamped, i * 2);
|
|
105
|
+
}
|
|
106
|
+
return output;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Apply a simple low-pass filter to reduce aliasing before downsampling
|
|
110
|
+
*/
|
|
111
|
+
export function lowPassFilter(pcm, cutoffRatio = 0.4) {
|
|
112
|
+
const output = Buffer.alloc(pcm.length);
|
|
113
|
+
const alpha = cutoffRatio;
|
|
114
|
+
let prevSample = pcm.readInt16LE(0);
|
|
115
|
+
output.writeInt16LE(prevSample, 0);
|
|
116
|
+
for (let i = 1; i < pcm.length / 2; i++) {
|
|
117
|
+
const sample = pcm.readInt16LE(i * 2);
|
|
118
|
+
const filtered = Math.round(alpha * sample + (1 - alpha) * prevSample);
|
|
119
|
+
output.writeInt16LE(filtered, i * 2);
|
|
120
|
+
prevSample = filtered;
|
|
121
|
+
}
|
|
122
|
+
return output;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Calculate RMS (root mean square) level of audio
|
|
126
|
+
* Returns value from 0-1
|
|
127
|
+
*/
|
|
128
|
+
export function calculateRms(pcm) {
|
|
129
|
+
let sum = 0;
|
|
130
|
+
const samples = pcm.length / 2;
|
|
131
|
+
for (let i = 0; i < samples; i++) {
|
|
132
|
+
const sample = pcm.readInt16LE(i * 2) / 32768;
|
|
133
|
+
sum += sample * sample;
|
|
134
|
+
}
|
|
135
|
+
return Math.sqrt(sum / samples);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if audio buffer contains silence (below threshold)
|
|
139
|
+
*/
|
|
140
|
+
export function isSilence(pcm, threshold = 0.01) {
|
|
141
|
+
return calculateRms(pcm) < threshold;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Concatenate multiple PCM buffers
|
|
145
|
+
*/
|
|
146
|
+
export function concatenatePcm(buffers) {
|
|
147
|
+
return Buffer.concat(buffers);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=pcm-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcm-utils.js","sourceRoot":"","sources":["../../../../src/lib/call/audio/pcm-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio resampling utilities for streaming conversion
|
|
3
|
+
* Converts 24kHz PCM to 8kHz µ-law in real-time
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Convert a single PCM sample to µ-law
|
|
7
|
+
*/
|
|
8
|
+
export declare function pcmSampleToMulaw(sample: number): number;
|
|
9
|
+
/**
|
|
10
|
+
* Streaming resampler: converts 24kHz 16-bit PCM to 8kHz µ-law
|
|
11
|
+
* Uses simple decimation (takes every 3rd sample) - not audiophile quality but fast
|
|
12
|
+
*/
|
|
13
|
+
export declare class StreamingResampler {
|
|
14
|
+
private remainder;
|
|
15
|
+
/**
|
|
16
|
+
* Process a chunk of 24kHz PCM and return 8kHz µ-law
|
|
17
|
+
* @param pcm24k - 16-bit little-endian PCM at 24kHz
|
|
18
|
+
* @returns µ-law audio at 8kHz
|
|
19
|
+
*/
|
|
20
|
+
process(pcm24k: Buffer): Buffer;
|
|
21
|
+
/**
|
|
22
|
+
* Flush any remaining audio
|
|
23
|
+
*/
|
|
24
|
+
flush(): Buffer;
|
|
25
|
+
/**
|
|
26
|
+
* Reset the resampler state
|
|
27
|
+
*/
|
|
28
|
+
reset(): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Simple one-shot conversion: 24kHz PCM to 8kHz µ-law
|
|
32
|
+
*/
|
|
33
|
+
export declare function pcm24kToMulaw8k(pcm24k: Buffer): Buffer;
|
|
34
|
+
//# sourceMappingURL=resample.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample.d.ts","sourceRoot":"","sources":["../../../../src/lib/call/audio/resample.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA8BH;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAA2B;IAE5C;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IA4B/B;;OAEG;IACH,KAAK,IAAI,MAAM;IAWf;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio resampling utilities for streaming conversion
|
|
3
|
+
* Converts 24kHz PCM to 8kHz µ-law in real-time
|
|
4
|
+
*/
|
|
5
|
+
// µ-law encoding table (linear 16-bit to 8-bit µ-law)
|
|
6
|
+
const LINEAR_TO_MULAW = new Uint8Array(65536);
|
|
7
|
+
// Bias and max for µ-law
|
|
8
|
+
const MULAW_BIAS = 0x84;
|
|
9
|
+
const MULAW_MAX = 0x7fff;
|
|
10
|
+
function linearToMulawSample(sample) {
|
|
11
|
+
const sign = sample < 0 ? 0x00 : 0x80;
|
|
12
|
+
if (sample < 0)
|
|
13
|
+
sample = -sample;
|
|
14
|
+
if (sample > MULAW_MAX)
|
|
15
|
+
sample = MULAW_MAX;
|
|
16
|
+
sample += MULAW_BIAS;
|
|
17
|
+
let exponent = 7;
|
|
18
|
+
for (let mask = 0x4000; !(sample & mask) && exponent > 0; mask >>= 1) {
|
|
19
|
+
exponent--;
|
|
20
|
+
}
|
|
21
|
+
const mantissa = (sample >> (exponent + 3)) & 0x0f;
|
|
22
|
+
return ~(sign | (exponent << 4) | mantissa) & 0xff;
|
|
23
|
+
}
|
|
24
|
+
// Initialize lookup table
|
|
25
|
+
for (let i = 0; i < 65536; i++) {
|
|
26
|
+
const sample = i < 32768 ? i : i - 65536;
|
|
27
|
+
LINEAR_TO_MULAW[i] = linearToMulawSample(sample);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert a single PCM sample to µ-law
|
|
31
|
+
*/
|
|
32
|
+
export function pcmSampleToMulaw(sample) {
|
|
33
|
+
const index = sample < 0 ? sample + 65536 : sample;
|
|
34
|
+
return LINEAR_TO_MULAW[index];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Streaming resampler: converts 24kHz 16-bit PCM to 8kHz µ-law
|
|
38
|
+
* Uses simple decimation (takes every 3rd sample) - not audiophile quality but fast
|
|
39
|
+
*/
|
|
40
|
+
export class StreamingResampler {
|
|
41
|
+
remainder = Buffer.alloc(0);
|
|
42
|
+
/**
|
|
43
|
+
* Process a chunk of 24kHz PCM and return 8kHz µ-law
|
|
44
|
+
* @param pcm24k - 16-bit little-endian PCM at 24kHz
|
|
45
|
+
* @returns µ-law audio at 8kHz
|
|
46
|
+
*/
|
|
47
|
+
process(pcm24k) {
|
|
48
|
+
// Combine with any leftover bytes from previous chunk
|
|
49
|
+
const input = Buffer.concat([this.remainder, pcm24k]);
|
|
50
|
+
// Each input sample is 2 bytes (16-bit)
|
|
51
|
+
// We need 3 samples of input (6 bytes) to produce 1 sample of output
|
|
52
|
+
// (24000 / 8000 = 3)
|
|
53
|
+
const inputSamples = Math.floor(input.length / 2);
|
|
54
|
+
const outputSamples = Math.floor(inputSamples / 3);
|
|
55
|
+
const output = Buffer.alloc(outputSamples);
|
|
56
|
+
for (let i = 0; i < outputSamples; i++) {
|
|
57
|
+
// Read every 3rd sample from input
|
|
58
|
+
const inputOffset = i * 3 * 2; // 3 samples * 2 bytes
|
|
59
|
+
const sample = input.readInt16LE(inputOffset);
|
|
60
|
+
// Convert to µ-law
|
|
61
|
+
output[i] = pcmSampleToMulaw(sample);
|
|
62
|
+
}
|
|
63
|
+
// Save any remaining bytes for next chunk
|
|
64
|
+
const consumedBytes = outputSamples * 3 * 2;
|
|
65
|
+
this.remainder = input.slice(consumedBytes);
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Flush any remaining audio
|
|
70
|
+
*/
|
|
71
|
+
flush() {
|
|
72
|
+
if (this.remainder.length >= 2) {
|
|
73
|
+
// Process what we have left
|
|
74
|
+
const sample = this.remainder.readInt16LE(0);
|
|
75
|
+
this.remainder = Buffer.alloc(0);
|
|
76
|
+
return Buffer.from([pcmSampleToMulaw(sample)]);
|
|
77
|
+
}
|
|
78
|
+
this.remainder = Buffer.alloc(0);
|
|
79
|
+
return Buffer.alloc(0);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reset the resampler state
|
|
83
|
+
*/
|
|
84
|
+
reset() {
|
|
85
|
+
this.remainder = Buffer.alloc(0);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Simple one-shot conversion: 24kHz PCM to 8kHz µ-law
|
|
90
|
+
*/
|
|
91
|
+
export function pcm24kToMulaw8k(pcm24k) {
|
|
92
|
+
const resampler = new StreamingResampler();
|
|
93
|
+
const main = resampler.process(pcm24k);
|
|
94
|
+
const tail = resampler.flush();
|
|
95
|
+
return Buffer.concat([main, tail]);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=resample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample.js","sourceRoot":"","sources":["../../../../src/lib/call/audio/resample.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming MP3 to µ-law decoder using ffmpeg
|
|
3
|
+
* Converts MP3 audio chunks to 8kHz µ-law in real-time
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'node:events';
|
|
6
|
+
export interface StreamingDecoderEvents {
|
|
7
|
+
data: (mulaw: Buffer) => void;
|
|
8
|
+
error: (error: Error) => void;
|
|
9
|
+
close: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Streaming MP3 to µ-law decoder
|
|
13
|
+
* Uses ffmpeg subprocess with pipes for real-time conversion
|
|
14
|
+
*/
|
|
15
|
+
export declare class StreamingDecoder extends EventEmitter {
|
|
16
|
+
private ffmpeg;
|
|
17
|
+
private isStarted;
|
|
18
|
+
/**
|
|
19
|
+
* Start the decoder
|
|
20
|
+
* Must be called before writing data
|
|
21
|
+
*/
|
|
22
|
+
start(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Write MP3 data to the decoder
|
|
25
|
+
* Converted µ-law will be emitted via 'data' events
|
|
26
|
+
*/
|
|
27
|
+
write(mp3Data: Buffer): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Signal end of input and flush remaining data
|
|
30
|
+
*/
|
|
31
|
+
end(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Stop the decoder immediately
|
|
34
|
+
*/
|
|
35
|
+
stop(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Check if decoder is running
|
|
38
|
+
*/
|
|
39
|
+
get running(): boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a streaming decoder for TTS audio
|
|
43
|
+
*/
|
|
44
|
+
export declare function createStreamingDecoder(): StreamingDecoder;
|
|
45
|
+
//# sourceMappingURL=streaming-decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-decoder.d.ts","sourceRoot":"","sources":["../../../../src/lib/call/audio/streaming-decoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,SAAS,CAAS;IAE1B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAwDb;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAa/B;;OAEG;IACH,GAAG,IAAI,IAAI;IAMX;;OAEG;IACH,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;CACF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CAEzD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming MP3 to µ-law decoder using ffmpeg
|
|
3
|
+
* Converts MP3 audio chunks to 8kHz µ-law in real-time
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { EventEmitter } from 'node:events';
|
|
7
|
+
/**
|
|
8
|
+
* Streaming MP3 to µ-law decoder
|
|
9
|
+
* Uses ffmpeg subprocess with pipes for real-time conversion
|
|
10
|
+
*/
|
|
11
|
+
export class StreamingDecoder extends EventEmitter {
|
|
12
|
+
ffmpeg = null;
|
|
13
|
+
isStarted = false;
|
|
14
|
+
/**
|
|
15
|
+
* Start the decoder
|
|
16
|
+
* Must be called before writing data
|
|
17
|
+
*/
|
|
18
|
+
start() {
|
|
19
|
+
if (this.isStarted) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Spawn ffmpeg to convert MP3 (stdin) to µ-law (stdout)
|
|
23
|
+
this.ffmpeg = spawn('ffmpeg', [
|
|
24
|
+
'-hide_banner',
|
|
25
|
+
'-loglevel',
|
|
26
|
+
'error',
|
|
27
|
+
'-i',
|
|
28
|
+
'pipe:0', // Read MP3 from stdin
|
|
29
|
+
'-f',
|
|
30
|
+
'mulaw', // Output format: µ-law
|
|
31
|
+
'-ar',
|
|
32
|
+
'8000', // 8kHz sample rate (Twilio)
|
|
33
|
+
'-ac',
|
|
34
|
+
'1', // Mono
|
|
35
|
+
'pipe:1', // Write to stdout
|
|
36
|
+
], {
|
|
37
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
38
|
+
});
|
|
39
|
+
// Handle µ-law output
|
|
40
|
+
this.ffmpeg.stdout?.on('data', (chunk) => {
|
|
41
|
+
this.emit('data', chunk);
|
|
42
|
+
});
|
|
43
|
+
// Handle errors
|
|
44
|
+
this.ffmpeg.stderr?.on('data', (data) => {
|
|
45
|
+
const msg = data.toString().trim();
|
|
46
|
+
if (msg && !msg.includes('size=')) {
|
|
47
|
+
console.error('[StreamingDecoder] ffmpeg:', msg);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
this.ffmpeg.on('error', (err) => {
|
|
51
|
+
this.emit('error', err);
|
|
52
|
+
});
|
|
53
|
+
this.ffmpeg.on('close', (code) => {
|
|
54
|
+
if (code !== 0 && code !== null) {
|
|
55
|
+
console.error(`[StreamingDecoder] ffmpeg exited with code ${code}`);
|
|
56
|
+
}
|
|
57
|
+
this.ffmpeg = null;
|
|
58
|
+
this.isStarted = false;
|
|
59
|
+
this.emit('close');
|
|
60
|
+
});
|
|
61
|
+
this.isStarted = true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Write MP3 data to the decoder
|
|
65
|
+
* Converted µ-law will be emitted via 'data' events
|
|
66
|
+
*/
|
|
67
|
+
write(mp3Data) {
|
|
68
|
+
if (!this.ffmpeg?.stdin?.writable) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return this.ffmpeg.stdin.write(mp3Data);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
this.emit('error', err instanceof Error ? err : new Error(String(err)));
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Signal end of input and flush remaining data
|
|
81
|
+
*/
|
|
82
|
+
end() {
|
|
83
|
+
if (this.ffmpeg?.stdin) {
|
|
84
|
+
this.ffmpeg.stdin.end();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Stop the decoder immediately
|
|
89
|
+
*/
|
|
90
|
+
stop() {
|
|
91
|
+
if (this.ffmpeg) {
|
|
92
|
+
this.ffmpeg.kill('SIGTERM');
|
|
93
|
+
this.ffmpeg = null;
|
|
94
|
+
this.isStarted = false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if decoder is running
|
|
99
|
+
*/
|
|
100
|
+
get running() {
|
|
101
|
+
return this.isStarted && this.ffmpeg !== null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a streaming decoder for TTS audio
|
|
106
|
+
*/
|
|
107
|
+
export function createStreamingDecoder() {
|
|
108
|
+
return new StreamingDecoder();
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=streaming-decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming-decoder.js","sourceRoot":"","sources":["../../../../src/lib/call/audio/streaming-decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call server - HTTP + WebSocket server for voice calls
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'node:events';
|
|
5
|
+
import { CallSession } from './call-session.js';
|
|
6
|
+
import type { CallConfig, CallState } from './call-types.js';
|
|
7
|
+
export interface CallServerOptions {
|
|
8
|
+
port: number;
|
|
9
|
+
publicUrl: string;
|
|
10
|
+
config: CallConfig;
|
|
11
|
+
}
|
|
12
|
+
export interface CallServerEvents {
|
|
13
|
+
started: () => void;
|
|
14
|
+
stopped: () => void;
|
|
15
|
+
call_started: (callId: string) => void;
|
|
16
|
+
call_ended: (callId: string, state: CallState) => void;
|
|
17
|
+
error: (error: Error) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare class CallServer extends EventEmitter {
|
|
20
|
+
private server;
|
|
21
|
+
private controlWss;
|
|
22
|
+
private mediaWss;
|
|
23
|
+
private readonly options;
|
|
24
|
+
private readonly sessions;
|
|
25
|
+
private readonly controlClients;
|
|
26
|
+
private statusReconcileTimer;
|
|
27
|
+
private isPreflightCallId;
|
|
28
|
+
constructor(options: CallServerOptions);
|
|
29
|
+
private timestamp;
|
|
30
|
+
private log;
|
|
31
|
+
private warn;
|
|
32
|
+
private error;
|
|
33
|
+
/**
|
|
34
|
+
* Start the server
|
|
35
|
+
*/
|
|
36
|
+
start(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Stop the server
|
|
39
|
+
*/
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Handle HTTP requests
|
|
43
|
+
*/
|
|
44
|
+
private handleHttpRequest;
|
|
45
|
+
/**
|
|
46
|
+
* Health check endpoint
|
|
47
|
+
*/
|
|
48
|
+
private handleHealthCheck;
|
|
49
|
+
/**
|
|
50
|
+
* Server status endpoint
|
|
51
|
+
*/
|
|
52
|
+
private handleStatusCheck;
|
|
53
|
+
/**
|
|
54
|
+
* Call status endpoint
|
|
55
|
+
*/
|
|
56
|
+
private handleCallStatusCheck;
|
|
57
|
+
/**
|
|
58
|
+
* Initiate a new call via HTTP
|
|
59
|
+
*/
|
|
60
|
+
private handleCallRequest;
|
|
61
|
+
/**
|
|
62
|
+
* Twilio voice webhook - returns TwiML for Media Streams
|
|
63
|
+
*/
|
|
64
|
+
private handleTwilioVoice;
|
|
65
|
+
/**
|
|
66
|
+
* Twilio status callback
|
|
67
|
+
*/
|
|
68
|
+
private handleTwilioStatus;
|
|
69
|
+
/**
|
|
70
|
+
* Handle control WebSocket connection
|
|
71
|
+
*/
|
|
72
|
+
private handleControlConnection;
|
|
73
|
+
/**
|
|
74
|
+
* Handle control messages from clients
|
|
75
|
+
*/
|
|
76
|
+
private handleControlMessage;
|
|
77
|
+
/**
|
|
78
|
+
* Handle media stream WebSocket connection
|
|
79
|
+
* Twilio sends callId in the 'start' event's customParameters, not in the URL
|
|
80
|
+
*/
|
|
81
|
+
private handleMediaStreamConnection;
|
|
82
|
+
/**
|
|
83
|
+
* Internal method to initiate a call
|
|
84
|
+
*/
|
|
85
|
+
private initiateCallInternal;
|
|
86
|
+
/**
|
|
87
|
+
* Broadcast message to all control clients
|
|
88
|
+
*/
|
|
89
|
+
private broadcastToControl;
|
|
90
|
+
/**
|
|
91
|
+
* Get a session by call ID
|
|
92
|
+
*/
|
|
93
|
+
getSession(callId: string): CallSession | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Get all active sessions
|
|
96
|
+
*/
|
|
97
|
+
getActiveSessions(): Map<string, CallSession>;
|
|
98
|
+
/**
|
|
99
|
+
* Check if server is running
|
|
100
|
+
*/
|
|
101
|
+
get isRunning(): boolean;
|
|
102
|
+
private startStatusReconcileLoop;
|
|
103
|
+
private reconcileStatusesWithProvider;
|
|
104
|
+
private preflightPublicWebhook;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create and configure a call server
|
|
108
|
+
*/
|
|
109
|
+
export declare function createCallServer(config: CallConfig, port: number, publicUrl: string): CallServer;
|
|
110
|
+
//# sourceMappingURL=call-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-server.d.ts","sourceRoot":"","sources":["../../../src/lib/call/call-server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAA4C,MAAM,iBAAiB,CAAC;AAyBvG,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACvD,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,oBAAoB,CAA+B;IAE3D,OAAO,CAAC,iBAAiB;gBAIb,OAAO,EAAE,iBAAiB;IAKtC,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,KAAK;IAQb;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmCzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAKzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;YACW,iBAAiB;IA2D/B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuDzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgF1B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA6B/B;;OAEG;YACW,oBAAoB;IAmClC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAyDnC;;OAEG;YACW,oBAAoB;IA+DlC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAInD;;OAEG;IACH,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;IAI7C;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,OAAO,CAAC,wBAAwB;YAWlB,6BAA6B;YAiC7B,sBAAsB;CAoErC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAMhG"}
|