@framers/agentos-ext-diarization 0.2.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/LICENSE +23 -0
- package/SKILL.md +71 -0
- package/dist/ClusteringStrategy.d.ts +56 -0
- package/dist/ClusteringStrategy.d.ts.map +1 -0
- package/dist/ClusteringStrategy.js +123 -0
- package/dist/ClusteringStrategy.js.map +1 -0
- package/dist/DiarizationEngine.d.ts +45 -0
- package/dist/DiarizationEngine.d.ts.map +1 -0
- package/dist/DiarizationEngine.js +84 -0
- package/dist/DiarizationEngine.js.map +1 -0
- package/dist/DiarizationSession.d.ts +90 -0
- package/dist/DiarizationSession.d.ts.map +1 -0
- package/dist/DiarizationSession.js +139 -0
- package/dist/DiarizationSession.js.map +1 -0
- package/dist/LocalDiarizationBackend.d.ts +90 -0
- package/dist/LocalDiarizationBackend.d.ts.map +1 -0
- package/dist/LocalDiarizationBackend.js +177 -0
- package/dist/LocalDiarizationBackend.js.map +1 -0
- package/dist/ProviderDiarizationBackend.d.ts +54 -0
- package/dist/ProviderDiarizationBackend.d.ts.map +1 -0
- package/dist/ProviderDiarizationBackend.js +101 -0
- package/dist/ProviderDiarizationBackend.js.map +1 -0
- package/dist/SlidingWindowExtractor.d.ts +85 -0
- package/dist/SlidingWindowExtractor.d.ts.map +1 -0
- package/dist/SlidingWindowExtractor.js +140 -0
- package/dist/SlidingWindowExtractor.js.map +1 -0
- package/dist/SpeakerEmbeddingCache.d.ts +117 -0
- package/dist/SpeakerEmbeddingCache.d.ts.map +1 -0
- package/dist/SpeakerEmbeddingCache.js +193 -0
- package/dist/SpeakerEmbeddingCache.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/manifest.json +8 -0
- package/package.json +48 -0
- package/src/ClusteringStrategy.ts +139 -0
- package/src/DiarizationEngine.ts +94 -0
- package/src/DiarizationSession.ts +178 -0
- package/src/LocalDiarizationBackend.ts +201 -0
- package/src/ProviderDiarizationBackend.ts +119 -0
- package/src/SlidingWindowExtractor.ts +174 -0
- package/src/SpeakerEmbeddingCache.ts +228 -0
- package/src/index.ts +105 -0
- package/src/types.ts +221 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Framers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: diarization
|
|
3
|
+
description: Speaker diarization — identifies and tracks who is speaking at each moment in an audio stream
|
|
4
|
+
category: voice
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Diarization Extension Pack
|
|
8
|
+
|
|
9
|
+
Speaker diarization for the AgentOS voice pipeline. Supports two modes:
|
|
10
|
+
|
|
11
|
+
1. **Provider-delegated** — extracts speaker labels from STT provider word-level results (e.g. Deepgram with `diarize: true`). Zero additional compute, no voiceprint model needed.
|
|
12
|
+
2. **Local clustering** — uses a sliding-window spectral-centroid voiceprint with agglomerative clustering, fully offline. An ONNX x-vector model can be plugged in later without API changes.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
No API key required for local mode. For provider mode, enable diarization on your STT provider (e.g. `providerOptions.diarize: true` on the Deepgram STT pack).
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"voice": {
|
|
23
|
+
"diarization": "local"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
To use provider-delegated diarization:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"voice": {
|
|
33
|
+
"diarization": "provider",
|
|
34
|
+
"stt": "deepgram",
|
|
35
|
+
"providerOptions": { "diarize": true }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Speaker enrollment (optional)
|
|
41
|
+
|
|
42
|
+
Pre-register known speakers so the engine can label them by name instead of `Speaker_N`:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
await session.enrollSpeaker('Alice', aliceVoiceprintFloat32Array);
|
|
46
|
+
await session.enrollSpeaker('Bob', bobVoiceprintFloat32Array);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Events
|
|
50
|
+
|
|
51
|
+
| Event | Payload | Description |
|
|
52
|
+
|-----------------------|----------------------|----------------------------------------------------------|
|
|
53
|
+
| `speaker_identified` | `SpeakerIdentified` | Emitted whenever the active speaker label changes |
|
|
54
|
+
| `segment_ready` | `DiarizedSegment` | A labelled audio or transcript segment is ready |
|
|
55
|
+
| `error` | `Error` | Unrecoverable diarization error |
|
|
56
|
+
| `close` | — | Session fully terminated |
|
|
57
|
+
|
|
58
|
+
## Local backend feature extraction
|
|
59
|
+
|
|
60
|
+
The built-in feature extractor computes a 16-dimensional vector per 1.5 s window (0.5 s overlap):
|
|
61
|
+
|
|
62
|
+
- Dimensions 0–3: octave-band RMS energy (sub-bass, bass, mid, high)
|
|
63
|
+
- Dimensions 4–7: spectral centroid per octave band
|
|
64
|
+
- Dimensions 8–11: zero-crossing rate per octave band
|
|
65
|
+
- Dimensions 12–15: delta energy (frame-to-frame change) per octave band
|
|
66
|
+
|
|
67
|
+
This is intentionally lightweight. Replace `LocalDiarizationBackend.extractSimpleEmbedding()` with an ONNX x-vector model for production-quality voiceprints.
|
|
68
|
+
|
|
69
|
+
## Clustering
|
|
70
|
+
|
|
71
|
+
`ClusteringStrategy` runs agglomerative merging whenever the centroid count exceeds `expectedSpeakers`. Centroids with cosine similarity above `mergeThreshold` (default 0.85) are collapsed into a single speaker identity.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ClusteringStrategy.ts
|
|
3
|
+
* @description Agglomerative clustering to merge drifted speaker centroids.
|
|
4
|
+
*
|
|
5
|
+
* Over the course of a long session, a single speaker's vocal characteristics
|
|
6
|
+
* may drift enough that two separate centroids are created for them.
|
|
7
|
+
* {@link ClusteringStrategy.mergeClusters} detects this by computing pairwise
|
|
8
|
+
* cosine similarity between all centroids and iteratively merging the closest
|
|
9
|
+
* pair until either no pair exceeds the merge threshold or the centroid count
|
|
10
|
+
* equals `expectedSpeakers`.
|
|
11
|
+
*
|
|
12
|
+
* @module diarization/ClusteringStrategy
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Agglomerative speaker-centroid merging strategy.
|
|
16
|
+
*
|
|
17
|
+
* This is an optional post-processing step applied by {@link DiarizationSession}
|
|
18
|
+
* when the number of tracked centroids exceeds the expected speaker count.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const strategy = new ClusteringStrategy(0.85);
|
|
23
|
+
* const mapping = strategy.mergeClusters(cache.centroids, 2);
|
|
24
|
+
* // mapping: Map<oldId, canonicalId>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class ClusteringStrategy {
|
|
28
|
+
private readonly mergeThreshold;
|
|
29
|
+
/**
|
|
30
|
+
* @param mergeThreshold - Minimum cosine similarity between two centroids
|
|
31
|
+
* for them to be considered the same speaker and merged.
|
|
32
|
+
* @defaultValue 0.85
|
|
33
|
+
*/
|
|
34
|
+
constructor(mergeThreshold?: number);
|
|
35
|
+
/**
|
|
36
|
+
* Identify centroid pairs that should be merged and return a renaming map.
|
|
37
|
+
*
|
|
38
|
+
* The algorithm:
|
|
39
|
+
* 1. Compute all pairwise cosine similarities.
|
|
40
|
+
* 2. If `expectedSpeakers` is set and the current count exceeds it, merge
|
|
41
|
+
* the closest pair regardless of the threshold.
|
|
42
|
+
* 3. Otherwise merge pairs that exceed `mergeThreshold`.
|
|
43
|
+
* 4. Repeat until no further merges are possible or the count matches
|
|
44
|
+
* `expectedSpeakers`.
|
|
45
|
+
*
|
|
46
|
+
* The returned `Map<string, string>` maps every old centroid ID that was
|
|
47
|
+
* subsumed into a canonical ID. IDs that were not merged are not present in
|
|
48
|
+
* the map. Callers should rename all occurrences of a key to its value.
|
|
49
|
+
*
|
|
50
|
+
* @param centroids - Current centroid snapshot (id → embedding).
|
|
51
|
+
* @param expectedSpeakers - Optional upper bound on speaker count.
|
|
52
|
+
* @returns Rename map: `oldId → canonicalId`.
|
|
53
|
+
*/
|
|
54
|
+
mergeClusters(centroids: Map<string, Float32Array>, expectedSpeakers?: number): Map<string, string>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=ClusteringStrategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClusteringStrategy.d.ts","sourceRoot":"","sources":["../src/ClusteringStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAkB;IAUjB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAL3C;;;;OAIG;gBAC0B,cAAc,GAAE,MAAa;IAM1D;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CACX,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EACpC,gBAAgB,CAAC,EAAE,MAAM,GACxB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAmEvB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ClusteringStrategy.ts
|
|
3
|
+
* @description Agglomerative clustering to merge drifted speaker centroids.
|
|
4
|
+
*
|
|
5
|
+
* Over the course of a long session, a single speaker's vocal characteristics
|
|
6
|
+
* may drift enough that two separate centroids are created for them.
|
|
7
|
+
* {@link ClusteringStrategy.mergeClusters} detects this by computing pairwise
|
|
8
|
+
* cosine similarity between all centroids and iteratively merging the closest
|
|
9
|
+
* pair until either no pair exceeds the merge threshold or the centroid count
|
|
10
|
+
* equals `expectedSpeakers`.
|
|
11
|
+
*
|
|
12
|
+
* @module diarization/ClusteringStrategy
|
|
13
|
+
*/
|
|
14
|
+
import { cosineSimilarity } from './SpeakerEmbeddingCache.js';
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Main class
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Agglomerative speaker-centroid merging strategy.
|
|
20
|
+
*
|
|
21
|
+
* This is an optional post-processing step applied by {@link DiarizationSession}
|
|
22
|
+
* when the number of tracked centroids exceeds the expected speaker count.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const strategy = new ClusteringStrategy(0.85);
|
|
27
|
+
* const mapping = strategy.mergeClusters(cache.centroids, 2);
|
|
28
|
+
* // mapping: Map<oldId, canonicalId>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class ClusteringStrategy {
|
|
32
|
+
mergeThreshold;
|
|
33
|
+
// -------------------------------------------------------------------------
|
|
34
|
+
// Constructor
|
|
35
|
+
// -------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* @param mergeThreshold - Minimum cosine similarity between two centroids
|
|
38
|
+
* for them to be considered the same speaker and merged.
|
|
39
|
+
* @defaultValue 0.85
|
|
40
|
+
*/
|
|
41
|
+
constructor(mergeThreshold = 0.85) {
|
|
42
|
+
this.mergeThreshold = mergeThreshold;
|
|
43
|
+
}
|
|
44
|
+
// -------------------------------------------------------------------------
|
|
45
|
+
// Public API
|
|
46
|
+
// -------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Identify centroid pairs that should be merged and return a renaming map.
|
|
49
|
+
*
|
|
50
|
+
* The algorithm:
|
|
51
|
+
* 1. Compute all pairwise cosine similarities.
|
|
52
|
+
* 2. If `expectedSpeakers` is set and the current count exceeds it, merge
|
|
53
|
+
* the closest pair regardless of the threshold.
|
|
54
|
+
* 3. Otherwise merge pairs that exceed `mergeThreshold`.
|
|
55
|
+
* 4. Repeat until no further merges are possible or the count matches
|
|
56
|
+
* `expectedSpeakers`.
|
|
57
|
+
*
|
|
58
|
+
* The returned `Map<string, string>` maps every old centroid ID that was
|
|
59
|
+
* subsumed into a canonical ID. IDs that were not merged are not present in
|
|
60
|
+
* the map. Callers should rename all occurrences of a key to its value.
|
|
61
|
+
*
|
|
62
|
+
* @param centroids - Current centroid snapshot (id → embedding).
|
|
63
|
+
* @param expectedSpeakers - Optional upper bound on speaker count.
|
|
64
|
+
* @returns Rename map: `oldId → canonicalId`.
|
|
65
|
+
*/
|
|
66
|
+
mergeClusters(centroids, expectedSpeakers) {
|
|
67
|
+
// Build a mutable working copy so we can iteratively merge.
|
|
68
|
+
const working = new Map(centroids);
|
|
69
|
+
// Accumulated rename mapping.
|
|
70
|
+
const renameMap = new Map();
|
|
71
|
+
while (true) {
|
|
72
|
+
const ids = Array.from(working.keys());
|
|
73
|
+
const count = ids.length;
|
|
74
|
+
// Nothing to merge.
|
|
75
|
+
if (count < 2)
|
|
76
|
+
break;
|
|
77
|
+
// Find the closest pair.
|
|
78
|
+
let bestSim = -Infinity;
|
|
79
|
+
let bestI = 0;
|
|
80
|
+
let bestJ = 1;
|
|
81
|
+
for (let i = 0; i < count; i++) {
|
|
82
|
+
for (let j = i + 1; j < count; j++) {
|
|
83
|
+
const sim = cosineSimilarity(working.get(ids[i]), working.get(ids[j]));
|
|
84
|
+
if (sim > bestSim) {
|
|
85
|
+
bestSim = sim;
|
|
86
|
+
bestI = i;
|
|
87
|
+
bestJ = j;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Decide whether to merge.
|
|
92
|
+
const shouldMergeDueToThreshold = bestSim >= this.mergeThreshold;
|
|
93
|
+
const shouldMergeDueToCount = expectedSpeakers !== undefined && count > expectedSpeakers;
|
|
94
|
+
if (!shouldMergeDueToThreshold && !shouldMergeDueToCount)
|
|
95
|
+
break;
|
|
96
|
+
// Merge ids[bestJ] into ids[bestI] (keep the lexicographically earlier
|
|
97
|
+
// ID as the canonical one for determinism).
|
|
98
|
+
const keepId = ids[bestI];
|
|
99
|
+
const dropId = ids[bestJ];
|
|
100
|
+
// Average the two centroids (equal weight — simple heuristic).
|
|
101
|
+
const keepEmb = working.get(keepId);
|
|
102
|
+
const dropEmb = working.get(dropId);
|
|
103
|
+
const merged = new Float32Array(keepEmb.length);
|
|
104
|
+
for (let k = 0; k < keepEmb.length; k++) {
|
|
105
|
+
merged[k] = (keepEmb[k] + dropEmb[k]) / 2;
|
|
106
|
+
}
|
|
107
|
+
working.set(keepId, merged);
|
|
108
|
+
working.delete(dropId);
|
|
109
|
+
// Record the rename. Chase any existing mappings so the final map is
|
|
110
|
+
// transitively resolved.
|
|
111
|
+
renameMap.set(dropId, keepId);
|
|
112
|
+
// Resolve transitive renames: if dropId was itself a canonical target of
|
|
113
|
+
// an earlier merge, update those entries to point to keepId.
|
|
114
|
+
for (const [old, target] of renameMap) {
|
|
115
|
+
if (target === dropId) {
|
|
116
|
+
renameMap.set(old, keepId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return renameMap;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=ClusteringStrategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClusteringStrategy.js","sourceRoot":"","sources":["../src/ClusteringStrategy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DiarizationEngine.ts
|
|
3
|
+
* @description Factory that creates {@link DiarizationSession} instances,
|
|
4
|
+
* selecting the appropriate backend based on the supplied configuration.
|
|
5
|
+
*
|
|
6
|
+
* When a provider backend is requested (`config.backend === 'provider'`), the
|
|
7
|
+
* engine creates a {@link ProviderDiarizationBackend} and wraps it in a
|
|
8
|
+
* session. Otherwise it constructs the full local pipeline:
|
|
9
|
+
* {@link SlidingWindowExtractor} → {@link LocalDiarizationBackend} →
|
|
10
|
+
* {@link SpeakerEmbeddingCache}.
|
|
11
|
+
*
|
|
12
|
+
* @module diarization/DiarizationEngine
|
|
13
|
+
*/
|
|
14
|
+
import type { IDiarizationEngine, IDiarizationSession, DiarizationConfig } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Factory for diarization sessions.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const engine = new DiarizationEngine();
|
|
21
|
+
*
|
|
22
|
+
* // Local mode (default)
|
|
23
|
+
* const session = engine.startSession();
|
|
24
|
+
* session.on('speaker_identified', ({ speakerId }) => console.log(speakerId));
|
|
25
|
+
*
|
|
26
|
+
* // Provider mode
|
|
27
|
+
* const providerSession = engine.startSession({ backend: 'provider' });
|
|
28
|
+
* sttSession.on('transcript', (e) => providerSession.labelTranscript(e));
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare class DiarizationEngine implements IDiarizationEngine {
|
|
32
|
+
/**
|
|
33
|
+
* Create and return a new {@link IDiarizationSession}.
|
|
34
|
+
*
|
|
35
|
+
* The backend is selected based on `config.backend`:
|
|
36
|
+
* - `'provider'` — use {@link ProviderDiarizationBackend}
|
|
37
|
+
* - `'local'` (default) — use {@link LocalDiarizationBackend} with a fresh
|
|
38
|
+
* {@link SpeakerEmbeddingCache} and {@link SlidingWindowExtractor}
|
|
39
|
+
*
|
|
40
|
+
* @param config - Optional session configuration.
|
|
41
|
+
* @returns A ready-to-use {@link DiarizationSession}.
|
|
42
|
+
*/
|
|
43
|
+
startSession(config?: DiarizationConfig): IDiarizationSession;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=DiarizationEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiarizationEngine.d.ts","sourceRoot":"","sources":["../src/DiarizationEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAoB7F;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,iBAAkB,YAAW,kBAAkB;IAK1D;;;;;;;;;;OAUG;IACH,YAAY,CAAC,MAAM,GAAE,iBAAsB,GAAG,mBAAmB;CA2BlE"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DiarizationEngine.ts
|
|
3
|
+
* @description Factory that creates {@link DiarizationSession} instances,
|
|
4
|
+
* selecting the appropriate backend based on the supplied configuration.
|
|
5
|
+
*
|
|
6
|
+
* When a provider backend is requested (`config.backend === 'provider'`), the
|
|
7
|
+
* engine creates a {@link ProviderDiarizationBackend} and wraps it in a
|
|
8
|
+
* session. Otherwise it constructs the full local pipeline:
|
|
9
|
+
* {@link SlidingWindowExtractor} → {@link LocalDiarizationBackend} →
|
|
10
|
+
* {@link SpeakerEmbeddingCache}.
|
|
11
|
+
*
|
|
12
|
+
* @module diarization/DiarizationEngine
|
|
13
|
+
*/
|
|
14
|
+
import { DiarizationSession } from './DiarizationSession.js';
|
|
15
|
+
import { SpeakerEmbeddingCache } from './SpeakerEmbeddingCache.js';
|
|
16
|
+
import { SlidingWindowExtractor } from './SlidingWindowExtractor.js';
|
|
17
|
+
import { LocalDiarizationBackend } from './LocalDiarizationBackend.js';
|
|
18
|
+
import { ProviderDiarizationBackend } from './ProviderDiarizationBackend.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Default values
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const DEFAULT_SIMILARITY_THRESHOLD = 0.7;
|
|
23
|
+
const DEFAULT_CHUNK_SIZE_MS = 1500;
|
|
24
|
+
const DEFAULT_OVERLAP_MS = 500;
|
|
25
|
+
const DEFAULT_SAMPLE_RATE = 16_000;
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Main class
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Factory for diarization sessions.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const engine = new DiarizationEngine();
|
|
35
|
+
*
|
|
36
|
+
* // Local mode (default)
|
|
37
|
+
* const session = engine.startSession();
|
|
38
|
+
* session.on('speaker_identified', ({ speakerId }) => console.log(speakerId));
|
|
39
|
+
*
|
|
40
|
+
* // Provider mode
|
|
41
|
+
* const providerSession = engine.startSession({ backend: 'provider' });
|
|
42
|
+
* sttSession.on('transcript', (e) => providerSession.labelTranscript(e));
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class DiarizationEngine {
|
|
46
|
+
// -------------------------------------------------------------------------
|
|
47
|
+
// IDiarizationEngine implementation
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Create and return a new {@link IDiarizationSession}.
|
|
51
|
+
*
|
|
52
|
+
* The backend is selected based on `config.backend`:
|
|
53
|
+
* - `'provider'` — use {@link ProviderDiarizationBackend}
|
|
54
|
+
* - `'local'` (default) — use {@link LocalDiarizationBackend} with a fresh
|
|
55
|
+
* {@link SpeakerEmbeddingCache} and {@link SlidingWindowExtractor}
|
|
56
|
+
*
|
|
57
|
+
* @param config - Optional session configuration.
|
|
58
|
+
* @returns A ready-to-use {@link DiarizationSession}.
|
|
59
|
+
*/
|
|
60
|
+
startSession(config = {}) {
|
|
61
|
+
const backend = config.backend ?? 'local';
|
|
62
|
+
if (backend === 'provider') {
|
|
63
|
+
return new DiarizationSession({
|
|
64
|
+
kind: 'provider',
|
|
65
|
+
backend: new ProviderDiarizationBackend(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Local mode — wire up the full pipeline.
|
|
69
|
+
const threshold = config.similarityThreshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
70
|
+
const chunkSizeMs = config.chunkSizeMs ?? DEFAULT_CHUNK_SIZE_MS;
|
|
71
|
+
const overlapMs = config.overlapMs ?? DEFAULT_OVERLAP_MS;
|
|
72
|
+
const sampleRate = config.sampleRate ?? DEFAULT_SAMPLE_RATE;
|
|
73
|
+
const cache = new SpeakerEmbeddingCache(threshold);
|
|
74
|
+
const extractor = new SlidingWindowExtractor(chunkSizeMs, overlapMs, sampleRate);
|
|
75
|
+
const localBackend = new LocalDiarizationBackend(cache, extractor);
|
|
76
|
+
return new DiarizationSession({
|
|
77
|
+
kind: 'local',
|
|
78
|
+
backend: localBackend,
|
|
79
|
+
cache,
|
|
80
|
+
extractor,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=DiarizationEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiarizationEngine.js","sourceRoot":"","sources":["../src/DiarizationEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,4BAA4B,GAAG,GAAG,CAAC;AACzC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,iBAAiB;IAC5B,4EAA4E;IAC5E,oCAAoC;IACpC,4EAA4E;IAE5E;;;;;;;;;;OAUG;IACH,YAAY,CAAC,SAA4B,EAAE;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC;QAE1C,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC3B,OAAO,IAAI,kBAAkB,CAAC;gBAC5B,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,IAAI,0BAA0B,EAAE;aAC1C,CAAC,CAAC;QACL,CAAC;QAED,0CAA0C;QAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,mBAAmB,IAAI,4BAA4B,CAAC;QAC7E,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,qBAAqB,CAAC;QAChE,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACzD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAC;QAE5D,MAAM,KAAK,GAAG,IAAI,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,SAAS,GAAG,IAAI,sBAAsB,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QACjF,MAAM,YAAY,GAAG,IAAI,uBAAuB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAEnE,OAAO,IAAI,kBAAkB,CAAC;YAC5B,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,YAAY;YACrB,KAAK;YACL,SAAS;SACV,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DiarizationSession.ts
|
|
3
|
+
* @description Live diarization session that wraps either a
|
|
4
|
+
* {@link ProviderDiarizationBackend} or a {@link LocalDiarizationBackend}.
|
|
5
|
+
*
|
|
6
|
+
* The session implements {@link IDiarizationSession} and bridges the raw audio
|
|
7
|
+
* / transcript inputs to the selected backend, forwarding `speaker_identified`
|
|
8
|
+
* and `segment_ready` events to callers.
|
|
9
|
+
*
|
|
10
|
+
* @module diarization/DiarizationSession
|
|
11
|
+
*/
|
|
12
|
+
import { EventEmitter } from 'node:events';
|
|
13
|
+
import type { IDiarizationSession, AudioFrame, TranscriptEvent, DiarizedSegment } from './types.js';
|
|
14
|
+
import type { ProviderDiarizationBackend } from './ProviderDiarizationBackend.js';
|
|
15
|
+
import type { LocalDiarizationBackend } from './LocalDiarizationBackend.js';
|
|
16
|
+
import type { SpeakerEmbeddingCache } from './SpeakerEmbeddingCache.js';
|
|
17
|
+
import type { SlidingWindowExtractor } from './SlidingWindowExtractor.js';
|
|
18
|
+
/** Provider-delegated backend descriptor. */
|
|
19
|
+
interface ProviderBackendDescriptor {
|
|
20
|
+
kind: 'provider';
|
|
21
|
+
backend: ProviderDiarizationBackend;
|
|
22
|
+
}
|
|
23
|
+
/** Local-clustering backend descriptor. */
|
|
24
|
+
interface LocalBackendDescriptor {
|
|
25
|
+
kind: 'local';
|
|
26
|
+
backend: LocalDiarizationBackend;
|
|
27
|
+
cache: SpeakerEmbeddingCache;
|
|
28
|
+
extractor: SlidingWindowExtractor;
|
|
29
|
+
}
|
|
30
|
+
type BackendDescriptor = ProviderBackendDescriptor | LocalBackendDescriptor;
|
|
31
|
+
/**
|
|
32
|
+
* A live diarization session.
|
|
33
|
+
*
|
|
34
|
+
* Create instances via {@link DiarizationEngine.startSession} rather than
|
|
35
|
+
* constructing directly.
|
|
36
|
+
*
|
|
37
|
+
* ### Events
|
|
38
|
+
* - `speaker_identified` — payload: {@link SpeakerIdentified}
|
|
39
|
+
* - `segment_ready` — payload: {@link DiarizedSegment}
|
|
40
|
+
* - `error` — payload: `Error`
|
|
41
|
+
* - `close` — no payload
|
|
42
|
+
*/
|
|
43
|
+
export declare class DiarizationSession extends EventEmitter implements IDiarizationSession {
|
|
44
|
+
private readonly descriptor;
|
|
45
|
+
private closed;
|
|
46
|
+
/** Most recently identified speaker (used for transcript labelling in local mode). */
|
|
47
|
+
private lastSpeakerId;
|
|
48
|
+
/**
|
|
49
|
+
* @param descriptor - Resolved backend descriptor.
|
|
50
|
+
*/
|
|
51
|
+
constructor(descriptor: BackendDescriptor);
|
|
52
|
+
/**
|
|
53
|
+
* Push a raw audio frame into the pipeline.
|
|
54
|
+
*
|
|
55
|
+
* In local mode the frame is forwarded to the sliding-window extractor.
|
|
56
|
+
* In provider mode this is a no-op.
|
|
57
|
+
*
|
|
58
|
+
* @param frame - Audio frame to process.
|
|
59
|
+
*/
|
|
60
|
+
pushAudio(frame: AudioFrame): void;
|
|
61
|
+
/**
|
|
62
|
+
* Attach speaker labels to a transcript event and emit a segment.
|
|
63
|
+
*
|
|
64
|
+
* In provider mode, word-level speaker labels are extracted directly. In
|
|
65
|
+
* local mode, the most recently identified speaker is used.
|
|
66
|
+
*
|
|
67
|
+
* @param event - Transcript event from the STT provider.
|
|
68
|
+
* @returns The labelled {@link DiarizedSegment}, or `null` if no speaker
|
|
69
|
+
* information is available yet.
|
|
70
|
+
*/
|
|
71
|
+
labelTranscript(event: TranscriptEvent): DiarizedSegment | null;
|
|
72
|
+
/**
|
|
73
|
+
* Pre-register a known speaker voiceprint.
|
|
74
|
+
*
|
|
75
|
+
* Only has effect in local mode. In provider mode, speaker IDs come
|
|
76
|
+
* directly from the STT provider and this call is a no-op.
|
|
77
|
+
*
|
|
78
|
+
* @param id - Human-readable speaker name.
|
|
79
|
+
* @param voiceprint - Reference embedding for this speaker.
|
|
80
|
+
*/
|
|
81
|
+
enrollSpeaker(id: string, voiceprint: Float32Array): void;
|
|
82
|
+
/**
|
|
83
|
+
* Terminate the session and release all resources.
|
|
84
|
+
*
|
|
85
|
+
* After `close()` no further events are emitted.
|
|
86
|
+
*/
|
|
87
|
+
close(): void;
|
|
88
|
+
}
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=DiarizationSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiarizationSession.d.ts","sourceRoot":"","sources":["../src/DiarizationSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAC;AACvH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAM1E,6CAA6C;AAC7C,UAAU,yBAAyB;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAED,2CAA2C;AAC3C,UAAU,sBAAsB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,uBAAuB,CAAC;IACjC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,SAAS,EAAE,sBAAsB,CAAC;CACnC;AAED,KAAK,iBAAiB,GAAG,yBAAyB,GAAG,sBAAsB,CAAC;AAM5E;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAmB,SAAQ,YAAa,YAAW,mBAAmB;IAiBrE,OAAO,CAAC,QAAQ,CAAC,UAAU;IAZvC,OAAO,CAAC,MAAM,CAAS;IAEvB,sFAAsF;IACtF,OAAO,CAAC,aAAa,CAAuB;IAM5C;;OAEG;gBAC0B,UAAU,EAAE,iBAAiB;IAkB1D;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IASlC;;;;;;;;;OASG;IACH,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,GAAG,IAAI;IA2B/D;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,GAAG,IAAI;IAOzD;;;;OAIG;IACH,KAAK,IAAI,IAAI;CAYd"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DiarizationSession.ts
|
|
3
|
+
* @description Live diarization session that wraps either a
|
|
4
|
+
* {@link ProviderDiarizationBackend} or a {@link LocalDiarizationBackend}.
|
|
5
|
+
*
|
|
6
|
+
* The session implements {@link IDiarizationSession} and bridges the raw audio
|
|
7
|
+
* / transcript inputs to the selected backend, forwarding `speaker_identified`
|
|
8
|
+
* and `segment_ready` events to callers.
|
|
9
|
+
*
|
|
10
|
+
* @module diarization/DiarizationSession
|
|
11
|
+
*/
|
|
12
|
+
import { EventEmitter } from 'node:events';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Main class
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* A live diarization session.
|
|
18
|
+
*
|
|
19
|
+
* Create instances via {@link DiarizationEngine.startSession} rather than
|
|
20
|
+
* constructing directly.
|
|
21
|
+
*
|
|
22
|
+
* ### Events
|
|
23
|
+
* - `speaker_identified` — payload: {@link SpeakerIdentified}
|
|
24
|
+
* - `segment_ready` — payload: {@link DiarizedSegment}
|
|
25
|
+
* - `error` — payload: `Error`
|
|
26
|
+
* - `close` — no payload
|
|
27
|
+
*/
|
|
28
|
+
export class DiarizationSession extends EventEmitter {
|
|
29
|
+
descriptor;
|
|
30
|
+
// -------------------------------------------------------------------------
|
|
31
|
+
// Private state
|
|
32
|
+
// -------------------------------------------------------------------------
|
|
33
|
+
closed = false;
|
|
34
|
+
/** Most recently identified speaker (used for transcript labelling in local mode). */
|
|
35
|
+
lastSpeakerId = null;
|
|
36
|
+
// -------------------------------------------------------------------------
|
|
37
|
+
// Constructor
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
/**
|
|
40
|
+
* @param descriptor - Resolved backend descriptor.
|
|
41
|
+
*/
|
|
42
|
+
constructor(descriptor) {
|
|
43
|
+
super();
|
|
44
|
+
this.descriptor = descriptor;
|
|
45
|
+
if (descriptor.kind === 'local') {
|
|
46
|
+
// Forward speaker_identified events from the local backend.
|
|
47
|
+
descriptor.backend.on('speaker_identified', (payload) => {
|
|
48
|
+
this.lastSpeakerId = payload.speakerId;
|
|
49
|
+
this.emit('speaker_identified', payload);
|
|
50
|
+
});
|
|
51
|
+
descriptor.backend.start();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// -------------------------------------------------------------------------
|
|
55
|
+
// IDiarizationSession implementation
|
|
56
|
+
// -------------------------------------------------------------------------
|
|
57
|
+
/**
|
|
58
|
+
* Push a raw audio frame into the pipeline.
|
|
59
|
+
*
|
|
60
|
+
* In local mode the frame is forwarded to the sliding-window extractor.
|
|
61
|
+
* In provider mode this is a no-op.
|
|
62
|
+
*
|
|
63
|
+
* @param frame - Audio frame to process.
|
|
64
|
+
*/
|
|
65
|
+
pushAudio(frame) {
|
|
66
|
+
if (this.closed)
|
|
67
|
+
return;
|
|
68
|
+
if (this.descriptor.kind === 'local') {
|
|
69
|
+
this.descriptor.extractor.pushAudio(frame);
|
|
70
|
+
}
|
|
71
|
+
// Provider mode: audio processing happens on the provider side.
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Attach speaker labels to a transcript event and emit a segment.
|
|
75
|
+
*
|
|
76
|
+
* In provider mode, word-level speaker labels are extracted directly. In
|
|
77
|
+
* local mode, the most recently identified speaker is used.
|
|
78
|
+
*
|
|
79
|
+
* @param event - Transcript event from the STT provider.
|
|
80
|
+
* @returns The labelled {@link DiarizedSegment}, or `null` if no speaker
|
|
81
|
+
* information is available yet.
|
|
82
|
+
*/
|
|
83
|
+
labelTranscript(event) {
|
|
84
|
+
if (this.closed)
|
|
85
|
+
return null;
|
|
86
|
+
let segment = null;
|
|
87
|
+
if (this.descriptor.kind === 'provider') {
|
|
88
|
+
segment = this.descriptor.backend.pushTranscriptEvent(event);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Local mode — use the last identified speaker.
|
|
92
|
+
if (this.lastSpeakerId !== null) {
|
|
93
|
+
segment = {
|
|
94
|
+
speakerId: this.lastSpeakerId,
|
|
95
|
+
text: event.text,
|
|
96
|
+
startMs: event.timestamp,
|
|
97
|
+
endMs: event.timestamp + event.text.length * 50, // rough estimate
|
|
98
|
+
confidence: 0.7,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (segment) {
|
|
103
|
+
this.emit('segment_ready', segment);
|
|
104
|
+
}
|
|
105
|
+
return segment;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Pre-register a known speaker voiceprint.
|
|
109
|
+
*
|
|
110
|
+
* Only has effect in local mode. In provider mode, speaker IDs come
|
|
111
|
+
* directly from the STT provider and this call is a no-op.
|
|
112
|
+
*
|
|
113
|
+
* @param id - Human-readable speaker name.
|
|
114
|
+
* @param voiceprint - Reference embedding for this speaker.
|
|
115
|
+
*/
|
|
116
|
+
enrollSpeaker(id, voiceprint) {
|
|
117
|
+
if (this.descriptor.kind === 'local') {
|
|
118
|
+
this.descriptor.cache.enrollSpeaker(id, voiceprint);
|
|
119
|
+
}
|
|
120
|
+
// Provider mode: enrolment is handled by the provider; ignore here.
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Terminate the session and release all resources.
|
|
124
|
+
*
|
|
125
|
+
* After `close()` no further events are emitted.
|
|
126
|
+
*/
|
|
127
|
+
close() {
|
|
128
|
+
if (this.closed)
|
|
129
|
+
return;
|
|
130
|
+
this.closed = true;
|
|
131
|
+
if (this.descriptor.kind === 'local') {
|
|
132
|
+
this.descriptor.backend.stop();
|
|
133
|
+
this.descriptor.extractor.flush();
|
|
134
|
+
}
|
|
135
|
+
this.emit('close');
|
|
136
|
+
this.removeAllListeners();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=DiarizationSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiarizationSession.js","sourceRoot":"","sources":["../src/DiarizationSession.ts"],"names":[],"mappings":"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"}
|