├── .cursorrules ├── .gitignore ├── Plugin_Socket ├── PluginSettings.cs ├── Plugin_WebSocket.cs ├── Plugin_WebSocket.csproj ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── SettingFormData.cs └── SettingPropertyGrid.cs ├── Plugin_WebSocket.sln ├── README.md └── bouyomi_api.md /.cursorrules: -------------------------------------------------------------------------------- 1 | # 棒読みちゃんプラグイン開発ルール 2 | 3 | ## .NET Framework バージョン 4 | - .NET Framework 2.0 を厳守すること 5 | - 新しい言語機能は使用不可 6 | 7 | 8 | ## コーディング規約 9 | - C# 2.0 の文法のみ使用可能 10 | - auto-implemented プロパティ (get; set;) は使用不可 11 | - var キーワードは使用不可 12 | - LINQ は使用不可 13 | - ラムダ式は使用不可 14 | - 文字列補間($"...")は使用不可 15 | - null 条件演算子(?.)は使用不可 16 | - null 合体演算子(??)は使用不可 17 | 18 | ## 設定ファイル 19 | - FNF.XmlSerializerSetting.SettingsBase を継承して実装 20 | - 設定ファイルは Plugin_*.config の形式で保存 21 | - 設定ファイルはプラグインと同じディレクトリに配置 22 | 23 | ## エラー処理 24 | - 例外は適切にキャッチして処理すること 25 | - ユーザーに分かりやすいエラーメッセージを表示 26 | 27 | ## スレッド 28 | - Thread クラスを使用したマルチスレッド処理は可能 29 | - Task クラスは使用不可 30 | 31 | ## その他 32 | - プラグインのファイル名は「Plugin_*.dll」の形式にすること 33 | - リソースファイルは Properties/Resources.resx に配置 34 | 35 | ## プラグイン実装の参考記事 36 | - https://c-loft.com/blog/?p=1673 37 | - https://c-loft.com/blog/?p=1751 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | syntax:glob 2 | *.obj 3 | *.exe 4 | *.exp 5 | *.pdb 6 | *.dll 7 | *.user 8 | *.aps 9 | *.pch 10 | *.vspscc 11 | *_i.c 12 | *_p.c 13 | *.ncb 14 | *.suo 15 | *.tlb 16 | *.tlh 17 | *.bak 18 | *.cache 19 | *.ilk 20 | *.log 21 | *.zip 22 | [Dd]ebug*/ 23 | *.lib 24 | *.sbr 25 | Thumbs.db 26 | [Ll]ib/ 27 | [Rr]elease*/ 28 | [Tt]est[Rr]esults/ 29 | _UpgradeReport_Files/ 30 | _ReSharper.*/ 31 | /Plugin_Socket/.vs 32 | /.vs 33 | -------------------------------------------------------------------------------- /Plugin_Socket/PluginSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using FNF.XmlSerializerSetting; 4 | 5 | namespace Plugin_WebSocket { 6 | /// 7 | /// WebSocketプラグインの設定を管理するクラス 8 | /// 9 | public class PluginSettings : SettingsBase { 10 | private int _port; 11 | 12 | // ポート番号が変更されたときに発生するイベント 13 | public event EventHandler PortChanged; 14 | 15 | // ポート番号 16 | public int Port { 17 | get { return _port; } 18 | set { 19 | if (_port != value) { 20 | int oldPort = _port; 21 | _port = value; 22 | // ポート番号が変更されたらイベントを発生 23 | if (PortChanged != null) { 24 | PortChanged(this, EventArgs.Empty); 25 | } 26 | } 27 | } 28 | } 29 | 30 | // コンストラクタ 31 | public PluginSettings() { 32 | _port = 55000; // デフォルトポート 33 | } 34 | 35 | /// 36 | /// 設定フォームが閉じられたときに呼ばれる 37 | /// 38 | public override void OnSettingFormClosed(FNF.XmlSerializerSetting.SettingForm.CloseKind closeKind) { 39 | base.OnSettingFormClosed(closeKind); 40 | // OKボタンで閉じられた場合のみ処理 41 | if (closeKind == FNF.XmlSerializerSetting.SettingForm.CloseKind.OK) { 42 | // ポート番号が変更されていたらイベントを発生 43 | if (PortChanged != null) { 44 | PortChanged(this, EventArgs.Empty); 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// 設定ファイルから設定を読み込む際に呼ばれる 51 | /// 52 | public override void ReadSettings() { 53 | return; 54 | } 55 | 56 | /// 57 | /// 設定ファイルに設定を保存する際に呼ばれる 58 | /// 59 | public override void WriteSettings() { 60 | return; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Plugin_Socket/Plugin_WebSocket.cs: -------------------------------------------------------------------------------- 1 | //プラグインのファイル名は、「Plugin_*.dll」という形式にして下さい。 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.IO; 7 | using System.Drawing; 8 | using System.Threading; 9 | using System.ComponentModel; 10 | using System.Windows.Forms; 11 | using FNF.Utility; 12 | using FNF.Controls; 13 | using FNF.XmlSerializerSetting; 14 | using FNF.BouyomiChanApp; 15 | using System.Net; 16 | using System.Net.Sockets; 17 | using System.Security.Cryptography; 18 | 19 | namespace Plugin_WebSocket { 20 | public class Plugin_WebSocket : IPlugin { 21 | 22 | #region ■フィールド 23 | private string _path = Base.CallAsmPath + Base.CallAsmName + ".setting"; 24 | private PluginSettings _settings; 25 | private SettingFormData _settingFormData; 26 | private int _currentPort; 27 | private Accept _wsAccept; 28 | #endregion 29 | 30 | 31 | #region ■IPluginメンバの実装 32 | 33 | public string Name { get { return "WebSocketサーバー"; } } 34 | public string Version { get { return "2025/03/02版"; } } 35 | public string Caption { get { return "WebSocketからの読み上げリクエストを受け付けます。"; } } 36 | public ISettingFormData SettingFormData { get { return _settingFormData; } } 37 | 38 | //プラグイン開始時処理 39 | public void Begin() { 40 | // 設定ファイルを読み込む 41 | _settings = new PluginSettings(); 42 | if (File.Exists(_path)) { 43 | _settings.Load(_path); 44 | } 45 | 46 | // 現在のポート番号を記憶 47 | _currentPort = _settings.Port; 48 | 49 | // ポート番号変更イベントを登録 50 | _settings.PortChanged += new EventHandler(Settings_PortChanged); 51 | 52 | // 設定画面の初期化 53 | _settingFormData = new SettingFormData(_settings); 54 | 55 | // サーバー起動 56 | StartServer(); 57 | } 58 | 59 | //プラグイン終了時処理 60 | public void End() { 61 | if (_wsAccept != null) { 62 | _wsAccept.Stop(); 63 | _wsAccept = null; 64 | } 65 | _settings.Save(this._path); 66 | } 67 | 68 | // ポート番号変更イベントハンドラ 69 | private void Settings_PortChanged(object sender, EventArgs e) { 70 | // ポート番号が変更されていたらサーバーを再起動 71 | if (_settings.Port != _currentPort) { 72 | Pub.AddTalkTask("ポート番号が" + _currentPort + "から" + _settings.Port + "に変更されました。サーバーを再起動します。", -1, -1, VoiceType.Default); 73 | _currentPort = _settings.Port; 74 | RestartServer(); 75 | } 76 | } 77 | 78 | // サーバーを起動 79 | private void StartServer() { 80 | if (_wsAccept != null) { 81 | _wsAccept.Stop(); 82 | _wsAccept = null; 83 | } 84 | 85 | _wsAccept = new Accept(_settings.Port); 86 | _wsAccept.Start(); 87 | } 88 | 89 | // サーバーを再起動 90 | private void RestartServer() { 91 | StartServer(); 92 | } 93 | 94 | #endregion 95 | 96 | // 受付クラス 97 | class Accept { 98 | private int mPort; 99 | public bool active = true; 100 | Thread thread; 101 | Socket server; 102 | 103 | // コンストラクタ 104 | public Accept(int port) { 105 | mPort = port; 106 | } 107 | 108 | public void Start() { 109 | thread = new Thread(new ThreadStart(Run)); 110 | Pub.AddTalkTask("ポート" + mPort + "でソケット受付を開始しました。", -1, -1, VoiceType.Default); 111 | thread.Start(); 112 | } 113 | 114 | public void Stop() { 115 | active = false; 116 | if (server != null) { 117 | try { 118 | server.Close(); 119 | } 120 | catch { 121 | // エラーは無視 122 | } 123 | } 124 | if (thread != null && thread.IsAlive) { 125 | try { 126 | thread.Abort(); 127 | } 128 | catch { 129 | // エラーは無視 130 | } 131 | } 132 | Pub.AddTalkTask("ソケット受付を終了しました。", -1, -1, VoiceType.Default); 133 | } 134 | 135 | private void Run() { 136 | try { 137 | // ポートが使用可能かチェック 138 | using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { 139 | socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 140 | socket.Bind(new IPEndPoint(IPAddress.Any, mPort)); 141 | socket.Close(); 142 | } 143 | 144 | // 元のサーバー起動コード 145 | server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 146 | server.Bind(new IPEndPoint(IPAddress.Any, mPort)); 147 | server.Listen(10); 148 | } 149 | catch (SocketException ex) { 150 | MessageBox.Show("サーバーの起動に失敗しました。\nポート" + mPort + "が既に使用されているか、アクセス権限がありません。\n\nエラー詳細: " + ex.Message, 151 | "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); 152 | throw; 153 | } 154 | 155 | // 要求待ち(無限ループ) 156 | while (active) { 157 | try { 158 | Socket client = server.Accept(); 159 | Response response = new Response(client); 160 | response.Start(); 161 | } 162 | catch (Exception) { 163 | if (active) { 164 | // アクティブな状態でエラーが発生した場合は少し待機 165 | Thread.Sleep(1000); 166 | } 167 | else { 168 | // 非アクティブならループを抜ける 169 | break; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | 177 | // 応答クラス 178 | class Response { 179 | enum STATUS { 180 | CHECKING, // 調査中 181 | OK, // OK 182 | ERROR, // ERROR 183 | }; 184 | 185 | private Socket mClient; 186 | private STATUS mStatus; 187 | private Thread thread; 188 | 189 | // コンストラクタ 190 | public Response(Socket client) { 191 | mClient = client; 192 | mStatus = STATUS.CHECKING; 193 | } 194 | 195 | // 開始 196 | public void Start() { 197 | thread = new Thread(new ThreadStart(Run)); 198 | thread.Start(); 199 | } 200 | 201 | // 応答実行 202 | private void Run() 203 | { 204 | try 205 | { 206 | // 要求受信 207 | int bsize = mClient.ReceiveBufferSize; 208 | byte[] buffer = new byte[bsize]; 209 | int recvLen = mClient.Receive(buffer); 210 | 211 | if (recvLen <= 0) 212 | return; 213 | 214 | String header = Encoding.ASCII.GetString(buffer, 0, recvLen); 215 | Console.WriteLine("【" + System.DateTime.Now + "】\n" + header); 216 | 217 | // 要求URL確認 & 応答内容生成 218 | int pos = header.IndexOf("GET / HTTP/"); 219 | 220 | if (mStatus == STATUS.CHECKING && 0 == pos) 221 | { 222 | doWebSocketMain(header); 223 | } 224 | 225 | } 226 | catch (System.Net.Sockets.SocketException e) 227 | { 228 | Console.Write(e.Message); 229 | } 230 | finally 231 | { 232 | mClient.Close(); 233 | } 234 | } 235 | 236 | // WebSocketメイン 237 | private void doWebSocketMain(String header) { 238 | String key = "Sec-WebSocket-Key: "; 239 | int pos = header.IndexOf(key); 240 | if (pos < 0) return; 241 | 242 | // "Sec-WebSocket-Accept"に設定する文字列を生成 243 | String value = header.Substring(pos + key.Length, (header.IndexOf("\r\n", pos) - (pos + key.Length))); 244 | byte[] byteValue = Encoding.UTF8.GetBytes(value + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 245 | SHA1 crypto = new SHA1CryptoServiceProvider(); 246 | byte[] hash = crypto.ComputeHash(byteValue); 247 | String resValue = Convert.ToBase64String(hash); 248 | 249 | // 応答内容送信 250 | byte[] buffer = Encoding.UTF8.GetBytes( 251 | "HTTP/1.1 101 OK\r\n" + 252 | "Upgrade: websocket\r\n" + 253 | "Connection: Upgrade\r\n" + 254 | "Sec-WebSocket-Accept: " + resValue + "\r\n" + 255 | "\r\n"); 256 | 257 | mClient.Send(buffer); 258 | 259 | // クライアントからテキストを受信 260 | int bsize = mClient.ReceiveBufferSize; 261 | byte[] request = new byte[bsize]; 262 | mClient.Receive(request); 263 | 264 | // WebSocketデータをデコード 265 | string jsonText = DecodeWebSocketData(request, bsize); 266 | if (string.IsNullOrEmpty(jsonText)) { 267 | return; 268 | } 269 | 270 | try { 271 | // JSONデータをパース 272 | Dictionary jsonData = ParseJson(jsonText); 273 | 274 | // コマンドタイプを取得 275 | string command = "talk"; // デフォルトは読み上げ 276 | if (jsonData.ContainsKey("command")) { 277 | command = jsonData["command"].ToString().ToLower(); 278 | } 279 | 280 | // コマンドに応じた処理 281 | switch (command) { 282 | case "talk": 283 | // 読み上げコマンド 284 | ProcessTalkCommand(jsonData); 285 | break; 286 | case "stop": 287 | // 停止コマンド 288 | Pub.ClearTalkTasks(); 289 | break; 290 | case "pause": 291 | // 一時停止コマンド 292 | Pub.Pause = true; 293 | break; 294 | case "resume": 295 | // 再開コマンド 296 | Pub.Pause = false; 297 | break; 298 | case "skip": 299 | // スキップコマンド 300 | Pub.SkipTalkTask(); 301 | break; 302 | default: 303 | // 不明なコマンドの場合は読み上げコマンドとして処理 304 | Pub.AddTalkTask("不明なコマンド: " + command + " を読み上げコマンドとして処理します", -1, -1, VoiceType.Default); 305 | ProcessTalkCommand(jsonData); 306 | break; 307 | } 308 | } 309 | catch (Exception ex) { 310 | // エラーログ 311 | Console.WriteLine("JSONパースエラー: " + ex.Message); 312 | } 313 | } 314 | 315 | // 読み上げコマンドの処理 316 | private void ProcessTalkCommand(Dictionary jsonData) { 317 | // 各パラメータを取得 318 | int speed = -1; 319 | int pitch = -1; 320 | int volume = -1; 321 | int voiceType = 0; 322 | string text = string.Empty; 323 | 324 | if (jsonData.ContainsKey("speed")) { 325 | speed = Convert.ToInt32(jsonData["speed"]); 326 | } 327 | if (jsonData.ContainsKey("pitch")) { 328 | pitch = Convert.ToInt32(jsonData["pitch"]); 329 | } 330 | if (jsonData.ContainsKey("volume")) { 331 | volume = Convert.ToInt32(jsonData["volume"]); 332 | } 333 | if (jsonData.ContainsKey("voiceType")) { 334 | voiceType = Convert.ToInt32(jsonData["voiceType"]); 335 | } 336 | if (jsonData.ContainsKey("text")) { 337 | text = jsonData["text"].ToString(); 338 | } 339 | 340 | // VoiceTypeを設定 341 | VoiceType vt = VoiceType.Default; 342 | switch (voiceType) { 343 | case 0: vt = VoiceType.Default; break; 344 | case 1: vt = VoiceType.Female1; break; 345 | case 2: vt = VoiceType.Female2; break; 346 | case 3: vt = VoiceType.Male1; break; 347 | case 4: vt = VoiceType.Male2; break; 348 | case 5: vt = VoiceType.Imd1; break; 349 | case 6: vt = VoiceType.Robot1; break; 350 | case 7: vt = VoiceType.Machine1; break; 351 | case 8: vt = VoiceType.Machine2; break; 352 | default: vt = (VoiceType)voiceType; break; 353 | } 354 | 355 | // 読み上げ 356 | Pub.AddTalkTask(text, pitch, volume, speed, vt); 357 | } 358 | 359 | // WebSocketのデータをデコード 360 | private string DecodeWebSocketData(byte[] buffer, int size) { 361 | try { 362 | if (size < 2) { 363 | return ""; 364 | } 365 | 366 | // 基本情報取得 367 | bool fin = (buffer[0] & 0x80) != 0; // 終了フレームかどうか 368 | int opcode = buffer[0] & 0x0F; // opcode 369 | bool mask = (buffer[1] & 0x80) != 0; // マスクされているかどうか 370 | int len = buffer[1] & 0x7F; // ペイロード長 371 | int pos = 2; // ヘッダサイズ 372 | 373 | // 長さが126以上の場合 374 | if (len == 126) { 375 | len = (buffer[2] << 8) + buffer[3]; 376 | pos = 4; 377 | } 378 | else if (len == 127) { 379 | // 64ビット長の処理 380 | long longLen = 0; 381 | for (int i = 0; i < 8; i++) { 382 | longLen = (longLen << 8) | buffer[2 + i]; 383 | } 384 | 385 | // int.MaxValueを超える場合は処理できない 386 | if (longLen > Int32.MaxValue) { 387 | Console.WriteLine("メッセージが大きすぎます: " + longLen + " バイト"); 388 | return ""; 389 | } 390 | 391 | len = (int)longLen; 392 | pos = 10; // 2 + 8バイト 393 | } 394 | 395 | // マスクキー取得 396 | byte[] maskKey = null; 397 | if (mask) { 398 | maskKey = new byte[4]; 399 | for (int i = 0; i < 4; i++) { 400 | maskKey[i] = buffer[pos + i]; 401 | } 402 | pos += 4; 403 | } 404 | 405 | // ペイロード取得 406 | byte[] payload = new byte[len]; 407 | for (int i = 0; i < len; i++) { 408 | if (pos + i < size) { 409 | payload[i] = buffer[pos + i]; 410 | } 411 | else { 412 | // サイズが足りない 413 | Console.WriteLine("不完全なメッセージ: 必要なサイズ " + len + " バイト, 実際のサイズ " + (size - pos) + " バイト"); 414 | return ""; 415 | } 416 | } 417 | 418 | // マスク解除 419 | if (mask) { 420 | for (int i = 0; i < len; i++) { 421 | payload[i] = (byte)(payload[i] ^ maskKey[i % 4]); 422 | } 423 | } 424 | 425 | // テキストの場合のみ処理 426 | if (opcode == 1) { 427 | return Encoding.UTF8.GetString(payload); 428 | } 429 | } 430 | catch (Exception ex) { 431 | // エラーログ 432 | Console.WriteLine("WebSocketデータのデコードエラー: " + ex.Message); 433 | } 434 | return ""; 435 | } 436 | 437 | // JSONデータをパースする簡易メソッド(.NET 2.0互換) 438 | private Dictionary ParseJson(string jsonText) { 439 | Dictionary result = new Dictionary(); 440 | 441 | try { 442 | // 中括弧を削除 443 | jsonText = jsonText.Trim(); 444 | if (jsonText.StartsWith("{") && jsonText.EndsWith("}")) { 445 | jsonText = jsonText.Substring(1, jsonText.Length - 2); 446 | } else { 447 | return result; 448 | } 449 | 450 | // キーと値のペアを解析 451 | bool inQuotes = false; 452 | bool escaped = false; 453 | StringBuilder keyBuilder = new StringBuilder(); 454 | StringBuilder valueBuilder = new StringBuilder(); 455 | bool buildingKey = true; 456 | 457 | for (int i = 0; i < jsonText.Length; i++) { 458 | char c = jsonText[i]; 459 | 460 | // エスケープシーケンスの処理 461 | if (escaped) { 462 | if (buildingKey) { 463 | keyBuilder.Append(c); 464 | } else { 465 | valueBuilder.Append(c); 466 | } 467 | escaped = false; 468 | continue; 469 | } 470 | 471 | // バックスラッシュはエスケープ文字 472 | if (c == '\\') { 473 | escaped = true; 474 | continue; 475 | } 476 | 477 | // 引用符の処理 478 | if (c == '"') { 479 | inQuotes = !inQuotes; 480 | continue; 481 | } 482 | 483 | // キーと値の区切り 484 | if (!inQuotes && c == ':' && buildingKey) { 485 | buildingKey = false; 486 | continue; 487 | } 488 | 489 | // 次のキーと値のペアへ 490 | if (!inQuotes && c == ',') { 491 | string key = keyBuilder.ToString().Trim().Trim('"'); 492 | string value = valueBuilder.ToString().Trim().Trim('"'); 493 | 494 | // 値を適切な型に変換 495 | if (value == "true") { 496 | result[key] = true; 497 | } else if (value == "false") { 498 | result[key] = false; 499 | } else if (value == "null") { 500 | result[key] = null; 501 | } else { 502 | // 数値かどうか確認 503 | int intValue; 504 | if (int.TryParse(value, out intValue)) { 505 | result[key] = intValue; 506 | } else { 507 | result[key] = value; 508 | } 509 | } 510 | 511 | keyBuilder.Length = 0; 512 | valueBuilder.Length = 0; 513 | buildingKey = true; 514 | continue; 515 | } 516 | 517 | // 文字を追加 518 | if (buildingKey) { 519 | keyBuilder.Append(c); 520 | } else { 521 | valueBuilder.Append(c); 522 | } 523 | } 524 | 525 | // 最後のキーと値のペアを処理 526 | if (keyBuilder.Length > 0) { 527 | string key = keyBuilder.ToString().Trim().Trim('"'); 528 | string value = valueBuilder.ToString().Trim().Trim('"'); 529 | 530 | // 値を適切な型に変換 531 | if (value == "true") { 532 | result[key] = true; 533 | } else if (value == "false") { 534 | result[key] = false; 535 | } else if (value == "null") { 536 | result[key] = null; 537 | } else { 538 | // 数値かどうか確認 539 | int intValue; 540 | if (int.TryParse(value, out intValue)) { 541 | result[key] = intValue; 542 | } else { 543 | result[key] = value; 544 | } 545 | } 546 | } 547 | } catch (Exception ex) { 548 | Console.WriteLine("JSONパースエラー: " + ex.Message); 549 | } 550 | 551 | return result; 552 | } 553 | } 554 | 555 | 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /Plugin_Socket/Plugin_WebSocket.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.50727 7 | 2.0 8 | {169CA507-E901-438D-A486-059DA01F9287} 9 | Library 10 | Properties 11 | Plugin_WebSocket 12 | Plugin_WebSocket 13 | 14 | 15 | v2.0 16 | 17 | 18 | 2.0 19 | 20 | 21 | publish\ 22 | true 23 | Disk 24 | false 25 | Foreground 26 | 7 27 | Days 28 | false 29 | false 30 | true 31 | 0 32 | 1.0.0.%2a 33 | false 34 | false 35 | true 36 | 37 | 38 | true 39 | full 40 | false 41 | bin\Debug\ 42 | TRACE;DEBUG 43 | prompt 44 | 4 45 | 46 | 47 | none 48 | true 49 | bin\Release\ 50 | 51 | 52 | prompt 53 | 4 54 | false 55 | 56 | 57 | 58 | C:\tools\Accesary\棒読みちゃん\BouyomiChan.exe 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | True 72 | True 73 | Resources.resx 74 | 75 | 76 | 77 | 78 | Designer 79 | ResXFileCodeGenerator 80 | Resources.Designer.cs 81 | 82 | 83 | 84 | 85 | False 86 | Microsoft .NET Framework 4 %28x86 および x64%29 87 | true 88 | 89 | 90 | False 91 | .NET Framework 3.5 SP1 Client Profile 92 | false 93 | 94 | 95 | False 96 | .NET Framework 3.5 SP1 97 | false 98 | 99 | 100 | False 101 | Windows インストーラー 3.1 102 | true 103 | 104 | 105 | 106 | 107 | 108 | 109 | 116 | -------------------------------------------------------------------------------- /Plugin_Socket/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // このコードはツールによって生成されました。 4 | // ランタイム バージョン:4.0.30319.34209 5 | // 6 | // このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 7 | // コードが再生成されるときに損失したりします。 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Plugin_WebSocket.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 17 | /// 18 | // このクラスは StronglyTypedResourceBuilder クラスが ResGen 19 | // または Visual Studio のようなツールを使用して自動生成されました。 20 | // メンバーを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に 21 | // ResGen を実行し直すか、または VS プロジェクトをビルドし直します。 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// このクラスで使用されているキャッシュされた ResourceManager インスタンスを返します。 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Plugin_WebSocket.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// 厳密に型指定されたこのリソース クラスを使用して、すべての検索リソースに対し、 51 | /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Plugin_Socket/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Plugin_Socket/SettingFormData.cs: -------------------------------------------------------------------------------- 1 | using FNF.XmlSerializerSetting; 2 | 3 | namespace Plugin_WebSocket { 4 | /// 5 | /// 設定画面を管理するクラス 6 | /// 7 | public class SettingFormData : ISettingFormData { 8 | private PluginSettings _settings = null; 9 | public SettingPropertyGrid _propertyGrid = null; 10 | 11 | // ISettingFormDataの実装 12 | public string Title { get { return "WebSocketサーバー設定"; } } 13 | public bool ExpandAll { get { return false; } } 14 | public SettingsBase Setting { get { return _settings; } } 15 | 16 | /// 17 | /// コンストラクタ 18 | /// 19 | /// 設定オブジェクト 20 | public SettingFormData(PluginSettings settings) { 21 | _settings = settings; 22 | _propertyGrid = new SettingPropertyGrid(_settings); 23 | 24 | } 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /Plugin_Socket/SettingPropertyGrid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using System.ComponentModel; 4 | using FNF.XmlSerializerSetting; 5 | 6 | namespace Plugin_WebSocket { 7 | /// 8 | /// 設定用プロパティグリッド 9 | /// 10 | public class SettingPropertyGrid : ISettingPropertyGrid { 11 | private PluginSettings _settings; 12 | 13 | // コンストラクタ 14 | public SettingPropertyGrid(PluginSettings setting) { 15 | _settings = setting; 16 | } 17 | 18 | //シート名 19 | public string GetName() { return "WebSocketサーバー設定"; } 20 | 21 | // ポート番号 22 | [Category("サーバー設定")] 23 | [DisplayName("ポート番号")] 24 | [Description("WebSocketサーバーが使用するポート番号を指定します。(1024~65535)")] 25 | [DefaultValue(55000)] 26 | public int Port { 27 | get { return _settings.Port; } 28 | set { _settings.Port = value; } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Plugin_WebSocket.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.10.34928.147 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin_WebSocket", "Plugin_Socket\Plugin_WebSocket.csproj", "{169CA507-E901-438D-A486-059DA01F9287}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {169CA507-E901-438D-A486-059DA01F9287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {169CA507-E901-438D-A486-059DA01F9287}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {169CA507-E901-438D-A486-059DA01F9287}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {169CA507-E901-438D-A486-059DA01F9287}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {6F6EC3B6-FF87-4683-BCC3-0989E8EAC61C} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 棒読みちゃん用のWebSocket受付プラグイン 2 | --------------------------------------- 3 | 4 | ビルド 5 | ------ 6 | 7 | コンパイル前に参照設定ですでに設定されているBouyomichanを削除し、 8 | 手持ちのBouyomiChan.exeを指定して追加してください。 9 | 10 | 11 | インストール 12 | ------------ 13 | コンパイルして作成したPlugin_WebSocket.dllをBouyomichan.exeと同じフォルダに入れるだけです。 14 | 15 | 棒読みちゃんを起動し、その他タブのプラグインでWebSocketサーバーにチェックを入れて有効化してください。 16 | 17 | 18 | JavaScriptからプラグインへ喋らせる方法 19 | --------------------------------------- 20 | 21 | ### JSON形式での送信方法 22 | ```javascript 23 | // WebSocketを使用して棒読みちゃんに接続 24 | var socket = new WebSocket('ws://localhost:55000/'); 25 | 26 | // 読み上げリクエスト 27 | socket.onopen = function() { 28 | // 読み上げコマンド 29 | var talkData = { 30 | command: "talk", 31 | speed: 100, // 速度50-200。-1を指定すると本体設定 32 | pitch: 100, // ピッチ50-200。-1を指定すると本体設定 33 | volume: 100, // ボリューム0-100。-1を指定すると本体設定 34 | voiceType: 0, // 声質(0.本体設定/1.女性1/2.女性2/3.男性1/4.男性2/5.中性/6.ロボット/7.機械1/8.機械2) 35 | text: "こんにちは、棒読みちゃんです" 36 | }; 37 | 38 | socket.send(JSON.stringify(talkData)); 39 | }; 40 | ``` 41 | 42 | ### 制御コマンド 43 | JSON形式では、読み上げ以外にも以下の制御コマンドが使用できます: 44 | 45 | ```javascript 46 | // 読み上げ停止 47 | socket.send(JSON.stringify({ command: "stop" })); 48 | 49 | // 読み上げ一時停止 50 | socket.send(JSON.stringify({ command: "pause" })); 51 | 52 | // 読み上げ再開 53 | socket.send(JSON.stringify({ command: "resume" })); 54 | 55 | // 現在の読み上げをスキップ 56 | socket.send(JSON.stringify({ command: "skip" })); 57 | ``` 58 | -------------------------------------------------------------------------------- /bouyomi_api.md: -------------------------------------------------------------------------------- 1 | # 棒読みちゃん プラグイン開発 API リファレンス 2 | このドキュメントは棒読みちゃんプラグイン開発で使用される主要なクラスとインターフェースの定義をまとめたものです。.NET Framework 2.0 に準拠しています。 3 | 4 | ## 目次 5 | 6 | - プラグイン開発の基本 7 | - FNF.BouyomiChanApp 名前空間 8 | - FNF.XmlSerializerSetting 名前空間 9 | - FNF.Utility 名前空間 10 | - FNF.Controls 名前空間 11 | 12 | ## プラグイン開発の基本 13 | 14 | ### プラグインの命名規則 15 | - プラグインのファイル名は「Plugin_.dll」という形式にする必要があります(例:「Plugin_WebSocket.dll」) 16 | - 設定ファイルは「Plugin_.config」または「.setting」という形式で保存されます 17 | 18 | ### プラグイン開発の基本手順 19 | 20 | 1. `IPlugin` インターフェースを実装したクラスを作成する 21 | 2. 設定が必要な場合は `SettingsBase` を継承した設定クラスを作成する 22 | 3. 設定画面が必要な場合は `ISettingFormData` と `ISettingPropertyGrid` を実装したクラスを作成する 23 | 4. `Begin()` メソッドで初期化処理を行い、`End()` メソッドで終了処理を行う 24 | 5. 読み上げ機能を使用する場合は `Pub.AddTalkTask()` メソッドを呼び出す 25 | 26 | ### 開発上の制約 27 | 28 | - .NET Framework 2.0 の機能のみ使用可能 29 | - C# 2.0 の文法のみ使用可能 30 | - 以下の機能は使用不可: 31 | - auto-implemented プロパティ (get; set;) 32 | - var キーワード 33 | - LINQ 34 | - ラムダ式 35 | - 文字列補間($"...") 36 | - null 条件演算子(?.) 37 | - null 合体演算子(??) 38 | 39 | ## FNF.BouyomiChanApp 名前空間 40 | 41 | ### IPlugin インターフェース 42 | 43 | プラグインの基本インターフェース。すべてのプラグインはこのインターフェースを実装する必要があります。 44 | 45 | ```csharp 46 | public interface IPlugin { 47 | string Name { get; } // プラグインの名前 48 | string Version { get; } // プラグインのバージョン 49 | string Caption { get; } // プラグインの説明 50 | ISettingFormData SettingFormData { get; } // プラグインの設定画面データ 51 | void Begin(); // プラグイン開始時処理 52 | void End(); // プラグイン終了時処理 53 | } 54 | ``` 55 | 56 | ### Pub クラス 57 | 棒読みちゃんの主要な機能を提供する静的クラス。 58 | 59 | ```csharp 60 | public static class Pub { 61 | // 読み上げタスクを追加 62 | public static void AddTalkTask(string text, int speaker, int speed, VoiceType voiceType); 63 | public static void AddTalkTask(string text, int speaker, int volume, int speed, VoiceType voiceType); 64 | 65 | // 読み上げを停止 66 | public static void Stop(); 67 | 68 | // 設定データ 69 | public static Settings Data { get; } 70 | 71 | // アプリケーション終了 72 | public static void ExitApplication(bool bReboot, bool bCloseLog); 73 | 74 | // メインフォーム 75 | public static FormMain FormMain { get; } 76 | } 77 | ``` 78 | 79 | ### Base クラス 80 | 棒読みちゃんの基本情報を提供する静的クラス。 81 | 82 | ```csharp 83 | public static class Base { 84 | public static string CallAsmPath { get; } // 呼び出し元アセンブリのパス 85 | public static string CallAsmName { get; } // 呼び出し元アセンブリの名前 86 | } 87 | ``` 88 | 89 | ### VoiceType 列挙型 90 | 音声の種類を定義する列挙型。 91 | 92 | ```csharp 93 | public enum VoiceType { 94 | Default, // デフォルト 95 | Female1, // 女性1 96 | Female2, // 女性2 97 | Male1, // 男性1 98 | Male2, // 男性2 99 | Imd1, // 中性 100 | Robot1, // ロボット 101 | Machine1, // 機械1 102 | Machine2 // 機械2 103 | } 104 | ``` 105 | 106 | ### FormMain クラス 107 | 棒読みちゃんのメインフォームクラス。 108 | 109 | ```csharp 110 | public class FormMain : Form { 111 | // プラグインの読み込み 112 | public void LoadPlugins(); 113 | 114 | // プラグインの解放 115 | public void UnloadPlugins(); 116 | 117 | // 読み上げタスクを追加 118 | public void AddTalkTask(string text, int speaker, int volume, int speed, VoiceType voiceType); 119 | 120 | // 読み上げを停止 121 | public void Stop(); 122 | } 123 | ``` 124 | 125 | ### BouyomiChanHttpServer クラス 126 | 棒読みちゃんのHTTPサーバークラス。 127 | 128 | ```csharp 129 | public class BouyomiChanHttpServer : IDisposable { 130 | // コンストラクタ 131 | public BouyomiChanHttpServer(int port, FormMain owner); 132 | 133 | // ポート番号 134 | public int Port { get; } 135 | 136 | // 実行中かどうか 137 | public bool IsRunning { get; } 138 | 139 | // リソースの解放 140 | public void Dispose(); 141 | } 142 | ``` 143 | 144 | ### BouyomiChanTcpServer クラス 145 | 棒読みちゃんのTCPサーバークラス。 146 | 147 | ```csharp 148 | public class BouyomiChanTcpServer : IDisposable { 149 | // コンストラクタ 150 | public BouyomiChanTcpServer(int port, FormMain owner); 151 | 152 | // ポート番号 153 | public int Port { get; } 154 | 155 | // 実行中かどうか 156 | public bool IsRunning { get; } 157 | 158 | // リソースの解放 159 | public void Dispose(); 160 | } 161 | ``` 162 | 163 | ## FNF.XmlSerializerSetting 名前空間 164 | 165 | ### SettingsBase クラス 166 | 設定ファイルの基本クラス。XMLシリアライズを使用して設定を保存・読み込みます。 167 | 168 | ```csharp 169 | public class SettingsBase { 170 | // 設定の読み込み 171 | public void Load(string path); 172 | 173 | // 設定の保存 174 | public void Save(string path); 175 | 176 | // 設定読み込み時に呼ばれる(オーバーライド用) 177 | public virtual void ReadSettings(); 178 | 179 | // 設定保存時に呼ばれる(オーバーライド用) 180 | public virtual void WriteSettings(); 181 | } 182 | ``` 183 | 184 | ### ISettingFormData インターフェース 185 | 設定画面のデータを管理するインターフェース。 186 | 187 | ```csharp 188 | public interface ISettingFormData { 189 | string Title { get; } // 設定画面のタイトル 190 | bool ExpandAll { get; } // すべての項目を展開するかどうか 191 | SettingsBase Setting { get; } // 設定オブジェクト 192 | 193 | // 設定画面のコントロールを作成 194 | void CreateControls(Form form); 195 | 196 | // リソースの解放 197 | void Dispose(); 198 | } 199 | ``` 200 | 201 | ### ISettingPropertyGrid インターフェース 202 | 設定プロパティグリッドのインターフェース。 203 | 204 | ```csharp 205 | public interface ISettingPropertyGrid { 206 | // シート名を取得 207 | string GetName(); 208 | } 209 | ``` 210 | 211 | ### XSSBase クラス 212 | 設定の基本クラス。ジェネリック型を使用して様々な設定を管理します。 213 | 214 | ```csharp 215 | public class XSSBase { 216 | // 設定の読み込み 217 | public void ReadSetting(T obj); 218 | 219 | // 設定の保存 220 | public void WriteSetting(T obj); 221 | } 222 | ``` 223 | 224 | ### XSSForm クラス 225 | 設定の基本クラス。ジェネリック型を使用して様々な設定を管理します。 226 | 227 | ```csharp 228 | public class XSSForm : XSSBase
{ 229 | public int Width { get; set; } 230 | public int Height { get; set; } 231 | public int Left { get; set; } 232 | public int Top { get; set; } 233 | public FormWindowState WindowState { get; set; } 234 | 235 | // 設定の読み込み 236 | public void ReadSetting(Form form); 237 | 238 | // 設定の保存 239 | public void WriteSetting(Form form); 240 | } 241 | ``` 242 | 243 | ### XSSDataGridView クラス 244 | DataGridViewの設定を管理するクラス。 245 | 246 | ```csharp 247 | public class XSSDataGridView : XSSBase { 248 | public class DgvColumns { 249 | public class DgvColumn { 250 | public int DisplayIndex { get; set; } 251 | public int Width { get; set; } 252 | } 253 | 254 | public DgvColumn[] Items { get; set; } 255 | } 256 | 257 | public DgvColumns ColumnItems { get; set; } 258 | 259 | // 設定の読み込み 260 | public void ReadSetting(DataGridView dgv); 261 | 262 | // 設定の保存 263 | public void WriteSetting(DataGridView dgv); 264 | } 265 | ``` 266 | 267 | ### XSSListView クラス 268 | ListViewの設定を管理するクラス。 269 | 270 | ```csharp 271 | public class XSSListView : XSSBase { 272 | public class Columns { 273 | public class Column { 274 | public int DisplayIndex { get; set; } 275 | public int Width { get; set; } 276 | } 277 | 278 | public Column[] Items { get; set; } 279 | } 280 | 281 | public Columns ColumnItems { get; set; } 282 | 283 | // 設定の読み込み 284 | public void ReadSetting(ListView lv); 285 | 286 | // 設定の保存 287 | public void WriteSetting(ListView lv); 288 | } 289 | ``` 290 | 291 | ## FNF.Utility 名前空間 292 | 293 | ### BouyomiChan クラス 294 | 棒読みちゃんの主要な機能を提供するクラス。 295 | 296 | ```csharp 297 | public class BouyomiChan { 298 | // 読み上げタスクを追加 299 | public void AddTalkTask(string text, int speaker, int volume, int speed, VoiceType voiceType); 300 | 301 | // 読み上げを停止 302 | public void Stop(); 303 | 304 | // 読み上げタスク開始イベント 305 | public event EventHandler TalkTaskStarted; 306 | 307 | // 読み上げタスク開始イベント引数 308 | public class TalkTaskStartedEventArgs : EventArgs { 309 | public int TaskId { get; } 310 | public string Text { get; } 311 | public int Speaker { get; } 312 | public int Volume { get; } 313 | public int Speed { get; } 314 | public VoiceType VoiceType { get; } 315 | } 316 | } 317 | ``` 318 | 319 | ### BouyomiChanRemoting クラス 320 | リモート操作用のクラス。 321 | 322 | ```csharp 323 | public class BouyomiChanRemoting { 324 | // 読み上げタスクを追加 325 | public void AddTalkTask(string text, int speaker, int volume, int speed, VoiceType voiceType); 326 | 327 | // 読み上げを停止 328 | public void Stop(); 329 | } 330 | ``` 331 | 332 | ### IpcRemotingServer クラス 333 | IPCリモーティングサーバークラス。 334 | 335 | ```csharp 336 | public class IpcRemotingServer { 337 | // サーバーを開始 338 | public void Start(string channelName, object obj); 339 | 340 | // サーバーを停止 341 | public void Stop(); 342 | } 343 | ``` 344 | 345 | ### MciSound クラス 346 | MCIを使用したサウンド再生クラス。 347 | 348 | ```csharp 349 | public class MciSound { 350 | // サウンドを開く 351 | public bool Open(string fileName); 352 | 353 | // サウンドを再生 354 | public bool Play(); 355 | 356 | // サウンドを停止 357 | public bool Stop(); 358 | 359 | // サウンドを閉じる 360 | public bool Close(); 361 | } 362 | ``` 363 | 364 | ### MultiReplacerRegex クラス 365 | 正規表現による複数置換を行うクラス。 366 | 367 | ```csharp 368 | public class MultiReplacerRegex { 369 | // 置換を追加 370 | public void Add(string pattern, string replacement); 371 | 372 | // 置換を実行 373 | public string Replace(string input); 374 | } 375 | ``` 376 | 377 | ## FNF.Controls 名前空間 378 | 379 | ### KeyValueList クラス 380 | キーと値のリストを表示するコントロール。 381 | 382 | ```csharp 383 | public class KeyValueList : Control { 384 | // アイテムを追加 385 | public KeyValueListItem Add(string key, string value); 386 | 387 | // アイテムを取得 388 | public KeyValueListItem GetItem(string key); 389 | 390 | // アイテムをクリア 391 | public void Clear(); 392 | 393 | // ダブルクリックイベント 394 | public event EventHandler DoubleClick; 395 | } 396 | ``` 397 | 398 | ### KeyValueListItem クラス 399 | KeyValueListのアイテムクラス。 400 | 401 | ```csharp 402 | public class KeyValueListItem { 403 | // キー 404 | public string Key { get; set; } 405 | 406 | // 値 407 | public string Value { get; set; } 408 | 409 | // 表示テキスト 410 | public string Text { get; set; } 411 | 412 | // タグ 413 | public object Tag { get; set; } 414 | } 415 | ``` 416 | 417 | ### KeyValueListUpdater クラス 418 | KeyValueListを更新するためのクラス。 419 | 420 | ```csharp 421 | public class KeyValueListUpdater { 422 | // アイテムを更新 423 | public void Update(string key, string value); 424 | 425 | // アイテムをクリア 426 | public void Clear(); 427 | } 428 | ``` 429 | 430 | 431 | ### WebBrowserForm クラス 432 | Webブラウザを表示するフォーム。 433 | 434 | ```csharp 435 | public class WebBrowserForm : Form { 436 | // URLを開く 437 | public void Navigate(string url); 438 | 439 | // HTMLを表示 440 | public void NavigateToString(string html); 441 | } 442 | ``` 443 | 444 | # プラグイン実装例 445 | 以下は基本的なプラグインの実装例です: 446 | 447 | ```csharp 448 | using System; 449 | using System.IO; 450 | using FNF.BouyomiChanApp; 451 | using FNF.XmlSerializerSetting; 452 | 453 | namespace Plugin_Sample { 454 | public class Plugin_Sample : IPlugin { 455 | private string _path = Base.CallAsmPath + Base.CallAsmName + ".setting"; 456 | private SampleSettings _settings; 457 | private SettingFormData _settingFormData; 458 | 459 | // IPluginメンバの実装 460 | public string Name { get { return "サンプルプラグイン"; } } 461 | public string Version { get { return "1.0.0"; } } 462 | public string Caption { get { return "サンプルプラグインです。"; } } 463 | public ISettingFormData SettingFormData { get { return _settingFormData; } } 464 | 465 | // プラグイン開始時処理 466 | public void Begin() { 467 | // 設定ファイルを読み込む 468 | _settings = new SampleSettings(); 469 | if (File.Exists(_path)) _settings.Load(_path); 470 | 471 | // 設定画面の初期化 472 | _settingFormData = new SettingFormData(_settings); 473 | 474 | // 初期化処理 475 | Pub.AddTalkTask("サンプルプラグインを開始しました。", -1, -1, VoiceType.Default); 476 | } 477 | 478 | // プラグイン終了時処理 479 | public void End() { 480 | // 終了処理 481 | _settings.Save(_path); 482 | Pub.AddTalkTask("サンプルプラグインを終了しました。", -1, -1, VoiceType.Default); 483 | } 484 | } 485 | 486 | // 設定クラス 487 | public class SampleSettings : SettingsBase { 488 | public int SampleValue; 489 | 490 | public SampleSettings() { 491 | SampleValue = 100; // デフォルト値 492 | } 493 | 494 | public override void ReadSettings() { 495 | // 必要に応じて追加の処理 496 | } 497 | 498 | public override void WriteSettings() { 499 | // 必要に応じて追加の処理 500 | } 501 | } 502 | 503 | // 設定画面データクラス 504 | public class SettingFormData : ISettingFormData { 505 | private SampleSettings _settings = null; 506 | public SettingPropertyGrid _propertyGrid = null; 507 | 508 | public string Title { get { return "サンプル設定"; } } 509 | public bool ExpandAll { get { return false; } } 510 | public SettingsBase Setting { get { return _settings; } } 511 | 512 | public SettingFormData(SampleSettings settings) { 513 | _settings = settings; 514 | _propertyGrid = new SettingPropertyGrid(_settings); 515 | } 516 | 517 | public void CreateControls(Form form) { 518 | // 必要に応じて追加のコントロールを作成 519 | } 520 | 521 | public void Dispose() { 522 | // リソースの解放が必要な場合はここで行う 523 | } 524 | } 525 | 526 | // 設定プロパティグリッドクラス 527 | public class SettingPropertyGrid : ISettingPropertyGrid { 528 | private SampleSettings _settings; 529 | 530 | public SettingPropertyGrid(SampleSettings setting) { 531 | _settings = setting; 532 | } 533 | 534 | public string GetName() { return "サンプル設定"; } 535 | 536 | [Category("基本設定")] 537 | [DisplayName("サンプル値")] 538 | [Description("サンプルの値を指定します。")] 539 | [DefaultValue(100)] 540 | public int SampleValue { 541 | get { return _settings.SampleValue; } 542 | set { _settings.SampleValue = value; } 543 | } 544 | } 545 | } 546 | ``` 547 | 548 | このプラグインは、棒読みちゃんの設定画面にサンプルの値を表示し、その値を変更することができます。 549 | 550 | 551 | --------------------------------------------------------------------------------