概覽
LyraType Tide 憑語音即時帶動虛擬角色的神情與上半身動作。LyraType Moon 讓角色在兩句話之間持續收聽,並自主決定何時開口。兩者可單獨使用,也可搭配:Moon 決定開口時機,Tide 讓神情提前一步跟上。
Tide:語音進,動作出
Tide 接在語音和形象之間。它聽著對話,持續帶動形象,角色在說話、在聽、在等待時都是如此。
- 輸入
- 語音,隨產生隨傳送
- 輸出
- 角色的神情與上半身動作
- 嘴型
- 交給你自己的嘴型同步,由同一段語音驅動
- 形象
- 沿用現有的 Live2D 模型
Tide 透過你已經在用的形象軟體帶動角色,所以能用臉部捕捉的模型就能用 Tide。
Moon:聽到的話進來,要說的話出去
Moon 與你的語音辨識、語音合成並行運作。兩句話之間它也一直在聽,所以接話快,也能在沒人發問時自己開口。
- 輸入
- 角色聽到的話,以文字形式隨到隨送
- 輸出
- 要說的話
- 搭配
- 聲音交給你的語音合成,面容交給 Tide
在 VTube Studio 中使用 Tide
研究預覽階段,Tide 透過 VTube Studio 公開的外掛 API 驅動形象。
- 在 VTube Studio 的設定裡開啟外掛 API(預設監聽 8001 連接埠)。
- 啟動 Tide 用戶端。VTube Studio 會詢問一次是否允許外掛連線,選擇允許。
- 隨後由 Tide 帶動模型。帶有身體參數的模型,上半身也會一起動。
- 嘴部仍由你原來的嘴型同步驅動,Tide 不接管。
使用 LyraType Studio
在 lyratype.com/studio 開啟 LyraType Studio,就能以我們的聲音與動作驅動你自己在 VTube Studio 裡的形象。請在執行 VTube Studio 的電腦上用 Chrome 或 Edge 開啟,不需要安裝任何東西。
- 在執行 VTube Studio 的電腦上,用 Chrome 或 Edge 開啟 lyratype.com/studio。
- 貼上 LyraType 郵件裡的工作室權杖,它以 lt_ 開頭。
- 在 VTube Studio 裡開啟設定,找到 VTube Studio Plugins,開啟 Start API(允許外掛)。連接埠預設為 8001,除非你改過。
- 瀏覽器詢問是否允許 lyratype.com 存取這台電腦上的應用程式時,選擇允許。如果先前封鎖了,點網址列左側的圖示開啟網站設定,把「裝置上的應用程式」(Apps on device)設為允許。
- VTube Studio 會跳出外掛「LyraType Studio」的授權視窗。只在你剛在 lyratype.com/studio 上點過之後,才按「允許」(Allow)。
- 等模型檢查跑完。缺少某個輸入時,可以在 VTube Studio 的模型設定裡把它連到模型的參數,也可以先略過,繼續下一步。
- 安裝虛擬音效線,例如 VB-Audio Virtual Cable,將 TTS 輸出設為 CABLE Input。在 OBS 中新增「音訊輸入擷取」,選擇 CABLE Output,並開啟音訊監聽以便自行監聽。
- 點「打個招呼」(Say hello),看看你的模型和 OBS。如果 OBS 中動作比聲音快,請在設定中調高「動作延遲」(Motion delay)。
- 在「角色」頁填寫角色設定、選擇聲音並調整動作參數;然後在「直播」頁開始對話。
取得方式
來信寫幾句你在做什麼、用哪種形象軟體,以及打算在雲端還是在自己的裝置上執行模型。我們會回信給你。
- 雲端 API
- 使用 Tide 與 Moon 的主要方式。Tide 的即時 API 開放免費試用:來信後我們以郵件發放權杖,也請留下寶貴意見。
- 本機授權
- 模型在你自己的裝置上執行,離線可用。
- 客製合作
- 針對特定角色或場景的調校與整合。
即時 API:一個角色一條連線
開一條 WebSocket,把角色自己的聲音隨合成隨送、把使用者的聲音隨聽到隨送,之後每三分之一秒收到一塊十個影格的動作,每個影格以序號標記時刻:第 f 個影格屬於工作階段時間 f / 30 秒。聲音和動作按同一個時鐘播放,顯示比時鐘晚十分之一秒左右,好讓每塊動作在第一個影格到期之前已經在手。
- GET /v1/session
- WebSocket 本身。一條連線一個工作階段。
- POST /v1/sessions
- 可選的准入介面,供自己管理使用者的後端使用:回傳工作階段編號和一張 60 秒內有效、只能用一次的票券,憑票開一條連線,無需權杖。
- GET /healthz
- 主機負載:工作階段數、上限、伺服端每拍(三分之一秒的動作)的計算耗時。無需權杖。
- 權杖
- Authorization: Bearer <token>,或連線位址上的 ?token=(瀏覽器無法替 WebSocket 加請求標頭)。票券放在 ?ticket= 或 Sec-WebSocket-Protocol 裡。
第一則訊息給出角色的風格參數(preset)和一個種子,回覆列出每個影格的通道。風格參數在工作階段內固定,換一組就是另開一段。相同的聲音、風格參數和種子,兩次的動作完全相同。
{"type": "start", "preset": {"smile_rest": 0.6, "head_activity": 0.5}, "seed": 0}
{"type": "started", "session": "9f2c…", "fps": 30, "commit": 10,
"channels": ["head_yaw", "head_pitch", "head_roll", "gaze_x", "gaze_y",
"eye_open_left", "eye_open_right", "brow_left", "brow_right", "smile",
"chest_yaw", "chest_pitch", "chest_roll", "sway_x",
"mouth_press", "mouth_pucker", "smile_skew"]}- smile_rest
- 0 靜止時嘴放鬆 … 1 靜止時帶笑
- brow_rest
- 0 眉低 … 1 眉揚
- head_activity
- 0 頭部安靜 … 1 頭部活躍
- blink_rate
- 0 少眨眼 … 1 多眨眼
- gaze_activity
- 0 目光穩定 … 1 目光游移
- listen_activity
- 0 聽時安靜 … 1 聽時多動
- listen_nods
- 0 聽時少點頭 … 1 聽時多點頭
沒給出的風格參數取中間值。工作階段時間的零點就是 started 訊息送出的那一刻,聲音的時間戳和影格序號都從這裡起算。
送入聲音
聲音以二進位訊息傳送:一個位元組標明是誰的聲音(0 角色自己,1 對方),八個位元組小端 float64 給出第一個取樣播放或被聽到的工作階段時間,其後是 16 kHz 單聲道小端 16 位元取樣。每塊長短不限,通常 50 到 200 毫秒一塊。
- 合成器一交出角色的聲音就立刻轉送,並標上它將要播放的時刻。不必為 Tide 推遲任何東西;聲音知道得越早,神情跟得越準。
- 想多早播就多早播。要不要延後播放由你定:對話不必延後,影片或獨白可以延後到半秒,動作會跟聲音更緊。
- 對方的聲音沒法提前,聽到多少就送多少。
- 被打斷時,撤回沒播出去的部分:從那個時刻起的聲音作廢。
- 告訴工作階段誰在說話:每路聲音已知的說話區間。短於一秒的區間按應聲處理。
- spans
- {"type":"spans","voice":"own"|"partner","spans":[[start_s,end_s],…]},該路聲音迄今已知的全部區間,整體取代上一次。尚未結束的區間到目前為止。
- withdraw
- {"type":"withdraw","at":seconds},從該時刻起角色的聲音沒有播出,相應區間在此截斷。
- ping
- {"type":"ping"},回覆 {"type":"pong","t":seconds}。六十秒沒有聲音也沒有 ping 的連線會被關閉。
- close
- {"type":"close"},結束工作階段,與直接關閉連線相同。
收到什麼
每拍一則二進位訊息:32 位元無號首個影格序號,32 位元無號影格數(10),然後是影格數 × 通道數的小端 32 位元浮點數,逐格排列,通道順序按 started 所列。每塊在首個影格到期的那一刻生成,再經一拍的計算送到你手裡:主機空閒時 40 到 70 毫秒,坐滿時最多三分之一秒。
每個影格十七個數,用追蹤器的單位。顯示端為每個模型做一次對應,用這個模型的角色都共用它。靜止值一欄給出工作階段起始的姿態。
- head_yaw, head_pitch, head_roll
- 弧度,向左、向上為正。明顯的點頭約 0.1。靜止約 0。
- gaze_x, gaze_y
- 眼球行程的 −1 到 1。靜止 0 與 0.03。
- eye_open_left, eye_open_right
- 0 閉到 1 睜大;放鬆睜開 0.65;眨眼落到 0。靜止 0.65。
- brow_left, brow_right
- 0 低到 1 揚,絕對值。靜止 0.33 與 0.28。
- smile
- 0 到 1,絕對值:0.37 為中性的嘴,0.7 為日常的滿笑。靜止 0.37。
- chest_yaw, chest_pitch, chest_roll
- 上半身的弧度。靜止 0。
- sway_x
- 左右傾身,−1 到 1。靜止 0。
- mouth_press
- 0 到 1,雙唇抿緊的程度。靜止 0.1。
- mouth_pucker
- 0 到 1,噘嘴。靜止 0.1。
- smile_skew
- 左嘴角減右嘴角,−1 到 1;歪笑約 0.3。靜止 0。
說話時的張嘴不在通道裡:由你的嘴型同步從聲音裡驅動,模型的 smile、mouth_press、mouth_pucker 和 smile_skew 疊在它上面。
import asyncio, json, struct
import numpy as np, soundfile, websockets
URL, TOKEN = "wss://<host>/v1/session", "<token>"
async def main():
own, _ = soundfile.read("own.wav", dtype="int16") # 單聲道,16 kHz
headers = {"Authorization": f"Bearer {TOKEN}"}
async with websockets.connect(URL, additional_headers=headers) as ws:
await ws.send(json.dumps({"type": "start", "preset": {"smile_rest": 0.6}, "seed": 0}))
channels = json.loads(await ws.recv())["channels"]
# 角色的聲音,標為 0.5 秒開始播放,先於播放交出
await ws.send(bytes([0]) + struct.pack("<d", 0.5) + own.tobytes())
end = 0.5 + len(own) / 16000
await ws.send(json.dumps({"type": "spans", "voice": "own", "spans": [[0.5, end]]}))
while True:
message = await ws.recv()
if isinstance(message, bytes):
first, count = struct.unpack_from("<II", message)
rows = np.frombuffer(message, dtype="<f4", offset=8).reshape(count, len(channels))
print(f"frames {first}-{first + count - 1} at {first / 30:.2f} s", dict(zip(channels, rows[0].round(3))))
asyncio.run(main())配額與錯誤
- 訊息大小
- 最多 8 MB。角色的聲音可以比播放提前任意多;服務只留它需要的部分。
- 未讀的影格
- 積壓約三十秒的影格不讀取,連線會被中斷(關閉碼 1013)。
- 容量
- 一台主機帶 16 到 20 個工作階段。超出之後,或主機的計算超過即時的 85 %,新的工作階段以 capacity 拒絕;既有的不會被拖慢。
- limit
- 權杖各有上限,隨權杖一併告知:同時開著的工作階段數,和每天(UTC)可用的分鐘數。超過任一項,新的工作階段以 limit 拒絕(申請票券得到 HTTP 429);用完當天分鐘數的工作階段以 limit 關閉,關閉碼 1008。
- 第一次連線
- 主機只在有人連線時運行。閒置一陣之後,第一次連線要等主機啟動,約 10 秒;之後的連線即時。使用者一到(開啟頁面、選定角色)就請求 GET /healthz,等第一句聲音出來時,啟動已經完成。
- unauthorized
- 沒有權杖、權杖錯誤,或票券重複使用、過期。回報後連線關閉。
- bad_request
- 訊息格式不對;連線保持。start 訊息不對則關閉。
- internal
- 工作階段在主機上失敗;連線關閉,請另開一條。
- 版本
- 路徑為 /v1。在列表末尾新增通道、新增訊息類型或欄位都不改版本;既有通道或欄位的含義、順序變了才改。從 started 讀通道表,就不會受影響。
常見問題
需要攝影機或動作捕捉設備嗎?
不需要。Tide 只憑語音運作。
支援哪些形象?
目前支援透過 VTube Studio 使用的 Live2D 模型。其他執行環境會隨 API 一併說明。
預覽模型可以用於正式產品嗎?
預覽版供評估使用。正式使用的條款請來信商議。