Closed17

高速なTTSサービス「Cartesia」を試す

kun432kun432

とあるところで知った。

https://www.cartesia.ai/

2つのサービスを提供している

  • Sonic: 音声生成API、いわゆるTTS。
    • ストリーミング用に最適化された高速なTTS
    • ピッチ・スピード・感情・発話の細かい制御が可能
    • 15秒で音声クローンが可能
    • 日本語を含む多言語に対応
  • On-Device: State Spaceモデルを使用したマルチモーダルAIサービス?

有償サービスではあるが、このSonicというTTSを試してみる。

参考

https://www.atpartners.co.jp/ja/news/2024-12-13-cartesia-a-developer-of-innovative-state-space-models-ssms-that-will-shape-the-next-wave-of-generative-ai-raises-22m-in-seed

kun432kun432

公式ドキュメント

https://docs.cartesia.ai/get-started/overview

概要

Cartesiaへようこそ

当社のAPIは、開発者が自然で応答性に優れたリアルタイムのマルチモーダルAI体験を構築することを可能にします。

Cartesia APIは現在、当社の最先端の多言語生成音声モデルであるSonicを提供しています。Sonicはテキスト入力を受け取り、極めてリアルな音声で応答します。また、音声を複製することも可能です。速度や感情、発音やアクセントなど、あらゆる側面を制御して生成することができます。

**Sonicは世界最速のテキスト読み上げモデルです。**最初のバイトのオーディオをわずか90ミリ秒でストリーミングできるため、リアルタイムや会話形式の体験に最適です。(例えるなら、まばたきの約2倍の速さです。)

**Sonicは、世界最高品質のテキスト読み上げモデルです。**スピードが最優先事項でない場合でも、コンテンツのナレーション、動画の吹き替え、その他どのような用途であっても、Sonicは最良の選択です。

利用可能なSonicモデルのバリエーションとその機能については、「モデル」セクションをご覧ください。

kun432kun432

利用可能なモデル

https://docs.cartesia.ai/build-with-sonic/models

Cartesiaは、テキスト音声変換用の高精度かつ低レイテンシのSonicモデルを含む、最先端のモデル群を提供しています。

利用可能なモデルのサマリ

モデルID 説明 利用可能な言語
sonic 現行安定版のエイリアス現在はsonic-2024-12-12を指す。 en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr
sonic-preview プレビュー版。 en
sonic-2024-12-12 2024年12月更新。 en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr
sonic-2024-10-19 2024年10月更新。 en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr
sonic-english [廃止予定] 英語専用モデル。 en
sonic-multilingual [廃止予定] 多言語対応モデル。 en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr

各モデルの説明

Sonic

特徴 詳細
モデルエイリアス sonic
リリース日 2024年12月
最新バージョン sonic-2024-12-12
特徴 高精度・低遅延・表現力豊かな音声生成

Sonic Preview

特徴 詳細
モデルエイリアス sonic-preview
利用可能な言語 en
特徴 プレビュー版で最新機能をテスト可能

Sonic English (廃止予定)

特徴 詳細
モデルID sonic-english
リリース日 2024年5月
最新バージョン 2024年10月
利用可能な言語 en
機能 略語、イニシャリズム、数字、日付、電話番号、SSNの対応
問題点 連続した単語や長い数字で生成がループ・高速化する場合がある

Sonic Multilingual (廃止予定)

特徴 詳細
モデルID sonic-multilingual
リリース日 2024年6月
最新バージョン 2024年9月
利用可能な言語 en, fr, de, es, pt, zh, ja, hi, it, ko, nl, pl, ru, sv, tr
機能 数字、日付、電話番号の対応(en, fr, de, es, zh)
問題点 日本語・ポルトガル語で一部不正確な結果が出る場合がある

推奨事項

  • sonicsonic-preview は最新安定版およびプレビュー版として使用可能。
  • 本番環境では日付バージョンのモデルを推奨。
  • 廃止予定のモデルは今後の使用を避け、sonic を利用することを推奨。
kun432kun432

料金

https://www.cartesia.ai/pricing

プラン

