Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented Oct 25, 2024

#198

Summary by CodeRabbit

  • 新機能

    • 音声合成タスクを管理するための新しい SpeakQueue クラスを追加しました。
    • 音声合成機能の改善により、複数のリクエストを効率的に処理可能に。
    • VRMモデルに感情を再生するための playEmotion メソッドを追加しました。
  • バグ修正

    • エラーハンドリングの改善により、TTSサービスのエラーが適切に通知されるようになりました。

@vercel
Copy link

vercel bot commented Oct 25, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
aituber-kit ✅ Ready (Inspect) Visit Preview 💬 Add feedback Oct 25, 2024 7:03pm

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2024

Walkthrough

このプルリクエストでは、音声合成タスクの管理を改善するために、新しい SpeakQueue クラスを導入し、createSpeakCharacter 関数を修正しました。これにより、音声合成と再生のためのタスクキュー機構が実装され、従来のプロミスチェーンが排除されました。また、Model クラスに感情再生のための新しいメソッド playEmotion が追加され、VRM表現の機能が拡張されました。

Changes

ファイル名 変更内容
src/features/messages/speakCharacter.ts SpeakQueue クラスのインスタンスを追加し、createSpeakCharacter 関数を修正。prevSpeakPromise 変数を削除。
src/features/messages/speakQueue.ts 新しい SpeakQueue クラスを追加。タスク管理のための addTaskprocessQueueclearQueue メソッドを実装。
src/features/vrmViewer/model.ts VRMExpressionPresetName をインポートし、playEmotion メソッドを追加。VRMモデルの感情再生機能を拡張。

Possibly related PRs


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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: キュー処理の信頼性向上について

現在の実装では、同時実行時のエッジケースに対して脆弱な可能性があります。以下の対策を検討することを推奨します:

  1. キューの状態変更時のロック機構の実装
  2. 再試行メカニズムの追加
  3. キューの最大サイズ制限の実装

これらの改善により、より信頼性の高い実装となります。

src/features/vrmViewer/model.ts (1)

94-99: 会話後の表情リセット機能の実装について

PRの目的である「会話後に表情をneutralに戻す」機能を実装するために、以下のアプローチを提案します:

  1. speakメソッド内でPromise.finally()を使用して、会話終了後に自動的に表情をリセットする
  2. または、新しい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

📥 Commits

Files that changed from the base of the PR and between d04c909 and c6569cb.

📒 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: キューベースの実装への移行が適切に行われています

プロミスチェーンからキューベースのシステムへの移行により、以下の利点が得られています:

  • タスクの実行順序の保証
  • コードの可読性の向上
  • 音声合成タスクの管理性の向上

Comment on lines +15 to +18
async addTask(task: SpeakTask) {
this.queue.push(task)
await this.processQueue()
}
Copy link

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.

Suggested change
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
}
}

Comment on lines 20 to 56
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()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

キュー処理ロジックの改善点について

以下の改善を提案します:

  1. 待機時間を定数として定義
  2. キュー空チェックロジックを別メソッドとして分離
  3. より具体的なエラーハンドリング
+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.

Suggested change
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
}

Comment on lines +94 to +99
/**
* 感情表現を再生する
*/
public async playEmotion(preset: VRMExpressionPresetName) {
this.emoteController?.playEmotion(preset)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

安全性とドキュメンテーションの改善が必要です

以下の改善点を提案させていただきます:

  1. emoteControllerが未定義の場合のエラーハンドリング
  2. 利用可能な感情プリセットのドキュメント追加
  3. 戻り値の型定義の追加

以下の実装を提案します:

  /**
   * 感情表現を再生する
+  * @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.

Suggested change
/**
* 感情表現を再生する
*/
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)
}

@tegnike tegnike merged commit 421f4f3 into develop Oct 25, 2024
@coderabbitai coderabbitai bot mentioned this pull request Oct 25, 2024
@tegnike tegnike deleted the feature/update-emotion-flow branch October 30, 2024 10:25
This was referenced Dec 26, 2024
@coderabbitai coderabbitai bot mentioned this pull request Apr 24, 2025
terisuke pushed a commit to terisuke/aituber-kit that referenced this pull request Dec 5, 2025
話終わったあとに表情をneutralに戻す
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants