@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.
- package/package.json +3 -3
- package/dist/audio-player.d.ts +0 -134
- package/dist/audio-player.js +0 -707
- package/dist/audio-stream-controller.d.ts +0 -52
- package/dist/audio-stream-controller.js +0 -121
- package/dist/audio-synthesizer.d.ts +0 -86
- package/dist/audio-synthesizer.js +0 -437
- package/dist/chunk-generation-manager.d.ts +0 -77
- package/dist/chunk-generation-manager.js +0 -178
- package/dist/constants.d.ts +0 -180
- package/dist/constants.js +0 -219
- package/dist/index.d.ts +0 -77
- package/dist/index.js +0 -194
- package/dist/speech-queue.d.ts +0 -52
- package/dist/speech-queue.js +0 -143
- package/dist/synthesis-processor.d.ts +0 -39
- package/dist/synthesis-processor.js +0 -131
- package/dist/test-helpers.d.ts +0 -19
- package/dist/test-helpers.js +0 -167
- package/dist/types.d.ts +0 -63
- package/dist/types.js +0 -5
- package/dist/voice-resolver.d.ts +0 -25
- package/dist/voice-resolver.js +0 -141
package/dist/test-helpers.js
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* テスト用ヘルパー関数とモックファクトリ
|
|
3
|
-
*/
|
|
4
|
-
import { vi } from 'vitest';
|
|
5
|
-
/**
|
|
6
|
-
* Speakerモックインスタンスを作成
|
|
7
|
-
* Node.js StreamのWritableインターフェースを完全に実装
|
|
8
|
-
*/
|
|
9
|
-
export function createMockSpeakerInstance() {
|
|
10
|
-
const mockInstance = {
|
|
11
|
-
write: vi.fn((chunk, encoding, callback) => {
|
|
12
|
-
// 引数の数に応じて適切にコールバックを処理
|
|
13
|
-
const cb = typeof encoding === 'function' ? encoding : callback;
|
|
14
|
-
if (cb)
|
|
15
|
-
cb();
|
|
16
|
-
return true;
|
|
17
|
-
}),
|
|
18
|
-
end: vi.fn((chunk, encoding, callback) => {
|
|
19
|
-
// 引数の数に応じて適切にコールバックを処理
|
|
20
|
-
let cb;
|
|
21
|
-
if (typeof chunk === 'function') {
|
|
22
|
-
cb = chunk;
|
|
23
|
-
}
|
|
24
|
-
else if (typeof encoding === 'function') {
|
|
25
|
-
cb = encoding;
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
cb = callback;
|
|
29
|
-
}
|
|
30
|
-
if (cb)
|
|
31
|
-
setTimeout(cb, 10);
|
|
32
|
-
}),
|
|
33
|
-
on: vi.fn(function (event, callback) {
|
|
34
|
-
if (event === 'close') {
|
|
35
|
-
setTimeout(callback, 10);
|
|
36
|
-
}
|
|
37
|
-
return this;
|
|
38
|
-
}),
|
|
39
|
-
once: vi.fn(function (event, callback) {
|
|
40
|
-
if (event === 'close') {
|
|
41
|
-
setTimeout(callback, 10);
|
|
42
|
-
}
|
|
43
|
-
return this;
|
|
44
|
-
}),
|
|
45
|
-
emit: vi.fn(),
|
|
46
|
-
removeListener: vi.fn(function () { return this; }),
|
|
47
|
-
removeAllListeners: vi.fn(function () { return this; }),
|
|
48
|
-
pipe: vi.fn(),
|
|
49
|
-
unpipe: vi.fn(),
|
|
50
|
-
destroy: vi.fn(),
|
|
51
|
-
_writableState: { ended: false },
|
|
52
|
-
writable: true,
|
|
53
|
-
readable: false,
|
|
54
|
-
};
|
|
55
|
-
// thisバインディングを修正
|
|
56
|
-
mockInstance.on = mockInstance.on.bind(mockInstance);
|
|
57
|
-
mockInstance.once = mockInstance.once.bind(mockInstance);
|
|
58
|
-
mockInstance.removeListener = mockInstance.removeListener.bind(mockInstance);
|
|
59
|
-
mockInstance.removeAllListeners = mockInstance.removeAllListeners.bind(mockInstance);
|
|
60
|
-
return mockInstance;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* エラーを発生させるSpeakerモックインスタンスを作成
|
|
64
|
-
*/
|
|
65
|
-
export function createErrorMockSpeakerInstance() {
|
|
66
|
-
const mockInstance = createMockSpeakerInstance();
|
|
67
|
-
mockInstance.on = vi.fn(function (event, callback) {
|
|
68
|
-
if (event === 'error') {
|
|
69
|
-
setTimeout(() => callback(new Error('Mock speaker error')), 10);
|
|
70
|
-
}
|
|
71
|
-
return this;
|
|
72
|
-
}).bind(mockInstance);
|
|
73
|
-
mockInstance.once = vi.fn(function (event, callback) {
|
|
74
|
-
if (event === 'error') {
|
|
75
|
-
setTimeout(() => callback(new Error('Mock speaker error')), 10);
|
|
76
|
-
}
|
|
77
|
-
return this;
|
|
78
|
-
}).bind(mockInstance);
|
|
79
|
-
return mockInstance;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* テスト用のConfigManagerモックを作成
|
|
83
|
-
*/
|
|
84
|
-
export function createMockConfigManager(overrides = {}) {
|
|
85
|
-
const defaultConfig = {
|
|
86
|
-
connection: {
|
|
87
|
-
host: 'localhost',
|
|
88
|
-
port: '50032',
|
|
89
|
-
},
|
|
90
|
-
operator: {
|
|
91
|
-
rate: 200,
|
|
92
|
-
timeout: 14400000,
|
|
93
|
-
assignmentStrategy: 'random',
|
|
94
|
-
},
|
|
95
|
-
audio: {
|
|
96
|
-
latencyMode: 'balanced',
|
|
97
|
-
splitMode: 'punctuation',
|
|
98
|
-
bufferSize: 256,
|
|
99
|
-
parallelGeneration: {
|
|
100
|
-
maxConcurrency: 2,
|
|
101
|
-
delayBetweenRequests: 50,
|
|
102
|
-
bufferAheadCount: 1,
|
|
103
|
-
pauseUntilFirstComplete: true,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
characters: {},
|
|
107
|
-
};
|
|
108
|
-
// overridesで深くマージ
|
|
109
|
-
const mergedConfig = deepMerge(defaultConfig, overrides);
|
|
110
|
-
// ConfigManagerのモックオブジェクトを作成
|
|
111
|
-
const mockConfigManager = {
|
|
112
|
-
getFullConfig: async () => mergedConfig,
|
|
113
|
-
buildDynamicConfig: async () => { },
|
|
114
|
-
getCharacterConfig: async (characterId) => {
|
|
115
|
-
// テスト用のキャラクター設定を返す
|
|
116
|
-
// test-speaker-1などのテスト用IDの場合は適切な設定を返す
|
|
117
|
-
if (characterId === 'test-speaker-1' || characterId === 'test-speaker-uuid') {
|
|
118
|
-
return {
|
|
119
|
-
speakerId: 'test-speaker-1', // テストのモックと一致させる
|
|
120
|
-
name: 'テストスピーカー1',
|
|
121
|
-
defaultStyle: 'ノーマル',
|
|
122
|
-
availableStyles: ['ノーマル'],
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
// tsukuyomiなど他のキャラクターも対応
|
|
126
|
-
if (characterId === 'tsukuyomi') {
|
|
127
|
-
return {
|
|
128
|
-
speakerId: '3c37646f-3881-5374-2a83-149267990abc', // 実際のtsukuyomiのspeakerId
|
|
129
|
-
name: 'つくよみちゃん',
|
|
130
|
-
defaultStyle: 'れいせい',
|
|
131
|
-
availableStyles: ['れいせい', 'おしとやか', 'げんき'],
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
// デフォルトのキャラクター設定
|
|
135
|
-
return {
|
|
136
|
-
speakerId: characterId + '-uuid',
|
|
137
|
-
name: characterId,
|
|
138
|
-
defaultStyle: 'ノーマル',
|
|
139
|
-
availableStyles: ['ノーマル'],
|
|
140
|
-
};
|
|
141
|
-
},
|
|
142
|
-
getAvailableCharacterIds: async () => ['test-speaker-1', 'tsukuyomi'],
|
|
143
|
-
getOperatorTimeout: async () => mergedConfig.operator.timeout,
|
|
144
|
-
getRate: async () => mergedConfig.operator.rate,
|
|
145
|
-
getAudioConfig: async () => mergedConfig.audio,
|
|
146
|
-
getConnectionConfig: async () => mergedConfig.connection,
|
|
147
|
-
};
|
|
148
|
-
return mockConfigManager;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* オブジェクトの深いマージ
|
|
152
|
-
*/
|
|
153
|
-
function deepMerge(target, source) {
|
|
154
|
-
const result = { ...target };
|
|
155
|
-
for (const key in source) {
|
|
156
|
-
if (source[key] !== undefined) {
|
|
157
|
-
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
|
158
|
-
result[key] = deepMerge(result[key], source[key]);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
result[key] = source[key];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
|
-
//# sourceMappingURL=test-helpers.js.map
|
package/dist/types.d.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* src/say/types.ts: 音声合成システムの型定義
|
|
3
|
-
*/
|
|
4
|
-
import { FullConfig, AudioConfig, ConnectionConfig } from '@coeiro-operator/core';
|
|
5
|
-
export type Config = FullConfig;
|
|
6
|
-
export type { AudioConfig, ConnectionConfig };
|
|
7
|
-
export interface StreamConfig {
|
|
8
|
-
chunkSizeChars: number;
|
|
9
|
-
overlapChars: number;
|
|
10
|
-
bufferSize: number;
|
|
11
|
-
audioBufferMs: number;
|
|
12
|
-
silencePaddingMs: number;
|
|
13
|
-
preloadChunks: number;
|
|
14
|
-
}
|
|
15
|
-
export interface Chunk {
|
|
16
|
-
text: string;
|
|
17
|
-
index: number;
|
|
18
|
-
isFirst: boolean;
|
|
19
|
-
isLast: boolean;
|
|
20
|
-
overlap: number;
|
|
21
|
-
}
|
|
22
|
-
export interface AudioResult {
|
|
23
|
-
chunk: Chunk;
|
|
24
|
-
audioBuffer: ArrayBuffer;
|
|
25
|
-
latency: number;
|
|
26
|
-
}
|
|
27
|
-
import type { Speaker } from '@coeiro-operator/core';
|
|
28
|
-
/**
|
|
29
|
-
* VoiceConfig: 音声合成に必要な最小限の情報
|
|
30
|
-
* Speaker情報と選択されたスタイルIDを含む
|
|
31
|
-
*/
|
|
32
|
-
export interface VoiceConfig {
|
|
33
|
-
speaker: Speaker;
|
|
34
|
-
selectedStyleId: number;
|
|
35
|
-
}
|
|
36
|
-
export type SpeechTaskType = 'speech';
|
|
37
|
-
export interface SpeechTask {
|
|
38
|
-
id: number;
|
|
39
|
-
type: SpeechTaskType;
|
|
40
|
-
text: string;
|
|
41
|
-
options: SynthesizeOptions;
|
|
42
|
-
timestamp: number;
|
|
43
|
-
resolve?: () => void;
|
|
44
|
-
reject?: (error: Error) => void;
|
|
45
|
-
}
|
|
46
|
-
export interface SynthesizeOptions {
|
|
47
|
-
voice?: string | VoiceConfig | null;
|
|
48
|
-
rate?: number;
|
|
49
|
-
outputFile?: string | null;
|
|
50
|
-
style?: string;
|
|
51
|
-
chunkMode?: 'none' | 'small' | 'medium' | 'large' | 'punctuation';
|
|
52
|
-
bufferSize?: number;
|
|
53
|
-
allowFallback?: boolean;
|
|
54
|
-
}
|
|
55
|
-
export interface SynthesizeResult {
|
|
56
|
-
success: boolean;
|
|
57
|
-
taskId?: number;
|
|
58
|
-
queueLength?: number;
|
|
59
|
-
outputFile?: string;
|
|
60
|
-
latency?: number;
|
|
61
|
-
mode?: string;
|
|
62
|
-
}
|
|
63
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
DELETED
package/dist/voice-resolver.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* voice-resolver.ts: 音声設定の解決を担当
|
|
3
|
-
*/
|
|
4
|
-
import { OperatorManager, ConfigManager } from '@coeiro-operator/core';
|
|
5
|
-
import { AudioSynthesizer } from './audio-synthesizer.js';
|
|
6
|
-
import type { VoiceConfig } from './types.js';
|
|
7
|
-
export declare class VoiceResolver {
|
|
8
|
-
private configManager;
|
|
9
|
-
private operatorManager;
|
|
10
|
-
private audioSynthesizer;
|
|
11
|
-
constructor(configManager: ConfigManager, operatorManager: OperatorManager, audioSynthesizer: AudioSynthesizer);
|
|
12
|
-
/**
|
|
13
|
-
* 現在のオペレータの音声設定を取得
|
|
14
|
-
*/
|
|
15
|
-
getCurrentVoiceConfig(styleName?: string | null): Promise<VoiceConfig | null>;
|
|
16
|
-
/**
|
|
17
|
-
* CharacterIdからVoiceConfigを生成
|
|
18
|
-
*/
|
|
19
|
-
resolveCharacterToConfig(characterId: string, styleName?: string | null): Promise<VoiceConfig>;
|
|
20
|
-
/**
|
|
21
|
-
* 音声設定を解決
|
|
22
|
-
*/
|
|
23
|
-
resolveVoiceConfig(voice: string | VoiceConfig | null | undefined, style?: string, allowFallback?: boolean): Promise<VoiceConfig>;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=voice-resolver.d.ts.map
|
package/dist/voice-resolver.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* voice-resolver.ts: 音声設定の解決を担当
|
|
3
|
-
*/
|
|
4
|
-
import { logger } from '@coeiro-operator/common';
|
|
5
|
-
export class VoiceResolver {
|
|
6
|
-
configManager;
|
|
7
|
-
operatorManager;
|
|
8
|
-
audioSynthesizer;
|
|
9
|
-
constructor(configManager, operatorManager, audioSynthesizer) {
|
|
10
|
-
this.configManager = configManager;
|
|
11
|
-
this.operatorManager = operatorManager;
|
|
12
|
-
this.audioSynthesizer = audioSynthesizer;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* 現在のオペレータの音声設定を取得
|
|
16
|
-
*/
|
|
17
|
-
async getCurrentVoiceConfig(styleName) {
|
|
18
|
-
try {
|
|
19
|
-
const currentStatus = await this.operatorManager.showCurrentOperator();
|
|
20
|
-
if (!currentStatus.characterId) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const character = await this.operatorManager.getCharacterInfo(currentStatus.characterId);
|
|
24
|
-
if (character && character.speaker && character.speaker.speakerId) {
|
|
25
|
-
// 保存されたセッション情報からスタイルを取得
|
|
26
|
-
const session = await this.operatorManager.getCurrentOperatorSession();
|
|
27
|
-
let selectedStyle;
|
|
28
|
-
if (styleName) {
|
|
29
|
-
// 明示的にスタイルが指定された場合はそれを使用
|
|
30
|
-
selectedStyle = this.operatorManager.selectStyle(character, styleName);
|
|
31
|
-
}
|
|
32
|
-
else if (session?.styleId !== undefined) {
|
|
33
|
-
// セッションに保存されたスタイルIDがある場合はそれを使用
|
|
34
|
-
const savedStyle = character.speaker.styles.find(s => s.styleId === session.styleId);
|
|
35
|
-
if (savedStyle) {
|
|
36
|
-
selectedStyle = savedStyle;
|
|
37
|
-
logger.debug(`保存されたスタイルを使用: ${savedStyle.styleName} (ID:${savedStyle.styleId})`);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
// 保存されたスタイルが見つからない場合はデフォルトを使用
|
|
41
|
-
selectedStyle = this.operatorManager.selectStyle(character, null);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
// セッション情報がない場合はデフォルトスタイルを使用
|
|
46
|
-
selectedStyle = this.operatorManager.selectStyle(character, null);
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
speaker: character.speaker,
|
|
50
|
-
selectedStyleId: selectedStyle.styleId,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
logger.error(`オペレータ音声取得エラー: ${error.message}`);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* CharacterIdからVoiceConfigを生成
|
|
62
|
-
*/
|
|
63
|
-
async resolveCharacterToConfig(characterId, styleName) {
|
|
64
|
-
try {
|
|
65
|
-
// ConfigManagerからCharacter設定を取得
|
|
66
|
-
const characterConfig = await this.configManager.getCharacterConfig(characterId);
|
|
67
|
-
if (!characterConfig) {
|
|
68
|
-
throw new Error(`Character not found: ${characterId}`);
|
|
69
|
-
}
|
|
70
|
-
// speakerIdからspeaker情報を取得(COEIROINKサーバーから)
|
|
71
|
-
const speakers = await this.audioSynthesizer.getSpeakers();
|
|
72
|
-
const speaker = speakers.find(s => s.speakerUuid === characterConfig.speakerId);
|
|
73
|
-
if (!speaker) {
|
|
74
|
-
throw new Error(`Speaker '${characterConfig.speakerId}' not found for character '${characterId}'`);
|
|
75
|
-
}
|
|
76
|
-
// スタイル選択(styleNameが指定されていればそれを使用、そうでなければdefaultStyle)
|
|
77
|
-
let selectedStyle = speaker.styles.find(s => s.styleName === (styleName || characterConfig.defaultStyle));
|
|
78
|
-
if (!selectedStyle) {
|
|
79
|
-
// デフォルトスタイルが見つからない場合は最初のスタイルを使用
|
|
80
|
-
selectedStyle = speaker.styles[0];
|
|
81
|
-
}
|
|
82
|
-
// speaker-provider.Speakerからoperator.Speakerへ変換
|
|
83
|
-
const operatorSpeaker = {
|
|
84
|
-
speakerId: speaker.speakerUuid,
|
|
85
|
-
speakerName: speaker.speakerName,
|
|
86
|
-
styles: speaker.styles,
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
speaker: operatorSpeaker,
|
|
90
|
-
selectedStyleId: selectedStyle.styleId,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
logger.error(`Character解決エラー: ${error.message}`);
|
|
95
|
-
throw new Error(`Failed to resolve character '${characterId}': ${error.message}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* 音声設定を解決
|
|
100
|
-
*/
|
|
101
|
-
async resolveVoiceConfig(voice, style, allowFallback = true) {
|
|
102
|
-
if (!voice) {
|
|
103
|
-
// オペレータから音声を取得(スタイル指定も渡す)
|
|
104
|
-
const operatorVoice = await this.getCurrentVoiceConfig(style);
|
|
105
|
-
if (operatorVoice) {
|
|
106
|
-
// スタイル情報を取得して詳細ログ出力
|
|
107
|
-
const selectedStyle = operatorVoice.speaker.styles.find(s => s.styleId === operatorVoice.selectedStyleId);
|
|
108
|
-
const styleName = selectedStyle?.styleName || `ID:${operatorVoice.selectedStyleId}`;
|
|
109
|
-
logger.info(`オペレータ音声を使用: ${operatorVoice.speaker.speakerName} (スタイル: ${styleName})`);
|
|
110
|
-
return operatorVoice;
|
|
111
|
-
}
|
|
112
|
-
else if (allowFallback) {
|
|
113
|
-
// CLIのみ: デフォルトキャラクターを使用(つくよみちゃん)
|
|
114
|
-
const defaultCharacterId = 'tsukuyomi';
|
|
115
|
-
logger.info(`デフォルトキャラクターを使用: ${defaultCharacterId}`);
|
|
116
|
-
return await this.resolveCharacterToConfig(defaultCharacterId, style);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
// MCP: オペレータが必須
|
|
120
|
-
throw new Error('オペレータが割り当てられていません。まず operator_assign を実行してください。');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else if (typeof voice === 'string') {
|
|
124
|
-
// string型の場合はCharacterIdとして解決
|
|
125
|
-
logger.info(`キャラクター解決: ${voice}`);
|
|
126
|
-
const voiceConfig = await this.resolveCharacterToConfig(voice, style);
|
|
127
|
-
const selectedStyle = voiceConfig.speaker.styles.find(s => s.styleId === voiceConfig.selectedStyleId);
|
|
128
|
-
const styleName = selectedStyle?.styleName || `ID:${voiceConfig.selectedStyleId}`;
|
|
129
|
-
logger.info(` → ${voiceConfig.speaker.speakerName} (スタイル: ${styleName})`);
|
|
130
|
-
return voiceConfig;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
// すでにVoiceConfig型の場合はそのまま使用
|
|
134
|
-
const selectedStyle = voice.speaker.styles.find(s => s.styleId === voice.selectedStyleId);
|
|
135
|
-
const styleName = selectedStyle?.styleName || `ID:${voice.selectedStyleId}`;
|
|
136
|
-
logger.info(`VoiceConfig使用: ${voice.speaker.speakerName} (スタイル: ${styleName})`);
|
|
137
|
-
return voice;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
//# sourceMappingURL=voice-resolver.js.map
|