音声の局所的編集について

NotebookLMから出力された音声を聴くと、読み間違い(特に漢字に多い)が気になってしまう。
一度の生成にかかる時間は数分。インストラクションに読み方を書いてもうまく反映されず、消しても消してもモグラ叩きのように修正箇所は位置を変えるばかりで一向に埒が明かない始末だった。
自己学習用途ではこのクオリティで十分だが、外部に公開できるクオリティにするには特定箇所を直接編集できるような仕組みが必要にだと強く感じた。

音声編集技術というのは現状どの程度実現されているのだろうか?先行研究や取り組みを調査する。
調査に当たってはPerplexity, Grok, Gemini, ChatGPTのDeepResearchを用いた。

Prompt:
NotebookLMから出力された音声を聴くと、読み間違い(特に漢字に多い)が気になってしまう。
一度の生成にかかる時間は数分。インストラクションに読み方を書いてもうまく反映されず、消しても消してもモグラ叩きのように修正箇所は位置を変えるばかりで一向に埒が明かない始末だった。
自己学習用途ではこのクオリティで十分だが、外部に公開できるクオリティにするには特定箇所を直接編集できるような仕組みが必要にだと強く感じた。
音声編集技術というのは現状どの程度実現されているのだろうか?
AIから生成された音声、つまりWavファイルやMp4ファイルなどに対して直接編集(局所的な編集)を行う技術について
1. 現在どこまで研究が進んでいるのか
2. 実装はどこまで進んでいるのか
3. 何ができて何ができていないのか
を明らかにしつつ、調査レポートをまとめなさい。
なお、調査時には以下の観点を含めなさい。
- 多言語対応を前提としつつ、日本語における現状にも焦点を当てる
- 商用利用を想定し、研究段階・実装状況・技術的限界を網羅する
- 編集対象はナレーションおよび会話で、歌唱音声は除外する

1. 研究最前線:主要手法 × レポート言及状況
手法 / モデル | 研究段階 | Perplexity | Gemini | ChatGPT | 日本語対応の言及 | 主な強み | 主な課題 |
---|---|---|---|---|---|---|---|
VoiceCraft | 2024 公開OSS | ✔︎ | △(触れのみ) | ✔︎ | ×(英語のみ) | ゼロショット話者編集・OSSで試せる | 多言語は未学習、GPU必須 |
FluentEditor 2 | 2023 研究 | ✔︎ | × | × | △ | 韻律を壊さず部分置換 | 実装コード非公開 |
EdiTTS | 2022 研究 | × | × | ✔︎ | ◎(日本語適用報告) | Score-based編集、改造しやすい | 感情音声で不安定 |
DiffVoice | 2023 研究 | △ | ✔︎ | ✔︎ | × | 潜在拡散で高自然度 | 生成遅い、日本語未 |
AudioLM / SoundStorm | 2022– | △ | ✔︎ | △ | × | 音声継続生成・高速 | 内容制御が弱い |
Voicebox (Meta) | 2023 非公開 | △ | ✔︎ | ✔︎ | × | 6 言語で万能編集 | モデル非公開 |
EditSpeech / CampNet | 2021–22 | ✔︎ | × | ✔︎ | △ | 軽量AR系、文脈融和 | 話者汎用性が低い |
✔︎ = 詳述 △ = 触れのみ × = 未言及/未対応
2. 商用ツール:実装レベルと日本語対応
ツール / サービス | 単語置換 | 音素編集 | 日本語クローン | API / 自動化 | 主要レポート |
---|---|---|---|---|---|
Descript Overdub | ◎ | − | × | × | Perplexity / Gemini / ChatGPT |
Resemble AI Edit | ◎ | − | ◎ | ◎ | Perplexity / Gemini / ChatGPT |
ElevenLabs | ○(再合成) | − | ◎ | ◎ | Gemini / ChatGPT |
A.I.VOICE Editor | ○(辞書) | △(抑揚調整) | ◎ | × | Perplexity |
CoeFont | ○(再合成) | − | ◎ | ○ | Perplexity |
Vozo AI | ◎ | − | △ | ○ | Perplexity |
Adobe Podcast / Premiere Pro | ○ | − | × | × | Gemini |
Cleanvoice AI / Auphonic | ノイズ除去特化 | − | − | ◎ | Gemini |
◎ 完全対応 ○ 部分対応(再合成で代替等) △ 研究レベル or α版 − 未対応
3. 編集粒度ごとの「できる / まだ難しい」
粒度 | 現在 広く実装 | 限定的に実装 | 研究段階 |
---|---|---|---|
ブロック(文〜段落) | ほぼ全商用TTSでOK(全文再合成) | − | − |
単語 | Descript, Resemble, Vozo など | ElevenLabs(API組み込み) | 一部拡散モデル |
音素 / アクセント | 辞書補正(A.I.VOICE) | – | EdiTTS, DiffVoice, Voicebox |
感情・スタイル | 基本的なスピード/トーン | ElevenLabs emotion, CoeFont声色 | 精密制御 (CampNet等) |
リアルタイム | − | 低遅延TTS(ElevenLabs v2) | 研究プロト (SoundStorm) |
4. 将来ロードマップ(編集視点)
期間 | 技術トレンド | 期待される実用化 |
---|---|---|
短期 (〜2026) | 単語置換の精度向上、日本語アクセント自動推定 | Resemble / ElevenLabs が日本語 Overdub を正式提供 |
中期 (2027–2029) | 音素レベル編集の商用化、Few‑shot 高品質クローン | VoiceCraft‑JP や DiffVoice‑JP が SaaS 化 |
長期 (2030〜) | リアルタイム対話中のオンザフライ修正、言語間スタイル保持 | 会議 / メタバースでライブ音声を即時書き換え |
使い分けガイド(日本語ナレーション前提)
-
即戦力で一箇所だけ直したい
→ Resemble AI Edit(Web UI or API) -
大量スクリプトを前処理から自動化したい
→ ElevenLabs + 自前スクリプトで全文再合成 → 差分を貼替 -
発音辞書を細かく管理したい
→ A.I.VOICE Editor(ローカル / SSML 自動生成) -
最先端モデルを試して将来に備えたい
→ VoiceCraft を Colab で動かし日本語コーパスで再学習 -
アクセントを含む細粒度編集を研究開発
→ EdiTTS / DiffVoice を日本語音素系に合わせてファインチューニング
要点まとめ
- 編集粒度は “文 → 単語 → 音素” へと進化中。
- 日本語対応は単語置換までは商用で可能、音素編集は研究段階。
- 現場の最適解は Resemble AI(多言語)+辞書/SSML整形で運用。
- R&D 視点では VoiceCraft‑JP の誕生がブレイクポイントになる見込み。

