MiMo TTS Studio 支持三种音色来源:预置音色(系统内置)、设计音色(文本描述生成)、克隆音色(参考音频复制)。三种模式的数据结构、API 调用方式完全不同,但需要在同一个角色管理界面中统一操作。

数据模型:Pinia Store 管理音色状态

核心设计是用 ttsStore.js 管理当前音色状态,characterStore.js 管理角色列表。每个角色 = 一个音色配置 + 一组台词:

1
2
3
4
5
6
7
8
9
// ttsStore.js — 简化示意
const ttsMode = ref('standard') // 'standard' | 'voicedesign' | 'voiceclone'
const voiceId = ref('mimo_default')
const speed = ref(1.0)
const emotion = ref('neutral')
const styleDescription = ref('')
const voiceDesignDesc = ref('')

// characterStore.js 管理角色列表,每个角色包含 name、ttsMode、voiceId 等属性

三种模式的 API 调用差异

预置音色

直接传 voiceId,API 从内置音色库匹配:

1
2
3
4
5
6
7
// 模式 A:预置音色
{
model: 'MiMo-V2.5-TTS',
voice_id: '冰糖', // 预置音色名
text: line.text,
speed: speed.value
}

设计音色

不传 voiceId,改传 styleDescription,API 根据文本描述动态生成音色:

1
2
3
4
5
6
7
8
9
// 模式 B:设计音色
{
model: 'MiMo-V2.5-TTS',
voice: {
customization: styleDescription.value // 如 "温柔的年轻女性,语速偏慢"
},
text: line.text,
speed: speed.value
}

克隆音色

传参考音频的 Base64 编码:

1
2
3
4
5
6
7
8
9
10
11
12
// 模式 C:克隆音色
{
model: 'MiMo-V2.5-TTS',
voice: {
clone: {
audio: base64Audio, // 参考音频 Base64
mime: 'audio/wav'
}
},
text: line.text,
speed: speed.value
}

统一调用入口

Service 层用一个方法统一处理三种模式(以下为简化示意):

1
2
3
4
5
6
7
8
// app/electron/service/tts.js — 实际的 _callApi 参数签名
async _callApi(text, voiceId, speed, styleDescription, exportFormat, apiBase, apiKey, params) {
// 根据 params 中的 useVoiceDesign/useVoiceClone 标志构建不同的请求体
// 预置模式:传 voiceId
// 设计模式:传 styleDescription(文本描述)
// 克隆模式:传参考音频的 base64 编码
// 实际实现包含重试机制和错误处理
}

克隆模式需要额外读取本地音频文件并编码为 Base64(通过 readFileAsBase64 工具函数),这是 Electron 的 fs 能力发挥作用的地方——Web 应用无法直接读取本地文件。

持久化:角色配置 JSON

音色配置独立于台词数据,持久化为 JSON 文件:

1
2
3
// characterStore.js — 简化示意
// 角色配置持久化为 JSON,包含 name、ttsMode、voiceId、speed、emotion 等属性
// 克隆模式的参考音频路径存绝对路径,跨机器需重新指定

保存时序列化为 JSON,加载时反序列化并校验。克隆模式的参考音频路径存绝对路径,意味着项目文件夹复制到另一台机器后,克隆音色需要重新指定音频文件。

情感与风格描述

1
2
3
// ttsStore.js — 实际用 emotion 和 styleDescription 两个独立字段
const emotion = ref('neutral') // 'happy' | 'sad' | 'angry' | 'neutral' | ...
const styleDescription = ref('') // 自由文本描述,如 "温柔的年轻女性,语速偏慢"

emotion 传给 API 的 emotion 字段,styleDescription 传给 style_description 字段。实际没有 styleTags 数组的实现。

设计取舍

为什么音色配置和台词分开存?

  • 同一个音色可能配多组台词(不同场景的配音脚本)
  • 台词变化频繁,音色配置相对稳定
  • 分开存减少序列化/反序列化的数据量

为什么克隆模式存绝对路径?

  • 音频文件可能很大(几 MB ~ 几十 MB),不适合嵌入 JSON
  • 用户可能随时修改参考音频,存路径可以实时读取最新版本
  • 代价是跨机器迁移需要重新指定

注意:以上代码均为简化示意,实际实现包含完整的错误处理和状态管理逻辑。

统一接口、分支实现。三种模式的差异被封装在 Service 层,前端和 Controller 无需关心音色来源。