@mcptoolshop/voice-soundboard-core 0.1.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 +21 -0
- package/dist/ambient/emitter.d.ts +34 -0
- package/dist/ambient/emitter.d.ts.map +1 -0
- package/dist/ambient/emitter.js +125 -0
- package/dist/ambient/emitter.js.map +1 -0
- package/dist/ambient/index.d.ts +5 -0
- package/dist/ambient/index.d.ts.map +1 -0
- package/dist/ambient/index.js +5 -0
- package/dist/ambient/index.js.map +1 -0
- package/dist/ambient/redact.d.ts +11 -0
- package/dist/ambient/redact.d.ts.map +1 -0
- package/dist/ambient/redact.js +40 -0
- package/dist/ambient/redact.js.map +1 -0
- package/dist/ambient/types.d.ts +38 -0
- package/dist/ambient/types.d.ts.map +1 -0
- package/dist/ambient/types.js +8 -0
- package/dist/ambient/types.js.map +1 -0
- package/dist/artifact.d.ts +37 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +61 -0
- package/dist/artifact.js.map +1 -0
- package/dist/chunking/chunker.d.ts +15 -0
- package/dist/chunking/chunker.d.ts.map +1 -0
- package/dist/chunking/chunker.js +156 -0
- package/dist/chunking/chunker.js.map +1 -0
- package/dist/chunking/index.d.ts +5 -0
- package/dist/chunking/index.d.ts.map +1 -0
- package/dist/chunking/index.js +4 -0
- package/dist/chunking/index.js.map +1 -0
- package/dist/chunking/limits.d.ts +12 -0
- package/dist/chunking/limits.d.ts.map +1 -0
- package/dist/chunking/limits.js +12 -0
- package/dist/chunking/limits.js.map +1 -0
- package/dist/chunking/types.d.ts +26 -0
- package/dist/chunking/types.d.ts.map +1 -0
- package/dist/chunking/types.js +3 -0
- package/dist/chunking/types.js.map +1 -0
- package/dist/dialogue/index.d.ts +4 -0
- package/dist/dialogue/index.d.ts.map +1 -0
- package/dist/dialogue/index.js +3 -0
- package/dist/dialogue/index.js.map +1 -0
- package/dist/dialogue/parser.d.ts +33 -0
- package/dist/dialogue/parser.d.ts.map +1 -0
- package/dist/dialogue/parser.js +182 -0
- package/dist/dialogue/parser.js.map +1 -0
- package/dist/dialogue/types.d.ts +29 -0
- package/dist/dialogue/types.d.ts.map +1 -0
- package/dist/dialogue/types.js +3 -0
- package/dist/dialogue/types.js.map +1 -0
- package/dist/emotion/index.d.ts +5 -0
- package/dist/emotion/index.d.ts.map +1 -0
- package/dist/emotion/index.js +5 -0
- package/dist/emotion/index.js.map +1 -0
- package/dist/emotion/map.d.ts +17 -0
- package/dist/emotion/map.d.ts.map +1 -0
- package/dist/emotion/map.js +27 -0
- package/dist/emotion/map.js.map +1 -0
- package/dist/emotion/parser.d.ts +14 -0
- package/dist/emotion/parser.d.ts.map +1 -0
- package/dist/emotion/parser.js +96 -0
- package/dist/emotion/parser.js.map +1 -0
- package/dist/emotion/types.d.ts +26 -0
- package/dist/emotion/types.d.ts.map +1 -0
- package/dist/emotion/types.js +12 -0
- package/dist/emotion/types.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/limits.d.ts +44 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +65 -0
- package/dist/limits.js.map +1 -0
- package/dist/orchestrator/concat.d.ts +20 -0
- package/dist/orchestrator/concat.d.ts.map +1 -0
- package/dist/orchestrator/concat.js +139 -0
- package/dist/orchestrator/concat.js.map +1 -0
- package/dist/orchestrator/emotionRunner.d.ts +38 -0
- package/dist/orchestrator/emotionRunner.d.ts.map +1 -0
- package/dist/orchestrator/emotionRunner.js +115 -0
- package/dist/orchestrator/emotionRunner.js.map +1 -0
- package/dist/orchestrator/index.d.ts +6 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +5 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/runner.d.ts +31 -0
- package/dist/orchestrator/runner.d.ts.map +1 -0
- package/dist/orchestrator/runner.js +106 -0
- package/dist/orchestrator/runner.js.map +1 -0
- package/dist/orchestrator/types.d.ts +47 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/orchestrator/types.js +3 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/presets.d.ts +20 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +22 -0
- package/dist/presets.js.map +1 -0
- package/dist/request.d.ts +26 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +33 -0
- package/dist/request.js.map +1 -0
- package/dist/sandbox.d.ts +15 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +27 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/schemas.d.ts +49 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sfx/generator.d.ts +15 -0
- package/dist/sfx/generator.d.ts.map +1 -0
- package/dist/sfx/generator.js +114 -0
- package/dist/sfx/generator.js.map +1 -0
- package/dist/sfx/index.d.ts +7 -0
- package/dist/sfx/index.d.ts.map +1 -0
- package/dist/sfx/index.js +7 -0
- package/dist/sfx/index.js.map +1 -0
- package/dist/sfx/parser.d.ts +16 -0
- package/dist/sfx/parser.d.ts.map +1 -0
- package/dist/sfx/parser.js +100 -0
- package/dist/sfx/parser.js.map +1 -0
- package/dist/sfx/registry.d.ts +17 -0
- package/dist/sfx/registry.d.ts.map +1 -0
- package/dist/sfx/registry.js +13 -0
- package/dist/sfx/registry.js.map +1 -0
- package/dist/sfx/stitcher.d.ts +29 -0
- package/dist/sfx/stitcher.d.ts.map +1 -0
- package/dist/sfx/stitcher.js +44 -0
- package/dist/sfx/stitcher.js.map +1 -0
- package/dist/sfx/types.d.ts +22 -0
- package/dist/sfx/types.d.ts.map +1 -0
- package/dist/sfx/types.js +10 -0
- package/dist/sfx/types.js.map +1 -0
- package/dist/ssml/index.d.ts +5 -0
- package/dist/ssml/index.d.ts.map +1 -0
- package/dist/ssml/index.js +4 -0
- package/dist/ssml/index.js.map +1 -0
- package/dist/ssml/limits.d.ts +14 -0
- package/dist/ssml/limits.d.ts.map +1 -0
- package/dist/ssml/limits.js +14 -0
- package/dist/ssml/limits.js.map +1 -0
- package/dist/ssml/parser.d.ts +28 -0
- package/dist/ssml/parser.d.ts.map +1 -0
- package/dist/ssml/parser.js +348 -0
- package/dist/ssml/parser.js.map +1 -0
- package/dist/ssml/types.d.ts +46 -0
- package/dist/ssml/types.d.ts.map +1 -0
- package/dist/ssml/types.js +3 -0
- package/dist/ssml/types.js.map +1 -0
- package/dist/validate.d.ts +29 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +60 -0
- package/dist/validate.js.map +1 -0
- package/dist/voices.d.ts +22 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +31 -0
- package/dist/voices.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chunking/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAQlE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Chunking limits. */
|
|
2
|
+
export declare const CHUNK_LIMITS: {
|
|
3
|
+
/** Maximum characters per chunk. */
|
|
4
|
+
readonly maxChunkChars: 600;
|
|
5
|
+
/** Minimum characters for a chunk to stand alone (merged with neighbor if smaller). */
|
|
6
|
+
readonly minChunkChars: 20;
|
|
7
|
+
/** Maximum total characters across all chunks. */
|
|
8
|
+
readonly maxTotalChars: 12000;
|
|
9
|
+
/** Maximum number of chunks. */
|
|
10
|
+
readonly maxChunks: 50;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/chunking/limits.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,eAAO,MAAM,YAAY;IACvB,oCAAoC;;IAEpC,uFAAuF;;IAEvF,kDAAkD;;IAElD,gCAAgC;;CAExB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Chunking limits. */
|
|
2
|
+
export const CHUNK_LIMITS = {
|
|
3
|
+
/** Maximum characters per chunk. */
|
|
4
|
+
maxChunkChars: 600,
|
|
5
|
+
/** Minimum characters for a chunk to stand alone (merged with neighbor if smaller). */
|
|
6
|
+
minChunkChars: 20,
|
|
7
|
+
/** Maximum total characters across all chunks. */
|
|
8
|
+
maxTotalChars: 12_000,
|
|
9
|
+
/** Maximum number of chunks. */
|
|
10
|
+
maxChunks: 50,
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/chunking/limits.ts"],"names":[],"mappings":"AAAA,uBAAuB;AAEvB,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,oCAAoC;IACpC,aAAa,EAAE,GAAG;IAClB,uFAAuF;IACvF,aAAa,EAAE,EAAE;IACjB,kDAAkD;IAClD,aAAa,EAAE,MAAM;IACrB,gCAAgC;IAChC,SAAS,EAAE,EAAE;CACL,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Chunking types — split text into manageable synthesis chunks. */
|
|
2
|
+
export interface ChunkingOptions {
|
|
3
|
+
/** Maximum characters per chunk (default 600). */
|
|
4
|
+
readonly maxChunkChars?: number;
|
|
5
|
+
/** Minimum characters for a chunk to stand on its own (default 20). */
|
|
6
|
+
readonly minChunkChars?: number;
|
|
7
|
+
/** Maximum total characters across all chunks (default 10000). */
|
|
8
|
+
readonly maxTotalChars?: number;
|
|
9
|
+
/** Maximum number of chunks allowed (default 50). */
|
|
10
|
+
readonly maxChunks?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ChunkResult {
|
|
13
|
+
/** The ordered text chunks. */
|
|
14
|
+
readonly chunks: readonly string[];
|
|
15
|
+
/** Whether the original text was chunked. */
|
|
16
|
+
readonly wasChunked: boolean;
|
|
17
|
+
/** Whether the text was truncated due to limits. */
|
|
18
|
+
readonly wasTruncated: boolean;
|
|
19
|
+
/** Non-fatal warnings. */
|
|
20
|
+
readonly warnings: readonly ChunkWarning[];
|
|
21
|
+
}
|
|
22
|
+
export interface ChunkWarning {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly message: string;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/chunking/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,kEAAkE;IAClE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,0BAA0B;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/chunking/types.ts"],"names":[],"mappings":"AAAA,oEAAoE"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Dialogue module — multi-speaker script parsing and casting. */
|
|
2
|
+
export { type DialogueLine, type DialoguePause, type DialogueCue, type CueSheet, type DialogueWarning, type CastMap, } from "./types.js";
|
|
3
|
+
export { parseDialogue, DialogueParseError, type ParseDialogueOptions, } from "./parser.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialogue/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAElE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,OAAO,GACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,KAAK,oBAAoB,GAC1B,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/dialogue/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAWlE,OAAO,EACL,aAAa,EACb,kBAAkB,GAEnB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialogue script parser.
|
|
3
|
+
*
|
|
4
|
+
* Script syntax:
|
|
5
|
+
* Speaker: text to speak
|
|
6
|
+
* [pause 350ms]
|
|
7
|
+
* # comment
|
|
8
|
+
* (blank lines are ignored)
|
|
9
|
+
*
|
|
10
|
+
* Casting:
|
|
11
|
+
* - Explicit cast map: { "Alice": "bf_alice", "Bob": "bm_george" }
|
|
12
|
+
* - Cast values can be voice IDs or preset names
|
|
13
|
+
* - Uncast speakers get auto-assigned from the approved roster
|
|
14
|
+
* - Auto-casting alternates between male and female voices
|
|
15
|
+
*/
|
|
16
|
+
import type { CueSheet, CastMap } from "./types.js";
|
|
17
|
+
export declare class DialogueParseError extends Error {
|
|
18
|
+
readonly code: string;
|
|
19
|
+
constructor(message: string, code?: string);
|
|
20
|
+
}
|
|
21
|
+
export interface ParseDialogueOptions {
|
|
22
|
+
/** Explicit speaker → voice/preset assignments. */
|
|
23
|
+
cast?: CastMap;
|
|
24
|
+
/** Maximum number of unique speakers (default 10). */
|
|
25
|
+
maxSpeakers?: number;
|
|
26
|
+
/** Maximum number of cues (default 100). */
|
|
27
|
+
maxCues?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a dialogue script into a CueSheet.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseDialogue(script: string, options?: ParseDialogueOptions): CueSheet;
|
|
33
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/dialogue/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EACV,QAAQ,EAKR,OAAO,EACR,MAAM,YAAY,CAAC;AAIpB,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,IAAI,EAAE,MAAM;gBAD5B,OAAO,EAAE,MAAM,EACC,IAAI,GAAE,MAAgC;CAKzD;AAED,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,QAAQ,CAyFtF"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialogue script parser.
|
|
3
|
+
*
|
|
4
|
+
* Script syntax:
|
|
5
|
+
* Speaker: text to speak
|
|
6
|
+
* [pause 350ms]
|
|
7
|
+
* # comment
|
|
8
|
+
* (blank lines are ignored)
|
|
9
|
+
*
|
|
10
|
+
* Casting:
|
|
11
|
+
* - Explicit cast map: { "Alice": "bf_alice", "Bob": "bm_george" }
|
|
12
|
+
* - Cast values can be voice IDs or preset names
|
|
13
|
+
* - Uncast speakers get auto-assigned from the approved roster
|
|
14
|
+
* - Auto-casting alternates between male and female voices
|
|
15
|
+
*/
|
|
16
|
+
import { APPROVED_VOICE_IDS } from "../voices.js";
|
|
17
|
+
import { getPreset } from "../presets.js";
|
|
18
|
+
// ── Public API ──
|
|
19
|
+
export class DialogueParseError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(message, code = "DIALOGUE_PARSE_FAILED") {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = "DialogueParseError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const DEFAULT_MAX_SPEAKERS = 10;
|
|
28
|
+
const DEFAULT_MAX_CUES = 100;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a dialogue script into a CueSheet.
|
|
31
|
+
*/
|
|
32
|
+
export function parseDialogue(script, options) {
|
|
33
|
+
const cast = options?.cast ?? {};
|
|
34
|
+
const maxSpeakers = options?.maxSpeakers ?? DEFAULT_MAX_SPEAKERS;
|
|
35
|
+
const maxCues = options?.maxCues ?? DEFAULT_MAX_CUES;
|
|
36
|
+
const warnings = [];
|
|
37
|
+
const cues = [];
|
|
38
|
+
const speakerSet = new Set();
|
|
39
|
+
const lines = script.split("\n");
|
|
40
|
+
for (const rawLine of lines) {
|
|
41
|
+
const trimmed = rawLine.trim();
|
|
42
|
+
// Skip empty lines and comments
|
|
43
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Pause directive: [pause 350ms]
|
|
47
|
+
const pauseMatch = trimmed.match(/^\[pause\s+(\d+)\s*(ms)?\]$/i);
|
|
48
|
+
if (pauseMatch) {
|
|
49
|
+
const ms = parseInt(pauseMatch[1], 10);
|
|
50
|
+
cues.push({ type: "pause", durationMs: Math.min(ms, 5000) });
|
|
51
|
+
if (cues.length > maxCues) {
|
|
52
|
+
throw new DialogueParseError(`Too many cues (max ${maxCues})`);
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// Speaker line: Speaker: text
|
|
57
|
+
const lineMatch = trimmed.match(/^([^:]+):\s+(.+)$/);
|
|
58
|
+
if (lineMatch) {
|
|
59
|
+
const speaker = lineMatch[1].trim();
|
|
60
|
+
const text = lineMatch[2].trim();
|
|
61
|
+
if (!text) {
|
|
62
|
+
warnings.push({
|
|
63
|
+
code: "DIALOGUE_EMPTY_LINE",
|
|
64
|
+
message: `Empty text for speaker "${speaker}", skipped`,
|
|
65
|
+
});
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
speakerSet.add(speaker);
|
|
69
|
+
if (speakerSet.size > maxSpeakers) {
|
|
70
|
+
throw new DialogueParseError(`Too many speakers (max ${maxSpeakers})`);
|
|
71
|
+
}
|
|
72
|
+
// Voice will be resolved after parsing all lines
|
|
73
|
+
cues.push({ type: "line", speaker, text, voiceId: "" });
|
|
74
|
+
if (cues.length > maxCues) {
|
|
75
|
+
throw new DialogueParseError(`Too many cues (max ${maxCues})`);
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// Unrecognized line
|
|
80
|
+
warnings.push({
|
|
81
|
+
code: "DIALOGUE_UNRECOGNIZED_LINE",
|
|
82
|
+
message: `Unrecognized line format: "${trimmed.slice(0, 50)}"`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (cues.length === 0) {
|
|
86
|
+
throw new DialogueParseError("No valid dialogue lines found in script");
|
|
87
|
+
}
|
|
88
|
+
// Resolve casting
|
|
89
|
+
const speakers = [...speakerSet];
|
|
90
|
+
const resolvedCast = resolveCast(speakers, cast, warnings);
|
|
91
|
+
// Apply voice IDs to line cues
|
|
92
|
+
const resolvedCues = cues.map((cue) => {
|
|
93
|
+
if (cue.type === "line") {
|
|
94
|
+
const voiceId = resolvedCast.get(cue.speaker) ?? "";
|
|
95
|
+
return { ...cue, voiceId };
|
|
96
|
+
}
|
|
97
|
+
return cue;
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
cues: resolvedCues,
|
|
101
|
+
cast: resolvedCast,
|
|
102
|
+
speakers,
|
|
103
|
+
warnings,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// ── Casting ──
|
|
107
|
+
/** Auto-cast voice pool — alternating gender for contrast. */
|
|
108
|
+
const AUTO_CAST_POOL = [
|
|
109
|
+
"bm_george", // male british
|
|
110
|
+
"af_jessica", // female american
|
|
111
|
+
"am_eric", // male american
|
|
112
|
+
"bf_emma", // female british
|
|
113
|
+
"am_fenrir", // male american
|
|
114
|
+
"af_aoede", // female american
|
|
115
|
+
"bm_lewis", // male british
|
|
116
|
+
"bf_alice", // female british
|
|
117
|
+
"am_liam", // male american
|
|
118
|
+
"af_sky", // female american
|
|
119
|
+
"am_onyx", // male american
|
|
120
|
+
"bf_isabella", // female british
|
|
121
|
+
];
|
|
122
|
+
function resolveCast(speakers, explicitCast, warnings) {
|
|
123
|
+
const result = new Map();
|
|
124
|
+
const usedVoices = new Set();
|
|
125
|
+
let autoIdx = 0;
|
|
126
|
+
for (const speaker of speakers) {
|
|
127
|
+
const explicit = explicitCast[speaker];
|
|
128
|
+
if (explicit) {
|
|
129
|
+
// Try to resolve as voice ID or preset
|
|
130
|
+
const voiceId = resolveToVoiceId(explicit);
|
|
131
|
+
if (voiceId) {
|
|
132
|
+
result.set(speaker, voiceId);
|
|
133
|
+
usedVoices.add(voiceId);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
warnings.push({
|
|
137
|
+
code: "DIALOGUE_CAST_INVALID",
|
|
138
|
+
message: `Cast "${explicit}" for speaker "${speaker}" is not a valid voice or preset, auto-casting instead`,
|
|
139
|
+
});
|
|
140
|
+
// Fall through to auto-cast
|
|
141
|
+
const auto = pickAutoVoice(usedVoices, autoIdx);
|
|
142
|
+
result.set(speaker, auto.voiceId);
|
|
143
|
+
usedVoices.add(auto.voiceId);
|
|
144
|
+
autoIdx = auto.nextIdx;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Auto-cast
|
|
149
|
+
const auto = pickAutoVoice(usedVoices, autoIdx);
|
|
150
|
+
result.set(speaker, auto.voiceId);
|
|
151
|
+
usedVoices.add(auto.voiceId);
|
|
152
|
+
autoIdx = auto.nextIdx;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
function resolveToVoiceId(input) {
|
|
158
|
+
const lower = input.trim().toLowerCase();
|
|
159
|
+
// Direct voice ID
|
|
160
|
+
if (APPROVED_VOICE_IDS.has(lower)) {
|
|
161
|
+
return lower;
|
|
162
|
+
}
|
|
163
|
+
// Preset name → voice ID
|
|
164
|
+
const preset = getPreset(lower);
|
|
165
|
+
if (preset) {
|
|
166
|
+
return preset.voice;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function pickAutoVoice(used, startIdx) {
|
|
171
|
+
// Find next unused voice in pool
|
|
172
|
+
for (let i = 0; i < AUTO_CAST_POOL.length; i++) {
|
|
173
|
+
const idx = (startIdx + i) % AUTO_CAST_POOL.length;
|
|
174
|
+
const voiceId = AUTO_CAST_POOL[idx];
|
|
175
|
+
if (!used.has(voiceId)) {
|
|
176
|
+
return { voiceId, nextIdx: idx + 1 };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// All used — wrap around from pool start
|
|
180
|
+
return { voiceId: AUTO_CAST_POOL[startIdx % AUTO_CAST_POOL.length], nextIdx: startIdx + 1 };
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/dialogue/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Dialogue types — multi-speaker script parsing and casting. */
|
|
2
|
+
export interface DialogueLine {
|
|
3
|
+
readonly type: "line";
|
|
4
|
+
readonly speaker: string;
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly voiceId: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DialoguePause {
|
|
9
|
+
readonly type: "pause";
|
|
10
|
+
readonly durationMs: number;
|
|
11
|
+
}
|
|
12
|
+
export type DialogueCue = DialogueLine | DialoguePause;
|
|
13
|
+
export interface CueSheet {
|
|
14
|
+
/** Ordered cues (lines and pauses). */
|
|
15
|
+
readonly cues: readonly DialogueCue[];
|
|
16
|
+
/** Speaker → voice mapping used. */
|
|
17
|
+
readonly cast: ReadonlyMap<string, string>;
|
|
18
|
+
/** Unique speakers found in the script. */
|
|
19
|
+
readonly speakers: readonly string[];
|
|
20
|
+
/** Parse warnings. */
|
|
21
|
+
readonly warnings: readonly DialogueWarning[];
|
|
22
|
+
}
|
|
23
|
+
export interface DialogueWarning {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
readonly message: string;
|
|
26
|
+
}
|
|
27
|
+
/** Explicit speaker → voice/preset mapping. */
|
|
28
|
+
export type CastMap = Record<string, string>;
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dialogue/types.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,aAAa,CAAC;AAEvD,MAAM,WAAW,QAAQ;IACvB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAC;IACtC,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,+CAA+C;AAC/C,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/dialogue/types.ts"],"names":[],"mappings":"AAAA,iEAAiE"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Emotion module — barrel exports. */
|
|
2
|
+
export { PUBLIC_EMOTIONS, type Emotion, type EmotionMapEntry, type EmotionSpan, type EmotionWarning, type EmotionParseResult, } from "./types.js";
|
|
3
|
+
export { EMOTION_MAP, EMOTION_NAMES, EMOTION_SPEED_MIN, EMOTION_SPEED_MAX, clampEmotionSpeed, } from "./map.js";
|
|
4
|
+
export { hasEmotionTags, parseEmotionSpans, } from "./parser.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/emotion/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AAEvC,OAAO,EACL,eAAe,EACf,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Emotion module — barrel exports. */
|
|
2
|
+
export { PUBLIC_EMOTIONS, } from "./types.js";
|
|
3
|
+
export { EMOTION_MAP, EMOTION_NAMES, EMOTION_SPEED_MIN, EMOTION_SPEED_MAX, clampEmotionSpeed, } from "./map.js";
|
|
4
|
+
export { hasEmotionTags, parseEmotionSpans, } from "./parser.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/emotion/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AAEvC,OAAO,EACL,eAAe,GAMhB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,cAAc,EACd,iBAAiB,GAClB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Emotion map — maps each emotion to a voice ID and speed. */
|
|
2
|
+
import type { Emotion, EmotionMapEntry } from "./types.js";
|
|
3
|
+
/** Speed clamp range for emotion-derived speeds. */
|
|
4
|
+
export declare const EMOTION_SPEED_MIN = 0.85;
|
|
5
|
+
export declare const EMOTION_SPEED_MAX = 1.15;
|
|
6
|
+
/**
|
|
7
|
+
* Canonical emotion → voice/speed mapping.
|
|
8
|
+
*
|
|
9
|
+
* Preset field is informational only — the voiceId is always used directly.
|
|
10
|
+
* "friendly" and "professional" have no matching preset in the preset roster.
|
|
11
|
+
*/
|
|
12
|
+
export declare const EMOTION_MAP: Readonly<Record<Emotion, EmotionMapEntry>>;
|
|
13
|
+
/** All valid emotion names as a set for O(1) lookup. */
|
|
14
|
+
export declare const EMOTION_NAMES: ReadonlySet<string>;
|
|
15
|
+
/** Clamp a speed value to the emotion speed range. */
|
|
16
|
+
export declare function clampEmotionSpeed(speed: number): number;
|
|
17
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/emotion/map.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE3D,oDAAoD;AACpD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,CASlE,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,CAAqC,CAAC;AAEpF,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Emotion map — maps each emotion to a voice ID and speed. */
|
|
2
|
+
/** Speed clamp range for emotion-derived speeds. */
|
|
3
|
+
export const EMOTION_SPEED_MIN = 0.85;
|
|
4
|
+
export const EMOTION_SPEED_MAX = 1.15;
|
|
5
|
+
/**
|
|
6
|
+
* Canonical emotion → voice/speed mapping.
|
|
7
|
+
*
|
|
8
|
+
* Preset field is informational only — the voiceId is always used directly.
|
|
9
|
+
* "friendly" and "professional" have no matching preset in the preset roster.
|
|
10
|
+
*/
|
|
11
|
+
export const EMOTION_MAP = {
|
|
12
|
+
neutral: { preset: "default", voiceId: "bm_george", speed: 1.00 },
|
|
13
|
+
serious: { preset: "narrator", voiceId: "bm_george", speed: 1.00 },
|
|
14
|
+
friendly: { preset: undefined, voiceId: "am_liam", speed: 1.00 },
|
|
15
|
+
professional: { preset: undefined, voiceId: "af_jessica", speed: 1.00 },
|
|
16
|
+
calm: { preset: "narrator", voiceId: "bm_george", speed: 0.95 },
|
|
17
|
+
joy: { preset: undefined, voiceId: "am_eric", speed: 1.03 },
|
|
18
|
+
urgent: { preset: "announcer", voiceId: "am_fenrir", speed: 1.08 },
|
|
19
|
+
whisper: { preset: "storyteller", voiceId: "am_onyx", speed: 0.92 },
|
|
20
|
+
};
|
|
21
|
+
/** All valid emotion names as a set for O(1) lookup. */
|
|
22
|
+
export const EMOTION_NAMES = new Set(Object.keys(EMOTION_MAP));
|
|
23
|
+
/** Clamp a speed value to the emotion speed range. */
|
|
24
|
+
export function clampEmotionSpeed(speed) {
|
|
25
|
+
return Math.max(EMOTION_SPEED_MIN, Math.min(EMOTION_SPEED_MAX, speed));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/emotion/map.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAI/D,oDAAoD;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAA+C;IACrE,OAAO,EAAO,EAAE,MAAM,EAAE,SAAS,EAAM,OAAO,EAAE,WAAW,EAAG,KAAK,EAAE,IAAI,EAAE;IAC3E,OAAO,EAAO,EAAE,MAAM,EAAE,UAAU,EAAK,OAAO,EAAE,WAAW,EAAG,KAAK,EAAE,IAAI,EAAE;IAC3E,QAAQ,EAAM,EAAE,MAAM,EAAE,SAAS,EAAM,OAAO,EAAE,SAAS,EAAK,KAAK,EAAE,IAAI,EAAE;IAC3E,YAAY,EAAE,EAAE,MAAM,EAAE,SAAS,EAAM,OAAO,EAAE,YAAY,EAAG,KAAK,EAAE,IAAI,EAAE;IAC5E,IAAI,EAAU,EAAE,MAAM,EAAE,UAAU,EAAK,OAAO,EAAE,WAAW,EAAG,KAAK,EAAE,IAAI,EAAE;IAC3E,GAAG,EAAW,EAAE,MAAM,EAAE,SAAS,EAAM,OAAO,EAAE,SAAS,EAAK,KAAK,EAAE,IAAI,EAAE;IAC3E,MAAM,EAAQ,EAAE,MAAM,EAAE,WAAW,EAAI,OAAO,EAAE,WAAW,EAAG,KAAK,EAAE,IAAI,EAAE;IAC3E,OAAO,EAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAK,KAAK,EAAE,IAAI,EAAE;CAC5E,CAAC;AAEF,wDAAwD;AACxD,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAEpF,sDAAsD;AACtD,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Emotion span parser — extracts {emotion}...{/emotion} spans from text. */
|
|
2
|
+
import type { EmotionParseResult } from "./types.js";
|
|
3
|
+
/** Check whether text contains any emotion span tags. */
|
|
4
|
+
export declare function hasEmotionTags(text: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Parse emotion spans from text.
|
|
7
|
+
*
|
|
8
|
+
* Tagged regions become spans with the specified emotion's voice/speed.
|
|
9
|
+
* Untagged text becomes `neutral` spans.
|
|
10
|
+
* Mismatched open/close names are treated as literal text with a warning.
|
|
11
|
+
* Unsupported emotion names → `neutral` + `EMOTION_UNSUPPORTED` warning.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseEmotionSpans(text: string): EmotionParseResult;
|
|
14
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/emotion/parser.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAUpB,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CA2ElE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Emotion span parser — extracts {emotion}...{/emotion} spans from text. */
|
|
2
|
+
import { EMOTION_MAP, EMOTION_NAMES, clampEmotionSpeed } from "./map.js";
|
|
3
|
+
/**
|
|
4
|
+
* Regex to match `{emotion}text{/emotion}` spans.
|
|
5
|
+
* Captures: [1] emotion name (open), [2] inner text, [3] emotion name (close — must match [1]).
|
|
6
|
+
* Uses lazy match for inner text to handle adjacent spans correctly.
|
|
7
|
+
*/
|
|
8
|
+
const EMOTION_SPAN_RE = /\{(\w+)\}([\s\S]*?)\{\/(\w+)\}/g;
|
|
9
|
+
/** Check whether text contains any emotion span tags. */
|
|
10
|
+
export function hasEmotionTags(text) {
|
|
11
|
+
return /\{\w+\}/.test(text) && /\{\/\w+\}/.test(text);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse emotion spans from text.
|
|
15
|
+
*
|
|
16
|
+
* Tagged regions become spans with the specified emotion's voice/speed.
|
|
17
|
+
* Untagged text becomes `neutral` spans.
|
|
18
|
+
* Mismatched open/close names are treated as literal text with a warning.
|
|
19
|
+
* Unsupported emotion names → `neutral` + `EMOTION_UNSUPPORTED` warning.
|
|
20
|
+
*/
|
|
21
|
+
export function parseEmotionSpans(text) {
|
|
22
|
+
const spans = [];
|
|
23
|
+
const warnings = [];
|
|
24
|
+
let lastIndex = 0;
|
|
25
|
+
for (const match of text.matchAll(EMOTION_SPAN_RE)) {
|
|
26
|
+
const openName = match[1];
|
|
27
|
+
const innerText = match[2];
|
|
28
|
+
const closeName = match[3];
|
|
29
|
+
const matchStart = match.index;
|
|
30
|
+
// Emit any untagged text before this span as neutral
|
|
31
|
+
if (matchStart > lastIndex) {
|
|
32
|
+
const before = text.slice(lastIndex, matchStart).trim();
|
|
33
|
+
if (before) {
|
|
34
|
+
pushNeutralSpan(spans, before);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Validate open/close match
|
|
38
|
+
if (openName !== closeName) {
|
|
39
|
+
warnings.push({
|
|
40
|
+
code: "EMOTION_MISMATCH",
|
|
41
|
+
message: `Mismatched tags: {${openName}} closed by {/${closeName}}`,
|
|
42
|
+
});
|
|
43
|
+
// Treat entire match as literal text in a neutral span
|
|
44
|
+
pushNeutralSpan(spans, match[0]);
|
|
45
|
+
lastIndex = matchStart + match[0].length;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const trimmed = innerText.trim();
|
|
49
|
+
if (!trimmed) {
|
|
50
|
+
lastIndex = matchStart + match[0].length;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Resolve emotion
|
|
54
|
+
let emotion;
|
|
55
|
+
if (EMOTION_NAMES.has(openName)) {
|
|
56
|
+
emotion = openName;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
warnings.push({
|
|
60
|
+
code: "EMOTION_UNSUPPORTED",
|
|
61
|
+
message: `Unknown emotion "${openName}", falling back to neutral`,
|
|
62
|
+
});
|
|
63
|
+
emotion = "neutral";
|
|
64
|
+
}
|
|
65
|
+
const entry = EMOTION_MAP[emotion];
|
|
66
|
+
spans.push({
|
|
67
|
+
emotion,
|
|
68
|
+
text: trimmed,
|
|
69
|
+
voiceId: entry.voiceId,
|
|
70
|
+
speed: clampEmotionSpeed(entry.speed),
|
|
71
|
+
});
|
|
72
|
+
lastIndex = matchStart + match[0].length;
|
|
73
|
+
}
|
|
74
|
+
// Any trailing untagged text
|
|
75
|
+
if (lastIndex < text.length) {
|
|
76
|
+
const tail = text.slice(lastIndex).trim();
|
|
77
|
+
if (tail) {
|
|
78
|
+
pushNeutralSpan(spans, tail);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Edge case: no spans parsed and no tags matched at all → entire text is neutral
|
|
82
|
+
if (spans.length === 0 && lastIndex === 0 && text.trim()) {
|
|
83
|
+
pushNeutralSpan(spans, text.trim());
|
|
84
|
+
}
|
|
85
|
+
return { spans, warnings };
|
|
86
|
+
}
|
|
87
|
+
function pushNeutralSpan(spans, text) {
|
|
88
|
+
const entry = EMOTION_MAP.neutral;
|
|
89
|
+
spans.push({
|
|
90
|
+
emotion: "neutral",
|
|
91
|
+
text,
|
|
92
|
+
voiceId: entry.voiceId,
|
|
93
|
+
speed: clampEmotionSpeed(entry.speed),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/emotion/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Emotion types — emotion span parsing and voice/speed mapping. */
|
|
2
|
+
export declare const PUBLIC_EMOTIONS: readonly ["neutral", "serious", "friendly", "professional", "calm", "joy", "urgent", "whisper"];
|
|
3
|
+
export type Emotion = (typeof PUBLIC_EMOTIONS)[number];
|
|
4
|
+
export interface EmotionMapEntry {
|
|
5
|
+
/** Preset name hint (undefined if no matching preset). */
|
|
6
|
+
readonly preset: string | undefined;
|
|
7
|
+
/** Resolved voice ID from the approved roster. */
|
|
8
|
+
readonly voiceId: string;
|
|
9
|
+
/** Base speed multiplier for this emotion. */
|
|
10
|
+
readonly speed: number;
|
|
11
|
+
}
|
|
12
|
+
export interface EmotionSpan {
|
|
13
|
+
readonly emotion: Emotion;
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly voiceId: string;
|
|
16
|
+
readonly speed: number;
|
|
17
|
+
}
|
|
18
|
+
export interface EmotionWarning {
|
|
19
|
+
readonly code: string;
|
|
20
|
+
readonly message: string;
|
|
21
|
+
}
|
|
22
|
+
export interface EmotionParseResult {
|
|
23
|
+
readonly spans: readonly EmotionSpan[];
|
|
24
|
+
readonly warnings: readonly EmotionWarning[];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/emotion/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,eAAO,MAAM,eAAe,iGASlB,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/emotion/types.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,SAAS;IACT,SAAS;IACT,UAAU;IACV,cAAc;IACd,MAAM;IACN,KAAK;IACL,QAAQ;IACR,SAAS;CACD,CAAC"}
|