-
-
Notifications
You must be signed in to change notification settings - Fork 143
話終わったあとに表情をneutralに戻す #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthroughこのプルリクエストでは、音声合成タスクの管理を改善するために、新しい Changes
Possibly related PRs
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Outside diff range and nitpick comments (5)
src/features/messages/speakQueue.ts (3)
4-9: 型定義にドキュメンテーションコメントを追加することを推奨します各プロパティの役割を明確にするため、JSDocコメントの追加を提案します。
+/** + * 音声合成タスクを表す型 + */ type SpeakTask = { + /** デコード前の音声データ */ audioBuffer: ArrayBuffer + /** 台本データ */ screenplay: Screenplay + /** デコードが必要かどうかのフラグ */ isNeedDecode: boolean + /** タスク完了時のコールバック */ onComplete?: () => void }
11-13: クラスの目的と責務を明確にするドキュメントの追加を推奨しますクラスの概要を説明するJSDocコメントの追加を提案します。
+/** + * 音声合成タスクのキュー管理を行うクラス + * - タスクの追加と実行を制御 + * - 連続した音声合成を管理 + * - 会話終了後の表情制御を実装 + */ export class SpeakQueue { private queue: SpeakTask[] = [] private isProcessing = false
20-56: キュー処理の信頼性向上について現在の実装では、同時実行時のエッジケースに対して脆弱な可能性があります。以下の対策を検討することを推奨します:
- キューの状態変更時のロック機構の実装
- 再試行メカニズムの追加
- キューの最大サイズ制限の実装
これらの改善により、より信頼性の高い実装となります。
src/features/vrmViewer/model.ts (1)
94-99: 会話後の表情リセット機能の実装についてPRの目的である「会話後に表情をneutralに戻す」機能を実装するために、以下のアプローチを提案します:
speakメソッド内でPromise.finally()を使用して、会話終了後に自動的に表情をリセットする- または、新しい
resetEmotionメソッドを追加して、必要な箇所で明示的に呼び出す例えば
speakメソッドを以下のように修正することができます:public async speak( buffer: ArrayBuffer, screenplay: Screenplay, isNeedDecode: boolean = true ) { - this.emoteController?.playEmotion(screenplay.expression) + try { + this.emoteController?.playEmotion(screenplay.expression) + await new Promise((resolve) => { + this._lipSync?.playFromArrayBuffer( + buffer, + () => { + resolve(true) + }, + isNeedDecode + ) + }) + } finally { + // 会話終了後に表情をneutralに戻す + await this.playEmotion('neutral') + } - await new Promise((resolve) => { - this._lipSync?.playFromArrayBuffer( - buffer, - () => { - resolve(true) - }, - isNeedDecode - ) - }) }src/features/messages/speakCharacter.ts (1)
111-111: コメントの改善を推奨します現在のコメント「キューを使用した処理に変更」は簡潔すぎるため、以下のような詳細な説明を推奨します:
-// キューを使用した処理に変更 +// 音声合成タスクの順序制御のため、プロミスチェーンからキューベースの実装に変更 +// これにより、複数の発話リクエストを適切に管理し、順序通りに実行することが可能
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
- src/features/messages/speakCharacter.ts (2 hunks)
- src/features/messages/speakQueue.ts (1 hunks)
- src/features/vrmViewer/model.ts (2 hunks)
🔇 Additional comments (3)
src/features/vrmViewer/model.ts (1)
2-7: LGTM!
VRMExpressionPresetNameのインポートが適切に追加されており、新しいplayEmotionメソッドの型定義に必要な変更が行われています。src/features/messages/speakCharacter.ts (2)
14-14: キューの初期化が適切に実装されています
SpeakQueueクラスのインポートとインスタンス化が適切に行われており、スコープも正しく設定されています。Also applies to: 22-23
111-120: キューベースの実装への移行が適切に行われていますプロミスチェーンからキューベースのシステムへの移行により、以下の利点が得られています:
- タスクの実行順序の保証
- コードの可読性の向上
- 音声合成タスクの管理性の向上
| async addTask(task: SpeakTask) { | ||
| this.queue.push(task) | ||
| await this.processQueue() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
addTaskメソッドにエラーハンドリングの追加を推奨します
タスク追加時の例外処理を実装することで、より堅牢な実装になります。
async addTask(task: SpeakTask) {
+ try {
this.queue.push(task)
await this.processQueue()
+ } catch (error) {
+ console.error('タスクの追加中にエラーが発生しました:', error)
+ throw error
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async addTask(task: SpeakTask) { | |
| this.queue.push(task) | |
| await this.processQueue() | |
| } | |
| async addTask(task: SpeakTask) { | |
| try { | |
| this.queue.push(task) | |
| await this.processQueue() | |
| } catch (error) { | |
| console.error('タスクの追加中にエラーが発生しました:', error) | |
| throw error | |
| } | |
| } |
| private async processQueue() { | ||
| if (this.isProcessing) return | ||
| this.isProcessing = true | ||
| const hs = homeStore.getState() | ||
|
|
||
| while (this.queue.length > 0) { | ||
| const task = this.queue.shift() | ||
| if (task) { | ||
| try { | ||
| const { audioBuffer, screenplay, isNeedDecode, onComplete } = task | ||
| await hs.viewer.model?.speak(audioBuffer, screenplay, isNeedDecode) | ||
| onComplete?.() | ||
| } catch (error) { | ||
| console.error('Error processing speak task:', error) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| this.isProcessing = false | ||
|
|
||
| // 一定時間待って、その間に新しいキューが追加されていないことを確認 | ||
| const checkQueueEmpty = async () => { | ||
| const initialLength = this.queue.length | ||
| await new Promise((resolve) => setTimeout(resolve, 1500)) | ||
|
|
||
| // 待機時間後もキューが空のままであることを確認 | ||
| if ( | ||
| initialLength === 0 && | ||
| this.queue.length === 0 && | ||
| !this.isProcessing | ||
| ) { | ||
| await hs.viewer.model?.playEmotion('neutral') | ||
| } | ||
| } | ||
|
|
||
| checkQueueEmpty() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
キュー処理ロジックの改善点について
以下の改善を提案します:
- 待機時間を定数として定義
- キュー空チェックロジックを別メソッドとして分離
- より具体的なエラーハンドリング
+private static readonly QUEUE_CHECK_DELAY = 1500
+
private async processQueue() {
if (this.isProcessing) return
this.isProcessing = true
const hs = homeStore.getState()
while (this.queue.length > 0) {
const task = this.queue.shift()
if (task) {
try {
const { audioBuffer, screenplay, isNeedDecode, onComplete } = task
await hs.viewer.model?.speak(audioBuffer, screenplay, isNeedDecode)
onComplete?.()
} catch (error) {
- console.error('Error processing speak task:', error)
+ console.error('音声合成タスクの処理中にエラーが発生しました:', error)
+ if (error instanceof Error) {
+ console.error('エラーの詳細:', error.message)
+ }
}
}
}
this.isProcessing = false
- const checkQueueEmpty = async () => {
- const initialLength = this.queue.length
- await new Promise((resolve) => setTimeout(resolve, 1500))
+ this.scheduleNeutralExpression()
+}
+
+private async scheduleNeutralExpression() {
+ const initialLength = this.queue.length
+ await new Promise((resolve) => setTimeout(resolve, SpeakQueue.QUEUE_CHECK_DELAY))
- if (
- initialLength === 0 &&
- this.queue.length === 0 &&
- !this.isProcessing
- ) {
- await hs.viewer.model?.playEmotion('neutral')
- }
+ if (this.shouldResetToNeutral(initialLength)) {
+ const hs = homeStore.getState()
+ await hs.viewer.model?.playEmotion('neutral')
}
+}
+
+private shouldResetToNeutral(initialLength: number): boolean {
+ return initialLength === 0 &&
+ this.queue.length === 0 &&
+ !this.isProcessing
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private async processQueue() { | |
| if (this.isProcessing) return | |
| this.isProcessing = true | |
| const hs = homeStore.getState() | |
| while (this.queue.length > 0) { | |
| const task = this.queue.shift() | |
| if (task) { | |
| try { | |
| const { audioBuffer, screenplay, isNeedDecode, onComplete } = task | |
| await hs.viewer.model?.speak(audioBuffer, screenplay, isNeedDecode) | |
| onComplete?.() | |
| } catch (error) { | |
| console.error('Error processing speak task:', error) | |
| } | |
| } | |
| } | |
| this.isProcessing = false | |
| // 一定時間待って、その間に新しいキューが追加されていないことを確認 | |
| const checkQueueEmpty = async () => { | |
| const initialLength = this.queue.length | |
| await new Promise((resolve) => setTimeout(resolve, 1500)) | |
| // 待機時間後もキューが空のままであることを確認 | |
| if ( | |
| initialLength === 0 && | |
| this.queue.length === 0 && | |
| !this.isProcessing | |
| ) { | |
| await hs.viewer.model?.playEmotion('neutral') | |
| } | |
| } | |
| checkQueueEmpty() | |
| } | |
| private static readonly QUEUE_CHECK_DELAY = 1500 | |
| private async processQueue() { | |
| if (this.isProcessing) return | |
| this.isProcessing = true | |
| const hs = homeStore.getState() | |
| while (this.queue.length > 0) { | |
| const task = this.queue.shift() | |
| if (task) { | |
| try { | |
| const { audioBuffer, screenplay, isNeedDecode, onComplete } = task | |
| await hs.viewer.model?.speak(audioBuffer, screenplay, isNeedDecode) | |
| onComplete?.() | |
| } catch (error) { | |
| console.error('音声合成タスクの処理中にエラーが発生しました:', error) | |
| if (error instanceof Error) { | |
| console.error('エラーの詳細:', error.message) | |
| } | |
| } | |
| } | |
| } | |
| this.isProcessing = false | |
| this.scheduleNeutralExpression() | |
| } | |
| private async scheduleNeutralExpression() { | |
| const initialLength = this.queue.length | |
| await new Promise((resolve) => setTimeout(resolve, SpeakQueue.QUEUE_CHECK_DELAY)) | |
| if (this.shouldResetToNeutral(initialLength)) { | |
| const hs = homeStore.getState() | |
| await hs.viewer.model?.playEmotion('neutral') | |
| } | |
| } | |
| private shouldResetToNeutral(initialLength: number): boolean { | |
| return initialLength === 0 && | |
| this.queue.length === 0 && | |
| !this.isProcessing | |
| } |
| /** | ||
| * 感情表現を再生する | ||
| */ | ||
| public async playEmotion(preset: VRMExpressionPresetName) { | ||
| this.emoteController?.playEmotion(preset) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
安全性とドキュメンテーションの改善が必要です
以下の改善点を提案させていただきます:
emoteControllerが未定義の場合のエラーハンドリング- 利用可能な感情プリセットのドキュメント追加
- 戻り値の型定義の追加
以下の実装を提案します:
/**
* 感情表現を再生する
+ * @param preset - 感情表現のプリセット
+ * - 'neutral' - 通常表情
+ * - 'happy' - 笑顔
+ * - 'angry' - 怒り
+ * - 'sad' - 悲しみ
+ * - etc...
+ * @throws Error emoteControllerが初期化されていない場合
+ * @returns Promise<void>
*/
- public async playEmotion(preset: VRMExpressionPresetName) {
+ public async playEmotion(preset: VRMExpressionPresetName): Promise<void> {
+ if (!this.emoteController) {
+ throw new Error('EmoteController is not initialized. Please load VRM first.');
+ }
- this.emoteController?.playEmotion(preset)
+ this.emoteController.playEmotion(preset)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * 感情表現を再生する | |
| */ | |
| public async playEmotion(preset: VRMExpressionPresetName) { | |
| this.emoteController?.playEmotion(preset) | |
| } | |
| /** | |
| * 感情表現を再生する | |
| * @param preset - 感情表現のプリセット | |
| * - 'neutral' - 通常表情 | |
| * - 'happy' - 笑顔 | |
| * - 'angry' - 怒り | |
| * - 'sad' - 悲しみ | |
| * - etc... | |
| * @throws Error emoteControllerが初期化されていない場合 | |
| * @returns Promise<void> | |
| */ | |
| public async playEmotion(preset: VRMExpressionPresetName): Promise<void> { | |
| if (!this.emoteController) { | |
| throw new Error('EmoteController is not initialized. Please load VRM first.'); | |
| } | |
| this.emoteController.playEmotion(preset) | |
| } |
話終わったあとに表情をneutralに戻す
#198
Summary by CodeRabbit
新機能
SpeakQueueクラスを追加しました。playEmotionメソッドを追加しました。バグ修正