voice-system-expert
Use when working with Quetrex's voice interface, OpenAI Realtime API, WebRTC, or echo cancellation. Knows Quetrex's specific voice architecture decisions and patterns. CRITICAL - prevents breaking working voice system.
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o voice-system-expert.zip https://jpskill.com/download/17477.zip && unzip -o voice-system-expert.zip && rm voice-system-expert.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17477.zip -OutFile "$d\voice-system-expert.zip"; Expand-Archive "$d\voice-system-expert.zip" -DestinationPath $d -Force; ri "$d\voice-system-expert.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
voice-system-expert.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
voice-system-expertフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Quetrex 音声システムエキスパート
重要: 最初にこれを読んでください
Quetrex の音声システムアーキテクチャは、広範囲に文書化され、実戦でテストされています。音声関連のコードに何らかの変更を加える前に、必ず以下をお読みください。
- ADR-001-VOICE-ECHO-CANCELLATION.md (決定的なアーキテクチャ上の決定)
- docs/architecture/VOICE-SYSTEM.md (技術的な実装)
- docs/features/voice-interface.md (ユーザー向けの機能)
場所: src/lib/openai-realtime.ts
コアアーキテクチャの決定
常時オンのマイク + ブラウザ AEC
これは VOICE-SYSTEM.md の 決定 4 であり、ADR-001 に文書化されている決定的なアプローチです。
// ✅ 正しい: 常時オンのマイク
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // 重要 - ブラウザがエコーキャンセレーションを処理
noiseSuppression: true,
autoGainControl: true,
},
})
// マイクのトラックは会話中ずっと有効
// ブラウザのネイティブ AEC がフィードバックループを防ぐ
// サーバー側の VAD (音声区間検出) がターンの検出を処理
仕組み
オーディオパイプライン
ユーザーが話す
↓
マイク (常に有効、echoCancellation: true)
↓
WebRTC → OpenAI Realtime API
↓
サーバー側の VAD が音声を検出
↓
OpenAI が処理して応答
↓
WebRTC 経由の音声応答
↓
HTMLAudioElement の再生 (ブラウザのパイプラインに残る)
↓
ブラウザ AEC がマイク入力 + スピーカー出力を比較
↓
エコーが自動的にキャンセルされる (フィードバックループなし)
これがうまくいく理由
ブラウザのエコーキャンセレーションの要件:
- マイク入力とスピーカー出力の両方がブラウザのオーディオグラフにある必要がある
- オーディオはブラウザの WebRTC スタックを通過する必要がある
- 手動での介入は不要 (ブラウザが処理する)
これは業界標準です:
- ChatGPT 音声モード
- Google Meet
- Zoom
- Discord
- Microsoft Teams
これらはすべて、常時オンのマイク + ブラウザ AEC を使用しています。
これらのことはしないでください
❌ しないでください: マイクのトラックを切り替える
// ❌ 間違い - これはエコーキャンセレーションを壊す
async function pauseRecording() {
microphone.enabled = false // これをしないでください
}
async function resumeRecording() {
microphone.enabled = true // これをしないでください
}
これが失敗する理由:
- 業界標準ではない
- オーディオのグリッチを引き起こす
- エコーキャンセレーションを壊す可能性がある
- 人工的な遅延を追加する
- より複雑な状態管理
- 実際のメリットがない
❌ しないでください: オーディオをブラウザの外にルーティングする
// ❌ 間違い - ネイティブオーディオへの AudioWorklet バイパス
const workletNode = new AudioWorkletNode(audioContext, 'bypass-processor')
workletNode.port.postMessage({ cmd: 'route-to-native' })
これが失敗する理由:
- ブラウザ AEC を壊す (オーディオがブラウザのパイプラインから離れる)
- エコー/フィードバックループを引き起こす
- 複雑なネイティブオーディオ処理が必要
- プラットフォーム固有の実装
- すでに試して失敗している (abandoned-approaches/ を参照)
❌ しないでください: カスタムのエコーキャンセレーションを実装する
// ❌ 間違い - カスタム AEC 実装
class CustomEchoCanceller {
cancelEcho(input: AudioBuffer, output: AudioBuffer) {
// 複雑な DSP コード...
}
}
これが失敗する理由:
- 車輪の再発明
- ブラウザ AEC は数十億人のユーザーによって実戦でテストされている
- 正しく実装するのは非常に複雑
- 深い DSP の知識が必要
- パフォーマンスの問題
- プラットフォーム固有の調整が必要
❌ しないでください: 人工的な遅延を追加する
// ❌ 間違い - エコー防止のための遅延
await new Promise(resolve => setTimeout(resolve, 500))
await playAudio()
await new Promise(resolve => setTimeout(resolve, 500))
resumeRecording()
これが失敗する理由:
- 不要 (ブラウザ AEC が処理する)
- ユーザーエクスペリエンスを低下させる
- レイテンシを追加する
- 実装が間違っている場合、それでもエコーを防ぐことはできない
変更できること
✅ してください: オーディオ設定を調整する
// ✅ これらの設定は調整できる
const constraints = {
audio: {
echoCancellation: true, // true である必要がある
noiseSuppression: true, // 調整可能
autoGainControl: true, // 調整可能
sampleRate: 24000, // 品質のために変更可能
channelCount: 1, // 音声にはモノラルで十分
},
}
✅ してください: 接続状態を処理する
// ✅ 接続ライフサイクルを管理できる
async function connectVoice() {
// WebRTC 接続をセットアップ
// ストリーミングを開始
// 接続イベントを処理
}
async function disconnectVoice() {
// WebRTC 接続をクリーンアップ
// ストリーミングを停止
// マイクを解放
}
✅ してください: UI フィードバックを追加する
// ✅ 視覚的なインジケーターを追加できる
function onVoiceActivity(active: boolean) {
if (active) {
// "リスニング" インジケーターを表示
// マイクのアイコンをアニメーション化
} else {
// "アイドル" インジケーターを表示
}
}
✅ してください: エラーを処理する
// ✅ エラー処理を改善できる
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
} catch (error) {
if (error.name === 'NotAllowedError') {
// 許可リクエスト UI を表示
} else if (error.name === 'NotFoundError') {
// "マイクが見つかりません" エラーを表示
}
}
実装場所
主要ファイル: src/lib/openai-realtime.ts
主要な関数:
setupMediaStream()- 正しい制約でマイクを初期化connectToOpenAI()- WebRTC 接続を確立handleAudioResponse()- HTMLAudioElement 経由で AI 応答を再生
変更しないでください:
- マイクの有効/無効ロジック (常にオンのままにする必要がある)
- エコーキャンセレーション設定 (true である必要がある)
- オーディオルーティング (ブラウザのパイプラインに残す必要がある)
変更できます:
- UI フィードバックと視覚的なインジケーター
- エラー処理とユーザーメッセージ
- 接続リトライロジック
- 音質設定 (サンプルレートなど)
このアーキテクチャが選択された理由
ADR-001 より:
オプション A: 業界パターンを信頼する (選択)
- ✅ ChatGPT、Google Meet、Zoom、Discord で使用
- ✅ ブラウザベンダーはこのパターンに合わせて最適化
- ✅ すべてのプラットフォーム (macOS、Windows、Linux、モバイル) で動作
- ✅ カスタム実装は不要
- ✅ 実証済みの信頼性
拒否されたオプション:
- ❌ 手動マイク切り替え
(原文はここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Quetrex Voice System Expert
CRITICAL: Read This First
Quetrex's voice system architecture is extensively documented and battle-tested. Before making ANY changes to voice-related code, you MUST read:
- ADR-001-VOICE-ECHO-CANCELLATION.md (definitive architectural decision)
- docs/architecture/VOICE-SYSTEM.md (technical implementation)
- docs/features/voice-interface.md (user-facing features)
Location: src/lib/openai-realtime.ts
Core Architecture Decision
ALWAYS-ON MICROPHONE + BROWSER AEC
This is Decision 4 from VOICE-SYSTEM.md and the definitive approach documented in ADR-001.
// ✅ CORRECT: Always-on microphone
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // CRITICAL - browser handles echo cancellation
noiseSuppression: true,
autoGainControl: true,
},
})
// Microphone track stays ENABLED throughout conversation
// Browser's native AEC prevents feedback loops
// Server-side VAD (Voice Activity Detection) handles turn detection
How It Works
Audio Pipeline
User speaks
↓
Microphone (always enabled, echoCancellation: true)
↓
WebRTC → OpenAI Realtime API
↓
Server-side VAD detects speech
↓
OpenAI processes and responds
↓
Audio response via WebRTC
↓
HTMLAudioElement playback (stays in browser pipeline)
↓
Browser AEC compares mic input + speaker output
↓
Echo automatically canceled (no feedback loop)
Why This Works
Browser Echo Cancellation Requirements:
- Both microphone input AND speaker output must be in browser's audio graph
- Audio must flow through browser's WebRTC stack
- No manual intervention needed (browser handles it)
This is the industry standard:
- ChatGPT voice mode
- Google Meet
- Zoom
- Discord
- Microsoft Teams
They ALL use always-on microphone + browser AEC.
DO NOT DO THESE THINGS
❌ DON'T: Toggle Microphone Track
// ❌ WRONG - This breaks echo cancellation
async function pauseRecording() {
microphone.enabled = false // DON'T DO THIS
}
async function resumeRecording() {
microphone.enabled = true // DON'T DO THIS
}
Why this fails:
- Not industry standard
- Causes audio glitches
- Can break echo cancellation
- Adds artificial delays
- More complex state management
- No real benefit
❌ DON'T: Route Audio Outside Browser
// ❌ WRONG - AudioWorklet bypass to native audio
const workletNode = new AudioWorkletNode(audioContext, 'bypass-processor')
workletNode.port.postMessage({ cmd: 'route-to-native' })
Why this fails:
- Breaks browser AEC (audio leaves browser pipeline)
- Causes echo/feedback loops
- Requires complex native audio handling
- Platform-specific implementations
- Already tried and failed (see abandoned-approaches/)
❌ DON'T: Implement Custom Echo Cancellation
// ❌ WRONG - Custom AEC implementation
class CustomEchoCanceller {
cancelEcho(input: AudioBuffer, output: AudioBuffer) {
// Complex DSP code...
}
}
Why this fails:
- Reinventing the wheel
- Browser AEC is battle-tested by billions of users
- Extremely complex to implement correctly
- Requires deep DSP knowledge
- Performance issues
- Platform-specific tuning needed
❌ DON'T: Add Artificial Delays
// ❌ WRONG - Delays for echo prevention
await new Promise(resolve => setTimeout(resolve, 500))
await playAudio()
await new Promise(resolve => setTimeout(resolve, 500))
resumeRecording()
Why this fails:
- Not necessary (browser AEC handles it)
- Degrades user experience
- Adds latency
- Still doesn't prevent echo if implementation is wrong
What You CAN Change
✅ DO: Adjust Audio Settings
// ✅ Can tweak these settings
const constraints = {
audio: {
echoCancellation: true, // MUST be true
noiseSuppression: true, // Can adjust
autoGainControl: true, // Can adjust
sampleRate: 24000, // Can change for quality
channelCount: 1, // Mono is fine for voice
},
}
✅ DO: Handle Connection States
// ✅ Can manage connection lifecycle
async function connectVoice() {
// Setup WebRTC connection
// Start streaming
// Handle connection events
}
async function disconnectVoice() {
// Clean up WebRTC connection
// Stop streaming
// Release microphone
}
✅ DO: Add UI Feedback
// ✅ Can add visual indicators
function onVoiceActivity(active: boolean) {
if (active) {
// Show "listening" indicator
// Animate microphone icon
} else {
// Show "idle" indicator
}
}
✅ DO: Handle Errors
// ✅ Can improve error handling
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
} catch (error) {
if (error.name === 'NotAllowedError') {
// Show permission request UI
} else if (error.name === 'NotFoundError') {
// Show "no microphone found" error
}
}
Implementation Location
Primary file: src/lib/openai-realtime.ts
Key functions:
setupMediaStream()- Initializes microphone with correct constraintsconnectToOpenAI()- Establishes WebRTC connectionhandleAudioResponse()- Plays AI responses via HTMLAudioElement
DO NOT modify:
- Microphone enable/disable logic (should stay always-on)
- Echo cancellation settings (must be true)
- Audio routing (must stay in browser pipeline)
CAN modify:
- UI feedback and visual indicators
- Error handling and user messages
- Connection retry logic
- Audio quality settings (sample rate, etc.)
Why This Architecture Was Chosen
From ADR-001:
Option A: Trust Industry Pattern (CHOSEN)
- ✅ Used by ChatGPT, Google Meet, Zoom, Discord
- ✅ Browser vendors optimize for this pattern
- ✅ Works across all platforms (macOS, Windows, Linux, mobile)
- ✅ No custom implementation needed
- ✅ Proven reliability
Options Rejected:
- ❌ Manual microphone toggling (not industry standard)
- ❌ AudioWorklet native bypass (breaks AEC)
- ❌ Custom echo cancellation (reinventing wheel)
- ❌ Native Rust WebRTC (2-4 weeks work for no benefit)
Platform Context: Web Application
IMPORTANT: Quetrex is now a pure web application, not a Tauri desktop app.
Why this matters for voice:
- Browser echo cancellation works perfectly in all browsers
- No WKWebView limitations (that was the Tauri problem)
- Universal compatibility (Chrome, Safari, Firefox, Edge)
- No platform-specific audio handling needed
- Just works™️
The WKWebView Problem (Historical): Quetrex was originally a Tauri desktop app. On macOS, Tauri uses WKWebView, which has a bug: WebRTC audio playback doesn't work. This forced us to try workarounds (AudioWorklet bypass, native audio routing), which all broke echo cancellation.
Solution: Convert to web application. Now echo cancellation works perfectly everywhere.
Testing Voice Changes
If you must modify voice code:
- Test on real browsers (not just dev tools)
- Test the echo scenario:
- Turn up speaker volume
- Start voice conversation
- Verify no feedback loop/echo
- Test across platforms:
- Chrome (most common)
- Safari (macOS, iOS)
- Firefox
- Edge
- Test edge cases:
- Poor network connection
- Microphone permission denied
- Mid-conversation disconnect
When to Consult Documentation
Before any voice changes, read:
docs/decisions/ADR-001-VOICE-ECHO-CANCELLATION.mddocs/architecture/VOICE-SYSTEM.mddocs/development/abandoned-approaches/
If you see mentions of:
- Tauri → Ignore (Quetrex is web app now)
- WKWebView → Ignore (not relevant anymore)
- AudioWorklet bypass → Don't implement (already tried, failed)
- Manual mic toggling → Don't implement (not industry standard)
Summary
The Golden Rule: Trust browser echo cancellation. Always-on microphone + echoCancellation: true + audio in browser pipeline = Perfect echo cancellation.
DO:
- Keep microphone enabled throughout conversation
- Use
echoCancellation: true - Keep audio in browser (HTMLAudioElement)
- Let OpenAI Realtime API handle VAD
- Trust the industry pattern
DON'T:
- Toggle microphone track
- Route audio outside browser
- Implement custom echo cancellation
- Add artificial delays
- Try to "improve" what already works
If in doubt: Read ADR-001 and VOICE-SYSTEM.md. The architecture is thoroughly documented for a reason.