プラン 料金 クレジット 同時生成数 利用可能な機能等
Free $0/月 10K(約13分に相当) 1 15言語での音声生成
Discordでのサポート
共有時はCartesiaのクレジット表記が必要
Pro $5/月 100K(約2時間に相当) 3 5秒の音声から音声クローンが可能
あらゆる音声クリップの音声を変更可能
音声をあらゆる言語・アクセントでローカライズ
商用利用
Startup $49/月 1.25M(約27時間に相当) 5 Proにあるすべての機能
スタートアップ向けにより高い上限設定
Scale $299/月 8M(約173時間に相当) 15 - Starupにあるすべての機能
ビジネスのスケールに対してより高い上限設定
Enterprise カスタム カスタム カスタム Scaleにあるすべての機能
Slackでの専任サポート
SOC2 Type IIコンプライアンス
SSO
音声モデルのファインチューニング
カスタムのSLAと制限

音声クローンや音声変換を利用するには、最低でもProプランが必要になる。TTSだけなら無料でも使えそう。

サービスごとのクレジット消費

  • TTS: 1文字で1クレジット
  • 音声変換(ボイスチェンジャー): 1秒で15クレジット

その他FAQもあるので、実際に利用する場合は見ておくと良い。

kun432kun432

クライアントSDK

PythonとJavaScript/TypeScriptがある。Pythonで試してみる。なお、試すのはTTSのみ。音声クローンや音声変換は料金プランの変更が必要になるので、一旦はFreeプランで進めようと思う。

https://github.com/cartesia-ai/cartesia-python

事前準備

今回はローカルのMac上で試す。以下の準備が必要。

  • Cartesiaでアカウント作成
  • APIキーを作成
  • ffmpegをインストール

以下でアカウントを作成

https://play.cartesia.ai/

アカウント作成後、Playgroundが表示されるので、APIキーを作成。APIキーは二度と表示されないので安全な場所に控えておくこと。

Macでffmpegをインストールしておく

brew install ffmpeg
brew info ffmpeg
出力
==> ffmpeg: stable 7.1 (bottled), HEAD
Play, record, convert, and stream audio and video
https://ffmpeg.org/
Installed
/opt/homebrew/Cellar/ffmpeg/7.1_4 (287 files, 52.1MB) *
(snip)

Python仮想環境を作成。自分はmiseを使うが、適宜。

mkdir cartesia-work && cd cartesia-work
mise use python@3.12
cat << 'EOS' >> .mise.toml

[env]
_.python.venv = { path = ".venv", create = true }
EOS

パッケージインストール

pip install cartesia
pip freeze | grep -i cartesia
出力
cartesia==1.3.1

APIキーを環境変数にセットしておく

export CARTESIA_API_KEY="XXXXXXXXXX"
kun432kun432

利用可能な音声の確認

利用可能な音声を確認する。

list_voices.py
from cartesia import Cartesia
import os
import json
from collections import Counter

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

# 各音声ごとに以下が返される
# - id
# - mode
# - is_public
# - name
# - description
# - created_at
# - gender
# - language
# - embedding
voices = client.voices.list()

print("全音声の数:", len(voices))
print()

print("各言語ごとの音声数:")
language_counts = Counter(voice.get("language") for voice in voices)
for language, count in sorted(language_counts.items()):
    print(f"{language}: {count}")
print()

print("日本語音声:")
for voice in voices:
    if voice.get("language") == "ja":  # languageが"ja"である場合
        # embeddingはデータが大きいので省略している
        filtered_voice = {key: value for key, value in voice.items() if key != "embedding"}
        print(json.dumps(filtered_voice, indent=2, ensure_ascii=False))
出力
全音声の数: 138

各言語ごとの音声数:
de: 7
en: 73
es: 10
fr: 7
hi: 5
it: 3
ja: 4
ko: 3
nl: 3
pl: 4
pt: 3
ru: 4
sv: 2
tr: 3
zh: 7