Descriptが特定ワードの除去機能を備えてるよう。

-
Descript Overdub
- 現時点で日本語非対応。
-
ElevenLabs(Free regeneration機能)
- デモを聞いてみたが「ザ・外国人訛りの日本語」といった読み上げ品質。

-
Resemble AI(Edit機能)
- これこれ!まさにこれがやりたいこと。
- 日本語読み上げはElevenLabsと比べると遥かに流暢。ただ音声編集の日本語における有効性は現時点では未知数

Resembleでは日本語を含む様々な言語の音声合成をサポートしており、アクセントや言語切替も設定できます。そのため、例えば日本語ナレーションの録音で一部を修正したい場合でも、Resemble Editなら対応できる可能性があります(要:十分な日本語クローン音声の品質確保)。(ChatGPT DeepResearch結果より)

Resemble AIが実現したいことを(恐らく)実現できているもよう。
利用価格を確認する。

リバースエンジニアリングを試みる
Resemble AI「Edit」──内部で走っていそうな処理フロー(推定)
# | 処理ブロック | 主なアルゴリズム候補 | 目的 / 出力 | 技術的ヒント |
---|---|---|---|---|
1 | アップロード & 解析 | - 高精度 ASR(Whisper‑large v3 相当 or 自社 CTC モデル) - Forced Alignment で単語位置決定(CTC‑Segmentation や Rev AI API 相当) |
① 秒単位の音声 ② 単語ごとのタイムスタンプ付き転写テキスト |
Resemble の UI は「自動で文字起こし→ハイライト編集」構造 (Resemble AI) |
2 | Voice Clone 準備 | - 既存の Custom Voice embedding をロード - 無ければ数十秒録音→x‑vector / speaker‑embedding を即時推論 |
話者固有の 256〜512 dim の埋め込みベクトル | Resemble の Custom Voice API が“build voices instantly”と明記 (Resemble AI) |
3 | 編集検出 | - フロントエンドでテキスト差分(diff-match‑patch) - 置換・挿入・削除ごとに エディットチャンク生成 |
(start, end, new_text) リスト | UI は「ハイライト → タイプ → 即再生」方式 (Resemble AI) |
4 | 言語前処理 | - Text‑Normalization(数字→読み等) - G2P + アクセント推定 - SSML 拡張(強弱・ポーズタグ) |
発音記号 & 韻律パラメータ列 | 日本語では JTS/OpenJTalk 系、英語では CMUdict + Prosody |
5 | TTS 再合成 | - 非自己回帰 FastSpeech2 or VITS 派生 + Speaker Embedding - 低遅延モード(>2×RT) - 生成単位はセンテンス or 句 + 前後0.2 s バッファ |
新しい波形チャンク | Resemble は「極低レイテンシ」謳う (Resemble AI) |
6 | ポストプロセス | 1. Loudness match(RMS/LUFS 揃え) 2. EQ カーブ近似 3. クロスフェード or 時間伸縮で滑らか接続 4. 最終全体ノイズ除去(RNNoise / Demucs) |
完整合成波形 | ブログ記事で「自然に溶け込む」と強調 (Resemble AI) |
7 | プレビュー & 書き出し | - WASM ベース AudioBuffer で即再生 - 非破壊編集のまま JSON/EDL 保存 - エクスポート時は FFmpeg で WAV/MP3/MP4 コンテナ化 |
ユーザ確認・ダウンロード | 編集結果は数秒でプレビュー可能との記載 (Resemble AI) |

処理を支える鍵技術(推定詳細)
技術ピース | なぜ必要か | 具体的実装イメージ |
---|---|---|
Forced Alignment | 元音声と転写テキストを「単語境界」で正確に同期させ、差し替え開始点/終了点を決めるため | torchaudio CTC‑Segmentation or Wav2Vec2 CTC forced_alignment (PyTorch) |
差分バッファ生成 | 置換箇所単体だと前後とのピッチ・息継ぎがずれるため、±200 ms 程度余白を含め再生成 | splice_start = edit_start ‑ 0.2 s etc. |
Speaker Embedding + Prosody Transfer | 新規生成部分を元の声質・トーンに合わせる | x‑vector → prosody encoder (pitch/energy) → conditional decoder |
Waveform Stitching | 長さが変わるとタイミングがずれるため、TD‑PSOLA 的 time‑scale‑modification かフェードで調整 | crossfade = 50 ms・cos curve |
低レイテンシ推論 | “タイプ後すぐ再生” UX を実現 | on‑device ONNX ORT / TensorRT 最適化、バッチなし |
キャッシュ & バージョン管理 | 同じ単語を何度も再生成せず高速化 / 取り消し可能に | (voice_id, text_hash) → wav cache DB |

従来の類似プロダクト(Descript Overdub)は ASR→diff→TTS の3段構えで実装されており、市場要件から見ても同系統である可能性が高い。


「aligner」=Forced Alignment(強制アラインメント)サービス
観点 | 内容 |
---|---|
何をする? | ① 音声ファイルと ② その文字起こし(テキスト) を入力すると、テキスト中の 各単語・フレーズが波形のどこに現れるか を 開始秒 / 終了秒 で割り出す。 |
なぜ必要? | 🔧 差し替える区間をピンポイントで切り出す ため。 録音済みナレーションの一単語だけを AI で置き換える場合、 「0.53 s〜0.88 s が こんにちは の発話」 と分からないと波形を綺麗にスプライスできない。 |
どうやって? | - 背後で ASR 音響モデル(例:Wav2Vec2)を使い、音声をフレームごとの音素確率列に変換 - CTC Segmentation(または Montreal Forced Aligner 方式)で、 テキスト列が確率列に最も自然に埋め込まれる位置を動的計画法で探索 - 結果として word → (start, end) のタイムスタンプを得る |
このプロジェクト内での役割 | 1️⃣ ASR サービスが全文文字起こし 2️⃣ aligner サービスが単語タイムコードを追加 3️⃣ TTS サービスが編集文を再合成 4️⃣ stitcher が original[0:0.53] + new_wav + original[0.88:] のように波形を滑らかに接合 |
代表的な実装 | - Montreal Forced Aligner (MFA): Kaldi ベースで高精度 - CTC‑Segmentation: 軽量 Python/Cython ライブラリ(今回採用) - Gentle / Aeneas: 英語中心の軽量ツール |

