@coeiro-operator/audio 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,52 +0,0 @@
1
- /**
2
- * src/core/say/audio-stream-controller.ts: 音声ストリーム制御
3
- * 並行生成と順序再生の協調制御を担当
4
- */
5
- import { GenerationOptions } from './chunk-generation-manager.js';
6
- import type { Chunk, AudioResult, VoiceConfig } from './types.js';
7
- export interface StreamControllerOptions extends GenerationOptions {
8
- bufferAheadCount: number;
9
- }
10
- /**
11
- * 音声ストリーム制御クラス
12
- * 生成と再生の協調制御を行い、並行生成とシリアル再生を実現
13
- */
14
- export declare class AudioStreamController {
15
- private generationManager;
16
- private options;
17
- private synthesizeFunction;
18
- constructor(synthesizeFunction: (chunk: Chunk, voiceConfig: VoiceConfig, speed: number) => Promise<AudioResult>, options?: Partial<StreamControllerOptions>);
19
- /**
20
- * 並行生成対応の音声合成ジェネレータ
21
- */
22
- synthesizeStream(chunks: Chunk[], voiceConfig: VoiceConfig, speed: number): AsyncGenerator<AudioResult>;
23
- /**
24
- * 並行生成(maxConcurrency=1なら逐次、2以上なら並行)
25
- */
26
- private synthesizeStreamParallel;
27
- /**
28
- * 並行生成の有効/無効を切り替え(maxConcurrencyで制御)
29
- */
30
- setParallelGenerationEnabled(enabled: boolean): void;
31
- /**
32
- * 並行生成オプションを更新
33
- */
34
- updateOptions(options: Partial<StreamControllerOptions>): void;
35
- /**
36
- * 現在の設定を取得
37
- */
38
- getOptions(): StreamControllerOptions;
39
- /**
40
- * 生成統計情報を取得
41
- */
42
- getGenerationStats(): {
43
- activeTasks: number;
44
- completedResults: number;
45
- totalMemoryUsage: number;
46
- };
47
- /**
48
- * すべての生成タスクをクリア
49
- */
50
- clear(): void;
51
- }
52
- //# sourceMappingURL=audio-stream-controller.d.ts.map
@@ -1,121 +0,0 @@
1
- /**
2
- * src/core/say/audio-stream-controller.ts: 音声ストリーム制御
3
- * 並行生成と順序再生の協調制御を担当
4
- */
5
- import { logger } from '@coeiro-operator/common';
6
- import { ChunkGenerationManager } from './chunk-generation-manager.js';
7
- /**
8
- * 音声ストリーム制御クラス
9
- * 生成と再生の協調制御を行い、並行生成とシリアル再生を実現
10
- */
11
- export class AudioStreamController {
12
- generationManager;
13
- options;
14
- synthesizeFunction;
15
- constructor(synthesizeFunction, options = {}) {
16
- this.synthesizeFunction = synthesizeFunction;
17
- this.options = {
18
- maxConcurrency: 2, // 1=逐次、2以上=並行、デフォルト: 2
19
- delayBetweenRequests: 50,
20
- bufferAheadCount: 1,
21
- pauseUntilFirstComplete: true, // デフォルトで初回ポーズを有効化
22
- ...options,
23
- };
24
- this.generationManager = new ChunkGenerationManager(synthesizeFunction, {
25
- maxConcurrency: this.options.maxConcurrency,
26
- delayBetweenRequests: this.options.delayBetweenRequests,
27
- pauseUntilFirstComplete: this.options.pauseUntilFirstComplete,
28
- });
29
- }
30
- /**
31
- * 並行生成対応の音声合成ジェネレータ
32
- */
33
- async *synthesizeStream(chunks, voiceConfig, speed) {
34
- // maxConcurrency=1なら逐次、2以上なら並行生成
35
- yield* this.synthesizeStreamParallel(chunks, voiceConfig, speed);
36
- }
37
- /**
38
- * 並行生成(maxConcurrency=1なら逐次、2以上なら並行)
39
- */
40
- async *synthesizeStreamParallel(chunks, voiceConfig, speed) {
41
- const mode = this.options.maxConcurrency === 1 ? '逐次' : '並行';
42
- logger.debug(`${mode}生成モードで音声合成開始 (maxConcurrency=${this.options.maxConcurrency})`);
43
- if (chunks.length === 0) {
44
- return;
45
- }
46
- try {
47
- // 最初のチャンクは即座に開始
48
- await this.generationManager.startGeneration(chunks[0], voiceConfig, speed);
49
- let currentIndex = 0;
50
- while (currentIndex < chunks.length) {
51
- // 先読み生成の開始(maxConcurrencyに基づく)
52
- const nextIndex = currentIndex + 1;
53
- if (nextIndex < chunks.length &&
54
- !this.generationManager.isInProgress(nextIndex) &&
55
- !this.generationManager.isCompleted(nextIndex)) {
56
- // バッファ先読み数に基づいて生成開始
57
- const generateUpTo = Math.min(currentIndex + this.options.bufferAheadCount + 1, chunks.length);
58
- for (let i = nextIndex; i < generateUpTo; i++) {
59
- if (!this.generationManager.isInProgress(i) && !this.generationManager.isCompleted(i)) {
60
- await this.generationManager.startGeneration(chunks[i], voiceConfig, speed);
61
- }
62
- }
63
- }
64
- // 現在のチャンクの完了を待機してyield
65
- logger.debug(`${mode}生成: チャンク${currentIndex}結果待機中`);
66
- const result = await this.generationManager.getResult(currentIndex);
67
- logger.debug(`${mode}生成: チャンク${currentIndex}結果取得、yield開始`);
68
- yield result;
69
- currentIndex++;
70
- }
71
- logger.debug(`${mode}生成モード完了`);
72
- }
73
- catch (error) {
74
- logger.error(`${mode}生成エラー: ${error.message}`);
75
- throw error;
76
- }
77
- finally {
78
- // クリーンアップ
79
- this.generationManager.clear();
80
- }
81
- }
82
- /**
83
- * 並行生成の有効/無効を切り替え(maxConcurrencyで制御)
84
- */
85
- setParallelGenerationEnabled(enabled) {
86
- this.options.maxConcurrency = enabled ? 2 : 1;
87
- logger.info(`並行生成モード: ${enabled ? '有効 (maxConcurrency=2)' : '無効 (maxConcurrency=1)'}`);
88
- }
89
- /**
90
- * 並行生成オプションを更新
91
- */
92
- updateOptions(options) {
93
- this.options = { ...this.options, ...options };
94
- // GenerationManagerのオプションも更新
95
- this.generationManager = new ChunkGenerationManager(this.synthesizeFunction, {
96
- maxConcurrency: this.options.maxConcurrency,
97
- delayBetweenRequests: this.options.delayBetweenRequests,
98
- pauseUntilFirstComplete: this.options.pauseUntilFirstComplete,
99
- });
100
- logger.debug('AudioStreamController オプション更新', this.options);
101
- }
102
- /**
103
- * 現在の設定を取得
104
- */
105
- getOptions() {
106
- return { ...this.options };
107
- }
108
- /**
109
- * 生成統計情報を取得
110
- */
111
- getGenerationStats() {
112
- return this.generationManager.getStats();
113
- }
114
- /**
115
- * すべての生成タスクをクリア
116
- */
117
- clear() {
118
- this.generationManager.clear();
119
- }
120
- }
121
- //# sourceMappingURL=audio-stream-controller.js.map
@@ -1,86 +0,0 @@
1
- /**
2
- * src/say/audio-synthesizer.ts: 音声合成処理
3
- * COEIROINK APIを使用した音声合成機能を担当
4
- */
5
- import type { Config, Chunk, AudioResult, VoiceConfig } from './types.js';
6
- import { StreamControllerOptions } from './audio-stream-controller.js';
7
- export declare class AudioSynthesizer {
8
- private config;
9
- private audioConfig;
10
- private speakerProvider;
11
- private streamController;
12
- constructor(config: Config);
13
- /**
14
- * スピーカー一覧を取得
15
- */
16
- getSpeakers(): Promise<import("@coeiro-operator/core/dist/environment/speaker-provider.js").Speaker[]>;
17
- /**
18
- * オーディオ設定を取得
19
- */
20
- private getAudioConfig;
21
- /**
22
- * 設定から音声生成時のサンプルレートを取得
23
- */
24
- private getSynthesisRate;
25
- /**
26
- * 設定ファイルに基づいて分割モード設定を生成
27
- */
28
- private getSplitModeConfig;
29
- /**
30
- * サーバー接続確認
31
- */
32
- checkServerConnection(): Promise<boolean>;
33
- /**
34
- * 利用可能な音声一覧を取得
35
- */
36
- listVoices(): Promise<void>;
37
- /**
38
- * 句読点に基づくテキスト分割
39
- */
40
- private splitByPunctuation;
41
- /**
42
- * 文字数で強制分割
43
- */
44
- private forceSplitByLength;
45
- /**
46
- * 長い文を読点で分割
47
- */
48
- private splitLongSentenceByComma;
49
- /**
50
- * テキストを音切れ防止のためのオーバーラップ付きチャンクに分割
51
- */
52
- splitTextIntoChunks(text: string, splitMode?: 'none' | 'small' | 'medium' | 'large' | 'punctuation'): Chunk[];
53
- /**
54
- * 単一チャンクの音声合成
55
- */
56
- synthesizeChunk(chunk: Chunk, voiceConfig: VoiceConfig, speed: number): Promise<AudioResult>;
57
- /**
58
- * レート値をスピード値に変換
59
- */
60
- convertRateToSpeed(rate: number): number;
61
- /**
62
- * ストリーミング音声合成(リファクタリング版)
63
- */
64
- synthesizeStream(text: string, voiceConfig: VoiceConfig, speed: number, chunkMode?: 'none' | 'small' | 'medium' | 'large' | 'punctuation'): AsyncGenerator<AudioResult>;
65
- /**
66
- * 並行生成モードの切り替え
67
- */
68
- setParallelGenerationEnabled(enabled: boolean): void;
69
- /**
70
- * ストリーム制御オプションの更新
71
- */
72
- updateStreamControllerOptions(options: Partial<StreamControllerOptions>): void;
73
- /**
74
- * 現在のストリーム制御設定を取得
75
- */
76
- getStreamControllerOptions(): StreamControllerOptions;
77
- /**
78
- * 生成統計情報を取得
79
- */
80
- getGenerationStats(): {
81
- activeTasks: number;
82
- completedResults: number;
83
- totalMemoryUsage: number;
84
- };
85
- }
86
- //# sourceMappingURL=audio-synthesizer.d.ts.map
@@ -1,437 +0,0 @@
1
- /**
2
- * src/say/audio-synthesizer.ts: 音声合成処理
3
- * COEIROINK APIを使用した音声合成機能を担当
4
- */
5
- import { logger } from '@coeiro-operator/common';
6
- import { SAMPLE_RATES, SPLIT_SETTINGS, PADDING_SETTINGS, SYNTHESIS_SETTINGS } from './constants.js';
7
- import { getSpeakerProvider } from '@coeiro-operator/core';
8
- import { AudioStreamController } from './audio-stream-controller.js';
9
- // ストリーミング設定
10
- const STREAM_CONFIG = {
11
- chunkSizeChars: 50, // 文字単位でのチャンク分割
12
- overlapChars: 5, // チャンク間のオーバーラップ(音切れ防止)
13
- bufferSize: 3, // 音声バッファサイズ(並列処理数)
14
- audioBufferMs: 100, // 音声出力バッファ時間
15
- silencePaddingMs: 50, // 音切れ防止用の無音パディング
16
- preloadChunks: 2, // 先読みチャンク数
17
- };
18
- export class AudioSynthesizer {
19
- config;
20
- audioConfig;
21
- speakerProvider = getSpeakerProvider();
22
- streamController;
23
- constructor(config) {
24
- this.config = config;
25
- this.audioConfig = this.getAudioConfig();
26
- // 接続設定を更新
27
- this.speakerProvider.updateConnection({
28
- host: this.config.connection.host,
29
- port: this.config.connection.port,
30
- });
31
- // AudioStreamControllerを初期化(設定ファイルベース)
32
- const parallelConfig = this.config.audio?.parallelGeneration || {};
33
- this.streamController = new AudioStreamController(this.synthesizeChunk.bind(this), {
34
- maxConcurrency: parallelConfig.maxConcurrency || 2,
35
- delayBetweenRequests: parallelConfig.delayBetweenRequests || 50,
36
- bufferAheadCount: parallelConfig.bufferAheadCount || 1,
37
- pauseUntilFirstComplete: parallelConfig.pauseUntilFirstComplete || true,
38
- });
39
- }
40
- /**
41
- * スピーカー一覧を取得
42
- */
43
- async getSpeakers() {
44
- return await this.speakerProvider.getSpeakers();
45
- }
46
- /**
47
- * オーディオ設定を取得
48
- */
49
- getAudioConfig() {
50
- const latencyMode = this.config.audio?.latencyMode || 'balanced';
51
- const presets = {
52
- 'ultra-low': {
53
- splitSettings: SPLIT_SETTINGS.PRESETS.ULTRA_LOW,
54
- paddingSettings: PADDING_SETTINGS.PRESETS.ULTRA_LOW,
55
- },
56
- balanced: {
57
- splitSettings: SPLIT_SETTINGS.PRESETS.BALANCED,
58
- paddingSettings: PADDING_SETTINGS.PRESETS.BALANCED,
59
- },
60
- quality: {
61
- splitSettings: SPLIT_SETTINGS.PRESETS.QUALITY,
62
- paddingSettings: PADDING_SETTINGS.PRESETS.QUALITY,
63
- },
64
- };
65
- const preset = presets[latencyMode];
66
- return {
67
- latencyMode,
68
- splitSettings: { ...preset.splitSettings, ...this.config.audio?.splitSettings },
69
- paddingSettings: { ...preset.paddingSettings, ...this.config.audio?.paddingSettings },
70
- };
71
- }
72
- /**
73
- * 設定から音声生成時のサンプルレートを取得
74
- */
75
- getSynthesisRate() {
76
- return this.config.audio.processing?.synthesisRate || SAMPLE_RATES.SYNTHESIS;
77
- }
78
- /**
79
- * 設定ファイルに基づいて分割モード設定を生成
80
- */
81
- getSplitModeConfig() {
82
- // latencyModeプリセットの値を優先し、個別設定で上書き
83
- const splitSettings = {
84
- smallSize: this.audioConfig.splitSettings?.smallSize || SPLIT_SETTINGS.DEFAULTS.SMALL_SIZE,
85
- mediumSize: this.audioConfig.splitSettings?.mediumSize || SPLIT_SETTINGS.DEFAULTS.MEDIUM_SIZE,
86
- largeSize: this.audioConfig.splitSettings?.largeSize || SPLIT_SETTINGS.DEFAULTS.LARGE_SIZE,
87
- overlapRatio: this.audioConfig.splitSettings?.overlapRatio || SPLIT_SETTINGS.DEFAULTS.OVERLAP_RATIO,
88
- };
89
- return {
90
- none: { chunkSize: Infinity, overlap: 0 },
91
- small: {
92
- chunkSize: splitSettings.smallSize,
93
- overlap: Math.round(splitSettings.smallSize * splitSettings.overlapRatio),
94
- },
95
- medium: {
96
- chunkSize: splitSettings.mediumSize,
97
- overlap: Math.round(splitSettings.mediumSize * splitSettings.overlapRatio),
98
- },
99
- large: {
100
- chunkSize: splitSettings.largeSize,
101
- overlap: Math.round(splitSettings.largeSize * splitSettings.overlapRatio),
102
- },
103
- punctuation: {
104
- chunkSize: SPLIT_SETTINGS.PUNCTUATION.MAX_CHUNK_SIZE,
105
- overlap: SPLIT_SETTINGS.PUNCTUATION.OVERLAP_CHARS,
106
- },
107
- };
108
- }
109
- /**
110
- * サーバー接続確認
111
- */
112
- async checkServerConnection() {
113
- return await this.speakerProvider.checkConnection();
114
- }
115
- /**
116
- * 利用可能な音声一覧を取得
117
- */
118
- async listVoices() {
119
- await this.speakerProvider.logAvailableVoices();
120
- }
121
- /**
122
- * 句読点に基づくテキスト分割
123
- */
124
- splitByPunctuation(text) {
125
- const chunks = [];
126
- const config = SPLIT_SETTINGS.PUNCTUATION;
127
- // 複数の句読点で分割(。!?)
128
- // 複合句読点も考慮(?!、!?等)
129
- const punctuationPattern = /([。!?]+)/;
130
- const parts = text.split(punctuationPattern);
131
- logger.debug(`Parts after punctuation split: ${JSON.stringify(parts)}`);
132
- const sentences = [];
133
- for (let i = 0; i < parts.length; i += 2) {
134
- const sentence = parts[i] ? parts[i].trim() : '';
135
- const punctuation = parts[i + 1] || '';
136
- if (sentence.length > 0) {
137
- sentences.push(sentence + punctuation);
138
- }
139
- }
140
- logger.debug(`Processed sentences: ${JSON.stringify(sentences)}`);
141
- // 短いテキストの場合は全体を1つのチャンクとして扱う
142
- if (text.trim().length > 0 && sentences.length === 0) {
143
- chunks.push({
144
- text: text.trim(),
145
- index: 0,
146
- isFirst: true,
147
- isLast: true,
148
- overlap: 0,
149
- });
150
- return chunks;
151
- }
152
- // 句読点分割:適切な長さのチャンクを作成し、短いチャンクは結合する
153
- let currentChunk = '';
154
- for (let i = 0; i < sentences.length; i++) {
155
- const sentence = sentences[i];
156
- // 文が最大文字数を超える場合は個別のチャンクとして処理
157
- if (sentence.length > config.MAX_CHUNK_SIZE) {
158
- // 現在のチャンクがあれば先に追加
159
- if (currentChunk.length > 0) {
160
- chunks.push({
161
- text: currentChunk,
162
- index: chunks.length,
163
- isFirst: chunks.length === 0,
164
- isLast: false,
165
- overlap: 0,
166
- });
167
- currentChunk = '';
168
- }
169
- // 長い文は個別処理(読点分割など)
170
- if (config.ALLOW_COMMA_SPLIT && sentence.includes('、')) {
171
- const subChunks = this.splitLongSentenceByComma(sentence, config.MAX_CHUNK_SIZE);
172
- subChunks.forEach(subChunk => {
173
- chunks.push({
174
- text: subChunk,
175
- index: chunks.length,
176
- isFirst: chunks.length === 0,
177
- isLast: false,
178
- overlap: 0,
179
- });
180
- });
181
- }
182
- else {
183
- // 長い文をそのままチャンクとして追加
184
- chunks.push({
185
- text: sentence,
186
- index: chunks.length,
187
- isFirst: chunks.length === 0,
188
- isLast: false,
189
- overlap: 0,
190
- });
191
- }
192
- }
193
- else {
194
- // 短い文の結合処理
195
- const combinedLength = currentChunk.length + sentence.length;
196
- if (currentChunk.length === 0) {
197
- // 最初の文
198
- currentChunk = sentence;
199
- }
200
- else if (combinedLength <= config.MAX_CHUNK_SIZE &&
201
- (currentChunk.length < config.MIN_CHUNK_SIZE || sentence.length < config.MIN_CHUNK_SIZE)) {
202
- // 結合条件: 最大サイズ以下 かつ どちらかが最小サイズ未満
203
- currentChunk += sentence;
204
- }
205
- else {
206
- // 現在のチャンクを確定し、新しいチャンクを開始
207
- chunks.push({
208
- text: currentChunk,
209
- index: chunks.length,
210
- isFirst: chunks.length === 0,
211
- isLast: false,
212
- overlap: 0,
213
- });
214
- currentChunk = sentence;
215
- }
216
- }
217
- }
218
- // 残ったチャンクを追加
219
- if (currentChunk.length > 0) {
220
- chunks.push({
221
- text: currentChunk,
222
- index: chunks.length,
223
- isFirst: chunks.length === 0,
224
- isLast: false,
225
- overlap: 0,
226
- });
227
- }
228
- // 最後のチャンクのisLastフラグを設定
229
- if (chunks.length > 0) {
230
- chunks[chunks.length - 1].isLast = true;
231
- }
232
- else {
233
- // 全てのチャンクが最小サイズ未満の場合、元のテキストを1つのチャンクとして作成
234
- chunks.push({
235
- text: text.trim(),
236
- index: 0,
237
- isFirst: true,
238
- isLast: true,
239
- overlap: 0,
240
- });
241
- }
242
- return chunks;
243
- }
244
- /**
245
- * 文字数で強制分割
246
- */
247
- forceSplitByLength(text, maxSize) {
248
- const chunks = [];
249
- for (let i = 0; i < text.length; i += maxSize) {
250
- chunks.push(text.slice(i, i + maxSize));
251
- }
252
- return chunks;
253
- }
254
- /**
255
- * 長い文を読点で分割
256
- */
257
- splitLongSentenceByComma(sentence, maxSize) {
258
- const parts = [];
259
- const commaParts = sentence.split('、');
260
- let currentChunk = '';
261
- for (let i = 0; i < commaParts.length; i++) {
262
- const part = commaParts[i] + (i < commaParts.length - 1 ? '、' : '');
263
- if (currentChunk.length + part.length <= maxSize) {
264
- currentChunk += part;
265
- }
266
- else {
267
- if (currentChunk.length > 0) {
268
- parts.push(currentChunk);
269
- currentChunk = part;
270
- }
271
- else {
272
- // 単一パートが最大サイズを超える場合は強制的に文字数分割
273
- parts.push(part);
274
- }
275
- }
276
- }
277
- if (currentChunk.length > 0) {
278
- parts.push(currentChunk);
279
- }
280
- return parts;
281
- }
282
- /**
283
- * テキストを音切れ防止のためのオーバーラップ付きチャンクに分割
284
- */
285
- splitTextIntoChunks(text, splitMode = 'punctuation') {
286
- logger.debug('=== SPLIT_TEXT_INTO_CHUNKS DEBUG ===');
287
- logger.debug(`Input splitMode: ${splitMode}`);
288
- logger.debug(`Text length: ${text.length}`);
289
- logger.debug(`Text preview: "${text.substring(0, 100)}${text.length > 100 ? '...' : ''}"`);
290
- // 句読点分割の場合は専用処理
291
- if (splitMode === 'punctuation') {
292
- logger.debug('Using punctuation-based splitting');
293
- const chunks = this.splitByPunctuation(text);
294
- logger.debug(`Punctuation splitting result: ${chunks.length} chunks`);
295
- chunks.forEach((chunk, index) => {
296
- logger.debug(` Chunk ${index}: "${chunk.text.substring(0, 50)}${chunk.text.length > 50 ? '...' : ''}"`);
297
- });
298
- return chunks;
299
- }
300
- logger.debug(`Using ${splitMode} splitting mode`);
301
- const chunks = [];
302
- const config = this.getSplitModeConfig()[splitMode];
303
- const chunkSize = config.chunkSize;
304
- const overlap = config.overlap;
305
- logger.debug(`Split config - chunkSize: ${chunkSize}, overlap: ${overlap}`);
306
- for (let i = 0; i < text.length; i += chunkSize - overlap) {
307
- const end = Math.min(i + chunkSize, text.length);
308
- const chunk = text.slice(i, end);
309
- if (chunk.trim().length > 0) {
310
- chunks.push({
311
- text: chunk,
312
- index: chunks.length,
313
- isFirst: i === 0,
314
- isLast: end >= text.length,
315
- overlap: i > 0 ? overlap : 0,
316
- });
317
- }
318
- }
319
- logger.debug(`Non-punctuation splitting result: ${chunks.length} chunks`);
320
- chunks.forEach((chunk, index) => {
321
- logger.debug(` Chunk ${index}: "${chunk.text.substring(0, 50)}${chunk.text.length > 50 ? '...' : ''}"`);
322
- });
323
- return chunks;
324
- }
325
- /**
326
- * 単一チャンクの音声合成
327
- */
328
- async synthesizeChunk(chunk, voiceConfig, speed) {
329
- logger.log(`音声合成: チャンク${chunk.index} "${chunk.text.substring(0, 30)}${chunk.text.length > 30 ? '...' : ''}"`);
330
- const url = `http://${this.config.connection.host}:${this.config.connection.port}/v1/synthesis`;
331
- // VoiceConfigから音声IDとスタイルIDを取得
332
- const voiceId = voiceConfig.speaker.speakerId;
333
- const styleId = voiceConfig.selectedStyleId;
334
- // 音切れ防止: 前後に無音パディングを追加(設定に基づく)
335
- let paddingMs = 0;
336
- let postPaddingMs = 0;
337
- if (this.audioConfig.paddingSettings?.enabled) {
338
- const basePrePadding = this.audioConfig.paddingSettings.prePhonemeLength ||
339
- PADDING_SETTINGS.DEFAULTS.PRE_PHONEME_LENGTH;
340
- const basePostPadding = this.audioConfig.paddingSettings.postPhonemeLength ||
341
- PADDING_SETTINGS.DEFAULTS.POST_PHONEME_LENGTH;
342
- const firstChunkOnly = this.audioConfig.paddingSettings.firstChunkOnly;
343
- if (!firstChunkOnly || chunk.isFirst) {
344
- paddingMs = (chunk.isFirst ? basePrePadding : basePrePadding / 2) * 1000;
345
- postPaddingMs = (chunk.isLast ? basePostPadding : basePostPadding / 2) * 1000;
346
- }
347
- }
348
- const synthesisParam = {
349
- text: chunk.text,
350
- speakerUuid: voiceId,
351
- styleId: styleId,
352
- speedScale: speed,
353
- volumeScale: SYNTHESIS_SETTINGS.DEFAULT_VOLUME,
354
- pitchScale: SYNTHESIS_SETTINGS.DEFAULT_PITCH,
355
- intonationScale: SYNTHESIS_SETTINGS.DEFAULT_INTONATION,
356
- prePhonemeLength: paddingMs / 1000,
357
- postPhonemeLength: postPaddingMs / 1000,
358
- outputSamplingRate: this.getSynthesisRate(),
359
- };
360
- const startTime = Date.now();
361
- try {
362
- const response = await fetch(url, {
363
- method: 'POST',
364
- headers: {
365
- 'Content-Type': 'application/json',
366
- },
367
- body: JSON.stringify(synthesisParam),
368
- });
369
- if (!response.ok) {
370
- const errorText = await response.text().catch(() => 'レスポンス読み取り失敗');
371
- logger.error(`COEIROINK APIエラー: ${response.status} ${response.statusText}, body: ${errorText}`);
372
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
373
- }
374
- const audioBuffer = await response.arrayBuffer();
375
- const latency = Date.now() - startTime;
376
- return {
377
- chunk,
378
- audioBuffer,
379
- latency,
380
- };
381
- }
382
- catch (error) {
383
- logger.error(`チャンク${chunk.index}合成エラー詳細:`, error);
384
- throw new Error(`チャンク${chunk.index}合成エラー: ${error.message}`);
385
- }
386
- }
387
- /**
388
- * レート値をスピード値に変換
389
- */
390
- convertRateToSpeed(rate) {
391
- const baseRate = 200;
392
- let speed = rate / baseRate;
393
- if (speed < 0.5)
394
- speed = 0.5;
395
- if (speed > 2.0)
396
- speed = 2.0;
397
- return speed;
398
- }
399
- /**
400
- * ストリーミング音声合成(リファクタリング版)
401
- */
402
- async *synthesizeStream(text, voiceConfig, speed, chunkMode = 'punctuation') {
403
- logger.debug('=== SYNTHESIZE_STREAM DEBUG ===');
404
- logger.debug(`chunkMode parameter: ${chunkMode}`);
405
- logger.debug(`text length: ${text.length}`);
406
- const chunks = this.splitTextIntoChunks(text, chunkMode);
407
- logger.debug(`Total chunks generated: ${chunks.length}`);
408
- // AudioStreamControllerを使用してストリーミング生成
409
- yield* this.streamController.synthesizeStream(chunks, voiceConfig, speed);
410
- logger.debug('=== SYNTHESIZE_STREAM COMPLETE ===');
411
- }
412
- /**
413
- * 並行生成モードの切り替え
414
- */
415
- setParallelGenerationEnabled(enabled) {
416
- this.streamController.setParallelGenerationEnabled(enabled);
417
- }
418
- /**
419
- * ストリーム制御オプションの更新
420
- */
421
- updateStreamControllerOptions(options) {
422
- this.streamController.updateOptions(options);
423
- }
424
- /**
425
- * 現在のストリーム制御設定を取得
426
- */
427
- getStreamControllerOptions() {
428
- return this.streamController.getOptions();
429
- }
430
- /**
431
- * 生成統計情報を取得
432
- */
433
- getGenerationStats() {
434
- return this.streamController.getGenerationStats();
435
- }
436
- }
437
- //# sourceMappingURL=audio-synthesizer.js.map