日本語音声:
{
  "id": "2b568345-1d48-4047-b25f-7baccf842eb0",
  "mode": "stability",
  "is_public": true,
  "name": "Japanese Woman Conversational",
  "description": "This voice is gentle and friendly, perfect for casual conversations in Japanese",
  "created_at": "1970-01-17T16:00:00-08:00",
  "gender": "feminine",
  "language": "ja"
}
{
  "id": "97e7d7a9-dfaa-4758-a936-f5f844ac34cc",
  "mode": "stability",
  "is_public": true,
  "name": "Japanese Man Book",
  "description": "This voice is deep and even, perfect for narrational content in Japanese",
  "created_at": "1970-01-01T16:00:00-08:00",
  "gender": "masculine",
  "language": "ja"
}
{
  "id": "e8a863c6-22c7-4671-86ca-91cacffc038d",
  "mode": "stability",
  "is_public": true,
  "name": "Japanese Male Conversational",
  "description": "This voice is clear and confident, perfect for a Japanese call center agent ",
  "created_at": "1970-01-08T16:00:00-08:00",
  "gender": "masculine",
  "language": "ja"
}
{
  "id": "44863732-e415-4084-8ba1-deabe34ce3d2",
  "mode": "stability",
  "is_public": true,
  "name": "Japanese Children Book",
  "description": "This voice is young and expressive, perfect for character work in games and videos",
  "created_at": "1970-02-06T16:00:00-08:00",
  "gender": "feminine",
  "language": "ja"
}

日本語は

  • 女性・会話
  • 男性・朗読
  • 男性・会話
  • 女性(子供)・朗読

の4種類みたい。TTSを行う際はこのIDを指定する。

kun432kun432

TTS

まずはファイルに出力してみる。

tts_to_file.py
from cartesia import Cartesia
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

data = client.tts.bytes(
    model_id="sonic",
    transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
    voice_id="2b568345-1d48-4047-b25f-7baccf842eb0",  # 日本語・女性・会話
    # 出力フォーマットは https://docs.cartesia.ai/api-reference/tts/bytes で確認
    output_format={
        "container": "wav",
        "encoding": "pcm_f32le",
        "sample_rate": 44100,
    },
    language="ja",
)

with open("cartesia_ja_female_conversational_sample01.wav", "wb") as f:
    f.write(data)

生成された音声は以下。

https://audio.com/kun432/audio/cartesia-sonic-sample-ja-female-conversational-01

ちょっと早口かな。多少違和感がある箇所は残るものの、結構自然な感じ。

他の音声についても。

日本語・男性・会話

https://audio.com/kun432/audio/cartesia-ja-male-conversational-sample01

日本語・男性・朗読

https://audio.com/kun432/audio/cartesia-ja-male-book-sample01

日本語・女性(子供)・朗読

https://audio.com/kun432/audio/cartesia-ja-children-book-sample01

説明とマッチしている音声か?というと微妙に違うような気もするが、発話の調整ができるようなので、どこまでできるかは後ほど見ていきたい。

Server-Sent Events (SSE)、つまりストリーミング。pyaudioを使う。

pip install pyaudio
pip freeze | grep -i pyaudio
出力
PyAudio==0.2.14
tts_sse.py
from cartesia import Cartesia
import pyaudio
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
voice_id = "2b568345-1d48-4047-b25f-7baccf842eb0"  # 日本語・女性・会話
voice = client.voices.get(id=voice_id)

transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"

model_id = "sonic"
output_format = {
    "container": "raw",
    "encoding": "pcm_f32le",
    "sample_rate": 44100,
}
language="ja"

p = pyaudio.PyAudio()
rate = 44100

stream = None

# オーディオの生成とストリーミング
for output in client.tts.sse(
    model_id=model_id,
    transcript=transcript,
    voice_embedding=voice["embedding"],
    stream=True,
    output_format=output_format,
    language=language,
):
    buffer = output["audio"]

    if not stream:
        stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)

    # オーディオデータをストリームに書き出す
    stream.write(buffer)

stream.stop_stream()
stream.close()
p.terminate()

これ何度か実行して気づいたのだけど、常に同じ音声が生成されるわけではなく、生成するたびに微妙に違う結果になる。

非同期の場合。AsyncCartesiaを使う。

tts_sse_async.py
from cartesia import AsyncCartesia
import asyncio
import pyaudio
import os