下図のような構成の検証環境を作成した。
- コア・ロジック
- ASR:
faster-whisper
- Aligner:
ctc-segmentation
- TTS:
TTS
- ASR:
- タスク管理
Celery
- 監視
Prometheus
Grafana
- API実装
FastAPI
GraphQL
docker-compose.yml
# app/docker-compose.yml
version: "3.9"
# -------------------------------------------------
# shared settings / overrides
# -------------------------------------------------
x-py-env: &py-env
PYTHONUNBUFFERED: "1" # show logs immediately
# -------------------------------------------------
# infrastructure
# -------------------------------------------------
services:
redis:
image: redis:7-alpine
restart: unless-stopped
ports: ["6379:6379"]
# -------------------------------------------------
# core micro‑services
# -------------------------------------------------
asr:
build: ./services/asr
environment:
<<: *py-env
volumes:
- ./samples:/data:ro # original & replacement wavs
- asr_tmp:/tmp # scratch area
- shared_data:/shared
ports:
- "8001:8000"
aligner:
build: ./services/aligner
environment:
<<: *py-env
volumes:
- ./samples:/data:ro
- aligner_tmp:/tmp
- shared_data:/shared
ports:
- "8002:8000"
depends_on:
- asr
tts:
build: ./services/tts
environment:
<<: *py-env
TTS_DEVICE: "${TTS_DEVICE:-cpu}" # set to 'cuda' to enable GPU
volumes:
- ./samples:/data:ro
- tts_tmp:/tmp
- shared_data:/shared
ports:
- "8003:8000"
stitcher:
build: ./services/stitcher
environment:
<<: *py-env
volumes:
- ./samples:/data # write stitched wavs here
- stitch_tmp:/tmp
- shared_data:/shared
ports:
- "8004:8000"
depends_on:
- tts
# -------------------------------------------------
# orchestration layer (GraphQL + Celery)
# -------------------------------------------------
api-gateway:
build: ./services/api-gateway
environment:
<<: *py-env
CELERY_BROKER_URL: "redis://redis:6379/0"
volumes:
- ./samples:/data
- shared_data:/shared
ports:
- "8080:8080"
depends_on:
- redis
- asr
- aligner
- tts
- stitcher
worker: # Celery worker (separate from API process)
build: ./services/api-gateway
command: celery -A app.tasks worker --loglevel=info --concurrency=2
environment:
<<: *py-env
CELERY_BROKER_URL: "redis://redis:6379/0"
volumes:
- ./samples:/data
- shared_data:/shared
depends_on:
- redis
- asr
- aligner
- tts
- stitcher
celery-exporter:
image: danihodovic/celery-exporter:latest
command: >
--broker-url=redis://redis:6379/0
ports:
- 9808:9808
depends_on:
- redis
- worker
prometheus:
image: prom/prometheus
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
ports:
- "9090:9090" # Prometheus UI
depends_on:
- celery-exporter
grafana:
image: grafana/grafana-oss
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: "admin"
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
# -------------------------------------------------
# named scratch volumes (kept between rebuilds)
# -------------------------------------------------
volumes:
asr_tmp:
aligner_tmp:
tts_tmp:
stitch_tmp:
shared_data:
grafana_data:

音声合成のサンプル

Celeryのタスクキューの状況をPrometheus+Grafanaで監視する構成にしている。

/ Monorepo Root
├ apps/
│ ├ api/ API & Microservices
│ │ ├ docker-compose.yml ASR, Aligner, TTS, Stitcher, Gateway
│ │ ├ monitoring/ Prometheus & Grafana configs
│ │ ├ samples/ テスト用 WAV
│ │ └ services/ 各サービス実装
│ └ web/ (フロントエンド)
├ docs/
│ ├ architecture/ アーキテクチャ図・設計ドキュメント
│ └study/ 調査メモ
├ experiments/ 検証用Python環境
├ assets/ デモ用動画・素材
├ packages/
│ └ sound2wave/ Rust ユーティリティライブラリ
├ scripts/ 開発支援スクリプト
├ pnpm-workspace.yaml pnpm ワークスペース設定
├ package.json / package-lock.json Web 依存管理
└ turbo.json Turborepo 設定

日本語の認識精度を測定する。

ゼロショットTTSを試す。

入力した音声と似た音声で置換してくれると嬉しい。
追加の学習無しで、入力した音声と似た音声を返してくれると理想(所謂Voice cloning)。
音声クローンの代表的な実装はVALL-E Xだろうか。ざっと内容を確認する。

OpenVoiceが良さそう

