Skip to content

feat(tts): 新增单句声音小样试听脚本,声音克隆手册补齐试听章节 - #1107

Merged
ThreeFish-AI merged 8 commits into
feature/1.x.xfrom
ThreeFish-AI/voice-clone-sample-guide
Aug 19, 2026
Merged

feat(tts): 新增单句声音小样试听脚本,声音克隆手册补齐试听章节#1107
ThreeFish-AI merged 8 commits into
feature/1.x.xfrom
ThreeFish-AI/voice-clone-sample-guide

Conversation

@ThreeFish-AI

@ThreeFish-AI ThreeFish-AI commented Aug 19, 2026

Copy link
Copy Markdown
Owner

背景

核心变更

  • 新增 media/pipeline/scripts/tts_sample.py(329 行):直调 tts_server.py 合成单句小样,不需要 narration.json、不需要视频工程。--style 单档 / --all-styles 五风格 A/B / --dry-run 秒级核参 / --play 顺序试听 / --text-file 自带文本。
  • 零重复的 SSOT 复用:风格预设 STYLE_PRESETS、风格解析 resolve_style()、口播文本预处理 tts_text()——)、http_synthesize()http_json()mp3_duration() 全部从 tts.py 导入——文档里不再抄第二份情感向量副本,且小样与成片走完全相同的合成路径,听感可直接外推。
  • 前置校验与 tts.py 同口径(样本存在性、Σvec×alpha ≤ 0.8、v2.5 语速支持、参数互斥、缺 mutagen 提前失败):单档合成 20 秒起,绝不让用户等到合成结束才报错。
  • VOICE-CLONING.md §五「逐集使用」扩写为「小样试听与逐集合成」5.1 小样试听(四步操作 + 开关速查表 + 实测耗时表 + 纯 curl 直调附录)/ 5.2 全量合成 / 5.3 合成后重渲染;§3.2 增补滑窗 RMS + 静音占比选段辅助,并把裁剪示例改为已上线三集成片的同源样本;§一 架构图补试听客户端节点,§4.4 指向 5.1。
  • media/pipeline/README.md 公共脚本表补 tts_sample.py 一行(脚本清单是 SSOT,不能漏)。
  • 不做章节重编号:六~九章标题原样保留,故 6 处外部 §七/§八 引用(pipeline README、voices README、三集 README、tts.py:342)全部仍然有效——以 1 个文件的改动面兑现结构收益。

风险与回滚

  • 主要风险:极低。纯新增脚本 + 文档,未触碰 tts.py / tts_server.py 任何一行,edgeindextts 两条既有合成链路字节级不变,Remotion 的 manifest.json 契约不变。唯一耦合是 tts_sample.pytts.py 的模块导入——若后者重命名符号会连带失效(已由 --dry-run 秒级自检覆盖);产物只写 .temp/voice-samples/.gitignore 全深度忽略),不进任何工程目录。
  • 回滚方式git revert dc78f445 即可,无数据/无迁移/无配置依赖;或单独删除新脚本,文档 §5.1 退化为纯 curl 手册仍可用。

验证证据

  • 单元测试:未新增——media/pipeline/ 五个脚本历史上零测试、且不在 pre-commit ruff 范围(files: ^apps/negentropy/);本 PR 不打破该边界,改以真实 E2E 自证(下)。ruff@0.14.14 check + format 对新脚本均已通过(最长行 121,符合仓库 120 口径)。
  • 参数正确性--dry-run --all-styles 输出五档解析结果,与手册 §4.1 表逐项一致——neutral(无向量/1.0/1.0)、passionate(.70/.20/.10, 0.7, 0.97)、lively(.55/.15/.15, 0.6, 0.95)、confident(.25/.65, 0.7, 1.05)、positive(.75/.20, 0.7, 1.0)。
  • E2E 实测(2026-08-19 · M3 · MPS fp32 · num_beams=1 · 34 字文本):五风格全跑成功,5 个 MP3 均 22.05 kHz、非静音、SHA1 互不相同;单档暖机后 6.0–6.9s 音频 / 20–22s 墙钟(RTF 3.2–3.4),首档含服务暖机 47.0s,五档合计仅 2.2 分钟
  • 负例(7/7 给出可操作报错):样本不存在、服务不可达(回显完整启动命令 + §2.3)、有效和超界(1.400 > 0.8)、--all-styles 与手动覆盖互斥、--style--emo-vector 互斥、未知情感键、缺 mutagen
  • 文档可执行性:§5.1 附录的 curl recipe 与 §3.2 的 RMS 选段片段均逐字执行验证——前者返回 200 / x-audio-format: mp3 / x-duration-sec: 5.770,4xx 时 cat 出的 detail 与文档描述一致;后者正常输出 Top-8 候选起点。
  • 溯源考证:文档中「--start 180 --duration 12 即成片同源样本」并非推测——用 digest_indextts()rome-v1 工作区 p0-01.sha 穷举 ref×style×beams×tag,唯一命中 ref_sha1=3ed0d9d60d4b / passionate / beams=1;再用互相关在 264s 源录音上定位偏移 = 180s(MSE 8.9e-10);本次重裁结果 SHA1 逐字节一致。
  • 口径校准:实测暴露原手册数据会被误读——小样 RTF≈3.3 与 §4.3b 整集折算 RTF≈12–14 是两个口径(后者含数小时长跑的降频、机器争用与逐句开销),已在 §5.1 加「口径提醒」并显式声明不可线性外推。

影响范围

  • 前端:无。
  • 后端:无(apps/negentropy 零改动)。
  • GitHub Actions / 文档:CI 无改动(新脚本不在任何 lint/test 范围)。文档为 media/pipeline/ 内 2 个文件;docs/.agents/knowledge-map.md 无需同步(未新增文档文件,仅既有手册内扩章);CHANGELOG 未改动,沿用前一次纯管线文档提交 f7d7281 的先例。

Next Best Action

  • 合并后先跑一次 --all-styles --play 听五档,确认 passionate 仍是科普长视频最优档(该结论目前只有三集成片的主观样本支撑);
  • 若某档试听后被长期弃用,回头精简 §4.1 预设表,避免 A/B 空耗 20 秒/档;
  • 可选增强(本次按 YAGNI 未做):--compare-with <成片 mp3> 直接与既有成片同句对拍,把「像不像成片」从主观听感变成可复核的 A/B。

新增 media/pipeline/scripts/tts_sample.py:直调 IndexTTS 服务合成单句小样,
不需要 narration.json 与视频工程。支持 --style 单档 / --all-styles 五风格 A/B /
--dry-run 秒级核参 / --play 顺序试听;风格预设、口播文本预处理与 HTTP 契约全部
复用 tts.py(单一事实源),故小样与成片走完全相同的合成路径、听感可直接外推。
产物落 .temp/voice-samples/(已被根 .gitignore 忽略;含本人音色,脚本提示试听后清理)。
前置校验与 tts.py 同口径(样本存在性、情感有效和 ≤0.8、v2.5 语速支持、参数互斥),
避免等到两分钟合成结束才报错。

VOICE-CLONING.md 原 §五「逐集使用」扩写为「小样试听与逐集合成」:5.1 小样试听
(四步操作 + 开关速查 + 实测耗时 + 纯 HTTP curl 直调附录)兑现原文里「小样试听」
的空承诺,5.2 全量合成、5.3 合成后重渲染;§3.2 增补滑窗 RMS/静音占比选段辅助,
并把裁剪示例改为已上线三集成片的同源样本(--start 180 --duration 12,sha1
3ed0d9d60d4b,由音频缓存 sidecar 摘要反查确认);§一 架构图补试听客户端节点,
§4.4 指向 5.1。六~九章编号不动,6 处外部 §七/§八 引用保持有效。

