Voice Studio ── 誰も録音しない、キャラクターごとの声
声を一文で説明し、3 つの候補を聴いて、ひとつ残す。声のデザインと発話を分けた理由、候補をまとめて 1 回で GPU に流す仕組み、使わないときに解放するランタイム、1 MB のメッセージバス越しにクリップを運ぶ方法、そして「録音なし」を抜け道にしないための署名。
私たちのプラットフォームのキャラクターはみんな話します。そして今週まで、その声の始まり方は 2 つだけでした。誰かが自分の声を録音するか、数十種類ある内蔵ボイスのどれかを借りるか。前者は「気難しい老船長」を作りたいだけのユーザーに多くを求めすぎます。後者だと、千人のキャラクターがひと握りの同じ声を分け合うことになります。Voice Studio はこの両方をなくしました。声を一文で説明し、いくつかの候補を聴いて、気に入ったものを残せば、それが自分だけの声になります。誰も録音しません。
一度デザインして、あとはクローン
いちばん大事な設計判断は、声をデザインすることと、その声で話すことを切り離したことです。デザインが生むものはひとつだけ ── 新しい声が一文を話す短い参照クリップ(5〜10 秒)と、その一文です。この組み合わせは、既存のクローン経路がもともと受け取る形そのものです。だからデザインした声は普通のクローン音声として保存され、それ以降は録音された声とまったく同じ音声エンジン、ストリーミング、リアルタイム通話、課金の経路を通ります。
これでホットパスには一切手を入れずに済みました。声をデザインするモデルは音声エンジンに比べて大きく遅く、必要なのはキャラクターごとに 1 回だけで、セリフごとではありません。毎回の文の前に置けば、チャットの返事がすべて遅くなるだけで何も得られません。
description ──▶ avionics ──▶ engine-server ──NATS──▶ GPU worker
(auth, (pick a worker voice-design runtime
credits) that has the loads on demand,
runtime selected) designs N clips in 1 batch
│
clips ◀── avionics ◀── engine-server ◀── object store ◀─┘ (1 WAV per description)
kept clip ──▶ POST /v1/audio/voices ──▶ an ordinary cloned voice
(speech, streaming, calls: unchanged)3 つの候補を 1 回で
最初の試作では、選択肢を出すために同じ説明で 2 回デザインしました。どちらも同じ人に聞こえ、選ぶ意味がありませんでした。サンプリングのゆらぎが変えるのは話し方で、声の人物像ではありません。人物像を変えるのは説明のほうです。そこでアプリはまず、ユーザーの書いた説明をはっきり違う 3 通りの解釈に書き直します。同じキャラクターでも、声の高さ、質感、指定された範囲内での年齢、速さ、態度が違うものです。あわせて、その声で話すキャラクターらしい一文も書きます。
3 つはひとつのバッチとして GPU に送られます。RTX 5090 では約 6 秒のクリップ 3 本のバッチが平均 8.2 秒、1 本だけなら 6.1 秒でした。候補を増やしても、待ち時間は 12 秒ではなく約 2 秒増えるだけです。
GPU を占有しないランタイム
ネットワークのワーカーは一般の方のマシンで、チャットモデル、埋め込みモデル、音声エンジンをすでに載せた 16 GB のカード 1 枚ということもよくあります。声をデザインするモデルは VRAM を約 4.2 GB(ピーク 4.4 GB)使い、8 秒以内に読み込まれます。キャラクターごとに 1 回しか使わない機能のためにそれをずっと払い続けるのは割に合わないので、ランタイムは最初のリクエストで読み込まれ、10 分使われなければ自分で解放します。
// The design runtime is needed once per character, not once per line.
// It loads on the first job and gives its ~4 GB back after 10 idle minutes.
frame, err := sc.Call(ctx, requestID, "design", params)
v.idle = time.AfterFunc(10*time.Minute, func() {
v.log.Info("voice design: idle, unloading")
v.Stop()
})ワーカー上では音声エンジンではなく、専用のスイッチを持つ任意のランタイムです。この区別は実際に重要でした。デスクトップアプリは音声エンジンを常にひとつだけ選択しておく仕組みで、初期のビルドのように声のデザインを音声エンジンの枠に入れていたら、有効にした瞬間にワーカーの本来の音声エンジンが外れていたはずです。
メッセージバスで音声を運ぶ
ジョブは NATS でワーカーに届きますが、NATS の返信は 1 MB までです。WAV 3 本は入りません。ワーカーは各クリップを短命なオブジェクトストアのバケット(TTL 15 分)に、リクエスト ID とクリップの順番に結びついたキーで書き込み、その参照だけを返します。サーバーは自分のリクエストに一致するキーしか読まないので、ワーカーが他人のオブジェクトを読ませることはできません。読み終えたクリップは削除します。透かし入りの曲を返す音声透かしランタイムと同じやり方です。
「録音しない」を正直に保つ
録音した声のクローンでは、まず同意を求めます。誰の声か、何に使うか。デザインした声は誰のものでもないので、保存時にはその手順を省きます。そこには明らかな穴があります。同意を省く保存経路が、クライアントが送るどんな音声でも受け付けてしまえば、本人に聞かずに実在の人の声をクローンする抜け道になります。そのため、デザインしたクリップにはすべてサーバーが発行したトークンが付きます。ユーザー、言語、セリフ、クリップ自体のハッシュに対する HMAC で、有効期限は 1 時間。保存時にはこれを確認し、それ以外は受け付けません。
// A designed voice belongs to nobody, so saving it skips the consent a
// recording needs. The save route therefore only accepts clips it issued:
mac := hmac.New(sha256.New, secret)
for _, part := range []string{issuedAt, userID, lang, line, sha256Hex(clip)} {
mac.Write([]byte(part)); mac.Write([]byte{0})
}
// Different user, different clip, different line, or older than an hour → 403.説明を書き直すステップにも、実在の特定できる人物の声を説明したりまねたりせず、似た一般的な特徴を持つオリジナルの声を説明するよう指示しています。
使ってみる
キャラクターアプリでは、内蔵ボイスの一覧が「声を言葉でつくる」に置き換わりました。新しいキャラクターには、作者がデザインしたか録音した声が付きます。 TTS プレイグラウンドではDesign a voice タブで説明を 3 つまで書き、並べて聴き比べて「My voices」に保存できます。API では 2 回の呼び出しで、課金は音声合成と同じく返ってきた音声の秒数単位です。
# 1. Design: one reference clip per description, all in one batch
curl https://api.openalchemy.io/v1/audio/voices/design \
-H "Authorization: Bearer $OA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{"instruct": "A warm young woman, clear gentle voice, speaks slowly with a smile",
"text": "Hey, it is really good to finally meet you.", "language": "en"},
{"instruct": "A calm middle-aged man, low and slightly husky, unhurried",
"text": "Hey, it is really good to finally meet you.", "language": "en"}
]
}'
# → {"voices": [{"audio_b64": "UklGR…", "format": "wav", "sample_rate": 24000, "seconds": 3.4}, …]}
# 2. Keep one: upload the clip with its line as the reference text
curl https://api.openalchemy.io/v1/audio/voices \
-H "Authorization: Bearer $OA_API_KEY" \
-F file=@voice1.wav -F name=hana -F language=en \
-F reference_text="Hey, it is really good to finally meet you."
# 3. Speak anything in it
curl https://api.openalchemy.io/v1/audio/speech \
-H "Authorization: Bearer $OA_API_KEY" -H "Content-Type: application/json" \
-d '{"model": "cosyvoice3", "voice": "hana", "input": "See you tomorrow!"}' -o hana.wavデザインできる言語は中国語、英語、日本語、韓国語、ドイツ語、フランス語、ロシア語、ポルトガル語、スペイン語、イタリア語の 10 言語です。ライブラリに保存できる声は現在、中国語・英語・日本語です。ワーカーは Engine Desktop 0.6.29 以降の設定 → ランタイム → Voice design から参加できます。
2026 年 10 月に計測。自分の GPU で声のデザインを提供するには Engine Desktop をダウンロードしてください。