@absolutejs/voice 0.0.22-beta.576 → 0.0.22-beta.578
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/dist/client/htmxBootstrap.js +186 -5
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +187 -5
- package/dist/client/timeStretch.d.ts +5 -0
- package/dist/index.js +5 -2
- package/dist/testing/index.js +186 -5
- package/package.json +155 -155
|
@@ -1533,12 +1533,165 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1533
1533
|
};
|
|
1534
1534
|
};
|
|
1535
1535
|
|
|
1536
|
+
// src/client/timeStretch.ts
|
|
1537
|
+
var HOP_MS = 10;
|
|
1538
|
+
var SEEK_MS = 5;
|
|
1539
|
+
var ENERGY_EPSILON = 0.000001;
|
|
1540
|
+
var HALF = 0.5;
|
|
1541
|
+
var MS_PER_SECOND = 1000;
|
|
1542
|
+
var makeHann = (length) => {
|
|
1543
|
+
const weights = new Float32Array(length);
|
|
1544
|
+
for (let index = 0;index < length; index += 1) {
|
|
1545
|
+
weights[index] = HALF - HALF * Math.cos(2 * Math.PI * index / length);
|
|
1546
|
+
}
|
|
1547
|
+
return weights;
|
|
1548
|
+
};
|
|
1549
|
+
var correlationScore = (base, start, ref, length) => {
|
|
1550
|
+
let dot = 0;
|
|
1551
|
+
let energy = 0;
|
|
1552
|
+
for (let index = 0;index < length; index += 1) {
|
|
1553
|
+
const sample = base[start + index] ?? 0;
|
|
1554
|
+
dot += sample * (ref[index] ?? 0);
|
|
1555
|
+
energy += sample * sample;
|
|
1556
|
+
}
|
|
1557
|
+
return dot / Math.sqrt(energy + ENERGY_EPSILON);
|
|
1558
|
+
};
|
|
1559
|
+
var overlapAddGrain = (src, off, tail, weights, hop) => {
|
|
1560
|
+
const out = new Float32Array(hop);
|
|
1561
|
+
const nextTail = new Float32Array(hop);
|
|
1562
|
+
for (let index = 0;index < hop; index += 1) {
|
|
1563
|
+
out[index] = (tail[index] ?? 0) + (src[off + index] ?? 0) * (weights[index] ?? 0);
|
|
1564
|
+
nextTail[index] = (src[off + hop + index] ?? 0) * (weights[hop + index] ?? 0);
|
|
1565
|
+
}
|
|
1566
|
+
return { nextTail, out };
|
|
1567
|
+
};
|
|
1568
|
+
var createTimeStretcher = () => {
|
|
1569
|
+
let sampleRate = 0;
|
|
1570
|
+
let channelCount = 0;
|
|
1571
|
+
let hop = 0;
|
|
1572
|
+
let frameLen = 0;
|
|
1573
|
+
let seek = 0;
|
|
1574
|
+
let weights = new Float32Array(0);
|
|
1575
|
+
let buffers = [];
|
|
1576
|
+
let inputStart = 0;
|
|
1577
|
+
let analysisPos = 0;
|
|
1578
|
+
let olaTail = [];
|
|
1579
|
+
let naturalRef = null;
|
|
1580
|
+
const init = (rate, channels) => {
|
|
1581
|
+
sampleRate = rate;
|
|
1582
|
+
channelCount = channels;
|
|
1583
|
+
hop = Math.max(1, Math.round(sampleRate * HOP_MS / MS_PER_SECOND));
|
|
1584
|
+
frameLen = hop * 2;
|
|
1585
|
+
seek = Math.max(1, Math.round(sampleRate * SEEK_MS / MS_PER_SECOND));
|
|
1586
|
+
weights = makeHann(frameLen);
|
|
1587
|
+
buffers = Array.from({ length: channels }, () => new Float32Array(0));
|
|
1588
|
+
olaTail = Array.from({ length: channels }, () => new Float32Array(hop));
|
|
1589
|
+
inputStart = 0;
|
|
1590
|
+
analysisPos = seek;
|
|
1591
|
+
naturalRef = null;
|
|
1592
|
+
};
|
|
1593
|
+
const reset = () => {
|
|
1594
|
+
buffers = buffers.map(() => new Float32Array(0));
|
|
1595
|
+
olaTail = olaTail.map(() => new Float32Array(hop));
|
|
1596
|
+
inputStart = 0;
|
|
1597
|
+
analysisPos = seek;
|
|
1598
|
+
naturalRef = null;
|
|
1599
|
+
};
|
|
1600
|
+
const append = (input) => {
|
|
1601
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1602
|
+
const incoming = input[channel] ?? input[0] ?? new Float32Array(0);
|
|
1603
|
+
const existing = buffers[channel] ?? new Float32Array(0);
|
|
1604
|
+
const merged = new Float32Array(existing.length + incoming.length);
|
|
1605
|
+
merged.set(existing, 0);
|
|
1606
|
+
merged.set(incoming, existing.length);
|
|
1607
|
+
buffers[channel] = merged;
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
const inputEnd = () => inputStart + (buffers[0]?.length ?? 0);
|
|
1611
|
+
const compact = () => {
|
|
1612
|
+
const keepFrom = Math.max(inputStart, Math.floor(analysisPos) - seek - 1);
|
|
1613
|
+
if (keepFrom <= inputStart)
|
|
1614
|
+
return;
|
|
1615
|
+
const drop = keepFrom - inputStart;
|
|
1616
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1617
|
+
buffers[channel] = (buffers[channel] ?? new Float32Array(0)).slice(drop);
|
|
1618
|
+
}
|
|
1619
|
+
inputStart = keepFrom;
|
|
1620
|
+
};
|
|
1621
|
+
const bestOffset = (center) => {
|
|
1622
|
+
if (!naturalRef)
|
|
1623
|
+
return 0;
|
|
1624
|
+
const [base] = buffers;
|
|
1625
|
+
if (!base)
|
|
1626
|
+
return 0;
|
|
1627
|
+
let bestDelta = 0;
|
|
1628
|
+
let bestScore = -Infinity;
|
|
1629
|
+
for (let delta = -seek;delta <= seek; delta += 1) {
|
|
1630
|
+
const score = correlationScore(base, center + delta - inputStart, naturalRef, frameLen);
|
|
1631
|
+
if (score <= bestScore)
|
|
1632
|
+
continue;
|
|
1633
|
+
bestScore = score;
|
|
1634
|
+
bestDelta = delta;
|
|
1635
|
+
}
|
|
1636
|
+
return bestDelta;
|
|
1637
|
+
};
|
|
1638
|
+
const process = (input, speed, rate) => {
|
|
1639
|
+
const channels = Math.max(1, input.length);
|
|
1640
|
+
if (sampleRate !== rate || channelCount !== channels)
|
|
1641
|
+
init(rate, channels);
|
|
1642
|
+
append(input);
|
|
1643
|
+
const analysisHop = hop * speed;
|
|
1644
|
+
const segments = Array.from({ length: channelCount }, () => []);
|
|
1645
|
+
const emitGrain = (pos) => {
|
|
1646
|
+
const off = pos - inputStart;
|
|
1647
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1648
|
+
const src = buffers[channel];
|
|
1649
|
+
const tail = olaTail[channel];
|
|
1650
|
+
if (!src || !tail)
|
|
1651
|
+
continue;
|
|
1652
|
+
const grain = overlapAddGrain(src, off, tail, weights, hop);
|
|
1653
|
+
olaTail[channel] = grain.nextTail;
|
|
1654
|
+
segments[channel]?.push(grain.out);
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
const captureRef = (pos) => {
|
|
1658
|
+
const ref = new Float32Array(frameLen);
|
|
1659
|
+
const refOff = pos + hop - inputStart;
|
|
1660
|
+
const [base] = buffers;
|
|
1661
|
+
if (base)
|
|
1662
|
+
ref.set(base.subarray(refOff, refOff + frameLen));
|
|
1663
|
+
naturalRef = ref;
|
|
1664
|
+
};
|
|
1665
|
+
const canEmit = () => Math.floor(analysisPos) - seek >= inputStart && Math.floor(analysisPos) + seek + frameLen + hop <= inputEnd();
|
|
1666
|
+
while (canEmit()) {
|
|
1667
|
+
const center = Math.round(analysisPos);
|
|
1668
|
+
const pos = center + bestOffset(center);
|
|
1669
|
+
emitGrain(pos);
|
|
1670
|
+
captureRef(pos);
|
|
1671
|
+
analysisPos += analysisHop;
|
|
1672
|
+
}
|
|
1673
|
+
compact();
|
|
1674
|
+
return segments.map((channelSegments) => {
|
|
1675
|
+
const total = channelSegments.reduce((sum, seg) => sum + seg.length, 0);
|
|
1676
|
+
const merged = new Float32Array(total);
|
|
1677
|
+
let offset = 0;
|
|
1678
|
+
for (const seg of channelSegments) {
|
|
1679
|
+
merged.set(seg, offset);
|
|
1680
|
+
offset += seg.length;
|
|
1681
|
+
}
|
|
1682
|
+
return merged;
|
|
1683
|
+
});
|
|
1684
|
+
};
|
|
1685
|
+
return { process, reset };
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1536
1688
|
// src/client/audioPlayer.ts
|
|
1537
1689
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1538
1690
|
var DEFAULT_VOLUME = 1;
|
|
1539
1691
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
1540
1692
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
1541
1693
|
var MAX_PLAYBACK_RATE = 2;
|
|
1694
|
+
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1542
1695
|
var createInitialState3 = () => ({
|
|
1543
1696
|
activeSourceCount: 0,
|
|
1544
1697
|
error: null,
|
|
@@ -1601,6 +1754,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1601
1754
|
let outputNode = null;
|
|
1602
1755
|
let volume = clampVolume(options.volume);
|
|
1603
1756
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
1757
|
+
let stretcher = null;
|
|
1604
1758
|
let queueEndTime = 0;
|
|
1605
1759
|
let syncPromise = Promise.resolve();
|
|
1606
1760
|
let interruptStartedAt = null;
|
|
@@ -1633,6 +1787,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1633
1787
|
const resolveInterrupt = (latencyMs) => {
|
|
1634
1788
|
clearInterruptTimer();
|
|
1635
1789
|
interruptStartedAt = null;
|
|
1790
|
+
stretcher?.reset();
|
|
1636
1791
|
setState({
|
|
1637
1792
|
activeSourceCount: sourceNodes.size,
|
|
1638
1793
|
isPlaying: false,
|
|
@@ -1697,13 +1852,11 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1697
1852
|
queueEndTime = audioContext.currentTime;
|
|
1698
1853
|
return audioContext;
|
|
1699
1854
|
};
|
|
1700
|
-
const
|
|
1701
|
-
const context = await ensureAudioContext();
|
|
1702
|
-
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1855
|
+
const scheduleBuffer = (context, buffer, rate) => {
|
|
1703
1856
|
const node = context.createBufferSource();
|
|
1704
1857
|
node.buffer = buffer;
|
|
1705
1858
|
if (node.playbackRate) {
|
|
1706
|
-
node.playbackRate.value =
|
|
1859
|
+
node.playbackRate.value = rate;
|
|
1707
1860
|
}
|
|
1708
1861
|
node.connect(outputNode ?? context.destination);
|
|
1709
1862
|
node.onended = () => {
|
|
@@ -1716,7 +1869,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1716
1869
|
maybeResolveInterrupt();
|
|
1717
1870
|
};
|
|
1718
1871
|
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1719
|
-
queueEndTime = startAt + buffer.duration /
|
|
1872
|
+
queueEndTime = startAt + buffer.duration / rate;
|
|
1720
1873
|
sourceNodes.add(node);
|
|
1721
1874
|
setState({
|
|
1722
1875
|
activeSourceCount: sourceNodes.size,
|
|
@@ -1724,6 +1877,34 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1724
1877
|
});
|
|
1725
1878
|
node.start(startAt);
|
|
1726
1879
|
};
|
|
1880
|
+
const scheduleChunk = async (chunk) => {
|
|
1881
|
+
const context = await ensureAudioContext();
|
|
1882
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1883
|
+
if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
|
|
1884
|
+
stretcher?.reset();
|
|
1885
|
+
scheduleBuffer(context, buffer, playbackRate);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
const channels = Math.max(1, chunk.format.channels);
|
|
1889
|
+
const input = [];
|
|
1890
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1891
|
+
input.push(buffer.getChannelData(channelIndex));
|
|
1892
|
+
}
|
|
1893
|
+
stretcher ??= createTimeStretcher();
|
|
1894
|
+
const stretched = stretcher.process(input, playbackRate, chunk.format.sampleRateHz);
|
|
1895
|
+
const outLength = stretched[0]?.length ?? 0;
|
|
1896
|
+
if (outLength === 0) {
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
const outBuffer = context.createBuffer(channels, outLength, chunk.format.sampleRateHz);
|
|
1900
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1901
|
+
const channelOut = stretched[channelIndex];
|
|
1902
|
+
if (!channelOut)
|
|
1903
|
+
continue;
|
|
1904
|
+
outBuffer.getChannelData(channelIndex).set(channelOut);
|
|
1905
|
+
}
|
|
1906
|
+
scheduleBuffer(context, outBuffer, 1);
|
|
1907
|
+
};
|
|
1727
1908
|
const stopQueuedPlayback = (options2) => {
|
|
1728
1909
|
for (const node of [...sourceNodes]) {
|
|
1729
1910
|
node.stop?.();
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { bindVoiceReactiveSource, voiceSseReactiveSource, } from "./reactiveSour
|
|
|
2
2
|
export type { VoiceReactiveSource, VoiceSseReactiveSourceOptions, } from "./reactiveSource";
|
|
3
3
|
export { createVoiceConnection } from "./connection";
|
|
4
4
|
export { createVoiceAudioPlayer, decodeVoiceAudioChunk } from "./audioPlayer";
|
|
5
|
+
export { createTimeStretcher, type TimeStretcher } from "./timeStretch";
|
|
5
6
|
export { createVoiceStream } from "./createVoiceStream";
|
|
6
7
|
export { createVoiceBrowserMediaReporter } from "./browserMedia";
|
|
7
8
|
export type { VoiceBrowserMediaReporter } from "./browserMedia";
|
package/dist/client/index.js
CHANGED
|
@@ -370,12 +370,165 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
370
370
|
getSessionId: () => state.sessionId
|
|
371
371
|
};
|
|
372
372
|
};
|
|
373
|
+
// src/client/timeStretch.ts
|
|
374
|
+
var HOP_MS = 10;
|
|
375
|
+
var SEEK_MS = 5;
|
|
376
|
+
var ENERGY_EPSILON = 0.000001;
|
|
377
|
+
var HALF = 0.5;
|
|
378
|
+
var MS_PER_SECOND = 1000;
|
|
379
|
+
var makeHann = (length) => {
|
|
380
|
+
const weights = new Float32Array(length);
|
|
381
|
+
for (let index = 0;index < length; index += 1) {
|
|
382
|
+
weights[index] = HALF - HALF * Math.cos(2 * Math.PI * index / length);
|
|
383
|
+
}
|
|
384
|
+
return weights;
|
|
385
|
+
};
|
|
386
|
+
var correlationScore = (base, start, ref, length) => {
|
|
387
|
+
let dot = 0;
|
|
388
|
+
let energy = 0;
|
|
389
|
+
for (let index = 0;index < length; index += 1) {
|
|
390
|
+
const sample = base[start + index] ?? 0;
|
|
391
|
+
dot += sample * (ref[index] ?? 0);
|
|
392
|
+
energy += sample * sample;
|
|
393
|
+
}
|
|
394
|
+
return dot / Math.sqrt(energy + ENERGY_EPSILON);
|
|
395
|
+
};
|
|
396
|
+
var overlapAddGrain = (src, off, tail, weights, hop) => {
|
|
397
|
+
const out = new Float32Array(hop);
|
|
398
|
+
const nextTail = new Float32Array(hop);
|
|
399
|
+
for (let index = 0;index < hop; index += 1) {
|
|
400
|
+
out[index] = (tail[index] ?? 0) + (src[off + index] ?? 0) * (weights[index] ?? 0);
|
|
401
|
+
nextTail[index] = (src[off + hop + index] ?? 0) * (weights[hop + index] ?? 0);
|
|
402
|
+
}
|
|
403
|
+
return { nextTail, out };
|
|
404
|
+
};
|
|
405
|
+
var createTimeStretcher = () => {
|
|
406
|
+
let sampleRate = 0;
|
|
407
|
+
let channelCount = 0;
|
|
408
|
+
let hop = 0;
|
|
409
|
+
let frameLen = 0;
|
|
410
|
+
let seek = 0;
|
|
411
|
+
let weights = new Float32Array(0);
|
|
412
|
+
let buffers = [];
|
|
413
|
+
let inputStart = 0;
|
|
414
|
+
let analysisPos = 0;
|
|
415
|
+
let olaTail = [];
|
|
416
|
+
let naturalRef = null;
|
|
417
|
+
const init = (rate, channels) => {
|
|
418
|
+
sampleRate = rate;
|
|
419
|
+
channelCount = channels;
|
|
420
|
+
hop = Math.max(1, Math.round(sampleRate * HOP_MS / MS_PER_SECOND));
|
|
421
|
+
frameLen = hop * 2;
|
|
422
|
+
seek = Math.max(1, Math.round(sampleRate * SEEK_MS / MS_PER_SECOND));
|
|
423
|
+
weights = makeHann(frameLen);
|
|
424
|
+
buffers = Array.from({ length: channels }, () => new Float32Array(0));
|
|
425
|
+
olaTail = Array.from({ length: channels }, () => new Float32Array(hop));
|
|
426
|
+
inputStart = 0;
|
|
427
|
+
analysisPos = seek;
|
|
428
|
+
naturalRef = null;
|
|
429
|
+
};
|
|
430
|
+
const reset = () => {
|
|
431
|
+
buffers = buffers.map(() => new Float32Array(0));
|
|
432
|
+
olaTail = olaTail.map(() => new Float32Array(hop));
|
|
433
|
+
inputStart = 0;
|
|
434
|
+
analysisPos = seek;
|
|
435
|
+
naturalRef = null;
|
|
436
|
+
};
|
|
437
|
+
const append = (input) => {
|
|
438
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
439
|
+
const incoming = input[channel] ?? input[0] ?? new Float32Array(0);
|
|
440
|
+
const existing = buffers[channel] ?? new Float32Array(0);
|
|
441
|
+
const merged = new Float32Array(existing.length + incoming.length);
|
|
442
|
+
merged.set(existing, 0);
|
|
443
|
+
merged.set(incoming, existing.length);
|
|
444
|
+
buffers[channel] = merged;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const inputEnd = () => inputStart + (buffers[0]?.length ?? 0);
|
|
448
|
+
const compact = () => {
|
|
449
|
+
const keepFrom = Math.max(inputStart, Math.floor(analysisPos) - seek - 1);
|
|
450
|
+
if (keepFrom <= inputStart)
|
|
451
|
+
return;
|
|
452
|
+
const drop = keepFrom - inputStart;
|
|
453
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
454
|
+
buffers[channel] = (buffers[channel] ?? new Float32Array(0)).slice(drop);
|
|
455
|
+
}
|
|
456
|
+
inputStart = keepFrom;
|
|
457
|
+
};
|
|
458
|
+
const bestOffset = (center) => {
|
|
459
|
+
if (!naturalRef)
|
|
460
|
+
return 0;
|
|
461
|
+
const [base] = buffers;
|
|
462
|
+
if (!base)
|
|
463
|
+
return 0;
|
|
464
|
+
let bestDelta = 0;
|
|
465
|
+
let bestScore = -Infinity;
|
|
466
|
+
for (let delta = -seek;delta <= seek; delta += 1) {
|
|
467
|
+
const score = correlationScore(base, center + delta - inputStart, naturalRef, frameLen);
|
|
468
|
+
if (score <= bestScore)
|
|
469
|
+
continue;
|
|
470
|
+
bestScore = score;
|
|
471
|
+
bestDelta = delta;
|
|
472
|
+
}
|
|
473
|
+
return bestDelta;
|
|
474
|
+
};
|
|
475
|
+
const process = (input, speed, rate) => {
|
|
476
|
+
const channels = Math.max(1, input.length);
|
|
477
|
+
if (sampleRate !== rate || channelCount !== channels)
|
|
478
|
+
init(rate, channels);
|
|
479
|
+
append(input);
|
|
480
|
+
const analysisHop = hop * speed;
|
|
481
|
+
const segments = Array.from({ length: channelCount }, () => []);
|
|
482
|
+
const emitGrain = (pos) => {
|
|
483
|
+
const off = pos - inputStart;
|
|
484
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
485
|
+
const src = buffers[channel];
|
|
486
|
+
const tail = olaTail[channel];
|
|
487
|
+
if (!src || !tail)
|
|
488
|
+
continue;
|
|
489
|
+
const grain = overlapAddGrain(src, off, tail, weights, hop);
|
|
490
|
+
olaTail[channel] = grain.nextTail;
|
|
491
|
+
segments[channel]?.push(grain.out);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
const captureRef = (pos) => {
|
|
495
|
+
const ref = new Float32Array(frameLen);
|
|
496
|
+
const refOff = pos + hop - inputStart;
|
|
497
|
+
const [base] = buffers;
|
|
498
|
+
if (base)
|
|
499
|
+
ref.set(base.subarray(refOff, refOff + frameLen));
|
|
500
|
+
naturalRef = ref;
|
|
501
|
+
};
|
|
502
|
+
const canEmit = () => Math.floor(analysisPos) - seek >= inputStart && Math.floor(analysisPos) + seek + frameLen + hop <= inputEnd();
|
|
503
|
+
while (canEmit()) {
|
|
504
|
+
const center = Math.round(analysisPos);
|
|
505
|
+
const pos = center + bestOffset(center);
|
|
506
|
+
emitGrain(pos);
|
|
507
|
+
captureRef(pos);
|
|
508
|
+
analysisPos += analysisHop;
|
|
509
|
+
}
|
|
510
|
+
compact();
|
|
511
|
+
return segments.map((channelSegments) => {
|
|
512
|
+
const total = channelSegments.reduce((sum, seg) => sum + seg.length, 0);
|
|
513
|
+
const merged = new Float32Array(total);
|
|
514
|
+
let offset = 0;
|
|
515
|
+
for (const seg of channelSegments) {
|
|
516
|
+
merged.set(seg, offset);
|
|
517
|
+
offset += seg.length;
|
|
518
|
+
}
|
|
519
|
+
return merged;
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
return { process, reset };
|
|
523
|
+
};
|
|
524
|
+
|
|
373
525
|
// src/client/audioPlayer.ts
|
|
374
526
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
375
527
|
var DEFAULT_VOLUME = 1;
|
|
376
528
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
377
529
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
378
530
|
var MAX_PLAYBACK_RATE = 2;
|
|
531
|
+
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
379
532
|
var createInitialState = () => ({
|
|
380
533
|
activeSourceCount: 0,
|
|
381
534
|
error: null,
|
|
@@ -438,6 +591,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
438
591
|
let outputNode = null;
|
|
439
592
|
let volume = clampVolume(options.volume);
|
|
440
593
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
594
|
+
let stretcher = null;
|
|
441
595
|
let queueEndTime = 0;
|
|
442
596
|
let syncPromise = Promise.resolve();
|
|
443
597
|
let interruptStartedAt = null;
|
|
@@ -470,6 +624,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
470
624
|
const resolveInterrupt = (latencyMs) => {
|
|
471
625
|
clearInterruptTimer();
|
|
472
626
|
interruptStartedAt = null;
|
|
627
|
+
stretcher?.reset();
|
|
473
628
|
setState({
|
|
474
629
|
activeSourceCount: sourceNodes.size,
|
|
475
630
|
isPlaying: false,
|
|
@@ -534,13 +689,11 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
534
689
|
queueEndTime = audioContext.currentTime;
|
|
535
690
|
return audioContext;
|
|
536
691
|
};
|
|
537
|
-
const
|
|
538
|
-
const context = await ensureAudioContext();
|
|
539
|
-
const buffer = decodePCM16LEChunk(context, chunk);
|
|
692
|
+
const scheduleBuffer = (context, buffer, rate) => {
|
|
540
693
|
const node = context.createBufferSource();
|
|
541
694
|
node.buffer = buffer;
|
|
542
695
|
if (node.playbackRate) {
|
|
543
|
-
node.playbackRate.value =
|
|
696
|
+
node.playbackRate.value = rate;
|
|
544
697
|
}
|
|
545
698
|
node.connect(outputNode ?? context.destination);
|
|
546
699
|
node.onended = () => {
|
|
@@ -553,7 +706,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
553
706
|
maybeResolveInterrupt();
|
|
554
707
|
};
|
|
555
708
|
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
556
|
-
queueEndTime = startAt + buffer.duration /
|
|
709
|
+
queueEndTime = startAt + buffer.duration / rate;
|
|
557
710
|
sourceNodes.add(node);
|
|
558
711
|
setState({
|
|
559
712
|
activeSourceCount: sourceNodes.size,
|
|
@@ -561,6 +714,34 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
561
714
|
});
|
|
562
715
|
node.start(startAt);
|
|
563
716
|
};
|
|
717
|
+
const scheduleChunk = async (chunk) => {
|
|
718
|
+
const context = await ensureAudioContext();
|
|
719
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
720
|
+
if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
|
|
721
|
+
stretcher?.reset();
|
|
722
|
+
scheduleBuffer(context, buffer, playbackRate);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const channels = Math.max(1, chunk.format.channels);
|
|
726
|
+
const input = [];
|
|
727
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
728
|
+
input.push(buffer.getChannelData(channelIndex));
|
|
729
|
+
}
|
|
730
|
+
stretcher ??= createTimeStretcher();
|
|
731
|
+
const stretched = stretcher.process(input, playbackRate, chunk.format.sampleRateHz);
|
|
732
|
+
const outLength = stretched[0]?.length ?? 0;
|
|
733
|
+
if (outLength === 0) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
const outBuffer = context.createBuffer(channels, outLength, chunk.format.sampleRateHz);
|
|
737
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
738
|
+
const channelOut = stretched[channelIndex];
|
|
739
|
+
if (!channelOut)
|
|
740
|
+
continue;
|
|
741
|
+
outBuffer.getChannelData(channelIndex).set(channelOut);
|
|
742
|
+
}
|
|
743
|
+
scheduleBuffer(context, outBuffer, 1);
|
|
744
|
+
};
|
|
564
745
|
const stopQueuedPlayback = (options2) => {
|
|
565
746
|
for (const node of [...sourceNodes]) {
|
|
566
747
|
node.stop?.();
|
|
@@ -12303,6 +12484,7 @@ export {
|
|
|
12303
12484
|
createVoiceAudioPlayer,
|
|
12304
12485
|
createVoiceAgentSquadStatusViewModel,
|
|
12305
12486
|
createVoiceAgentSquadStatusStore,
|
|
12487
|
+
createTimeStretcher,
|
|
12306
12488
|
createMicrophoneCapture,
|
|
12307
12489
|
buildVoiceAgentSquadStatusReport,
|
|
12308
12490
|
bindVoiceReactiveSource,
|
package/dist/index.js
CHANGED
|
@@ -45633,15 +45633,18 @@ var createCachedTTS = (inner, options) => {
|
|
|
45633
45633
|
}
|
|
45634
45634
|
};
|
|
45635
45635
|
const cached = cache.get(key) ?? await loadFromStore(key);
|
|
45636
|
-
if (cached) {
|
|
45636
|
+
if (cached && cached.length > 0) {
|
|
45637
45637
|
await replayEvents(cached);
|
|
45638
45638
|
return;
|
|
45639
45639
|
}
|
|
45640
45640
|
capture = [];
|
|
45641
45641
|
await session.send(text);
|
|
45642
45642
|
const rendered = capture;
|
|
45643
|
-
remember(key, rendered);
|
|
45644
45643
|
capture = null;
|
|
45644
|
+
if (rendered.length === 0) {
|
|
45645
|
+
return;
|
|
45646
|
+
}
|
|
45647
|
+
remember(key, rendered);
|
|
45645
45648
|
if (store) {
|
|
45646
45649
|
try {
|
|
45647
45650
|
await store.set(key, rendered);
|
package/dist/testing/index.js
CHANGED
|
@@ -1577,12 +1577,165 @@ var buildSessionCorrectionAudit = (raw, generic, experimental, benchmarkSeeded,
|
|
|
1577
1577
|
}
|
|
1578
1578
|
};
|
|
1579
1579
|
};
|
|
1580
|
+
// src/client/timeStretch.ts
|
|
1581
|
+
var HOP_MS = 10;
|
|
1582
|
+
var SEEK_MS = 5;
|
|
1583
|
+
var ENERGY_EPSILON = 0.000001;
|
|
1584
|
+
var HALF = 0.5;
|
|
1585
|
+
var MS_PER_SECOND = 1000;
|
|
1586
|
+
var makeHann = (length) => {
|
|
1587
|
+
const weights = new Float32Array(length);
|
|
1588
|
+
for (let index = 0;index < length; index += 1) {
|
|
1589
|
+
weights[index] = HALF - HALF * Math.cos(2 * Math.PI * index / length);
|
|
1590
|
+
}
|
|
1591
|
+
return weights;
|
|
1592
|
+
};
|
|
1593
|
+
var correlationScore = (base, start, ref, length) => {
|
|
1594
|
+
let dot = 0;
|
|
1595
|
+
let energy = 0;
|
|
1596
|
+
for (let index = 0;index < length; index += 1) {
|
|
1597
|
+
const sample = base[start + index] ?? 0;
|
|
1598
|
+
dot += sample * (ref[index] ?? 0);
|
|
1599
|
+
energy += sample * sample;
|
|
1600
|
+
}
|
|
1601
|
+
return dot / Math.sqrt(energy + ENERGY_EPSILON);
|
|
1602
|
+
};
|
|
1603
|
+
var overlapAddGrain = (src, off, tail, weights, hop) => {
|
|
1604
|
+
const out = new Float32Array(hop);
|
|
1605
|
+
const nextTail = new Float32Array(hop);
|
|
1606
|
+
for (let index = 0;index < hop; index += 1) {
|
|
1607
|
+
out[index] = (tail[index] ?? 0) + (src[off + index] ?? 0) * (weights[index] ?? 0);
|
|
1608
|
+
nextTail[index] = (src[off + hop + index] ?? 0) * (weights[hop + index] ?? 0);
|
|
1609
|
+
}
|
|
1610
|
+
return { nextTail, out };
|
|
1611
|
+
};
|
|
1612
|
+
var createTimeStretcher = () => {
|
|
1613
|
+
let sampleRate = 0;
|
|
1614
|
+
let channelCount = 0;
|
|
1615
|
+
let hop = 0;
|
|
1616
|
+
let frameLen = 0;
|
|
1617
|
+
let seek = 0;
|
|
1618
|
+
let weights = new Float32Array(0);
|
|
1619
|
+
let buffers = [];
|
|
1620
|
+
let inputStart = 0;
|
|
1621
|
+
let analysisPos = 0;
|
|
1622
|
+
let olaTail = [];
|
|
1623
|
+
let naturalRef = null;
|
|
1624
|
+
const init = (rate, channels) => {
|
|
1625
|
+
sampleRate = rate;
|
|
1626
|
+
channelCount = channels;
|
|
1627
|
+
hop = Math.max(1, Math.round(sampleRate * HOP_MS / MS_PER_SECOND));
|
|
1628
|
+
frameLen = hop * 2;
|
|
1629
|
+
seek = Math.max(1, Math.round(sampleRate * SEEK_MS / MS_PER_SECOND));
|
|
1630
|
+
weights = makeHann(frameLen);
|
|
1631
|
+
buffers = Array.from({ length: channels }, () => new Float32Array(0));
|
|
1632
|
+
olaTail = Array.from({ length: channels }, () => new Float32Array(hop));
|
|
1633
|
+
inputStart = 0;
|
|
1634
|
+
analysisPos = seek;
|
|
1635
|
+
naturalRef = null;
|
|
1636
|
+
};
|
|
1637
|
+
const reset = () => {
|
|
1638
|
+
buffers = buffers.map(() => new Float32Array(0));
|
|
1639
|
+
olaTail = olaTail.map(() => new Float32Array(hop));
|
|
1640
|
+
inputStart = 0;
|
|
1641
|
+
analysisPos = seek;
|
|
1642
|
+
naturalRef = null;
|
|
1643
|
+
};
|
|
1644
|
+
const append = (input) => {
|
|
1645
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1646
|
+
const incoming = input[channel] ?? input[0] ?? new Float32Array(0);
|
|
1647
|
+
const existing = buffers[channel] ?? new Float32Array(0);
|
|
1648
|
+
const merged = new Float32Array(existing.length + incoming.length);
|
|
1649
|
+
merged.set(existing, 0);
|
|
1650
|
+
merged.set(incoming, existing.length);
|
|
1651
|
+
buffers[channel] = merged;
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
const inputEnd = () => inputStart + (buffers[0]?.length ?? 0);
|
|
1655
|
+
const compact = () => {
|
|
1656
|
+
const keepFrom = Math.max(inputStart, Math.floor(analysisPos) - seek - 1);
|
|
1657
|
+
if (keepFrom <= inputStart)
|
|
1658
|
+
return;
|
|
1659
|
+
const drop = keepFrom - inputStart;
|
|
1660
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1661
|
+
buffers[channel] = (buffers[channel] ?? new Float32Array(0)).slice(drop);
|
|
1662
|
+
}
|
|
1663
|
+
inputStart = keepFrom;
|
|
1664
|
+
};
|
|
1665
|
+
const bestOffset = (center) => {
|
|
1666
|
+
if (!naturalRef)
|
|
1667
|
+
return 0;
|
|
1668
|
+
const [base] = buffers;
|
|
1669
|
+
if (!base)
|
|
1670
|
+
return 0;
|
|
1671
|
+
let bestDelta = 0;
|
|
1672
|
+
let bestScore = -Infinity;
|
|
1673
|
+
for (let delta = -seek;delta <= seek; delta += 1) {
|
|
1674
|
+
const score = correlationScore(base, center + delta - inputStart, naturalRef, frameLen);
|
|
1675
|
+
if (score <= bestScore)
|
|
1676
|
+
continue;
|
|
1677
|
+
bestScore = score;
|
|
1678
|
+
bestDelta = delta;
|
|
1679
|
+
}
|
|
1680
|
+
return bestDelta;
|
|
1681
|
+
};
|
|
1682
|
+
const process2 = (input, speed, rate) => {
|
|
1683
|
+
const channels = Math.max(1, input.length);
|
|
1684
|
+
if (sampleRate !== rate || channelCount !== channels)
|
|
1685
|
+
init(rate, channels);
|
|
1686
|
+
append(input);
|
|
1687
|
+
const analysisHop = hop * speed;
|
|
1688
|
+
const segments = Array.from({ length: channelCount }, () => []);
|
|
1689
|
+
const emitGrain = (pos) => {
|
|
1690
|
+
const off = pos - inputStart;
|
|
1691
|
+
for (let channel = 0;channel < channelCount; channel += 1) {
|
|
1692
|
+
const src = buffers[channel];
|
|
1693
|
+
const tail = olaTail[channel];
|
|
1694
|
+
if (!src || !tail)
|
|
1695
|
+
continue;
|
|
1696
|
+
const grain = overlapAddGrain(src, off, tail, weights, hop);
|
|
1697
|
+
olaTail[channel] = grain.nextTail;
|
|
1698
|
+
segments[channel]?.push(grain.out);
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
const captureRef = (pos) => {
|
|
1702
|
+
const ref = new Float32Array(frameLen);
|
|
1703
|
+
const refOff = pos + hop - inputStart;
|
|
1704
|
+
const [base] = buffers;
|
|
1705
|
+
if (base)
|
|
1706
|
+
ref.set(base.subarray(refOff, refOff + frameLen));
|
|
1707
|
+
naturalRef = ref;
|
|
1708
|
+
};
|
|
1709
|
+
const canEmit = () => Math.floor(analysisPos) - seek >= inputStart && Math.floor(analysisPos) + seek + frameLen + hop <= inputEnd();
|
|
1710
|
+
while (canEmit()) {
|
|
1711
|
+
const center = Math.round(analysisPos);
|
|
1712
|
+
const pos = center + bestOffset(center);
|
|
1713
|
+
emitGrain(pos);
|
|
1714
|
+
captureRef(pos);
|
|
1715
|
+
analysisPos += analysisHop;
|
|
1716
|
+
}
|
|
1717
|
+
compact();
|
|
1718
|
+
return segments.map((channelSegments) => {
|
|
1719
|
+
const total = channelSegments.reduce((sum, seg) => sum + seg.length, 0);
|
|
1720
|
+
const merged = new Float32Array(total);
|
|
1721
|
+
let offset = 0;
|
|
1722
|
+
for (const seg of channelSegments) {
|
|
1723
|
+
merged.set(seg, offset);
|
|
1724
|
+
offset += seg.length;
|
|
1725
|
+
}
|
|
1726
|
+
return merged;
|
|
1727
|
+
});
|
|
1728
|
+
};
|
|
1729
|
+
return { process: process2, reset };
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1580
1732
|
// src/client/audioPlayer.ts
|
|
1581
1733
|
var DEFAULT_LOOKAHEAD_MS = 15;
|
|
1582
1734
|
var DEFAULT_VOLUME = 1;
|
|
1583
1735
|
var DEFAULT_PLAYBACK_RATE = 1;
|
|
1584
1736
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
1585
1737
|
var MAX_PLAYBACK_RATE = 2;
|
|
1738
|
+
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1586
1739
|
var createInitialState = () => ({
|
|
1587
1740
|
activeSourceCount: 0,
|
|
1588
1741
|
error: null,
|
|
@@ -1645,6 +1798,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1645
1798
|
let outputNode = null;
|
|
1646
1799
|
let volume = clampVolume(options.volume);
|
|
1647
1800
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
1801
|
+
let stretcher = null;
|
|
1648
1802
|
let queueEndTime = 0;
|
|
1649
1803
|
let syncPromise = Promise.resolve();
|
|
1650
1804
|
let interruptStartedAt = null;
|
|
@@ -1677,6 +1831,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1677
1831
|
const resolveInterrupt = (latencyMs) => {
|
|
1678
1832
|
clearInterruptTimer();
|
|
1679
1833
|
interruptStartedAt = null;
|
|
1834
|
+
stretcher?.reset();
|
|
1680
1835
|
setState({
|
|
1681
1836
|
activeSourceCount: sourceNodes.size,
|
|
1682
1837
|
isPlaying: false,
|
|
@@ -1741,13 +1896,11 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1741
1896
|
queueEndTime = audioContext.currentTime;
|
|
1742
1897
|
return audioContext;
|
|
1743
1898
|
};
|
|
1744
|
-
const
|
|
1745
|
-
const context = await ensureAudioContext();
|
|
1746
|
-
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1899
|
+
const scheduleBuffer = (context, buffer, rate) => {
|
|
1747
1900
|
const node = context.createBufferSource();
|
|
1748
1901
|
node.buffer = buffer;
|
|
1749
1902
|
if (node.playbackRate) {
|
|
1750
|
-
node.playbackRate.value =
|
|
1903
|
+
node.playbackRate.value = rate;
|
|
1751
1904
|
}
|
|
1752
1905
|
node.connect(outputNode ?? context.destination);
|
|
1753
1906
|
node.onended = () => {
|
|
@@ -1760,7 +1913,7 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1760
1913
|
maybeResolveInterrupt();
|
|
1761
1914
|
};
|
|
1762
1915
|
const startAt = Math.max(context.currentTime + lookaheadSeconds, queueEndTime);
|
|
1763
|
-
queueEndTime = startAt + buffer.duration /
|
|
1916
|
+
queueEndTime = startAt + buffer.duration / rate;
|
|
1764
1917
|
sourceNodes.add(node);
|
|
1765
1918
|
setState({
|
|
1766
1919
|
activeSourceCount: sourceNodes.size,
|
|
@@ -1768,6 +1921,34 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1768
1921
|
});
|
|
1769
1922
|
node.start(startAt);
|
|
1770
1923
|
};
|
|
1924
|
+
const scheduleChunk = async (chunk) => {
|
|
1925
|
+
const context = await ensureAudioContext();
|
|
1926
|
+
const buffer = decodePCM16LEChunk(context, chunk);
|
|
1927
|
+
if (Math.abs(playbackRate - 1) <= STRETCH_BYPASS_EPSILON) {
|
|
1928
|
+
stretcher?.reset();
|
|
1929
|
+
scheduleBuffer(context, buffer, playbackRate);
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
const channels = Math.max(1, chunk.format.channels);
|
|
1933
|
+
const input = [];
|
|
1934
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1935
|
+
input.push(buffer.getChannelData(channelIndex));
|
|
1936
|
+
}
|
|
1937
|
+
stretcher ??= createTimeStretcher();
|
|
1938
|
+
const stretched = stretcher.process(input, playbackRate, chunk.format.sampleRateHz);
|
|
1939
|
+
const outLength = stretched[0]?.length ?? 0;
|
|
1940
|
+
if (outLength === 0) {
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const outBuffer = context.createBuffer(channels, outLength, chunk.format.sampleRateHz);
|
|
1944
|
+
for (let channelIndex = 0;channelIndex < channels; channelIndex += 1) {
|
|
1945
|
+
const channelOut = stretched[channelIndex];
|
|
1946
|
+
if (!channelOut)
|
|
1947
|
+
continue;
|
|
1948
|
+
outBuffer.getChannelData(channelIndex).set(channelOut);
|
|
1949
|
+
}
|
|
1950
|
+
scheduleBuffer(context, outBuffer, 1);
|
|
1951
|
+
};
|
|
1771
1952
|
const stopQueuedPlayback = (options2) => {
|
|
1772
1953
|
for (const node of [...sourceNodes]) {
|
|
1773
1954
|
node.stop?.();
|
package/package.json
CHANGED
|
@@ -1,157 +1,157 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
2
|
+
"name": "@absolutejs/voice",
|
|
3
|
+
"version": "0.0.22-beta.578",
|
|
4
|
+
"description": "Voice primitives and Elysia plugin for AbsoluteJS",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/absolutejs/voice.git"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"license": "BSL-1.1",
|
|
16
|
+
"author": "Alex Kahn",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts ./src/drizzle/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media --external drizzle-orm && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && bun build ./src/embed/index.ts --outfile dist/embed/voice-widget.js --target browser --format iife --minify && bun build ./src/embed/index.ts --outdir dist/embed --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
19
|
+
"config": "absolute config",
|
|
20
|
+
"format": "absolute prettier --write",
|
|
21
|
+
"knip": "knip",
|
|
22
|
+
"lint": "absolute eslint",
|
|
23
|
+
"release": "bun run format && bun run build && bun publish",
|
|
24
|
+
"test": "bun test ./test/*.test.ts",
|
|
25
|
+
"test:adapters": "bun test ./test/live/*.test.ts",
|
|
26
|
+
"test:assemblyai": "bun test ./test/live/assemblyai.live.test.ts",
|
|
27
|
+
"test:deepgram": "bun test ./test/live/deepgram.live.test.ts",
|
|
28
|
+
"test:elevenlabs": "bun test ./test/live/elevenlabs.live.test.ts",
|
|
29
|
+
"test:openai": "bun test ./test/live/openai.live.test.ts",
|
|
30
|
+
"typecheck": "absolute typecheck"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"browser": "./dist/client/index.js",
|
|
39
|
+
"import": "./dist/client/index.js",
|
|
40
|
+
"types": "./dist/client/index.d.ts"
|
|
41
|
+
},
|
|
42
|
+
"./react": {
|
|
43
|
+
"browser": "./dist/react/index.js",
|
|
44
|
+
"import": "./dist/react/index.js",
|
|
45
|
+
"types": "./dist/react/index.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./vue": {
|
|
48
|
+
"browser": "./dist/vue/index.js",
|
|
49
|
+
"import": "./dist/vue/index.js",
|
|
50
|
+
"types": "./dist/vue/index.d.ts"
|
|
51
|
+
},
|
|
52
|
+
"./svelte": {
|
|
53
|
+
"import": "./dist/svelte/index.js",
|
|
54
|
+
"types": "./dist/svelte/index.d.ts"
|
|
55
|
+
},
|
|
56
|
+
"./angular": {
|
|
57
|
+
"import": "./dist/angular/index.js",
|
|
58
|
+
"types": "./dist/angular/index.d.ts"
|
|
59
|
+
},
|
|
60
|
+
"./drizzle": {
|
|
61
|
+
"import": "./dist/drizzle/index.js",
|
|
62
|
+
"types": "./dist/drizzle/index.d.ts",
|
|
63
|
+
"default": "./dist/drizzle/index.js"
|
|
64
|
+
},
|
|
65
|
+
"./testing": {
|
|
66
|
+
"import": "./dist/testing/index.js",
|
|
67
|
+
"types": "./dist/testing/index.d.ts"
|
|
68
|
+
},
|
|
69
|
+
"./embed": {
|
|
70
|
+
"browser": "./dist/embed/index.js",
|
|
71
|
+
"import": "./dist/embed/index.js",
|
|
72
|
+
"types": "./dist/embed/index.d.ts"
|
|
73
|
+
},
|
|
74
|
+
"./embed/voice-widget.js": "./dist/embed/voice-widget.js"
|
|
75
|
+
},
|
|
76
|
+
"typesVersions": {
|
|
77
|
+
"*": {
|
|
78
|
+
"testing": [
|
|
79
|
+
"dist/testing/index.d.ts"
|
|
80
|
+
],
|
|
81
|
+
"client": [
|
|
82
|
+
"dist/client/index.d.ts"
|
|
83
|
+
],
|
|
84
|
+
"react": [
|
|
85
|
+
"dist/react/index.d.ts"
|
|
86
|
+
],
|
|
87
|
+
"svelte": [
|
|
88
|
+
"dist/svelte/index.d.ts"
|
|
89
|
+
],
|
|
90
|
+
"vue": [
|
|
91
|
+
"dist/vue/index.d.ts"
|
|
92
|
+
],
|
|
93
|
+
"angular": [
|
|
94
|
+
"dist/angular/index.d.ts"
|
|
95
|
+
],
|
|
96
|
+
"drizzle": [
|
|
97
|
+
"dist/drizzle/index.d.ts"
|
|
98
|
+
],
|
|
99
|
+
"embed": [
|
|
100
|
+
"dist/embed/index.d.ts"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"peerDependencies": {
|
|
105
|
+
"@absolutejs/absolute": ">=0.19.0-beta.646",
|
|
106
|
+
"@absolutejs/ai": ">=0.0.5",
|
|
107
|
+
"@angular/core": ">=21.0.0",
|
|
108
|
+
"drizzle-orm": ">=1.0.0-rc.1",
|
|
109
|
+
"elysia": ">=1.4.18",
|
|
110
|
+
"react": ">=19.0.0",
|
|
111
|
+
"vue": ">=3.5.0"
|
|
112
|
+
},
|
|
113
|
+
"peerDependenciesMeta": {
|
|
114
|
+
"@absolutejs/ai": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"drizzle-orm": {
|
|
118
|
+
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"@angular/core": {
|
|
121
|
+
"optional": true
|
|
122
|
+
},
|
|
123
|
+
"react": {
|
|
124
|
+
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"vue": {
|
|
127
|
+
"optional": true
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"dependencies": {
|
|
131
|
+
"@absolutejs/media": "0.0.1-beta.19"
|
|
132
|
+
},
|
|
133
|
+
"devDependencies": {
|
|
134
|
+
"@absolutejs/absolute": "0.19.0-beta.1009",
|
|
135
|
+
"@absolutejs/ai": "0.0.5",
|
|
136
|
+
"@angular/core": "^21.0.0",
|
|
137
|
+
"@electric-sql/pglite": "^0.4.5",
|
|
138
|
+
"@eslint/js": "^10.0.1",
|
|
139
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
140
|
+
"@types/bun": "1.3.9",
|
|
141
|
+
"@types/react": "19.2.0",
|
|
142
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
143
|
+
"drizzle-orm": "1.0.0-rc.3",
|
|
144
|
+
"elysia": "1.4.18",
|
|
145
|
+
"eslint": "^10.1.0",
|
|
146
|
+
"eslint-plugin-absolute": "0.11.0-beta.3",
|
|
147
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
148
|
+
"eslint-plugin-security": "^4.0.0",
|
|
149
|
+
"globals": "^17.4.0",
|
|
150
|
+
"knip": "^6.14.1",
|
|
151
|
+
"prettier": "^3.4.0",
|
|
152
|
+
"react": "19.2.1",
|
|
153
|
+
"typescript": "^5.9.3",
|
|
154
|
+
"typescript-eslint": "^8.57.2",
|
|
155
|
+
"vue": "3.5.27"
|
|
156
|
+
}
|
|
157
157
|
}
|