实测(2026-08-19 · M3 · MPS fp32 · num_beams=1 · 34 字文本):单档暖机后
6.0–6.9s 音频 / 20–22s 墙钟(RTF 3.2–3.4;首档含暖机 47.0s),五风格 A/B 全跑
2.2 分钟;已注明该口径与 §4.3b 整集折算 RTF≈12–14 不可互推。文档中的 curl recipe
与 RMS 选段片段均逐字执行验证(200 / X-Audio-Format: mp3 / 4xx detail 可 cat 读出),
另跑通七条负例(样本缺失、服务不可达、有效和超界、参数互斥、未知情感键、缺 mutagen)。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
用户反馈五档预设配音「都有点不自然、不够轻快阳光」。定位到两个根因,均已给出工具:

**根因一:样本定基线,而成片在用的那段恰是本人最闷的一档。** 克隆会继承参考样本的
韵律,情感向量只能在基线上微调。对同一位说话人的 4 段录音做 12s 滑窗勘探后实测:
成片所用 me-1@180s 的 F0 中位 142.2 Hz / 起伏 31.2 / 音节率 4.42 / 质心 1698 Hz,
综合分仅排 157/275——换成 me-1@0s 或 me-1@28s,纯克隆(零情感注入)即可让音高
+12~16%、语调起伏 +25~40%、明亮度 +5~15%。新增 `scripts/prospect_ref.py` 把这套
筛选固化为工具(F0 中位/四分位距/音节率/谱质心 + 静音与发声占比扣分,多文件同尺度
排序,输出可直接当 prepare_ref.py 的 --start)。

**根因二:向量注入越多越假,而既有预设都偏重。** 读上游混合式
`emovec = Σ(wᵢ·基向量ᵢ) + (1 − Σwᵢ)·参考音频情感` 可知 Σw 就是「合成情感挤掉本人
真实情感」的比例:现有 passionate/positive 的有效和 0.70/0.665,意味着只剩 30% 是
本人语调。故新增两条更自然的情感通路:
- `--emo-ref <另一段录音>`:音色仍取 --ref,语调/情绪整体迁移自这段录音,零向量注入;
- `--emo-text "轻快爽朗、自信阳光"`:服务端 QwenEmotion 转向量(需 --use-qwen-emo),
  按 ≤0.8 规则等比缩放后使用,并经 X-Emo-Vector 回显供 --emo-vector 固化复现。
服务端对三来源显式互斥报错(上游遇「向量+情感音频」是静默丢弃音频,静默降级更难排查)。

实现要点:`tts_server.py` 新增 emo_ref_path/emo_text 字段、`--use-qwen-emo` 启动开关、
`/health.supports_emo_text`;`tts.py`/`tts_sample.py` 同步 CLI 与前置校验,摘要新增
`|emoref=<sha1>` / `|emotext=<原文>` 可选后缀,`tts_sample.py` 另加 `--label` 便于横向
对比不留覆盖。文档:§四 前置「情感三来源」对照表 + 少注入更自然的机制说明、新增 §3.3
「样本决定基线」实测表、§3.2 改用 prospect_ref.py、§2.3/§2.4/§七 补 --use-qwen-emo
与权重缺失兜底、§六 摘要公式补可选后缀。

验证:①存量缓存零失效——对已上线三集 189 句逐句重算摘要,100% 与 sidecar 一致;
②生产路径 E2E——单句 mini 工程跑通 `tts.py --emo-ref`(manifest durationSec 5.666、
sidecar 生成、同参数重跑 0.24s 命中缓存、换情感样本/去掉情感源摘要均改变);
③新增负例 4 条全部给出可操作报错(三来源互斥/与 --all-styles 互斥/情感音频不存在/
服务未加载 Qwen);④X-Emo-Vector 回显实测(「轻快、爽朗、自信、阳光」→
happy 0.76/surprised 0.016/calm 0.024,恰好被压到 Σ=0.8 上限);⑤本轮共产出 24 个
候选小样(5 样本 × 5 档低注入向量 + 4 档语调迁移 + 3 档自然语言 + 2 档固化后减弱),
客观指标已逐个测量待用户听选。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
用户在 29 个候选小样中选定「Qwen 选方向 + 人工压强度」那一档,本次将其固化为
`STYLE_PRESETS["sunny"]`(happy 0.95 / surprised 0.02 / calm 0.03,alpha 0.35,
df 0.95),并把配套样本固化为 `voices/me-bright.wav`(me-1.mp3 --start 0.36
--duration 12,sha1 54b699cce97f)——`--style sunny --ref me-bright.wav` 与用户
听中的那次调用参数逐项一致(已用 --dry-run 核对)。