TTS: MeloTTS
Converter: OpenVoice
で実装した。
melo.api
は以下のメソッドを持つ。
def tts_to_file(self, text, speaker_id, output_path=None, sdp_ratio=0.2, noise_scale=0.6, noise_scale_w=0.8, speed=1.0, pbar=None, format=None, position=None, quiet=False,):
language = self.language
texts = self.split_sentences_into_pieces(text, language, quiet)
audio_list = []
if pbar:
tx = pbar(texts)
else:
if position:
tx = tqdm(texts, position=position)
elif quiet:
tx = texts
else:
tx = tqdm(texts)
for t in tx:
if language in ['EN', 'ZH_MIX_EN']:
t = re.sub(r'([a-z])([A-Z])', r'\1 \2', t)
device = self.device
bert, ja_bert, phones, tones, lang_ids = utils.get_text_for_tts_infer(t, language, self.hps, device, self.symbol_to_id)
with torch.no_grad():
x_tst = phones.to(device).unsqueeze(0)
tones = tones.to(device).unsqueeze(0)
lang_ids = lang_ids.to(device).unsqueeze(0)
bert = bert.to(device).unsqueeze(0)
ja_bert = ja_bert.to(device).unsqueeze(0)
x_tst_lengths = torch.LongTensor([phones.size(0)]).to(device)
del phones
speakers = torch.LongTensor([speaker_id]).to(device)
audio = self.model.infer(
x_tst,
x_tst_lengths,
speakers,
tones,
lang_ids,
bert,
ja_bert,
sdp_ratio=sdp_ratio,
noise_scale=noise_scale,
noise_scale_w=noise_scale_w,
length_scale=1. / speed,
)[0][0, 0].data.cpu().float().numpy()
del x_tst, tones, lang_ids, bert, ja_bert, x_tst_lengths, speakers
#
audio_list.append(audio)
torch.cuda.empty_cache()
audio = self.audio_numpy_concat(audio_list, sr=self.hps.data.sampling_rate, speed=speed)
if output_path is None:
return audio
else:
if format:
soundfile.write(output_path, audio, self.hps.data.sampling_rate, format=format)
else:
soundfile.write(output_path, audio, self.hps.data.sampling_rate)
また、openvoice.api
は以下のメソッドを持つ。
def extract_se(self, ref_wav_list, se_save_path=None):
if isinstance(ref_wav_list, str):
ref_wav_list = [ref_wav_list]
device = self.device
hps = self.hps
gs = []
for fname in ref_wav_list:
audio_ref, sr = librosa.load(fname, sr=hps.data.sampling_rate)
y = torch.FloatTensor(audio_ref)
y = y.to(device)
y = y.unsqueeze(0)
y = spectrogram_torch(y, hps.data.filter_length,
hps.data.sampling_rate, hps.data.hop_length, hps.data.win_length,
center=False).to(device)
with torch.no_grad():
g = self.model.ref_enc(y.transpose(1, 2)).unsqueeze(-1)
gs.append(g.detach())
gs = torch.stack(gs).mean(0)
if se_save_path is not None:
os.makedirs(os.path.dirname(se_save_path), exist_ok=True)
torch.save(gs.cpu(), se_save_path)
return gs
これらを組み合わせて、以下のように書けば動かせる
# 1) generate base TTS with MeloTTS
source_se = torch.load(
f'checkpoints_v2/base_speakers/ses/{_LANG.lower().replace("_", "-")}.pth',
map_location=device)
base_path = str(pathlib.Path(tempfile.gettempdir()) / f"ov_base_{uuid.uuid4()}.wav")
melo.tts_to_file(
text=text,
speaker_id=speaker_ids[_LANG],
output_path=base_path,
)
# 2) extract embedding from reference voice
# embed_path = str(pathlib.Path(tempfile.gettempdir()) / f"ov_embed_{uuid.uuid4()}.pth")
converter = ToneColorConverter(f'{ckpt_converter}/config.json', device='cpu')
converter.load_ckpt(f'{ckpt_converter}/checkpoint.pth')
if torch.backends.mps.is_available() and device == 'cpu':
torch.backends.mps.is_available = lambda: False
target_se, audio_name = se_extractor.get_se(ref_wav, converter, vad=True)
# 3) convert tone color
converter.convert(
audio_src_path=base_path,
src_se=source_se,
tgt_se=target_se,
output_path=out_path
)

出力されたサンプルファイルをREADME.mdに掲載した。