async def write_stream():
    client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
    voice_id="2b568345-1d48-4047-b25f-7baccf842eb0"  # 日本語・女性・会話
    voice = client.voices.get(id=voice_id)
    transcript = "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"
    model_id = "sonic"
    output_format = {
        "container": "raw",
        "encoding": "pcm_f32le",
        "sample_rate": 44100,
    }
    language="ja"

    p = pyaudio.PyAudio()
    rate = 44100

    stream = None

    # オーディオの生成とストリーミング
    async for output in await client.tts.sse(
        model_id=model_id,
        transcript=transcript,
        voice_embedding=voice["embedding"],
        stream=True,
        output_format=output_format,
        language=language,
    ):
        buffer = output["audio"]

        if not stream:
            stream = p.open(
                format=pyaudio.paFloat32, channels=1, rate=rate, output=True
            )

        # オーディオデータをストリームに書き出す
        stream.write(buffer)

    stream.stop_stream()
    stream.close()
    p.terminate()
    await client.close()


asyncio.run(write_stream())

同期・非同期ともにストリーミングだとかなりレスポンスは速く感じる。

kun432kun432

Websocketを使用したTTS

CartesiaはWebsocketも提供している

tts_websocket.py
from cartesia import Cartesia
import pyaudio
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
voice_id="2b568345-1d48-4047-b25f-7baccf842eb0"  # 日本語・女性・会話
voice = client.voices.get(id=voice_id)
transcript = "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。"

model_id = "sonic"
output_format = {
    "container": "raw",
    "encoding": "pcm_f32le",
    "sample_rate": 22050,
}
language="ja"

p = pyaudio.PyAudio()
rate = 22050

stream = None

# websocket接続を作成
ws = client.tts.websocket()

# websocketを使用してオーディオの生成とストリーミング
for output in ws.send(
    model_id=model_id,
    transcript=transcript,
    voice_embedding=voice["embedding"],
    stream=True,
    output_format=output_format,
    language="ja",
):
    buffer = output["audio"]

    if not stream:
        stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)

    # オーディオデータをストリームに書き出す
    stream.write(buffer)

stream.stop_stream()
stream.close()
p.terminate()

ws.close()  # websocket接続をクローズ

Websocketの場合は「Conditioning speech on previous generations」に対応している。これは何かというと、

  • 長い入力テキストでもリアルタイム性を確保
    • 一般的には、長い文章をまとめて送信すると、処理に時間がかかる
    • テキストを小分けにして逐次送信しつつ音声生成が可能することで、リアルタイム性を向上可能
  • 音声の一貫性を確保
    • 一般的には、文章を小分けにして音声生成した場合、文脈のつながりが考慮されないため、発話が不自然になってしまうことがある
    • 前に生成した音声の情報を考慮し、文脈や音調が自然になるように続きの音声を生成可能。

が可能になる、つまりストリーミングは入出力の両方で可能、かつ、その場合でも自然に音声生成できるということらしい。Cartesiaではこれを"Audio Continuations"(オーディオ継続)と呼んでいる。

tts_websocket_with_context.py
import asyncio
import os
import pyaudio
from cartesia import AsyncCartesia

async def send_transcripts(ctx):
    voice_id="2b568345-1d48-4047-b25f-7baccf842eb0"  # 日本語・女性・会話
    model_id = "sonic"
    output_format = {
        "container": "raw",
        "encoding": "pcm_f32le",
        "sample_rate": 44100,
    }
    language="ja"

    transcripts = [
        "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
        "このAPIを使えば、高品質な音声をリアルタイムで生成することができます。",
        "さらに、文章を小分けにして送信しながら、文脈を考慮したスムーズな音声生成が可能です。",
        "多言語対応や感情表現を組み込むこともでき、幅広い用途で活用できます。",
        "音声の速度や感情のトーンを調整することで、より自然なコミュニケーションを実現します。",
        "Cartesia APIを使って、新しい音声体験をぜひお試しください!"
    ]

    for transcript in transcripts:
        # 入力テキストが利用可能になったら送信
        await ctx.send(
            model_id=model_id,
            transcript=transcript,
            voice_id=voice_id,
            continue_=True,  # 文脈を保持して次の入力を待つ
            output_format=output_format,
            language=language,
        )

    # 入力がこれ以上送信されないことを示す。これをしない場合、5秒間の無操作後にコンテキストが閉じられる。
    await ctx.no_more_inputs()