为何是 0.35 而非 Qwen 原始强度:Qwen 对「轻快、爽朗、自信、阳光」推出的向量会顶到
Σ=0.8 上限,实测把克隆音高推到 199–223 Hz,而该说话人自然区间仅 142–163 Hz,听感
「像另一个人在用力」。保留方向、把强度压到 0.35(留 65% 给本人真实语调)后即为 sunny。
这条「Qwen 选方向 → 人工压强度 → 固化预设」定式已写进手册 §4.1/§4.4。

文档同步:§4.1 预设表新增 sunny 行并补「有效注入」列(一眼看出各档挤掉多少真实语调)
+ 来历说明;§4.4 调参建议重写为四步定式(先定样本 → 再定方向 → 最后压强度 → 微调语速)
并给出 0.3–0.45 自然平衡带的实测依据,推荐位由 passionate 改为 sunny;§5.1/§5.2 示例
全部改用 me-bright.wav + sunny,步骤 1 改为推荐档裁剪命令;voices/README.md 补选段勘探
与小样试听两步、点明「样本决定基线」;CHANGELOG 与知识索引同步本轮能力。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
用户选定第四轮的 79 档(= sunny 同参数 + 束宽 3)。束宽此前只是命令行开关、不在
STYLE_PRESETS 里,`--style` 选了也不会生效,故本次把它变成预设的一等公民:

- `STYLE_PRESETS` 支持可选键 `beams`(缺省 1);新增 `sunny-steady`(明快稳健)=
  sunny 的向量/alpha/语速 + `beams: 3`;
- `resolve_style()` 返回值扩展为 5 元组并统一"命令行显式优先、否则取预设"的解析口径;
  `--num-beams` 的 argparse 默认值由 `1` 改为 `None`——否则无法区分「没给」与「给了 1」,
  预设束宽会被永久压掉;两个客户端(tts.py / tts_sample.py)同步按 job 传束宽;
- `--list-styles` 增列「有效注入」与「束宽」,一眼看出每档挤掉多少真实语调、跑多慢。

为何值得单独成档:同文本同样本实测,束宽 1→3 把语调起伏从 48.4 收到 40–44(更稳、
更「令人信服」),而亮度基本不掉(谱质心 1245 → 1214–1223)——是目前唯一不牺牲明快度
就能让语气更可信的旋钮。代价是 GPT 段耗时按束宽放大:单句墙钟 20–35 秒 → 56–131 秒,
整集 2.5–3.5 小时 → 8–10 小时。故定位为「日常/批量用 sunny,成片定稿用 sunny-steady」,
手册 §4.1/§4.3b/§4.4/§5.1/§5.2 均已按此口径改写并标注排期差异。

验证:①`--dry-run` 四例——sunny-steady 解析出 beams=3、sunny 仍为 1、显式 `--num-beams 1`
能压过预设的 3、`--all-styles` 七档各带自己的束宽;②实机跑通 `--style sunny-steady`
(墙钟 131.1s / RTF 25.4,客观指标与用户选中的 79 档一致:F0 175.0、起伏 40.3、质心 1214);
③摘要回归——已上线三集 189 句仍 100% 与 sidecar 一致(束宽默认值改动零影响),且
sunny 与 sunny-steady 摘要不同(换档会全量重合成,符合预期);④edge 引擎误传
`--num-beams` 仍正确提示忽略。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
3 束(sunny-steady)韵律更稳但整集要 9.9 小时,而真正决定第一印象的只是冷开场与各幕
金句。新增两个开关把「稳」买在关键处:

- `--steady 'P0,p3-25b,p5-*'`:命中句改用高束宽(`--steady-beams`,默认 3),其余仍按
  风格束宽。选择器规则可预测无歧义——以 `*` 结尾为前缀通配、含 `-` 为句 id、其余为幕名,
  大小写不敏感;**任一项匹配不到句子立即报错**(拼错幕名/句 id 后静默按低束宽跑完 3 小时
  是最难发现的失败模式)。缓存 sidecar 按句独立(摘要含 |beams=N),故同集混用两种束宽
  安全,也支持先全集跑 sunny、事后只补跑 --steady 命中的那几句。
- `--plan`:纯本地算摘要并与 sidecar 比对,打印各束宽的待合成/已缓存句数与估算墙钟后退出,
  不连服务。长跑前必经一步——改几行稿子后往往只有那几句 miss,不必按整集排期。

估时口径修正:初版 --plan 用「单句空闲机器」RTF(1 束 6.5)会低估近一半,改为**长跑折算
口径**并提为模块常量 RTF_1BEAM=13(三集 596 句 8.5 h / 40.2 min 语音实测)、
RTF_MULTIBEAM=45(同句 1↔3 束 A/B 实测约 3.2 倍,与早前 3 束直测 RTF 40–58 吻合)、
AVG_SEC_PER_LINE=4.2。校准后 189 句一集估 2.9 h,与三集真实用时 2.5–3.5 h 一致。

同时修正三处已失实的注释/文档口径:束宽 3 的整集倍数由「×3」改为「×3.4」;§4.3b 补入
本轮实测「短句最贵、数字句更贵」(4–6 s 短句 3 束 RTF 19.6–31.5 / 1 束 6.0–7.4,而
13–15 s 长句 3 束仅 8.9–13.8;数字密集句 5.87 s 音频烧 185 s,因数字被归一展开、token 暴涨)
——逐字稿为字幕可读性都拆到 ≤43 字,正好落在最贵区间。手册新增 §5.2.1 混合档(选择器语法
表 + 四方案代价对照表),§5.2 把 --plan 写成长跑前必经步骤。

验证:①选择器 7 例——幕名/句 id/前缀通配/大小写混用均正确命中,句 id 与幕名拼错各自报错,
`--steady-beams` 不高于基础束宽时报错;②E2E 双句工程实跑,逐句 sidecar 反查束宽(P0→3、
P1→1)全部命中期望,重跑 0.19 s 全缓存,--plan 缓存计数正确;③摘要回归——三集 596 句
逐句重算 100% 与 sidecar 一致(束宽传参链路改动零影响)。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
评审发现四处「参数被静默丢弃」,均会让用户以为跑了 A 实际跑了 B:

1. `tts.py` edge 分支未处理 `--plan`/`--steady`/`--steady-beams`。`--engine`
   缺省是 edge,漏写 `--engine indextts` 时 `--plan` 会跳过计划模式直接走
   `synth_edge` 全量合成;两引擎摘要必然不同且 `{id}.mp3` 是单槽位,会把整集
   已合成的克隆音频逐句改写成 edge 预置音色。`--plan` 语义是「只看不跑」,故
   改为直接 `parser.error`;`--steady`/`--steady-beams` 按既有约定进「已忽略」提示表。
2. `tts_sample.py` 的 `--style` 互斥只查 `--emo-vector`,漏了同批新增的
   `--emo-ref`/`--emo-text`:`--style sunny --emo-text "..."` 会静默丢弃 sunny
   (连带丢掉预设的 alpha 0.35 与 df 0.95),而 `tts.py` 对同组合是硬报错。
   试听工具的用途正是判定「哪档风格合适」,静默换档直接误导选档结论。
