ssml-builder-js 2.8.1 → 2.10.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.
@@ -959,6 +959,11 @@ var SSML_ATTRIBUTE_PRESETS = {
959
959
  "mstts:audioduration": {
960
960
  value: AUDIO_DURATION_PRESETS
961
961
  },
962
+ "mstts:backgroundaudio": {
963
+ volume: PROSODY_VOLUME_PRESETS,
964
+ fadein: AUDIO_DURATION_PRESETS,
965
+ fadeout: AUDIO_DURATION_PRESETS
966
+ },
962
967
  silence: {
963
968
  type: SILENCE_TYPE_PRESETS,
964
969
  value: SILENCE_VALUE_PRESETS
@@ -1343,7 +1348,13 @@ var SSML_TAGS = {
1343
1348
  SILENCE: "silence",
1344
1349
  MSTTS_VISEME: "mstts:viseme",
1345
1350
  VISEME: "viseme",
1346
- MSTTS_AUDIO_DURATION: "mstts:audioduration"
1351
+ MSTTS_AUDIO_DURATION: "mstts:audioduration",
1352
+ MSTTS_DIALOG: "mstts:dialog",
1353
+ MSTTS_TURN: "mstts:turn",
1354
+ MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
1355
+ MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
1356
+ MSTTS_EMBEDDING: "mstts:embedding",
1357
+ MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
1347
1358
  };
1348
1359
  var SSML_ATTRS = {
1349
1360
  VERSION: "version",
@@ -1352,6 +1363,8 @@ var SSML_ATTRS = {
1352
1363
  LANG: "lang",
1353
1364
  MSTTS_XMLNS: "xmlns:mstts",
1354
1365
  NAME: "name",
1366
+ VOICE: "voice",
1367
+ SPEAKER: "speaker",
1355
1368
  EFFECT: "effect",
1356
1369
  RATE: "rate",
1357
1370
  PITCH: "pitch",
@@ -1382,8 +1395,15 @@ var SSML_ATTRS = {
1382
1395
  ALIAS: "alias",
1383
1396
  MARK: "mark",
1384
1397
  URI: "uri",
1398
+ ID: "id",
1399
+ MODEL: "model",
1400
+ PROFILE: "profile",
1401
+ URL: "url",
1402
+ SPEAKER_PROFILE_ID: "speakerProfileId",
1385
1403
  TYPE: "type",
1386
- VALUE: "value"
1404
+ VALUE: "value",
1405
+ FADE_IN: "fadein",
1406
+ FADE_OUT: "fadeout"
1387
1407
  };
1388
1408
  function escapeText(value) {
1389
1409
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1473,6 +1493,30 @@ function getAttributes(element) {
1473
1493
  case SSML_TAGS.MSTTS_AUDIO_DURATION:
1474
1494
  addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
1475
1495
  break;
1496
+ case SSML_TAGS.MSTTS_TURN:
1497
+ addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
1498
+ addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
1499
+ break;
1500
+ case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
1501
+ addAttribute(attributes, SSML_ATTRS.SRC, element.src);
1502
+ addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
1503
+ addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
1504
+ addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
1505
+ break;
1506
+ case SSML_TAGS.MSTTS_DIALOG:
1507
+ break;
1508
+ case SSML_TAGS.MSTTS_TTS_EMBEDDING:
1509
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1510
+ break;
1511
+ case SSML_TAGS.MSTTS_EMBEDDING:
1512
+ addAttribute(attributes, SSML_ATTRS.ID, element.id);
1513
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1514
+ break;
1515
+ case SSML_TAGS.MSTTS_VOICE_CONVERSION:
1516
+ addAttribute(attributes, SSML_ATTRS.URL, element.url);
1517
+ addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
1518
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1519
+ break;
1476
1520
  case SSML_TAGS.PARAGRAPH:
1477
1521
  case SSML_TAGS.SENTENCE:
1478
1522
  case SSML_TAGS.WORD:
@@ -2037,6 +2081,54 @@ function convertElement(node) {
2037
2081
  if (value !== void 0) element.value = value;
2038
2082
  return finishElement(element, node, attributes);
2039
2083
  }
2084
+ case SSML_TAGS.MSTTS_DIALOG: {
2085
+ const element = { type: SSML_TAGS.MSTTS_DIALOG };
2086
+ return finishElement(element, node, attributes);
2087
+ }
2088
+ case SSML_TAGS.MSTTS_TURN: {
2089
+ const element = { type: SSML_TAGS.MSTTS_TURN };
2090
+ const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
2091
+ const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
2092
+ if (voice !== void 0) element.voice = voice;
2093
+ if (speaker !== void 0) element.speaker = speaker;
2094
+ return finishElement(element, node, attributes);
2095
+ }
2096
+ case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
2097
+ const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
2098
+ const src = readAttribute(attributes, SSML_ATTRS.SRC);
2099
+ const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
2100
+ const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
2101
+ const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
2102
+ if (src !== void 0) element.src = src;
2103
+ if (volume !== void 0) element.volume = volume;
2104
+ if (fadeIn !== void 0) element.fadeIn = fadeIn;
2105
+ if (fadeOut !== void 0) element.fadeOut = fadeOut;
2106
+ return finishElement(element, node, attributes);
2107
+ }
2108
+ case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
2109
+ const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
2110
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2111
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2112
+ return finishElement(element, node, attributes);
2113
+ }
2114
+ case SSML_TAGS.MSTTS_EMBEDDING: {
2115
+ const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
2116
+ const id = readAttribute(attributes, SSML_ATTRS.ID);
2117
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2118
+ if (id !== void 0) element.id = id;
2119
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2120
+ return finishElement(element, node, attributes);
2121
+ }
2122
+ case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
2123
+ const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
2124
+ const url = readAttribute(attributes, SSML_ATTRS.URL);
2125
+ const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
2126
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2127
+ if (url !== void 0) element.url = url;
2128
+ if (profile !== void 0) element.profile = profile;
2129
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2130
+ return finishElement(element, node, attributes);
2131
+ }
2040
2132
  default: {
2041
2133
  const element = {
2042
2134
  name: node.name,
@@ -2157,6 +2249,683 @@ function validateSsml(xmlString) {
2157
2249
  };
2158
2250
  }
2159
2251
  }
2252
+ var AZURE_VOICE_DEFINITIONS = [
2253
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
2254
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
2255
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
2256
+ {
2257
+ name: "en-US-GuyNeural",
2258
+ locale: "en-US",
2259
+ styles: [
2260
+ "angry",
2261
+ "cheerful",
2262
+ "excited",
2263
+ "friendly",
2264
+ "hopeful",
2265
+ "newscast",
2266
+ "sad",
2267
+ "shouting",
2268
+ "terrified",
2269
+ "unfriendly",
2270
+ "whispering"
2271
+ ]
2272
+ },
2273
+ {
2274
+ name: "en-US-JennyMultilingualNeural",
2275
+ locale: "en-US",
2276
+ styles: [
2277
+ "cheerful",
2278
+ "empathetic",
2279
+ "excited",
2280
+ "friendly",
2281
+ "hopeful",
2282
+ "sad",
2283
+ "shouting",
2284
+ "terrified",
2285
+ "unfriendly",
2286
+ "whispering"
2287
+ ]
2288
+ },
2289
+ {
2290
+ name: "en-US-JennyNeural",
2291
+ locale: "en-US",
2292
+ styles: [
2293
+ "assistant",
2294
+ "chat",
2295
+ "customerservice",
2296
+ "newscast",
2297
+ "cheerful",
2298
+ "empathetic",
2299
+ "excited",
2300
+ "friendly",
2301
+ "hopeful",
2302
+ "sad",
2303
+ "shouting",
2304
+ "terrified",
2305
+ "unfriendly",
2306
+ "whispering"
2307
+ ]
2308
+ },
2309
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
2310
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
2311
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2312
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2313
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
2314
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
2315
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
2316
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
2317
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
2318
+ { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
2319
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" },
2320
+ { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
2321
+ {
2322
+ name: "ru-RU-SvetlanaNeural",
2323
+ locale: "ru-RU",
2324
+ styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
2325
+ },
2326
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
2327
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
2328
+ {
2329
+ name: "zh-CN-XiaoxiaoNeural",
2330
+ locale: "zh-CN",
2331
+ styles: [
2332
+ "assistant",
2333
+ "chat",
2334
+ "customerservice",
2335
+ "newscast",
2336
+ "cheerful",
2337
+ "empathetic",
2338
+ "excited",
2339
+ "friendly",
2340
+ "hopeful",
2341
+ "sad",
2342
+ "terrified",
2343
+ "whispering",
2344
+ "poetry-reading",
2345
+ "sports_commentary",
2346
+ "sports_commentary_excited",
2347
+ "story"
2348
+ ]
2349
+ },
2350
+ {
2351
+ name: "zh-CN-YunxiNeural",
2352
+ locale: "zh-CN",
2353
+ styles: [
2354
+ "narration-relaxed",
2355
+ "embarrassed",
2356
+ "fearful",
2357
+ "sad",
2358
+ "disgruntled",
2359
+ "serious",
2360
+ "angry",
2361
+ "depressed",
2362
+ "chat",
2363
+ "cheerful",
2364
+ "assistant"
2365
+ ]
2366
+ },
2367
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
2368
+ ];
2369
+ var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
2370
+ var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
2371
+ "characters",
2372
+ "spell-out",
2373
+ "cardinal",
2374
+ "ordinal",
2375
+ "number",
2376
+ "date",
2377
+ "time",
2378
+ "telephone",
2379
+ "fraction",
2380
+ "address",
2381
+ "name",
2382
+ "currency",
2383
+ "number_digit"
2384
+ ]);
2385
+ var ALLOWED_ROLES = /* @__PURE__ */ new Set([
2386
+ "Girl",
2387
+ "Boy",
2388
+ "YoungAdultFemale",
2389
+ "YoungAdultMale",
2390
+ "OlderAdultFemale",
2391
+ "OlderAdultMale",
2392
+ "SeniorFemale",
2393
+ "SeniorMale"
2394
+ ]);
2395
+ var ALLOWED_EMPHASIS_LEVELS = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
2396
+ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
2397
+ "Leading",
2398
+ "Tailing",
2399
+ "Sentenceboundary",
2400
+ "Comma",
2401
+ "Semicolon",
2402
+ "Enumerationcomma"
2403
+ ]);
2404
+ var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
2405
+ function decodeAttribute(value) {
2406
+ return value.replace(
2407
+ /&(?:amp|apos|gt|lt|quot);/gi,
2408
+ (entity) => ({ "&amp;": "&", "&apos;": "'", "&gt;": ">", "&lt;": "<", "&quot;": '"' })[entity.toLowerCase()] ?? entity
2409
+ );
2410
+ }
2411
+ function findTagEnd2(source, start) {
2412
+ let quote = "";
2413
+ for (let index = start; index < source.length; index += 1) {
2414
+ const character = source[index];
2415
+ if (quote) {
2416
+ if (character === quote) quote = "";
2417
+ } else if (character === '"' || character === "'") quote = character;
2418
+ else if (character === ">") return index;
2419
+ }
2420
+ return source.length - 1;
2421
+ }
2422
+ function tokenizeElements(source) {
2423
+ const tokens = [];
2424
+ const openElements = [];
2425
+ let index = 0;
2426
+ while (index < source.length) {
2427
+ const start = source.indexOf("<", index);
2428
+ if (start === -1) break;
2429
+ if (source.startsWith("<!--", start)) {
2430
+ const end2 = source.indexOf("-->", start + 4);
2431
+ index = end2 === -1 ? source.length : end2 + 3;
2432
+ continue;
2433
+ }
2434
+ if (source.startsWith("<![CDATA[", start)) {
2435
+ const end2 = source.indexOf("]]>", start + 9);
2436
+ index = end2 === -1 ? source.length : end2 + 3;
2437
+ continue;
2438
+ }
2439
+ if (source.startsWith("<?", start)) {
2440
+ const end2 = source.indexOf("?>", start + 2);
2441
+ index = end2 === -1 ? source.length : end2 + 2;
2442
+ continue;
2443
+ }
2444
+ const end = findTagEnd2(source, start + 1);
2445
+ const raw = source.slice(start, end + 1);
2446
+ if (raw.startsWith("</")) {
2447
+ openElements.pop();
2448
+ index = end + 1;
2449
+ continue;
2450
+ }
2451
+ const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
2452
+ if (!nameMatch?.[1]) {
2453
+ index = end + 1;
2454
+ continue;
2455
+ }
2456
+ const attributes = /* @__PURE__ */ new Map();
2457
+ const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
2458
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
2459
+ for (const match of attributeSource.matchAll(attributePattern)) {
2460
+ attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
2461
+ }
2462
+ const selfClosing = /\/\s*>$/.test(raw);
2463
+ const parent = openElements[openElements.length - 1];
2464
+ const childElementIndex = parent?.childElementCount;
2465
+ if (parent) parent.childElementCount += 1;
2466
+ const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
2467
+ const tokenName = nameMatch[1];
2468
+ const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
2469
+ tokens.push({
2470
+ attributes,
2471
+ childElementIndex,
2472
+ end,
2473
+ name: tokenName,
2474
+ parentName: parent?.name,
2475
+ parentVoiceName,
2476
+ selfClosing,
2477
+ start
2478
+ });
2479
+ if (!selfClosing) {
2480
+ openElements.push({
2481
+ childElementCount: 0,
2482
+ name: tokenName,
2483
+ voiceName: tokenVoiceName
2484
+ });
2485
+ }
2486
+ index = end + 1;
2487
+ }
2488
+ return tokens;
2489
+ }
2490
+ function location(source, offset) {
2491
+ const before = source.slice(0, Math.max(0, offset));
2492
+ const line = before.split("\n").length;
2493
+ return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
2494
+ }
2495
+ function addDiagnostic(diagnostics, source, offset, message, severity = "error", code2) {
2496
+ diagnostics.push({
2497
+ ...location(source, offset),
2498
+ message,
2499
+ severity,
2500
+ source: "ssml-static-validator",
2501
+ ...code2 ? { code: code2 } : {}
2502
+ });
2503
+ }
2504
+ function isSupportedProsodyRate(value) {
2505
+ const trimmed = value.trim();
2506
+ if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
2507
+ const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
2508
+ if (!multiplier) return false;
2509
+ const numericValue = Number(multiplier[1]);
2510
+ return numericValue >= 0.5 && numericValue <= 2;
2511
+ }
2512
+ function isValidAzureAudioDuration(value) {
2513
+ const trimmed = value.trim();
2514
+ const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
2515
+ if (numeric) return Number(numeric[1]) > 0;
2516
+ const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
2517
+ if (!clock) return false;
2518
+ return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
2519
+ }
2520
+ function isValidAzureBackgroundAudioDuration(value) {
2521
+ const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
2522
+ if (!match) return false;
2523
+ const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
2524
+ return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
2525
+ }
2526
+ function attr(token, name) {
2527
+ return token.attributes.get(name.toLowerCase());
2528
+ }
2529
+ var DEFAULT_LANGUAGE_ALIASES = {
2530
+ "zh-CN": ["zh-Hans"],
2531
+ "zh-TW": ["zh-Hant"]
2532
+ };
2533
+ function canonicalLanguageTag(language) {
2534
+ const trimmed = language.trim();
2535
+ if (!trimmed) return "";
2536
+ try {
2537
+ return new Intl.Locale(trimmed).toString().toLowerCase();
2538
+ } catch {
2539
+ return trimmed.toLowerCase();
2540
+ }
2541
+ }
2542
+ function createLanguageNormalizer(options) {
2543
+ const aliases = /* @__PURE__ */ new Map();
2544
+ const addAliasGroup = (canonical, values) => {
2545
+ const normalizedCanonical = canonicalLanguageTag(canonical);
2546
+ if (!normalizedCanonical) return;
2547
+ aliases.set(normalizedCanonical, normalizedCanonical);
2548
+ for (const value of values) {
2549
+ const normalizedValue = canonicalLanguageTag(value);
2550
+ if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
2551
+ }
2552
+ };
2553
+ for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
2554
+ for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
2555
+ addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
2556
+ return (language) => {
2557
+ const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
2558
+ const normalized = canonicalLanguageTag(customValue);
2559
+ return aliases.get(normalized) ?? normalized;
2560
+ };
2561
+ }
2562
+ function voiceLocalePrefix(voiceName) {
2563
+ const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
2564
+ if (!match?.groups) return void 0;
2565
+ const tag = `${match.groups.language}-${match.groups.region}`;
2566
+ return {
2567
+ language: match.groups.language.toLowerCase(),
2568
+ region: match.groups.region.toLowerCase(),
2569
+ tag
2570
+ };
2571
+ }
2572
+ function definitionFromStyleMap(voiceName, styles) {
2573
+ return {
2574
+ name: voiceName,
2575
+ locale: voiceLocalePrefix(voiceName)?.tag ?? "",
2576
+ styles
2577
+ };
2578
+ }
2579
+ function normalizeVoiceCatalog(options) {
2580
+ const definitions = /* @__PURE__ */ new Map();
2581
+ for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
2582
+ for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
2583
+ for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
2584
+ for (const definition of options.customVoiceDefinitions ?? [])
2585
+ definitions.set(definition.name.toLowerCase(), definition);
2586
+ for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
2587
+ const key = voiceName.toLowerCase();
2588
+ const current = definitions.get(key);
2589
+ definitions.set(key, {
2590
+ ...current ?? definitionFromStyleMap(voiceName, styles),
2591
+ name: current?.name ?? voiceName,
2592
+ styles: styles.map((style) => style.toLowerCase())
2593
+ });
2594
+ }
2595
+ return definitions;
2596
+ }
2597
+ function diagnosticSeverity(policy) {
2598
+ if (policy === "ignore") return void 0;
2599
+ return policy === "error" ? "error" : "warning";
2600
+ }
2601
+ function languagePart(language) {
2602
+ try {
2603
+ return new Intl.Locale(language).language.toLowerCase();
2604
+ } catch {
2605
+ return language.split("-")[0]?.toLowerCase() ?? "";
2606
+ }
2607
+ }
2608
+ function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
2609
+ const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
2610
+ if (candidateLanguages.length === 0 || !language.trim()) return void 0;
2611
+ const normalizedLanguage = normalizeLanguage(language);
2612
+ const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
2613
+ if (normalizedCandidates.includes(normalizedLanguage)) return true;
2614
+ if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
2615
+ return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
2616
+ }
2617
+ function canonicalTagName(name) {
2618
+ const normalized = name.toLowerCase();
2619
+ if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
2620
+ if (normalized === "sayas") return "say-as";
2621
+ return normalized;
2622
+ }
2623
+ function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
2624
+ if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
2625
+ return;
2626
+ const tagName = canonicalTagName(token.name);
2627
+ const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
2628
+ const supportedTags = definition.supportedTags?.map(canonicalTagName);
2629
+ if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
2630
+ addDiagnostic(
2631
+ diagnostics,
2632
+ source,
2633
+ token.start,
2634
+ `Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
2635
+ "error",
2636
+ "azure-unsupported-tag-for-voice"
2637
+ );
2638
+ }
2639
+ }
2640
+ function validateAudioSource(token, source, diagnostics, options, elementName2) {
2641
+ const src = attr(token, "src");
2642
+ if (!src) {
2643
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
2644
+ return;
2645
+ }
2646
+ let parsed;
2647
+ try {
2648
+ parsed = new URL(src);
2649
+ } catch {
2650
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
2651
+ return;
2652
+ }
2653
+ if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
2654
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
2655
+ const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
2656
+ try {
2657
+ return new URL(allowedOrigin).origin === parsed.origin;
2658
+ } catch {
2659
+ return allowedOrigin === parsed.origin;
2660
+ }
2661
+ }) ?? false;
2662
+ if (options.allowedAudioOrigins && !isAllowedOrigin)
2663
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
2664
+ else if (!isAllowedOrigin && !options.allowExternalAudio)
2665
+ addDiagnostic(
2666
+ diagnostics,
2667
+ source,
2668
+ token.start,
2669
+ `<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
2670
+ );
2671
+ }
2672
+ function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
2673
+ const name = token.name.toLowerCase();
2674
+ if (name === "voice" && !attr(token, "name")?.trim())
2675
+ addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
2676
+ if (name === "break") {
2677
+ const time = attr(token, "time");
2678
+ const strength = attr(token, "strength");
2679
+ if (!time && !strength)
2680
+ addDiagnostic(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
2681
+ if (time && strength)
2682
+ addDiagnostic(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
2683
+ if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
2684
+ addDiagnostic(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
2685
+ if (strength && !ALLOWED_BREAK_STRENGTHS.has(strength))
2686
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
2687
+ }
2688
+ if (name === "prosody") {
2689
+ const rate = attr(token, "rate");
2690
+ const pitch = attr(token, "pitch");
2691
+ const volume = attr(token, "volume");
2692
+ if (rate && !isSupportedProsodyRate(rate))
2693
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
2694
+ if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
2695
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
2696
+ if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
2697
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
2698
+ }
2699
+ if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
2700
+ const style = attr(token, "style");
2701
+ if (!style?.trim())
2702
+ addDiagnostic(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
2703
+ const degree = attr(token, "styledegree") ?? attr(token, "style-degree");
2704
+ if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
2705
+ addDiagnostic(
2706
+ diagnostics,
2707
+ source,
2708
+ token.start,
2709
+ "<mstts:express-as styledegree> must be a number between 0.01 and 2."
2710
+ );
2711
+ const role = attr(token, "role");
2712
+ if (role && !ALLOWED_ROLES.has(role))
2713
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
2714
+ const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
2715
+ const supportedStyles = definition?.styles;
2716
+ const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
2717
+ if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
2718
+ addDiagnostic(
2719
+ diagnostics,
2720
+ source,
2721
+ token.start,
2722
+ `Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
2723
+ severity,
2724
+ "azure-unsupported-style"
2725
+ );
2726
+ if (style && voiceName && !definition && severity)
2727
+ addDiagnostic(
2728
+ diagnostics,
2729
+ source,
2730
+ token.start,
2731
+ `Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
2732
+ severity
2733
+ );
2734
+ }
2735
+ if (name === "say-as" || name === "sayas") {
2736
+ const interpretAs = attr(token, "interpret-as");
2737
+ if (!interpretAs || !ALLOWED_SAY_AS.has(interpretAs))
2738
+ addDiagnostic(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
2739
+ }
2740
+ if (name === "phoneme" && (!attr(token, "alphabet") || !attr(token, "ph")))
2741
+ addDiagnostic(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
2742
+ if (name === "emphasis" && attr(token, "level") && !ALLOWED_EMPHASIS_LEVELS.has(attr(token, "level") ?? ""))
2743
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr(token, "level")}".`);
2744
+ if (name === "sub" && !attr(token, "alias")?.trim())
2745
+ addDiagnostic(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
2746
+ if (name === "lang" && !attr(token, "xml:lang")?.trim() && !attr(token, "lang")?.trim())
2747
+ addDiagnostic(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
2748
+ if (name === "mark" && !attr(token, "name")?.trim())
2749
+ addDiagnostic(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
2750
+ if (name === "bookmark" && !attr(token, "mark")?.trim())
2751
+ addDiagnostic(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
2752
+ if (name === "lexicon") {
2753
+ const uri = attr(token, "uri");
2754
+ if (!uri) addDiagnostic(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
2755
+ else {
2756
+ try {
2757
+ const parsed = new URL(uri);
2758
+ if (parsed.protocol !== "https:")
2759
+ addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
2760
+ } catch {
2761
+ addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
2762
+ }
2763
+ }
2764
+ }
2765
+ if (name === "mstts:silence") {
2766
+ const type = attr(token, "type");
2767
+ const value = attr(token, "value");
2768
+ if (!type || !ALLOWED_SILENCE_TYPES.has(type))
2769
+ addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
2770
+ if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
2771
+ addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
2772
+ }
2773
+ if (name === "mstts:audioduration") {
2774
+ const value = attr(token, "value");
2775
+ if (!value || !isValidAzureAudioDuration(value))
2776
+ addDiagnostic(
2777
+ diagnostics,
2778
+ source,
2779
+ token.start,
2780
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
2781
+ );
2782
+ if (!token.selfClosing)
2783
+ addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
2784
+ }
2785
+ if (name === "mstts:viseme") {
2786
+ const type = attr(token, "type");
2787
+ if (!type || !ALLOWED_VISEME_TYPES.has(type))
2788
+ addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
2789
+ }
2790
+ if (name === "audio") {
2791
+ validateAudioSource(token, source, diagnostics, options, "audio");
2792
+ }
2793
+ if (name === "mstts:turn") {
2794
+ if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
2795
+ addDiagnostic(
2796
+ diagnostics,
2797
+ source,
2798
+ token.start,
2799
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
2800
+ );
2801
+ if (token.parentName?.toLowerCase() !== "mstts:dialog")
2802
+ addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
2803
+ }
2804
+ if (name === "mstts:backgroundaudio") {
2805
+ validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
2806
+ const volume = attr(token, "volume");
2807
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
2808
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
2809
+ for (const [attribute, value] of [
2810
+ ["fadein", attr(token, "fadein")],
2811
+ ["fadeout", attr(token, "fadeout")]
2812
+ ]) {
2813
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
2814
+ addDiagnostic(
2815
+ diagnostics,
2816
+ source,
2817
+ token.start,
2818
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
2819
+ );
2820
+ }
2821
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
2822
+ addDiagnostic(
2823
+ diagnostics,
2824
+ source,
2825
+ token.start,
2826
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
2827
+ );
2828
+ if (!token.selfClosing)
2829
+ addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
2830
+ }
2831
+ }
2832
+ function validateAzureSsml(ssml, options = {}) {
2833
+ const diagnostics = [];
2834
+ if (typeof ssml !== "string") {
2835
+ return [
2836
+ {
2837
+ line: 1,
2838
+ column: 1,
2839
+ message: "SSML input must be a string",
2840
+ severity: "error",
2841
+ source: "ssml-static-validator"
2842
+ }
2843
+ ];
2844
+ }
2845
+ const maxLength = options.maxLength ?? 1e4;
2846
+ if (ssml.length > maxLength)
2847
+ addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
2848
+ try {
2849
+ parseSsml(ssml);
2850
+ } catch (error) {
2851
+ const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
2852
+ const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
2853
+ addDiagnostic(diagnostics, ssml, match ? Number(match[1]) : 0, message);
2854
+ return diagnostics;
2855
+ }
2856
+ const tokens = tokenizeElements(ssml);
2857
+ const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
2858
+ const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
2859
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
2860
+ for (const [index, token] of backgroundAudioTokens.entries()) {
2861
+ if (index > 0)
2862
+ addDiagnostic(
2863
+ diagnostics,
2864
+ ssml,
2865
+ token.start,
2866
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
2867
+ );
2868
+ }
2869
+ if (!speak || voices.length === 0)
2870
+ addDiagnostic(
2871
+ diagnostics,
2872
+ ssml,
2873
+ speak?.start ?? 0,
2874
+ "Azure SSML requires at least one <voice> element under <speak>."
2875
+ );
2876
+ const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
2877
+ const voiceCatalog = normalizeVoiceCatalog(options);
2878
+ const normalizeLanguage = createLanguageNormalizer(options);
2879
+ const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
2880
+ const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
2881
+ for (const token of voicesToValidate) {
2882
+ const name = attr(token, "name")?.trim();
2883
+ const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
2884
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
2885
+ if (name && !definition && policySeverity)
2886
+ addDiagnostic(
2887
+ diagnostics,
2888
+ ssml,
2889
+ token.start,
2890
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
2891
+ policySeverity,
2892
+ "azure-unknown-voice"
2893
+ );
2894
+ if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
2895
+ addDiagnostic(
2896
+ diagnostics,
2897
+ ssml,
2898
+ token.start,
2899
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
2900
+ "warning",
2901
+ "azure-locale-mismatch"
2902
+ );
2903
+ }
2904
+ for (const token of tokens) {
2905
+ const tokenName = token.name.toLowerCase();
2906
+ const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
2907
+ validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
2908
+ const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
2909
+ validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
2910
+ if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
2911
+ addDiagnostic(
2912
+ diagnostics,
2913
+ ssml,
2914
+ token.start,
2915
+ `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
2916
+ "error",
2917
+ "azure-unsupported-model-for-voice"
2918
+ );
2919
+ }
2920
+ }
2921
+ return diagnostics;
2922
+ }
2923
+ var AZURE_VOICE_CATALOG_METADATA = {
2924
+ apiVersion: "2025-10-01",
2925
+ generatedAt: "2026-08-28T00:00:00.000Z",
2926
+ regions: [],
2927
+ voiceCount: AZURE_VOICE_DEFINITIONS.length
2928
+ };
2160
2929
 
2161
2930
  // packages/ssml-editor-react/src/clearSsmlDocument.ts
2162
2931
  function getDocumentChildren(document) {
@@ -2281,7 +3050,7 @@ function validateEntityReferences(value) {
2281
3050
  index = semicolon + 1;
2282
3051
  }
2283
3052
  }
2284
- function findTagEnd2(source, start, hasInternalSubset = false) {
3053
+ function findTagEnd3(source, start, hasInternalSubset = false) {
2285
3054
  let quote;
2286
3055
  let subsetDepth = 0;
2287
3056
  for (let index = start + 1; index < source.length; index += 1) {
@@ -2454,7 +3223,7 @@ function parseXml(source) {
2454
3223
  continue;
2455
3224
  }
2456
3225
  if (source.startsWith("</", index)) {
2457
- const end2 = findTagEnd2(source, index);
3226
+ const end2 = findTagEnd3(source, index);
2458
3227
  const name2 = parseClosingTag(source, index, end2);
2459
3228
  const element = currentElement(stack);
2460
3229
  if (element === void 0 || element.name !== name2) {
@@ -2466,7 +3235,7 @@ function parseXml(source) {
2466
3235
  continue;
2467
3236
  }
2468
3237
  if (source.startsWith("<!", index)) {
2469
- const end2 = findTagEnd2(source, index, true);
3238
+ const end2 = findTagEnd3(source, index, true);
2470
3239
  parseDeclaration(source, index, end2);
2471
3240
  appendNode2(currentElement(stack)?.children ?? children, {
2472
3241
  kind: "declaration",
@@ -2475,7 +3244,7 @@ function parseXml(source) {
2475
3244
  index = end2 + 1;
2476
3245
  continue;
2477
3246
  }
2478
- const end = findTagEnd2(source, index);
3247
+ const end = findTagEnd3(source, index);
2479
3248
  const { name, selfClosing } = parseStartTag(source, index, end);
2480
3249
  if (stack.length === 0) {
2481
3250
  if (root !== void 0) {
@@ -2942,6 +3711,24 @@ var SSML_COMPLETION_SNIPPETS = [
2942
3711
  label: "mstts:audioduration",
2943
3712
  insertText: '<mstts:audioduration value="10s" />'
2944
3713
  },
3714
+ {
3715
+ label: "mstts:dialog",
3716
+ insertText: `<mstts:dialog>
3717
+ <mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
3718
+ </mstts:dialog>`
3719
+ },
3720
+ {
3721
+ label: "mstts:turn",
3722
+ insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
3723
+ },
3724
+ {
3725
+ label: "mstts:backgroundaudio",
3726
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
3727
+ },
3728
+ {
3729
+ label: "mstts:ttsembedding",
3730
+ insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
3731
+ },
2945
3732
  {
2946
3733
  label: "sub",
2947
3734
  insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
@@ -3312,6 +4099,63 @@ var SSML_TAG_DEFINITIONS = [
3312
4099
  example: "10s"
3313
4100
  }
3314
4101
  ]
4102
+ },
4103
+ {
4104
+ name: "mstts:dialog",
4105
+ description: "Groups multiple Azure dialog turns that can use different voices.",
4106
+ parameters: []
4107
+ },
4108
+ {
4109
+ name: "mstts:turn",
4110
+ description: "Adds one dialog turn using the required Azure voice name.",
4111
+ parameters: [
4112
+ {
4113
+ name: "voice",
4114
+ description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
4115
+ example: "en-US-JennyNeural"
4116
+ }
4117
+ ]
4118
+ },
4119
+ {
4120
+ name: "mstts:backgroundaudio",
4121
+ description: "Plays background audio while speech is synthesized.",
4122
+ parameters: [
4123
+ {
4124
+ name: "src",
4125
+ description: "An absolute HTTP(S) URL for the background audio file.",
4126
+ example: "https://example.com/music.mp3"
4127
+ },
4128
+ {
4129
+ name: "volume",
4130
+ description: "The background audio volume, for example `-3dB` or `medium`.",
4131
+ example: "-3dB"
4132
+ },
4133
+ {
4134
+ name: "fadein",
4135
+ description: "The fade-in duration, for example `1s`.",
4136
+ example: "1s"
4137
+ },
4138
+ {
4139
+ name: "fadeout",
4140
+ description: "The fade-out duration, for example `500ms`.",
4141
+ example: "500ms"
4142
+ }
4143
+ ]
4144
+ },
4145
+ {
4146
+ name: "mstts:ttsembedding",
4147
+ description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
4148
+ parameters: []
4149
+ },
4150
+ {
4151
+ name: "mstts:embedding",
4152
+ description: "Specifies embedding metadata for custom voice scenarios.",
4153
+ parameters: []
4154
+ },
4155
+ {
4156
+ name: "mstts:voiceconversion",
4157
+ description: "Specifies voice conversion metadata for custom voice scenarios.",
4158
+ parameters: []
3315
4159
  }
3316
4160
  ];
3317
4161
  var definitionsByName = /* @__PURE__ */ new Map();
@@ -3370,7 +4214,7 @@ function toRange(source, token) {
3370
4214
  function containsOffset(token, offset) {
3371
4215
  return offset >= token.start && offset < token.end;
3372
4216
  }
3373
- function findTagEnd3(source, start) {
4217
+ function findTagEnd4(source, start) {
3374
4218
  let quote;
3375
4219
  for (let index = start; index < source.length; index += 1) {
3376
4220
  const character = source[index];
@@ -3501,7 +4345,7 @@ function findTagAtOffset(source, offset) {
3501
4345
  searchStart = tokenEnd2;
3502
4346
  continue;
3503
4347
  }
3504
- const tagEnd = findTagEnd3(source, start + 1);
4348
+ const tagEnd = findTagEnd4(source, start + 1);
3505
4349
  const contentEnd = tagEnd ?? source.length;
3506
4350
  const tokenEnd = tagEnd === void 0 ? source.length : tagEnd + 1;
3507
4351
  if (offset < tokenEnd) {
@@ -3650,6 +4494,7 @@ export {
3650
4494
  parseSsml,
3651
4495
  buildPartialSsml,
3652
4496
  validateSsml,
4497
+ validateAzureSsml,
3653
4498
  clearSsmlDocument,
3654
4499
  formatXmlFragment,
3655
4500
  getEditableRegion,
@@ -3663,4 +4508,4 @@ export {
3663
4508
  findSsmlHoverTarget,
3664
4509
  formatSsmlHover
3665
4510
  };
3666
- //# sourceMappingURL=chunk-MWSDFGXW.mjs.map
4511
+ //# sourceMappingURL=chunk-BYIZQL2W.mjs.map