メインコンテンツまでスキップ

キャラクターボイスと seed

eleven_v3 は、同じ文を同じ声で合成しても、毎回ちがう音が返ります。 声質もテンションも目に見えて変わります。

ナレーション1本を作るだけなら気になりません。 けれど「このキャラクターの声」として繰り返し使うなら、呼ぶたびに別人では成立しません。

このページは、その止め方の話です。

:::tip 結論から

  • AICU 登録ボイス(elena 等)は seed 固定済み。voice に slug を渡すだけでよい
  • 自分で固定するなら seed を明示する。同じ seed + 同じ文 = 同じ音
  • seed を変えたのに音が変わらないときは、キャッシュを疑う(force: true) :::

:::warning エンドポイントによって引数名が違う

ボイスの指定は、使うエンドポイントで名前が変わります。

エンドポイントボイス本文
/v1/audio/speech・/v1/tts/speech(OpenAI 互換)voiceinput
/v1/tts/generate(AICU 独自)slugtext

/v1/tts/generate に voice を渡してもエラーになりません。 黙って無視され、既定ボイスで 生成されます。200 が返り音声も正常なので、気づくのは難しいです。

2026-09-22 に seed を固定して実測しました(固定すると応答サイズが決定的になります)。

リクエスト応答サイズ(2 回とも)
ボイス指定なし22,196 / 22,196 バイト
"voice": "mei"22,196 / 22,196 バイト — 指定なしと 1 バイトも違わない
"slug": "mei"26,794 / 26,794 バイト

長文を一括生成してから気づくと、そのぶんが全部ちがう声で、しかも課金されます。 まず 1 行だけ生成して、音が実際に変わったことを確かめてから量を流してください。

なお値が不正な場合はきちんと失敗します。{"slug": "no_such_character"} は 400 {"error":"Unknown character: no_such_character. GET /v1/tts/voices を参照"} を返します。 黙殺されるのは引数名の取り違えだけです。

:::

何が起きるか​

同じリクエストを3回投げて、返ってきた MP3 のハッシュを比べます。

for i in 1 2 3; do
curl -s -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "mina", "model": "eleven_v3", "input": "こんにちは", "seed": '"$RANDOM"'}' \
| md5sum
done

seed を散らすと、3回とも違うハッシュになります。耳で聴いても別人です。 seed を固定すれば、3回とも同じハッシュになります。

AiCuty の既定 seed​

AICU 登録ボイスには採用済みの seed が入っています。seed を省略すればそれが使われます。

from openai import OpenAI

client = OpenAI(base_url="https://api.aicu.ai/v1", api_key="aicu_live_…")

# seed を書かなくてよい。何度呼んでも同じ声
client.audio.speech.create(
model="eleven_v3", voice="mina", input="こんにちは"
).stream_to_file("mina.mp3")
slug表記声質v3v2
elenaエレナ・ブルーム落ち着いて聞き取りやすい、標準語の女性ナレーション330330
meiメイ・ソレイユ元気で子供でも聴きやすい、明るい少女の声721721
minaミナ・アズール落ち着いた知的なナレーション。長時間でも聴き疲れしない101105
naoナオ・ヴェルデ優しい理系男子のソフトボイス5555
sakiサキ・ノワール優しさとあやしい魅力がある囁きボイス10331035
marshaマーシャ・アランチャさわやかで聴きやすい元気な女性418414
luc4ルカ(全力肯定彼氏くん)気持ちのいい低音の男性11041104

実際の音は AiCuty 公式ボイス で聴けます (日本語・英語・フランス語、Marsha はスペイン語・イタリア語も)。

v2 と v3 で値が違う理由​

同じ seed でも、モデルが違えば声の出方が変わります。 v3 で良かった個体が v2 でも良いとは限らないので、それぞれで選び直しています。

ミナは v3 が 101、v2 が 105。サキは v3 が 1033、v2 が 1035 です。

値の決め方​

キャラクターの誕生日(月+日を連結)を起点に、そこから近傍を聴き比べて決めています。 ミナは10月1日生まれなので 101 が起点、そこから 102 103 104 105 を聴きます。

誕生日起点なので覚えやすく、由来を説明でき、キャラクター間で衝突しません。

自分のボイスで固定する​

生の ElevenLabs voice_id を渡す場合、既定 seed はありません。自分で決めて明示します。

curl -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "pqHfZKP75CvOlQylNhV4", "model": "eleven_v3", "input": "…", "seed": 42}' \
--output out.mp3

決め方に正解はありませんが、値そのものより「決めて、書き残すこと」が大事です。 あとから「なぜこの声なのか」を再現できなくなるのが一番困ります。

候補をいくつか聴き比べてから決めてください。近い seed でも印象はかなり変わります。

seed を変えたのに音が変わらないとき​

キャッシュを疑ってください。

api.aicu.ai は「同じ声 × 同じモデル × 同じ文 × 同じ seed」を1つのキーとしてキャッシュします。 キャッシュヒットは課金されません(x-aicu-ap-cost: 0)。

curl -sS -D - -o /dev/null -X POST https://api.aicu.ai/v1/audio/speech \
-H "Authorization: Bearer $AICU_API_KEY" -H "Content-Type: application/json" \
-d '{"voice": "mina", "model": "eleven_v3", "input": "こんにちは"}' \
| grep -i 'x-aicu-ap-cost\|x-cache-hit'

x-aicu-ap-cost: 0 が返っていれば、それはキャッシュから返した音です。 どうしても作り直したいときは /v1/tts/generate の force: true を使ってください。

:::caution 「同じはずなのに違う音」が返っていた時期があります 以前のゲートウェイは seed を上流へ転送せず、キャッシュキーにも含めていませんでした。 そのため seed を変えても前の音声が返り、しかも 200 が返るので気づけませんでした。 現在は転送・キーの両方が直っています。 :::

声を揃えて並べるとき​

複数キャラクターの音声をつなぐなら、合成する前に1本ずつラウドネスを揃えてください。 声ごとに音量差があり、そのまま並べると大きい声だけが前に出ます。

ffmpeg -i in.mp3 -af "loudnorm=I=-18:TP=-2:LRA=9" -ac 1 -ar 44100 out.mp3

つなぎ目は 0.1秒ほどクロスフェードすると自然になります。 単純に連結すると、息継ぎの余韻がぶつ切りになります。

ffmpeg -i a.mp3 -i b.mp3 -filter_complex "[0][1]acrossfade=d=0.1:c1=tri:c2=tri" out.mp3

:::note mp3 の連結に -c copy を使わない concat demuxer + -c copy は、mp3 のエンコーダ遅延でタイムスタンプが巻き戻り (non monotonically increasing dts)継ぎ目が乱れます。concat フィルタで作り直してください。 :::

関連​