async def receive_and_play_audio(ctx):
    p = pyaudio.PyAudio()
    stream = None
    rate = 44100

    async for output in ctx.receive():
        buffer = output["audio"]

        if not stream:
            stream = p.open(
                format=pyaudio.paFloat32,
                channels=1,
                rate=rate,
                output=True
            )

        stream.write(buffer)

    stream.stop_stream()
    stream.close()
    p.terminate()

async def stream_and_listen():
    client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

    # WebSocket接続を設定
    ws = await client.tts.websocket()

    # 音声送受信用のコンテキストを作成
    ctx = ws.context()  # コンテキストIDが指定されていない場合はランダムに生成される

    send_task = asyncio.create_task(send_transcripts(ctx))
    listen_task = asyncio.create_task(receive_and_play_audio(ctx))

    # 2つのコルーチンタスクを同時に呼び出す
    await asyncio.gather(send_task, listen_task)

    await ws.close()
    await client.close()

asyncio.run(stream_and_listen())

コードとしては、

  1. WebSocket接続を確立し、「コンテキスト」を作成する
    • ws.context()で、音声生成用のコンテキストが作成される
  2. テキストを順次送信
    • ctx.send()で、コンテキスト内でテキストを逐次送信
    • continue_=True を指定すると、コンテキストが維持される
    • これにより、サーバー側で前後の文脈を考慮した音声生成が可能になる
  3. 音声データをリアルタイムで再生
    • ctx.receive()で、サーバーから逐次送られる音声データを受信。
    • buffer に格納された音声データをリアルタイムで再生。
  4. コンテキストの終了
    • テキストの送信が完了したら、no_more_inputs() を呼び出してコンテキストを終了。
    • 呼び出さない場合、コンテキストは5秒間の無操作後に自動終了。

という感じ。

実際に試した動画はこちら。

https://www.youtube.com/watch?v=-qD2AeVWo64

多少の違和感はあるものの、それでもかなりのリアルタイム性が維持されているのは結構凄いのではないだろうか?

上記の例は非同期を使用しているが、同期の場合でも1秒未満の間隔でテキストチャンクを生成するジェネレータを使えば同じことができる。(ただし非同期の場合とは異なるAPIを使うらしい)

tts_websocket_with_context_sync.py
from cartesia import Cartesia
import pyaudio
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

transcripts = [
    "こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
    "このAPIを使えば、高品質な音声をリアルタイムで生成することができます。",
    "さらに、文章を小分けにして送信しながら、文脈を考慮したスムーズな音声生成が可能です。",
    "多言語対応や感情表現を組み込むこともでき、幅広い用途で活用できます。",
    "音声の速度や感情のトーンを調整することで、より自然なコミュニケーションを実現します。",
    "Cartesia APIを使って、新しい音声体験をぜひお試しください!"
]

# 各テキストをスペースで終わらせると、音声がスムーズになる
def chunk_generator(transcripts):
    for transcript in transcripts:
        if transcript.endswith(" "):
            yield transcript
        else:
            yield transcript + " "


voice_id="2b568345-1d48-4047-b25f-7baccf842eb0"  # 日本語・女性・会話
model_id = "sonic-english"
output_format = {
    "container": "raw",
    "encoding": "pcm_f32le",
    "sample_rate": 44100,
}
language = "ja"

p = pyaudio.PyAudio()
rate = 44100

stream = None

# WebSocket接続を設定
ws = client.tts.websocket()

# 音声送受信用のコンテキストを作成
ctx = ws.context()  # コンテキストIDが指定されていない場合はランダムに生成される

# テキストジェネレータを渡して音声を生成・ストリーム送信
output_stream = ctx.send(
    model_id=model_id,
    transcript=chunk_generator(transcripts),
    voice_id=voice_id,
    output_format=output_format,
    language=language,
)

for output in output_stream:
    buffer = output["audio"]

    if not stream:
        stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)

    # 音声データをストリームに書き込む
    stream.write(buffer)

stream.stop_stream()
stream.close()
p.terminate()

ws.close()  # WebSocket接続を閉じる

ドキュメントにも記載がある。
https://docs.cartesia.ai/build-with-sonic/capability-guides/stream-inputs-using-continuations

また、WebSocketではタイムスタンプも取得できる。音声生成時の各単語に開始時刻と終了時刻を付与することができる。これを使えば、音声と入力テキストの同期が必要なユースケース、例えば、字幕生成などで活用できる。ただし日本語は非対応なので、英語の場合の例。

from cartesia import Cartesia
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

transcript = "Hello! This is a sample of audio generation using Cartesia's Text-to-Speech API with timestamps added via WebSocket."

voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
model_id = "sonic"
output_format = {
    "container": "raw",
    "encoding": "pcm_f32le",
    "sample_rate": 44100,
}
language = "en"

# WebSocket接続を作成
ws = client.tts.websocket()

# タイムスタンプを含む音声生成リクエストを送信
response = ws.send(
    model_id=model_id,
    transcript=transcript,
    voice_id=voice_id,
    output_format=output_format,
    language=language,
    stream=False,  # ストリーミングを無効化して完全なレスポンスを取得
    add_timestamps=True  # タイムスタンプを有効化
)

# タイムスタンプ情報を取得
word_timestamps = response['word_timestamps']

# 単語ごとのタイムスタンプを出力
words = word_timestamps['words']
start_times = word_timestamps['start']
end_times = word_timestamps['end']

for word, start, end in zip(words, start_times, end_times):
    print(f"単語: {word}, 開始時刻: {start:.2f}, 終了時刻: {end:.2f}")

# WebSocket接続を閉じる
ws.close()
出力
単語: Hello!, 開始時刻: 0.02, 終了時刻: 0.36
単語: This, 開始時刻: 1.32, 終了時刻: 1.46
単語: is, 開始時刻: 1.46, 終了時刻: 1.60
単語: a, 開始時刻: 1.78, 終了時刻: 1.78
単語: sample, 開始時刻: 1.94, 終了時刻: 2.34
単語: of, 開始時刻: 2.34, 終了時刻: 2.40
単語: audio, 開始時刻: 2.64, 終了時刻: 2.98
単語: generation, 開始時刻: 3.14, 終了時刻: 3.60
単語: using, 開始時刻: 3.82, 終了時刻: 4.06
単語: Cartesia's, 開始時刻: 4.18, 終了時刻: 4.50
単語: Text-to-Speech, 開始時刻: 4.50, 終了時刻: 5.73
単語: API, 開始時刻: 5.73, 終了時刻: 6.48
単語: with, 開始時刻: 6.94, 終了時刻: 6.94
単語: timestamps, 開始時刻: 7.24, 終了時刻: 7.90
単語: added, 開始時刻: 8.02, 終了時刻: 8.38
単語: via, 開始時刻: 8.38, 終了時刻: 8.64
単語: WebSocket., 開始時刻: 8.64, 終了時刻: 9.18
kun432kun432

マルチリンガルなTTS

アルファ版扱いのようだが、同じ音声で複数の言語を発話することができる。

といっても、実は上でやっているようにlanguageで異なる言語を指定するだけ。

from cartesia import Cartesia
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

data = client.tts.bytes(
    model_id="sonic",
    transcript="Hello! This is a sample of audio generation using Cartesia's Text-to-Speech API with Japanese voice speaking in English.",
    voice_id="2b568345-1d48-4047-b25f-7baccf842eb0",  # 日本語・女性・会話
    output_format={
        "container": "wav",
        "encoding": "pcm_f32le",
        "sample_rate": 44100,
    },
    language="en",  # 英語
)

with open("cartesia_ja_female_conversation_sample_en.wav", "wb") as f:
    f.write(data)

なお、READMEではsonic-multilingualを使っているが、このモデルはDeprecatedなので、通常のsonicを使用した。

ちなみにドキュメントに以下のようなことが書いてあった。

https://docs.cartesia.ai/build-with-sonic/capability-guides/localize-voices

Sonicの音声にはアクセントがあります。 Sonicの音声を使って別の言語の音声を生成しようとすると、やはり同じアクセントになります。 例えば、アメリカ人の声を使ってスペイン語の音声を作成しようとすると、出来上がったスペイン語の音声はアメリカ人のアクセントになってしまいます。

ターゲットとする言語のネイティブスピーカーのような音声を得るには、APIまたはプレイグラウンドを通じてSonicのローカライズ機能を使用します。

ローカライズ機能は、ローカライズする音声、音声の性別、ローカライズするターゲット言語とアクセントを受け取り、音声を生成するために使用できるエンベッディングを生成します(または、新しい音声として保存します)。

なるほど、どうやら設定を行ったうえで新しい音声を作成するような流れになる様子。

kun432kun432

音声の速度や感情を制御する

Experimentalステータスではあるが、音声の再生速度や感情を調整することができる。

from cartesia import Cartesia
import os

client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))

data = client.tts.bytes(
    model_id="sonic",
    transcript="こんにちは!これは、Cartesiaの音声生成APIを使用して作成した、日本語音声のサンプルです。",
    voice_id="2b568345-1d48-4047-b25f-7baccf842eb0",  # 日本語・女性・会話
    output_format={
        "container": "wav",
        "encoding": "pcm_f32le",
        "sample_rate": 44100,
    },
    language="ja",
    # _experimental_voice_controls で速度や感情を設定
    _experimental_voice_controls={
        # speed: 音声速度を速める。2つの指定方法がある。
        # - slowest/slow/normal/fast/fastestの文字列で指定
        # - -1.0〜1.0の少数で指定(-が遅い)
        "speed": "slowest",
        # emotion: 感情を設定する。`emotion_name:level`で指定。
        # - emotion_name: anger/positivity/surprise/sadness/curiosity から指定
        # - level: lowest/low/high/hightestから指定(mediumがデフォルトで指定不要)
        "emotion": ["positivity:highest"]  # 感情をポジティブで強調
    },
)

with open("cartesia_ja_female_conversational_slowest_hightest_positivity_sample01.wav", "wb") as f:
    f.write(data)

ドキュメントにも記載がある

https://docs.cartesia.ai/build-with-sonic/capability-guides/control-speed-and-emotion

kun432kun432

まとめ

全然知らなかったけど、リアルタイム性を踏まえるとかなり凄いのでは?

ここのところ、LLMをSTT・TTSと組み合わせてリアルタイムに近い音声インタフェースを色々試している。自分が試した限りでは、

  • STTはまあそれなりに高速化できる、結構複雑化するけど。
  • LLMのオーバーヘッドはたしかに一番大きいのだけども、出力はストリーミングでなんとか。
  • TTSにストリーミングで渡したとしても、オーバーヘッドはそこそこ気になるレベル&処理が複雑になる。

という感じで、実はTTSもなかなか難しいと感じていたのだが、Cartesiaはめちゃめちゃレスポンスが速い上に入力ストリーミングもあって、音声インタフェースでレイテンシーを極力小さくしたい、というニーズをかなりいい感じに満たせるのではないか?と感じた。

多少の読み間違いはあるし、イントネーションがおかしいところもあるのだが、

  • OpenAIでも結構読み間違えるし、ElevenLabsはかなり間違える。それらと比較してもそこまで悪くはない気がする。
  • 生成速度も高速、かつ、入出力でストリーミングに対応しており、入力ストリーミングの場合にチャンク単位での生成時における不自然さを解消する仕組みがある。かなりリアルタイムに近いTTSが可能
  • 上記をかなりシンプルに書ける
  • 試していないが、音声クローンにも対応している

ということを考えると、かなり良いのではないか?という気がする。

kun432kun432

のだが、調べても日本語で書いた記事は出てこない。なぜだろう?と思って調べてみたけど、TTSは去年の夏頃に出たばかりなのね。多分まだ新しくて知名度がそれほどないのではないかという気がする。

kun432kun432

LLMのストリーミングと組み合わせてみたい。ネイティブで実装しても良いと思うけど、インテグレーションで記載されている、PipecatとかLiveKitと組み合わせるのも良さそう。

kun432kun432

やっぱりちょっと長い文章を食わせたり、固有名詞なんかはかなり読み間違える感があるなぁ。ElevenLabsと比較すると幾分かマシではあるが、これはもうモデルの学習率の問題だよな。

このスクラップは2日前にクローズされました