diff --git a/README.md b/README.md index f47d969..e0e83aa 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,12 @@ bun run devices bun run start:local ``` +TTS만 단독으로 확인: + +```bash +bun run tts:test -- "안녕하세요. 출력 장치 테스트입니다." +``` + Discord 모드: ```bash diff --git a/package.json b/package.json index 64cc66f..20e47b5 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "start": "bun src/index.ts discord", "start:discord": "bun src/index.ts discord", "start:local": "bun src/index.ts local", + "tts:test": "bun src/index.ts local-say", "setup:local-ai": "bun src/setup-local-ai.ts", "devices": "bun src/index.ts local-devices", "audio:devices": "bun src/index.ts local-devices", diff --git a/src/index.ts b/src/index.ts index c931977..49a3f59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import process from "node:process"; import { loadConfig, requireAssistantRuntimeConfig, requireDiscordRuntimeConfig } from "./config.js"; import { runDiscordBot } from "./discord-main.js"; import { Logger } from "./logger.js"; -import { printLocalAudioDevices, runLocalAssistant } from "./local-main.js"; +import { printLocalAudioDevices, runLocalAssistant, runLocalTtsSmokeTest } from "./local-main.js"; const mode = process.argv[2] ?? "discord"; const config = loadConfig(); @@ -20,8 +20,13 @@ async function main(): Promise { case "local-devices": await printLocalAudioDevices(); return; + case "local-say": { + const text = process.argv.slice(3).join(" ").trim() || "안녕하세요. TTS 단독 재생 테스트입니다."; + await runLocalTtsSmokeTest(requireAssistantRuntimeConfig(config), logger, text); + return; + } default: - throw new Error(`알 수 없는 실행 모드입니다: ${mode}. 사용 가능: discord, local, local-devices`); + throw new Error(`알 수 없는 실행 모드입니다: ${mode}. 사용 가능: discord, local, local-devices, local-say`); } } diff --git a/src/local-main.ts b/src/local-main.ts index 7199c72..6fc3e91 100644 --- a/src/local-main.ts +++ b/src/local-main.ts @@ -5,9 +5,11 @@ import type { AssistantRuntimeConfig } from "./config.js"; import { Logger } from "./logger.js"; import { LocalVoiceSession } from "./audio/local-voice-session.js"; import { requireFfmpegPath } from "./audio/ffmpeg-path.js"; +import type { LlmService } from "./services/llm.js"; import { LocalFasterWhisperSttService } from "./services/local-stt.js"; import { LocalKokoroTtsService } from "./services/local-tts.js"; import { OllamaLlmService } from "./services/ollama-llm.js"; +import type { SttService } from "./services/stt.js"; import { WindowsSystemTtsService } from "./services/windows-system-tts.js"; export async function printLocalAudioDevices(): Promise { @@ -120,3 +122,47 @@ export async function runLocalAssistant(config: AssistantRuntimeConfig, logger: await session.start(); } + +export async function runLocalTtsSmokeTest( + config: AssistantRuntimeConfig, + logger: Logger, + text: string, +): Promise { + const tts = + process.platform === "win32" + ? new WindowsSystemTtsService(config.LOCAL_TTS_SPEED) + : new LocalKokoroTtsService(config, logger); + + const noOpStt: SttService = { + async transcribePcm16() { + return null; + }, + }; + const noOpLlm: LlmService = { + async generateReply() { + return ""; + }, + }; + + await tts.warmup(); + + const session = new LocalVoiceSession({ + config, + logger, + stt: noOpStt, + tts, + llm: noOpLlm, + }); + + console.log("TTS 단독 재생 테스트를 시작합니다."); + console.log(`재생 문장: ${text}`); + if (process.platform === "win32") { + console.log("Windows에서는 시스템 기본 출력 장치로 재생됩니다."); + } + + try { + await session.speakText(text); + } finally { + await Promise.allSettled([session.destroy(), tts.destroy?.()]); + } +}