Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
with:
repository: soniqo/speech-swift
path: speech-swift
ref: main
ref: aa0c0dacfc14778c4b9a20f0a67095eb75a3df85

- name: Show Xcode version
run: xcodebuild -version
Expand Down Expand Up @@ -80,7 +80,8 @@ jobs:

- name: Build Swift sidecar
working-directory: speech-studio/swift-sidecar
run: swift build -c release
# Release tests import the executable target with @testable.
run: swift build -c release --build-tests -Xswiftc -enable-testing

- name: Stage sidecar + metallib for Tauri bundler
# Has to run before any `cargo` invocation: tauri-build verifies
Expand Down Expand Up @@ -110,8 +111,20 @@ jobs:
-o /tmp/speech-release.tar.gz
tar -xzf /tmp/speech-release.tar.gz -C /tmp mlx.metallib
cp /tmp/mlx.metallib src-tauri/binaries/mlx.metallib
# MLX initializes at test-process shutdown, including CPU-only tests.
# Stage its library beside both the sidecar and test executable.
cp /tmp/mlx.metallib swift-sidecar/.build/release/mlx.metallib
for test_dir in swift-sidecar/.build/release/*.xctest/Contents/MacOS; do
if [[ -d "$test_dir" ]]; then
cp /tmp/mlx.metallib "$test_dir/"
fi
done
ls -la src-tauri/binaries/

- name: Test audio cleanup and debug capture
working-directory: speech-studio/swift-sidecar
run: swift test -c release --skip-build --filter AudioPostprocessingTests

- name: Run Rust unit tests
working-directory: speech-studio/src-tauri
run: cargo test --lib
Expand Down Expand Up @@ -451,7 +464,7 @@ jobs:
with:
repository: soniqo/speech-swift
path: speech-swift
ref: main
ref: aa0c0dacfc14778c4b9a20f0a67095eb75a3df85

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -485,7 +498,8 @@ jobs:

- name: Build Swift sidecar
working-directory: speech-studio/swift-sidecar
run: swift build -c release
# Release tests import the executable target with @testable.
run: swift build -c release --build-tests -Xswiftc -enable-testing

- name: Stage sidecar + metallib for Tauri bundler
env:
Expand All @@ -500,6 +514,16 @@ jobs:
-o /tmp/speech-release.tar.gz
tar -xzf /tmp/speech-release.tar.gz -C /tmp mlx.metallib
cp /tmp/mlx.metallib src-tauri/binaries/mlx.metallib
cp /tmp/mlx.metallib swift-sidecar/.build/release/mlx.metallib
for test_dir in swift-sidecar/.build/release/*.xctest/Contents/MacOS; do
if [[ -d "$test_dir" ]]; then
cp /tmp/mlx.metallib "$test_dir/"
fi
done

- name: Test audio cleanup and debug capture
working-directory: speech-studio/swift-sidecar
run: swift test -c release --skip-build --filter AudioPostprocessingTests

# A release build is still a tested build.
- name: Run Rust unit tests
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,51 @@ Hit **Load demo** in the top bar. It bootstraps a Scene 04 storyboard with two c

The **Activity** button in the transport bar opens a live log of what the engine is doing: model downloads and loads, every synthesis step, and the sidecar's memory snapshots after each load and render (MLX active/peak plus the real process footprint). *Copy* puts the visible log on the clipboard for a bug report; *Log file* reveals the full, rotating file — `~/Library/Logs/audio.soniqo.studio/speech-studio.log` on macOS, `%LOCALAPPDATA%\audio.soniqo.studio\logs\` on Windows, `~/.local/share/audio.soniqo.studio/logs/` on Linux.

### Compare Qwen audio before and after cleanup

Launch the development app with `SONIQO_TTS_DEBUG_AUDIO=1 pnpm tauri dev`, then
generate a Qwen clip. Each take saves three local WAVs in
`~/Library/Caches/audio.soniqo.studio/clips/`; the Activity log lists their paths
and sample counts:

- `<take>.raw.wav`: the runtime's output after reference separation, before Studio cleanup.
- `<take>.trimmed.wav`: after leading-junk detection, before edge conditioning.
- `<take>.wav`: the final output, including the 20 ms pre-trim, edge fades, and silence padding.

Direct sidecar callers can instead send `"debugAudio": true` with
`synthesize_icl`. All three files come from the same generation, so comparisons
keep the seed, language, token budget, reference, and sampling settings fixed.
For a long clip, inspect each chunk's files before the concatenated output.
If a syllable is already absent in the raw waveform, Studio's cleanup did not
remove it. If it disappears between stages, the difference identifies the
cleanup step to investigate. Debug capture is off by default.

Japanese, Chinese, and Korean text receive character-aware audio budgets and
chunking at their sentence/clause punctuation. Language choices are retained
across engine switches for the session, even when an intervening engine uses a
fallback. Qwen language codes such as `ja` are sent as canonical names such as
`japanese`; both aliases resolve to the same runtime language token.

Run the audio cleanup checks without loading models with
`cd swift-sidecar && swift test --filter AudioPostprocessingTests`.
MLX's Metal library must still be available beside the test executable; CI
stages it automatically.

The opt-in Japanese regression generates a matched system-voice reference and
checks the first word, middle syllables, and sentence ending in both raw and final
audio at two fixed seeds, plus the app's individual sentence chunks. It uses
Japanese-capable Qwen ASR and releases the TTS
process before loading the recognizer:

```bash
cargo test --manifest-path src-tauri/Cargo.toml --test sidecar \
qwen_japanese_preserves_first_word_and_sentence_end -- --ignored --nocapture
```

The test requires macOS's Kyoko voice and the `speech` CLI. Set
`SONIQO_TEST_SPEECH_CLI` if the CLI is not on `PATH`.
Set `QWEN_TTS_TEST_OUTPUT_DIR` to retain the verified WAVs.

### Packaging your own .app / .dmg

```bash
Expand Down
46 changes: 46 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,52 @@ pnpm tauri dev

Нажмите **«Загрузить демо»** в верхней панели. Приложение подготовит сцену «Scene 04» с двумя клонированными голосами (Anna и Marek) и четырьмя репликами — каждая со своим маркером эмоции — а затем синтезирует всё через текущий выбранный движок.

### Сравнение аудио Qwen до и после обработки

Запустите приложение для разработки командой `SONIQO_TTS_DEBUG_AUDIO=1 pnpm tauri dev`
и создайте реплику через Qwen. Для каждого варианта сохраняются три локальных WAV-файла
в `~/Library/Caches/audio.soniqo.studio/clips/`; панель **Активность** показывает их пути
и количество отсчётов:

- `<take>.raw.wav`: результат движка после отделения эталонной записи, до обработки в Studio.
- `<take>.trimmed.wav`: после удаления начальных артефактов, до обработки краёв.
- `<take>.wav`: итоговое аудио после обрезки первых 20 мс, плавного изменения громкости по краям и добавления тишины.

При прямом вызове sidecar можно передать `"debugAudio": true` в запросе
`synthesize_icl`. Все три файла получаются из одного запуска генерации: seed,
язык, лимит токенов, эталонная запись и параметры сэмплирования совпадают.
Для длинной реплики сравнивайте файлы каждого фрагмента до объединения.
Если слог отсутствует уже в исходном аудио, обработка Studio его не удаляла.
Если он исчезает между этапами, сравнение укажет этап для проверки.
По умолчанию диагностическое сохранение выключено.

Для японского, китайского и корейского текста лимит аудиотокенов учитывает
символы, а разбиение — знаки конца предложения и границы частей предложения.
Выбранный язык сохраняется при переключении движков в текущем сеансе, даже
если промежуточный движок использует другой язык. Коды Qwen, например `ja`,
передаются как полные названия, например `japanese`; оба варианта соответствуют
одному языковому токену движка.

Проверки обработки аудио без загрузки моделей:
`cd swift-sidecar && swift test --filter AudioPostprocessingTests`.
Библиотека Metal для MLX всё равно должна находиться рядом с исполняемым
файлом тестов; CI размещает её автоматически.

Дополнительный японский регрессионный тест создаёт эталонную запись системным
голосом с точным текстом и проверяет первое слово, середину и окончание реплики
в исходном и итоговом аудио при двух фиксированных seed, а также отдельные
предложения, формируемые приложением. Он использует Qwen ASR с поддержкой
японского и завершает процесс TTS перед загрузкой распознавателя:

```bash
cargo test --manifest-path src-tauri/Cargo.toml --test sidecar \
qwen_japanese_preserves_first_word_and_sentence_end -- --ignored --nocapture
```

Для теста нужны голос Kyoko в macOS и CLI `speech`. Если CLI отсутствует
в `PATH`, задайте `SONIQO_TEST_SPEECH_CLI`.
Чтобы сохранить проверенные WAV-файлы, задайте `QWEN_TTS_TEST_OUTPUT_DIR`.

### Упаковка собственного .app / .dmg

```bash
Expand Down
37 changes: 37 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,43 @@ MLX 缓冲缓存上限为 1 GB(可用 `SONIQO_MLX_CACHE_MB` 覆盖)——若

点击顶栏的 **Load demo(加载演示)**。它会引导出一个 Scene 04 分镜,包含两个克隆音色(Anna 与 Marek)和四行对白——每行带一个情感标记——然后通过当前选择的引擎合成全部内容(macOS 默认是 CosyVoice 3)。

### 比较 Qwen 清理前后的音频

使用 `SONIQO_TTS_DEBUG_AUDIO=1 pnpm tauri dev` 启动开发版应用,然后通过 Qwen
生成片段。每次生成会在 `~/Library/Caches/audio.soniqo.studio/clips/` 保存三个本地
WAV 文件;**Activity** 面板会显示文件路径和采样点数:

- `<take>.raw.wav`:运行时移除参考音频后的输出,尚未经过 Studio 清理。
- `<take>.trimmed.wav`:移除开头杂音后、处理音频边缘前的输出。
- `<take>.wav`:最终输出,包含开头 20 毫秒的裁剪、边缘淡入淡出和静音填充。

直接调用 sidecar 时,也可以在 `synthesize_icl` 请求中传入 `"debugAudio": true`。
三个文件都来自同一次生成,因此 seed、语言、token 预算、参考音频和采样参数完全一致。
对于长片段,请先检查各个分段的文件,再检查拼接后的输出。如果某个音节在原始波形中
就已缺失,则不是 Studio 清理时删除的;如果它在两个阶段之间消失,则可以据此确定
需要检查的处理步骤。默认不保存这些诊断文件。

日文、中文和韩文文本的音频 token 预算会考虑字符数量,分段会识别相应的句末和
分句标点。在同一会话内切换引擎时,会保留所选语言,即使中间使用的引擎需要回退到
其他语言。Qwen 语言代码(如 `ja`)会转换为完整名称(如 `japanese`);这两种写法
在运行时对应同一个语言 token。

不加载模型即可运行音频清理测试:
`cd swift-sidecar && swift test --filter AudioPostprocessingTests`。
测试可执行文件旁仍需放置 MLX 的 Metal 库;CI 会自动完成这一步。

可选的日文回归测试会使用系统语音生成文本完全匹配的参考音频,在两个固定 seed 下
检查原始和最终音频的首词、中间音节及句末,并检查应用生成的各个句子分段。
测试使用支持日文的 Qwen ASR,并在加载识别模型之前结束 TTS 进程:

```bash
cargo test --manifest-path src-tauri/Cargo.toml --test sidecar \
qwen_japanese_preserves_first_word_and_sentence_end -- --ignored --nocapture
```

测试需要 macOS 的 Kyoko 语音和 `speech` CLI。如果 CLI 不在 `PATH` 中,请设置
`SONIQO_TEST_SPEECH_CLI`。设置 `QWEN_TTS_TEST_OUTPUT_DIR` 可以保留验证后的 WAV 文件。

### 打包你自己的 .app / .dmg

```bash
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "speech-studio",
"private": true,
"version": "0.0.10",
"version": "0.0.11",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "speech-studio"
version = "0.0.10"
version = "0.0.11"
description = "Speech Studio — voice cloning + scripted synthesis"
authors = ["Soniqo"]
edition = "2021"
Expand Down Expand Up @@ -29,3 +29,4 @@ dirs = "5"
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
base64 = "0.22"
unicode-segmentation = "1.13"
Loading
Loading