3. `--all-styles` 的手动覆盖拦截表漏了 `--num-beams`,`build_jobs()` 又把它硬写为
   None,`--all-styles --num-beams 1` 被静默忽略。统一压成 1 会让 sunny 与
   sunny-steady 产出完全相同的音频(两档只差束宽),A/B 失去意义,故与
   alpha/语速同口径拒绝。
4. 「下一步全量合成」回显命令在 `--emo-ref`/`--emo-text` 模式下打印
   `--style emoref`,而 `tts.py --style` 的 choices 无此取值,照抄必然 argparse
   报错、真正生效的开关也丢失。改为回显各自开关,并把显式给的
   alpha/语速/束宽一并带上,避免全量合成时悄悄退回预设值。

验证:edge+--plan 硬报错、edge+--steady 进提示表、indextts+--plan/--steady 功能不变
(189 句中 19 句升档);三组 `--style X + 情感来源` 均报错而合法单用仍通过;
`--all-styles --num-beams` 报错、单用仍 7 档末档 3 束、非 all-styles 时显式束宽仍
压过预设;`--emo-ref` 实跑小样回显的命令原样喂回 `tts.py` 可正常解析执行。
已上线三集 596 句摘要逐句比对零变化(存量缓存零失效),ruff check 通过。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
`--all-styles` 遍历 `STYLE_PRESETS` 全表,加入 sunny/sunny-steady 后实际是 7 档,
而 §5.1 仍写旧的 6 档且顺序不符;「耗时实测」表的「五风格 A/B 全跑 2.2 分钟」
同步失真——该表标注 num_beams=1,而 sunny-steady 自带束宽 3。

- §5.1 步骤 4 改为按 `STYLE_PRESETS` 顺序列出 7 档(neutral→…→sunny-steady);
- 耗时表以本次实测替换失真行:全 7 档 A/B 合计音频 46.2s / 墙钟 6.3 分钟;条件
  标注下沉到行内(前两行机器空闲、A/B 行机器有其它负载),不再由表头统一声称空闲;
- 并入「口径提醒」:该次 A/B 单档墙钟散布 37.7–86.6 秒(最慢者为该服务会话内首次
  用该样本的那档),其中 3 束的 sunny-steady 只用 38.6 秒、并未比 1 束档更慢——单句
  墙钟受机器负载支配,不足以据单次样本推断束宽代价,整集排期一律以 §4.3b 长跑折算
  口径为准(避免与 §4.1 的 56–131 秒区间互相拆台);
- 开关表同步 fix 提交的约束:`--all-styles` 与 `--emo-vector`/`--emo-alpha`/
  `--duration-factor`/`--num-beams` 互斥(否则 sunny 与 sunny-steady 会产出完全
  相同的音频),`--dry-run` 打印项补「束宽」;
- CHANGELOG 的「`--all-styles` 五档 A/B」改为「全预设 A/B」,不再随预设增减失真。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
`--text-file` 指向空文件或纯空白文件时,`args.text` 被 strip 成空串,
随后 `(args.text or DEFAULT_TEXT)` 把它当作「没给文本」而静默替换为内置
默认句;紧随其后的 `if not args.text: parser.error("试听文本为空")` 对该
路径已成死代码。用户以为在听自己指定的文本、实则听到的是内置那句,并据此
选定风格与样本——与本分支其余各处(edge 分支 --plan 硬失败、--steady 未
命中即报错、--all-styles 与覆盖参数互斥)一致对抗静默降级的取向相悖。

改为读取后立即校验:空内容直接 parser.error 并回显文件路径。fallback 语义
收窄为「两个文本开关都没给」,`--text` 与 `--text-file` 行为就此对称。

实测七例:空文件/纯空白文件报错、正常文件生效、不给文本仍回退内置默认、
`--text '   '` 与文件不存在两条既有错误路径不变、`--all-styles` 无回归。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
@ThreeFish-AI
ThreeFish-AI merged commit cb871a5 into feature/1.x.x Aug 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant