From 2e9d9a11c54cca00bd3b23fa0ed511ee37cd0258 Mon Sep 17 00:00:00 2001 From: Yothuba <39334911+Yothuba3@users.noreply.github.com> Date: Sun, 27 Sep 2026 10:37:55 +0900 Subject: [PATCH] =?UTF-8?q?Windows=E3=81=A7=E8=B5=B7=E5=8B=95=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E3=81=A0=E3=81=A3=E3=81=9Fv3=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=80=81CI(ci-v3.yml)=E3=81=A8=E9=80=9A?= =?UTF-8?q?=E3=81=97=E7=A2=BA=E8=AA=8D=E3=82=B9=E3=82=AF=E3=83=AA=E3=83=97?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - v3.0.0のWindows版はapp.manifest不足で起動直後に落ちており、公開物が使えない状態だったため修正 - WebView2データ配置、デバイス列挙のキャッシュ化、生成器の設定反映などWindows実機検証(2026-09-27)で見つかった不具合を併せて修正 - CIとリリースワークフローにWindows起動smokeを追加し、同種の不具合を再発防止 --- .github/workflows/ci-v3.yml | 98 ++++++++++ .github/workflows/release-v3.yml | 19 ++ README.md | 2 +- docs/v3/README.md | 38 +++- docs/v3/bridge-protocol.md | 2 +- docs/v3/verification-2026-09-27-windows.md | 52 +++++ src/TimecodeBridge.Host/App.cs | 11 +- .../Bridge/CommandRouter.cs | 34 +++- src/TimecodeBridge.Host/Bridge/HostState.cs | 25 ++- src/TimecodeBridge.Host/MainWindow.cs | 6 + src/TimecodeBridge.Host/Services/AppPaths.cs | 9 + .../Services/CachedAudioDeviceService.cs | 67 +++++++ .../TimecodeBridge.Host.csproj | 2 + src/TimecodeBridge.Host/app.manifest | 13 ++ src/TimecodeBridge.Web/dist/assets/app.js | 14 +- src/TimecodeBridge.Web/dist/styles/m3.css | 1 + .../src/components/receive-panel.tsx | 8 +- src/TimecodeBridge.Web/src/styles/m3.css | 1 + .../tests/receive-panel.test.ts | 15 +- .../CommandRouterTests.cs | 99 +++++++++- .../CueEditingCommandTests.cs | 4 +- tests/TimecodeBridge.Host.Tests/Fakes.cs | 14 +- .../TimecodeBridge.Host.Tests/HostHarness.cs | 13 +- .../ProtocolTests.cs | 5 +- .../RecentProjectsStoreTests.cs | 2 +- tools/tcb3/smoke-windows.ps1 | 178 ++++++++++++++++++ 26 files changed, 691 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/ci-v3.yml create mode 100644 docs/v3/verification-2026-09-27-windows.md create mode 100644 src/TimecodeBridge.Host/Services/CachedAudioDeviceService.cs create mode 100644 src/TimecodeBridge.Host/app.manifest create mode 100644 tools/tcb3/smoke-windows.ps1 diff --git a/.github/workflows/ci-v3.yml b/.github/workflows/ci-v3.yml new file mode 100644 index 0000000..7e1d191 --- /dev/null +++ b/.github/workflows/ci-v3.yml @@ -0,0 +1,98 @@ +name: CI v3 + +# v3(TimecodeBridge3)の Windows 検証。v3.0.0 の Windows 版は起動直後に落ちていた(app.manifest が無く WebView を埋め込めない)が、 +# タグを打つまで Windows で一度も起動しない流れだったため気づけなかった。PR と feature/v3 への push で、 +# ビルド・テスト・発行物の起動 smoke・インストーラーで入れたものの起動 smoke まで通す。 +# (GitHub の Windows ランナーには音声デバイスが無いので LTC の受信は確かめない。LTC は手元で smoke-windows.ps1 -LoopbackDevice) +on: + push: + branches: [feature/v3] + pull_request: + branches: [feature/v3] + workflow_dispatch: + +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: src/TimecodeBridge.Web/package-lock.json + - uses: ilammy/msvc-dev-cmd@v1 + + # dist の差分検査は release-v3 の macOS ジョブで行う(Windows は改行コードの自動変換で偽の差分が出る) + - name: Build Web UI + working-directory: src/TimecodeBridge.Web + shell: bash + run: | + npm ci + npm run typecheck + npm test + npm run build + + - name: Build libltc.dll + shell: cmd + run: native\libltc\build-windows.cmd native\libltc\out + + - name: Test (Ltc / Host) + shell: bash + run: | + dotnet test tests/TimecodeBridge.Ltc.Tests + dotnet test tests/TimecodeBridge.Host.Tests + + - name: Publish (win-x64) + shell: bash + run: | + set -euo pipefail + dotnet publish src/TimecodeBridge.Host/TimecodeBridge.Host.csproj \ + -c Release --self-contained -r win-x64 -o publish + test -f publish/libltc.dll + test -f publish/web/index.html + cp THIRD_PARTY_NOTICES.md publish/ + + - name: Ensure WebView2 runtime + shell: pwsh + run: | + $id = '{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' + $pv = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\$id", "HKCU:\Software\Microsoft\EdgeUpdate\Clients\$id" | + ForEach-Object { (Get-ItemProperty $_ -ErrorAction SilentlyContinue).pv } | Where-Object { $_ -and $_ -ne '0.0.0.0' } | Select-Object -First 1 + if ($pv) { "WebView2 runtime $pv"; exit 0 } + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:RUNNER_TEMP\MicrosoftEdgeWebview2Setup.exe" + Start-Process "$env:RUNNER_TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList '/silent', '/install' -Wait + + - name: Smoke (publish) + shell: pwsh + run: pwsh tools/tcb3/smoke-windows.ps1 -Exe publish\TimecodeBridge3.exe -RunDir "$env:RUNNER_TEMP\smoke-publish" + + - name: Build installer + shell: pwsh + run: | + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile installer/MicrosoftEdgeWebview2Setup.exe + iscc /DAppVersion=0.0.0 installer\TimecodeBridge3.iss + + # 利用者と同じくインストーラーで入れ(ユーザー単位・サイレント)、入ったものを起動して確かめる + - name: Install and smoke (installer) + shell: pwsh + run: | + $dir = Join-Path $env:RUNNER_TEMP 'tcb3-installed' + $log = Join-Path $env:RUNNER_TEMP 'install.log' + $setup = (Get-Item installer-output\TimecodeBridge3-Setup-*.exe).FullName + $p = Start-Process $setup -ArgumentList '/VERYSILENT', '/SUPPRESSMSGBOXES', '/NORESTART', "/DIR=$dir", "/LOG=$log" -Wait -PassThru + if ($p.ExitCode -ne 0) { Get-Content $log -Tail 40; throw "installer exit $($p.ExitCode)" } + pwsh tools/tcb3/smoke-windows.ps1 -Exe (Join-Path $dir 'TimecodeBridge3.exe') -RunDir "$env:RUNNER_TEMP\smoke-installed" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: windows-smoke + path: | + ${{ runner.temp }}/smoke-publish/ + ${{ runner.temp }}/smoke-installed/ + ${{ runner.temp }}/install.log + if-no-files-found: ignore diff --git a/.github/workflows/release-v3.yml b/.github/workflows/release-v3.yml index e6fb858..21c9484 100644 --- a/.github/workflows/release-v3.yml +++ b/.github/workflows/release-v3.yml @@ -101,6 +101,25 @@ jobs: test -f publish/libltc.dll test -f publish/web/index.html cp THIRD_PARTY_NOTICES.md publish/ + + # 起動しないビルドを出さない(v3.0.0 の Windows 版は起動直後に落ちていた)。詳しい確認は ci-v3.yml + - name: Ensure WebView2 runtime + shell: pwsh + run: | + $id = '{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' + $pv = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\$id", "HKCU:\Software\Microsoft\EdgeUpdate\Clients\$id" | + ForEach-Object { (Get-ItemProperty $_ -ErrorAction SilentlyContinue).pv } | Where-Object { $_ -and $_ -ne '0.0.0.0' } | Select-Object -First 1 + if ($pv) { "WebView2 runtime $pv"; exit 0 } + Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:RUNNER_TEMP\MicrosoftEdgeWebview2Setup.exe" + Start-Process "$env:RUNNER_TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList '/silent', '/install' -Wait + + - name: Smoke (publish) + shell: pwsh + run: pwsh tools/tcb3/smoke-windows.ps1 -Exe publish\TimecodeBridge3.exe -RunDir "$env:RUNNER_TEMP\smoke-publish" + + - name: Zip (win-x64) + shell: bash + run: | cd publish && 7z a "../TimecodeBridge3-${{ github.ref_name }}-win-x64.zip" . # WebView2 Evergreen ブートストラッパーを取得し、未導入 PC で実行するインストーラーを作る diff --git a/README.md b/README.md index f55a42b..5375116 100644 --- a/README.md +++ b/README.md @@ -38,4 +38,4 @@ v1(Windows / WPF)と v2(macOS / Avalonia)の後継です。プロジェクトフ ## ライセンス -本体は [LICENSE](LICENSE) のとおり。LTC の復号・生成に使う libltc は LGPL-2.1 で、動的リンクのまま同梱しています。同梱物の一覧は [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。 +本体は [LICENSE](LICENSE) のとおり。LTC の復号に使う libltc は LGPL-3.0-or-later で、動的リンクのまま同梱しています。同梱物の一覧は [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。 diff --git a/docs/v3/README.md b/docs/v3/README.md index 22ee6b8..33728a9 100644 --- a/docs/v3/README.md +++ b/docs/v3/README.md @@ -12,6 +12,21 @@ dotnet build src/TimecodeBridge.Host/TimecodeBridge.Host.csproj `dotnet build` は `native/libltc/out/libltc.dylib` と `src/TimecodeBridge.Web/dist` を実行ファイルの隣(`libltc.dylib`, `web/`)へコピーします。どちらかが無いとビルド時に警告が出て、起動時に見つからない旨のエラーになります。 +## 初回セットアップ(Windows) + +Visual Studio 2022(C++ によるデスクトップ開発)、.NET 8 以降の SDK、Node.js 22 以降が要ります。libltc は Developer Command Prompt(vcvars64)で `cl` を使ってビルドします。 + +```powershell +cmd /c '"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" && native\libltc\build-windows.cmd native\libltc\out' +cd src\TimecodeBridge.Web; npm ci; npm run build; cd ..\.. +dotnet build src\TimecodeBridge.Host\TimecodeBridge.Host.csproj # → src\TimecodeBridge.Host\bin\Debug\net8.0\TimecodeBridge3.exe +``` + +- 日本語 Windows では libltc のビルドで C4819 の警告が出ますが、`ltc.h` のコメント内の文字(`ß` `±`)なので無害です。 +- Windows で `npm run build` すると `dist/` に差分が出ることがありますが、改行コード(CRLF/LF)だけです。コミットしないでください(`git checkout -- src/TimecodeBridge.Web/dist`)。 +- `src/TimecodeBridge.Host/app.manifest` は消さないでください。対応 OS の宣言が無いと、Avalonia が WebView 用の子ウィンドウを作れず起動直後に落ちます(v3.0.0 の Windows 版で発生)。 +- WebView2 のユーザーデータは `%LOCALAPPDATA%\TimecodeBridge3\WebView2`(`TIMECODEBRIDGE_DATA_DIR` があればその下の `WebView2`)に置きます。既定の「実行ファイルの隣」は Program Files に入れると書けず、起動直後に `E_ACCESSDENIED` で落ちます。 + ## 起動 ```sh @@ -22,13 +37,13 @@ dotnet build src/TimecodeBridge.Host/TimecodeBridge.Host.csproj | 変数 | 内容 | |---|---| -| `TIMECODEBRIDGE_SELFTEST_OUTPUT=<出力デバイス名の一部>` | 同一プロセス内で LTC(30fps, 01:00:00:00 から)を生成してその出力へ流す | +| `TIMECODEBRIDGE_SELFTEST_OUTPUT=<出力デバイス名の一部>` | 同一プロセス内で LTC(30fps, 01:00:00:00 から)を生成してその出力へ流す(macOS のみ。Windows は下記の smoke-windows.ps1 で 2 つ目の Host に生成させる) | | `TIMECODEBRIDGE_AUTOSTART_INPUT=<入力デバイス名の一部>` | 接続直後にそのデバイスで LTC 受信を始める | | `TIMECODEBRIDGE_BRIDGE_TRACE=1` / `2` | Web との送受信・受信状態を標準出力へ(2 は毎フレームの時計も) | | `TIMECODEBRIDGE_WEB_DIST=` | 同梱の web/ ではなくそのディレクトリの index.html を開く(Web 開発中に便利) | | `TIMECODEBRIDGE_LIBLTC=` | 同梱の libltc ではなくそのファイルを読み込む | | `TIMECODEBRIDGE_AUTOMATION_PORT=` | 127.0.0.1: で自動操作用の HTTP を待ち受ける(下記「人手なしの実機確認」) | -| `TIMECODEBRIDGE_DATA_DIR=` | 設定(最近使ったプロジェクト)の保存先を変える。tcb3ctl は `$TCB3_RUN_DIR/data` を渡し、試験用インスタンスが利用者の設定を書き換えないようにする | +| `TIMECODEBRIDGE_DATA_DIR=` | 設定(最近使ったプロジェクト)の保存先を変える。tcb3ctl は `$TCB3_RUN_DIR/data` を渡し、試験用インスタンスが利用者の設定を書き換えないようにする。Windows では WebView2 のユーザーデータもこの下の `WebView2` に置く | Pro Tools Audio Bridge のような仮想ループバックは、再生と取り込みが別プロセスだとタイムコードが乱れます。ハードウェアなしで確かめるときは `TIMECODEBRIDGE_SELFTEST_OUTPUT` を使ってください。 @@ -90,6 +105,21 @@ tools/tcb3/tcb3ctl reload && tools/tcb3/tcb3ctl shot /tmp/tcb3ctl/after.png # H Codex を herdr のペインで動かす場合(`herdr agent start --kind codex --pane -- -s workspace-write -a never -c sandbox_workspace_write.network_access=true`)も、この `tcb3ctl reload` / `shot` をそのまま使えます。`network_access=true` が無いとサンドボックスから 127.0.0.1 の自動操作口に届きません。Codex は撮った PNG を自分の画像表示ツールで開いて確認できます(実機で確認済み)。Codex のサンドボックスでは headless Chrome が起動できない(プロファイル先やヘルパープロセスの制約で SIGABRT)ので、見た目の確認は実機一本にしてください。ループバックのデバイス名は `TCB3_SELFTEST_DEVICE`、作業ファイルの置き場は `TCB3_RUN_DIR`(既定 `/tmp/tcb3ctl`)で変えられます。`osc_listen.py` は単体でも使え、届いた OSC を 1 行 1 メッセージの JSON で出します。 +## 人手なしの実機確認(Windows) + +tcb3ctl / smoke.sh は macOS 専用です(`pgrep`・`ps eww`・`jq` を使い、`/screenshot` も Windows では 501)。Windows では `tools/tcb3/smoke-windows.ps1`(pwsh 7、jq・python 不要)で通し確認をします。自動操作口(`/health` `/state` `/command` `/eval` など)は Windows でもそのまま使えます。 + +```powershell +pwsh tools/tcb3/smoke-windows.ps1 # Debug ビルドを起動 → Web ready → OSC 発火と受信 → 送信ログと画面 → 撮影 +pwsh tools/tcb3/smoke-windows.ps1 -Exe publish\TimecodeBridge3.exe # 発行物(CI と同じ) +pwsh tools/tcb3/smoke-windows.ps1 -LoopbackDevice "CABLE In 16ch" # 2 つ目の Host が LTC を生成してその出力へ流し、1 つ目がループバックで受けてキュー発火まで確認 +``` + +- `SMOKE PASS` / `SMOKE FAIL` で終わり、終了コードも 0 / 1。作業ファイル・ログ・撮影(`smoke.png`)は `-RunDir`(既定 `%TEMP%\tcb3-smoke`)に出ます。`-Keep` で Host を残します。 +- Windows では出力デバイスが入力一覧に「(Loopback)」付きで並び、WASAPI のループバック取り込みで受信できます(再生と取り込みが別プロセスでも乱れない)。`-LoopbackDevice` には VB-Audio Virtual Cable のような聞こえない出力を指定してください(スピーカーを指定すると LTC が鳴ります)。 +- 撮影は `PrintWindow`(PW_RENDERFULLCONTENT)で行うので、前面化しなくても WebView の中身が写ります。 +- GitHub の Windows ランナーには音声デバイスが無いので、CI(`.github/workflows/ci-v3.yml`)は `-LoopbackDevice` なしで、発行物とインストーラーで入れたものの両方を確認します。 + ## Web UI の変更 Web は Codex が担当しています。`dist/` は git 管理で、CI が再ビルドして差分が無いことを検査するため、変更後は必ず `npm run build` の結果をコミットしてください。見た目の確認は、手元の macOS では上の tcb3ctl(実機)を使ってください。Codex のサンドボックス外で素早く確認するだけなら headless Chrome でも描画できます(WebKit だけの見え方、たとえば number input のスピナーは Chrome では出ません)。 @@ -102,4 +132,6 @@ Web は Codex が担当しています。`dist/` は git 管理で、CI が再 ## リリース -`v3.*` のタグで `.github/workflows/release-v3.yml` が動き、macOS(Apple Silicon)の `TimecodeBridge3.app`(zip)と Windows の zip・インストーラー(WebView2 ランタイムを未導入なら導入)を作ります。libltc は LGPL-3.0 のため動的リンクで同梱し、`THIRD_PARTY_NOTICES.md` と `libltc-COPYING.txt` を含めます。 +`v3.*` のタグで `.github/workflows/release-v3.yml` が動き、macOS(Apple Silicon)の `TimecodeBridge3.app`(zip)と Windows の zip・インストーラー(WebView2 ランタイムを未導入なら導入)を作ります。Windows は zip にする前に発行物を smoke-windows.ps1 で起動して確かめ、起動しないビルドは出しません。libltc は LGPL-3.0 のため動的リンクで同梱し、`THIRD_PARTY_NOTICES.md` と `libltc-COPYING.txt` を含めます。 + +PR と `feature/v3` への push では `.github/workflows/ci-v3.yml` が Windows でビルド・テスト(Ltc / Host)・発行物の smoke・インストーラーを作ってサイレントインストールしたものの smoke を行います。 diff --git a/docs/v3/bridge-protocol.md b/docs/v3/bridge-protocol.md index c25e888..df25a6d 100644 --- a/docs/v3/bridge-protocol.md +++ b/docs/v3/bridge-protocol.md @@ -94,7 +94,7 @@ M0合否目安(推測): clock+wave、1000 cues表示を10分継続し、入 |`project.new/open/save/saveAs`|`{path?,suggestedName?}`|`{cancelled,path?}` / `invalidProject`,`ioError`。open/save/saveAs は `path` を渡すとネイティブダイアログを出さずにそのファイルを読む/書く(自動操作向け。フォルダが無ければ `ioError`)| |`mode.set`|`{mode}`|`{ltcStarted}` / `validation`。ltc へ切り替えたとき、選択済みの入力デバイスがあれば Host が自動で受信を再開する(未選択なら停止のまま)| |`ltc.start/stop/reconnect`|`{deviceId?}`|`deviceNotFound`,`audioError`,`nativeError`| -|`audio.refreshDevices`|`{direction}`|patch / `audioError`| +|`audio.refreshDevices`|`{direction}`|patch / `audioError`。Host は裏のスレッドでデバイスを列挙し直し、終わってから一覧の patch と result を返す(Windows で 0.5〜1 秒。その間も時計や他の command は止まらず、一覧は古いまま)。Web は result が返るまでスキャン中を表示する。一覧はこの command まで使い回す| |`receive.selectDevice`|`{deviceId}`|`deviceId` が null/空なら「未選択」に戻す(受信を止めて選択を消す)。それ以外は `ltc.reconnect` と同じ / `deviceNotFound`,`audioError`| |`receive.setOffset`|`{value}`|`{normalized}` / `validation`| |`receive.setTriggerWindow`|`{frames}`|`{frames}` / `validation`| diff --git a/docs/v3/verification-2026-09-27-windows.md b/docs/v3/verification-2026-09-27-windows.md new file mode 100644 index 0000000..fddbffd --- /dev/null +++ b/docs/v3/verification-2026-09-27-windows.md @@ -0,0 +1,52 @@ +# v3 の Windows 実機検証と修正の記録(2026-09-27) + +`verification-2026-09-14.md` の「次にやるべきこと」2 番(Windows の実機・CI)。macOS で作った v3 を Windows で初めてビルド・起動・通し確認し、見つけたものを直した。 + +## 環境 + +- Windows 11 Pro 10.0.26200(x64、日本語)、WebView2 ランタイム 153.0.4234.48 +- .NET SDK 9 / 10(net8.0 ランタイム 8.0.25 で起動)、Visual Studio 2022(MSVC 14.44)、Node.js 24、Inno Setup 6 +- 音声: VB-Audio Virtual Cable(CABLE In 16ch、16 チャンネルの出力)、Steinberg UR22C。LTC は CABLE In 16ch へ生成し、そのループバック取り込みで受信した + +## 見つかった問題と修正 + +| # | 症状 | 原因 | 修正 | +|---|---|---|---| +| 1 | **v3.0.0 の Windows 版が起動直後に落ちる**(リリースの zip で再現。GUI アプリなので利用者には何も出ずに終わる) | Host に対応 OS を宣言した `app.manifest` が無く、Avalonia が WebView(NativeControlHost)用の子ウィンドウを作れない(`Unable to create child window for native control host`) | `src/TimecodeBridge.Host/app.manifest`(Windows 10/11 の supportedOS)を追加 | +| 2 | Program Files のような書けない場所に入れると起動直後に落ちる(インストーラーで「すべてのユーザー」を選んだ場合) | WebView2 が既定で実行ファイルの隣(`TimecodeBridge3.exe.WebView2`)にユーザーデータを作ろうとして `E_ACCESSDENIED`。書き込みを禁止したフォルダで修正前の版が落ちることを再現した | `NativeWebView.EnvironmentRequested` で `UserDataFolder` を `%LOCALAPPDATA%\TimecodeBridge3\WebView2` に。アンインストール後にフォルダが残る問題も消えた | +| 3 | 入力デバイス一覧に出力デバイスが「(Loopback)」付きと無しの 2 回、同じ id で並ぶ | `HostState.BuildReceive` がキャプチャとレンダーの一覧をつないでいた。Windows ではループバック取り込みがキャプチャ側に入る(IAudioDeviceService の契約) | 入力の候補はキャプチャ側だけにした。**macOS でも出力専用デバイスが入力一覧に出なくなる**(macOS 実機では未確認) | +| 4 | 生成中にフレームレートや開始 TC を変えて「停止→再生」すると、画面は新しい設定なのに古いレートと位置のまま LTC を出す。「リセット」では新しい開始 TC を古いレートで数える(24fps の 10:59:52:00 が 30fps の 08:47:55 になる) | `generator.start` が保留中の設定を捨てて一時停止から再開し、`generator.reset` は開始 TC だけを差し替えていた(OS 共通) | 保留中の変更があれば最初から生成し直し、リセットはレートが変わっていれば生成器を作り直す。テスト 2 件を追加 | +| 5 | Host のテストが Windows で終わらない(`HostPingReportsReachabilityAndLatency`) | テストの `HostHarness.Run` が UI スレッドで非同期 command を同期待ちしており、Windows の `Ping.SendPingAsync` は必ず非同期で完了するためデッドロック。実機の Host は await しているので起きない(実機の疎通確認は成功) | `HostHarness.RunAsync` を足してテストを async に | +| 6 | Host のテスト 4 件が Windows で落ちる | パス区切り(`/` 固定)と `platform=="macos"` 固定の期待値 | パスは JSON にエスケープして渡し、期待値を OS から求める | +| 7 | ルート README の libltc が「LGPL-2.1」「復号・生成に使う」 | 同梱の COPYING は LGPL v3。生成は Core の自前の LtcEncoder | 「復号に使う libltc は LGPL-3.0-or-later」に | +| 8 | LTC受信 ⇄ 内部生成の切り替えに時間がかかる(画面の切り替えまで 内部生成へ約 0.66 秒・LTC受信へ約 2.6 秒、受信開始まで約 3.9 秒) | Windows のデバイス列挙はエンドポイントのプロパティ読みで 1 回 0.35〜0.5 秒かかる(この PC は 16 エンドポイント)。Host は状態を組み立てるたび(transport の入力デバイス名、receive / generator の一覧)とデバイスを探すたびに列挙しており、切り替え 1 回で何度も列挙していた。transport はオートミュートの切り替わりなどでも組み直す | `CachedAudioDeviceService` で一覧を「再スキャン」(audio.refreshDevices)まで使い回す。ID で見つからないときだけ一度列挙し直す(起動後に挿したデバイス)。画面の切り替えは 17〜65ms、受信開始まで約 0.2 秒に。画面の一覧が更新される契機(再スキャン)は従来どおり。再スキャンは裏のスレッドで列挙して終わってから一覧を差し替え(その間も UI スレッドは止まらない。スキャン中の状態取得 2〜6ms)、Web は result が返るまでボタンを「スキャン中…」にして帯を流す(この PC で約 0.65 秒)。最初の列挙は起動時に裏で先に始める(起動直後の状態取得 約 620ms → 59ms) | + +## 確認した範囲(修正後、すべて通過) + +- ビルド: `build-windows.cmd` で libltc.dll(日本語 Windows では `ltc.h` のコメント内の文字で C4819 が出るが無害)、Web の typecheck / test 51 件 / build(dist の差分は改行コードだけ)、Host のビルド、`dotnet publish -r win-x64 --self-contained`、`iscc` でインストーラー +- テスト: Ltc 13 件、Host 60 件 +- 起動: WebView2 で Web UI が表示される(日本語フォント・ダークテーマ・波形)。自動操作口(`/health` `/state` `/clock` `/command` `/eval`) +- LTC: 2 つの Host で 生成 → CABLE In 16ch → ループバック受信。24 / 25 / 29.97df / 30fps のいずれも同じレート(29.97 はドロップ判定)で受信、誤り率は 5 秒受けた時点で 0〜0.4%(受信開始からの累計なので、開始直後に弾かれる数フレームが効く。2.5 秒時点では 1.2% のこともあった)。モノラル 16bit の出力を 16ch デバイスへ、16ch のループバックから先頭チャンネルを取り込む経路も動く +- 生成: フレームレート・開始 TC の変更がリセット、停止→再生で反映される(修正 4 の後) +- キュー: LTC の時刻で自動発火した OSC を受信(引数 int32 と日本語の string)、手動発火、CUE SYNC、送信ログと発火記録、疎通確認(ICMP 0ms) +- 配布: 発行物を起動、インストーラーでユーザー単位にサイレントインストール → 起動して LTC 受信とキュー発火まで確認 → サイレントアンインストール(登録とフォルダが消える)、書き込みを禁止したフォルダに置いた発行物でも起動する(修正 2 の後) +- 通し確認: `tools/tcb3/smoke-windows.ps1 -LoopbackDevice "CABLE In 16ch"` を 3 回続けて SMOKE PASS + +## 追加したもの + +- `tools/tcb3/smoke-windows.ps1`: smoke.sh の Windows 版(pwsh 7、jq・python 不要)。起動 → Web ready → OSC 発火と受信 → 送信ログと画面 → WebView2 のデータが実行ファイルの隣にできていないこと → 撮影(PrintWindow)。`-LoopbackDevice` で 2 つ目の Host に LTC を生成させ、ループバック受信とキュー発火まで確かめる +- `.github/workflows/ci-v3.yml`: PR と feature/v3 への push で、Windows のビルド・テスト・発行物の smoke・インストーラーでサイレントインストールしたものの smoke(ランナーに音声デバイスが無いので LTC は確かめない) +- `release-v3.yml`: zip にする前に発行物を smoke し、起動しないビルドを出さない + +## 未対応・気づいたこと + +- **v3.0.0 の Windows 版(zip・インストーラー)は使えない状態のまま公開されている。** 修正を含めた v3.0.1 を出す必要がある +- ci-v3.yml はまだ一度も GitHub で動かしていない。ランナーで WebView2 の画面が作れるか(GUI セッション)、自動操作口の HttpListener が使えるかは初回の実行で確かめる。release-v3.yml の smoke も同じ前提なので、先に ci-v3 を通してからタグを打つ +- 修正 3・4 は macOS にも効く(入力一覧から出力専用デバイスが消える、生成の設定変更の反映)。macOS 実機での確認が要る +- 起動後に出た未処理例外はどこにも記録されない(起動エラー画面と startup-error.log は DI の組み立てでの失敗だけ)。修正 1・2 のような落ち方をしたとき利用者の手元に手がかりが残らないので、未処理例外を DataDirectory に記録する仕組みがあると良い +- `TIMECODEBRIDGE_SELFTEST_OUTPUT`(同一プロセスで生成と受信)は macOS のみ。Windows はループバック取り込みが別プロセスでも乱れないので、2 つの Host で代替した +- 自動操作口の `/screenshot` は Windows では 501 のまま(smoke-windows.ps1 は外から PrintWindow で撮る)。tcb3ctl / smoke.sh も macOS 専用のまま +- `TIMECODEBRIDGE_BRIDGE_TRACE` の標準出力は Windows では日本語が文字化けする(コンソールのコードページ)。開発用のログだけ +- ウィンドウのタイトルが「TimecodeBridge」(3 が付かない)。OS 共通 +- 実際のオーディオインターフェース(UR22C)への物理的な LTC 入出力、管理者権限での「すべてのユーザー」インストールは行っていない(後者は書き込み禁止のフォルダで代替) +- v1/v2 のテストが Windows で落ちる(v3 の対象外なので直していない): `TimecodeBridge.Tests` の CoreProjectStructureTests 12 件(実行ディレクトリからの相対パス)、OscTriggerPanelManagerTests 1 件(v3 で `Trigger` が `IOscSender.SendWithResult` に判定を任せたため、既定実装のままのテスト用スパイでは「送信先なし」にならない。OS 共通)、`TimecodeBridge.App.Tests` 2 件(macOS 前提の DI テスト、実際の利用者の設定を読む最近使ったプロジェクトのテスト) diff --git a/src/TimecodeBridge.Host/App.cs b/src/TimecodeBridge.Host/App.cs index 29d6253..a7b8334 100644 --- a/src/TimecodeBridge.Host/App.cs +++ b/src/TimecodeBridge.Host/App.cs @@ -45,9 +45,17 @@ public override void OnFrameworkInitializationCompleted() public static void ConfigureServices(IServiceCollection services) { + // 一覧は「再スキャン」まで使い回す(Windows は列挙が遅い。CachedAudioDeviceService のコメント参照)。 + // 最初の列挙は裏で先に始め、WebView の準備と重ねて起動時の待ちを隠す + services.AddSingleton(sp => + { + var devices = new CachedAudioDeviceService(OperatingSystem.IsWindows() ? new WindowsAudioDeviceService() : new CoreAudioDeviceService()); + _ = devices.RefreshAsync(); + return devices; + }); + if (OperatingSystem.IsWindows()) { - services.AddSingleton(); services.AddSingleton(sp => new TimecodeEngine( FrameRate.Fps30, sp.GetRequiredService(), @@ -57,7 +65,6 @@ public static void ConfigureServices(IServiceCollection services) } else { - services.AddSingleton(); services.AddSingleton(sp => new TimecodeEngine( FrameRate.Fps30, sp.GetRequiredService(), diff --git a/src/TimecodeBridge.Host/Bridge/CommandRouter.cs b/src/TimecodeBridge.Host/Bridge/CommandRouter.cs index e61e890..d221677 100644 --- a/src/TimecodeBridge.Host/Bridge/CommandRouter.cs +++ b/src/TimecodeBridge.Host/Bridge/CommandRouter.cs @@ -36,11 +36,17 @@ public async Task ExecuteAsync(WebMessage msg) if (msg.ExpectedRevision is { } expected && expected != _state.Revision) return Fail(id, ErrorCode.Conflict, $"状態が更新されています (expectedRevision: {expected}, currentRevision: {_state.Revision})", retryable: true); - // ダイアログを伴うものだけ非同期。それ以外は同期処理へ + // ダイアログを伴うもの・待ち時間のあるものだけ非同期。それ以外は同期処理へ try { switch (msg.Command) { + case "audio.refreshDevices": + // 列挙(Windows で 0.5〜1 秒)は裏のスレッドで行い、その間も UI スレッド(時計の送信・描画)を止めない。 + // 終わるまで古い一覧を使い、終わってから新しい一覧を送る。Web は result が返るまでスキャン中を表示する + await _state.RefreshDevicesAsync(); + _state.MarkDirty(Domain.Receive | Domain.Generator); + return Ok(id); case "project.new": return Ok(id, new { cancelled = !await _projects.NewAsync() }); case "project.open": @@ -176,9 +182,6 @@ public ResultMessage Execute(WebMessage msg) _state.MarkDirty(Domain.Receive | Domain.Transport); return Ok(id); } - case "audio.refreshDevices": - _state.MarkDirty(Domain.Receive | Domain.Generator); - return Ok(id); // ---- 受信設定 ---- case "receive.setOffset": @@ -246,7 +249,9 @@ public ResultMessage Execute(WebMessage msg) case "generator.start": try { - if (_engine.ActiveSource == TimecodeSourceType.Generator && !_state.GeneratorRunning && _engine.CurrentRawTimecode.ToOrdinal() > 0) + // 一時停止からの再開は、開始 TC・フレームレートの変更が保留されていないときだけ。 + // 保留中に再開すると、画面は新しい設定を示したまま古いレートと位置で LTC を出し続ける + if (_engine.ActiveSource == TimecodeSourceType.Generator && !_state.GeneratorRunning && !_state.GeneratorSettingsPending && _engine.CurrentRawTimecode.ToOrdinal() > 0) _engine.ResumeGenerator(); else _engine.StartGenerator(_state.Generator); @@ -267,7 +272,24 @@ public ResultMessage Execute(WebMessage msg) _state.MarkDirty(Domain.Generator | Domain.Transport); return Ok(id); case "generator.reset": - _engine.ResetGenerator(_state.Generator.StartTime); + // ResetGenerator は開始 TC だけを差し替え、生成器は古いレートのまま数える。レートが変わっていれば作り直す + if (_engine.ActiveSource == TimecodeSourceType.Generator && _engine.FrameRate != _state.Generator.FrameRate) + { + try + { + _engine.StartGenerator(_state.Generator); + if (!_state.GeneratorRunning) _engine.StopGenerator(); // 一時停止中なら開始位置で止めておく + } + catch (Exception ex) + { + _state.SetError(ex.Message); + return Fail(id, ErrorCode.AudioError, $"内部生成をリセットできません: {ex.Message}", retryable: true); + } + } + else + { + _engine.ResetGenerator(_state.Generator.StartTime); + } _state.GeneratorSettingsPending = false; _state.MarkDirty(Domain.Generator | Domain.NextCue); return Ok(id); diff --git a/src/TimecodeBridge.Host/Bridge/HostState.cs b/src/TimecodeBridge.Host/Bridge/HostState.cs index 56de48d..a123d2e 100644 --- a/src/TimecodeBridge.Host/Bridge/HostState.cs +++ b/src/TimecodeBridge.Host/Bridge/HostState.cs @@ -212,9 +212,11 @@ private TransportState BuildTransport() LastLtcReceivedAtUtc: _lastLtcReceivedAtUtc?.ToString("O")); } + // 入力の候補はキャプチャデバイスだけ(Windows ではレンダーデバイスのループバック取り込みもここに含まれる)。 + // レンダーデバイスを足すと、Windows では同じ id が「(Loopback)」付きと無しで二重に並び、出力専用デバイスまで入力に出る private ReceiveState BuildReceive() => new( SelectedInputDeviceId, - _devices.GetCaptureDevices().Concat(_devices.GetRenderDevices()).Select(ToDto).ToArray(), + _devices.GetCaptureDevices().Select(ToDto).ToArray(), _engine.Offset.ToString(), _cues.TriggerWindowFrames, _engine.FreerunDurationSeconds); @@ -414,12 +416,27 @@ private void OnOscSendCompleted(object? sender, OscSendResultEventArgs e) // ---- 変換 ---------------------------------------------------------------------- public string? DeviceName(string? id) => - id is null ? null : _devices.GetCaptureDevices().Concat(_devices.GetRenderDevices()).FirstOrDefault(d => d.Id == id)?.DisplayName; + id is null ? null : AllDevices().FirstOrDefault(d => d.Id == id)?.DisplayName; public IEnumerable AllDevices() => _devices.GetCaptureDevices().Concat(_devices.GetRenderDevices()); - public AudioDeviceInfo? FindDevice(string? id) => - id is null ? null : _devices.GetCaptureDevices().Concat(_devices.GetRenderDevices()).FirstOrDefault(d => d.Id == id); + /// デバイス一覧を次の取得で列挙し直す。 + public void RefreshDevices() + { + if (_devices is CachedAudioDeviceService cached) cached.Refresh(); + } + + /// 「再スキャン」。裏のスレッドで列挙し、終わるまでは古い一覧を使う。 + public Task RefreshDevicesAsync() => _devices is CachedAudioDeviceService cached ? cached.RefreshAsync() : Task.CompletedTask; + + // 一覧は使い回しなので、見つからなければ一度だけ列挙し直す(起動後に挿したデバイスを、再スキャン無しで開くプロジェクトが指している場合など) + public AudioDeviceInfo? FindDevice(string? id) + { + if (id is null) return null; + if (AllDevices().FirstOrDefault(d => d.Id == id) is { } found) return found; + RefreshDevices(); + return AllDevices().FirstOrDefault(d => d.Id == id); + } private static AudioDevice ToDto(AudioDeviceInfo d) => new(d.Id, d.DisplayName, d.IsLoopback); diff --git a/src/TimecodeBridge.Host/MainWindow.cs b/src/TimecodeBridge.Host/MainWindow.cs index 422e3b6..f1cb563 100644 --- a/src/TimecodeBridge.Host/MainWindow.cs +++ b/src/TimecodeBridge.Host/MainWindow.cs @@ -50,6 +50,12 @@ public MainWindow(HostState state, CommandRouter router, ProjectCoordinator proj _bridge = null!; return; } + // WebView2 のユーザーデータを実行ファイルの隣に作らせない(Program Files では書けず表示できない)。生成前に聞かれるので Content より先に購読する + webView.EnvironmentRequested += (_, e) => + { + if (e is Avalonia.Platform.WindowsWebView2EnvironmentRequestedEventArgs windows) + windows.UserDataFolder = AppPaths.WebView2UserDataDirectory; + }; Content = webView; _bridge = new BridgeServer(webView, state, router, Close); _automation = DevAutomation.StartIfRequested(new HostAutomationTarget(this, webView, state, router, _bridge)); diff --git a/src/TimecodeBridge.Host/Services/AppPaths.cs b/src/TimecodeBridge.Host/Services/AppPaths.cs index 14b9571..b41c0c2 100644 --- a/src/TimecodeBridge.Host/Services/AppPaths.cs +++ b/src/TimecodeBridge.Host/Services/AppPaths.cs @@ -19,4 +19,13 @@ public static string DataDirectoryFor(string appFolderName) public static string DataDirectory => Environment.GetEnvironmentVariable(DataDirectoryVariable) is { Length: > 0 } overridden ? overridden : DataDirectoryFor(AppFolderName); + + /// + /// Windows の WebView2 のユーザーデータ(キャッシュなど)。既定の「実行ファイルの隣」は Program Files に入れると書けず表示できないので、 + /// ローミングしない LocalApplicationData の下に置く。TIMECODEBRIDGE_DATA_DIR があればその下。 + /// + public static string WebView2UserDataDirectory => + Environment.GetEnvironmentVariable(DataDirectoryVariable) is { Length: > 0 } overridden + ? Path.Combine(overridden, "WebView2") + : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppFolderName, "WebView2"); } diff --git a/src/TimecodeBridge.Host/Services/CachedAudioDeviceService.cs b/src/TimecodeBridge.Host/Services/CachedAudioDeviceService.cs new file mode 100644 index 0000000..f92c3fa --- /dev/null +++ b/src/TimecodeBridge.Host/Services/CachedAudioDeviceService.cs @@ -0,0 +1,67 @@ +using TimecodeBridge.Core.Models; +using TimecodeBridge.Core.Services.Interfaces; + +namespace TimecodeBridge.Host.Services; + +/// +/// デバイス一覧を「再スキャン」(audio.refreshDevices)まで使い回す。 +/// Windows の列挙はエンドポイントのプロパティ読みで 1 回 0.5 秒ほどかかり、状態を組み立てるたびに列挙すると +/// モード切替や発火(オートミュートで transport を組み直す)のたびに UI スレッドが 1〜2 秒止まっていた。 +/// Web へ一覧を送るのは receive/generator の patch だけなので、画面の一覧が更新される契機は従来どおり。 +/// 再スキャンは で裏のスレッドで列挙し、終わるまでは古い一覧を返す。 +/// +public sealed class CachedAudioDeviceService : IAudioDeviceService +{ + private readonly IAudioDeviceService _inner; + private readonly object _lock = new(); + private IReadOnlyList? _capture; + private IReadOnlyList? _render; + private Task? _scan; + + public CachedAudioDeviceService(IAudioDeviceService inner) => _inner = inner; + + public IReadOnlyList GetCaptureDevices() + { + WaitForFirstScan(); + lock (_lock) return _capture ??= _inner.GetCaptureDevices(); + } + + public IReadOnlyList GetRenderDevices() + { + WaitForFirstScan(); + lock (_lock) return _render ??= _inner.GetRenderDevices(); + } + + /// 次の取得で(呼び出したスレッドで)列挙し直す。 + public void Refresh() + { + lock (_lock) { _capture = null; _render = null; } + } + + /// + /// 裏のスレッドで列挙し、終わったら一覧を差し替える。その間の取得は古い一覧を返す(一覧がまだ無いときだけ終わるのを待つ)。 + /// 走っている列挙があればそれを返し、二重に列挙しない。起動時の先読みにも使う。 + /// + public Task RefreshAsync() + { + lock (_lock) + { + if (_scan is { IsCompleted: false } running) return running; + return _scan = Task.Run(() => + { + var capture = _inner.GetCaptureDevices(); + var render = _inner.GetRenderDevices(); + lock (_lock) { _capture = capture; _render = render; } + }); + } + } + + private void WaitForFirstScan() + { + Task? scan; + lock (_lock) scan = _capture is null || _render is null ? _scan : null; + if (scan is null || scan.IsCompleted) return; + try { scan.Wait(); } + catch (AggregateException) { /* 失敗したら呼び出し元で同期に列挙し直す */ } + } +} diff --git a/src/TimecodeBridge.Host/TimecodeBridge.Host.csproj b/src/TimecodeBridge.Host/TimecodeBridge.Host.csproj index bec0428..d4f3223 100644 --- a/src/TimecodeBridge.Host/TimecodeBridge.Host.csproj +++ b/src/TimecodeBridge.Host/TimecodeBridge.Host.csproj @@ -14,6 +14,8 @@ 3.0.0 Major + + app.manifest diff --git a/src/TimecodeBridge.Host/app.manifest b/src/TimecodeBridge.Host/app.manifest new file mode 100644 index 0000000..f0c8a04 --- /dev/null +++ b/src/TimecodeBridge.Host/app.manifest @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/TimecodeBridge.Web/dist/assets/app.js b/src/TimecodeBridge.Web/dist/assets/app.js index 6ff08ef..99dc013 100644 --- a/src/TimecodeBridge.Web/dist/assets/app.js +++ b/src/TimecodeBridge.Web/dist/assets/app.js @@ -1,12 +1,12 @@ -"use strict";(()=>{var le,I,Ke,Wt,K,Le,Je,Xe,Ge,we,ve,he,Vt,Y={},qe=[],Kt=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,ce=Array.isArray;function B(e,t){for(var n in t)e[n]=t[n];return e}function Ce(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Jt(e,t,n){var o,c,s,u={};for(s in t)s=="key"?o=t[s]:s=="ref"?c=t[s]:u[s]=t[s];if(arguments.length>2&&(u.children=arguments.length>3?le.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)u[s]===void 0&&(u[s]=e.defaultProps[s]);return se(e,u,o,c,null)}function se(e,t,n,o,c){var s={type:e,props:t,key:n,ref:o,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:c??++Ke,__i:-1,__u:0};return c==null&&I.vnode!=null&&I.vnode(s),s}function H(e){return e.children}function ae(e,t){this.props=e,this.context=t}function G(e,t){if(t==null)return e.__?G(e.__,e.__i+1):null;for(var n;tp&&K.sort(Xe),e=K.shift(),p=K.length,e.__d&&(n=void 0,c=(o=(t=e).__v).__e,s=[],u=[],t.__P&&((n=B({},o)).__v=o.__v+1,I.vnode&&I.vnode(n),Te(t.__P,n,o,t.__n,t.__P.namespaceURI,32&o.__u?[c]:null,s,c??G(o),!!(32&o.__u),u),n.__v=o.__v,n.__.__k[n.__i]=n,Ze(s,n,u),n.__e!=c&&ze(n)));ie.__r=0}function Qe(e,t,n,o,c,s,u,p,b,d,i){var a,g,h,S,l,_,m,v=o&&o.__k||qe,w=t.length;for(b=Xt(n,t,v,b,w),a=0;a0?se(u.type,u.props,u.key,u.ref?u.ref:null,u.__v):u).__=e,u.__b=e.__b+1,p=null,(d=u.__i=Gt(u,n,b,a))!=-1&&(a--,(p=n[d])&&(p.__u|=2)),p==null||p.__v==null?(d==-1&&(c>i?g--:cb?g--:g++,u.__u|=4))):e.__k[s]=null;if(a)for(s=0;s(i?1:0)){for(c=n-1,s=n+1;c>=0||s=0?c--:s++])!=null&&(2&d.__u)==0&&p==d.key&&b==d.type)return u}return-1}function We(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||Kt.test(t)?n:n+"px"}function oe(e,t,n,o,c){var s,u;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof o=="string"&&(e.style.cssText=o=""),o)for(t in o)n&&t in n||We(e.style,t,"");if(n)for(t in n)o&&n[t]==o[t]||We(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")s=t!=(t=t.replace(Ge,"$1")),u=t.toLowerCase(),t=u in e||t=="onFocusOut"||t=="onFocusIn"?u.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+s]=n,n?o?n.u=o.u:(n.u=we,e.addEventListener(t,s?he:ve,s)):e.removeEventListener(t,s?he:ve,s);else{if(c=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function Ve(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t.t==null)t.t=we++;else if(t.t0?e:ce(e)?e.map(et):B({},e)}function qt(e,t,n,o,c,s,u,p,b){var d,i,a,g,h,S,l,_=n.props,m=t.props,v=t.type;if(v=="svg"?c="http://www.w3.org/2000/svg":v=="math"?c="http://www.w3.org/1998/Math/MathML":c||(c="http://www.w3.org/1999/xhtml"),s!=null){for(d=0;d=n.__.length&&n.__.push({}),n.__[e]}function T(e){return de=1,Qt(mt,e)}function Qt(e,t,n){var o=pe(q++,2);if(o.t=e,!o.__c&&(o.__=[n?n(t):mt(void 0,t),function(p){var b=o.__N?o.__N[0]:o.__[0],d=o.t(b,p);b!==d&&(o.__N=[d,o.__[1]],o.__c.setState({}))}],o.__c=E,!E.__f)){var c=function(p,b,d){if(!o.__c.__H)return!0;var i=o.__c.__H.__.filter(function(g){return!!g.__c});if(i.every(function(g){return!g.__N}))return!s||s.call(this,p,b,d);var a=o.__c.props!==p;return i.forEach(function(g){if(g.__N){var h=g.__[0];g.__=g.__N,g.__N=void 0,h!==g.__[0]&&(a=!0)}}),s&&s.call(this,p,b,d)||a};E.__f=!0;var s=E.shouldComponentUpdate,u=E.componentWillUpdate;E.componentWillUpdate=function(p,b,d){if(this.__e){var i=s;s=void 0,c(p,b,d),s=i}u&&u.call(this,p,b,d)},E.shouldComponentUpdate=c}return o.__N||o.__}function M(e,t){var n=pe(q++,3);!O.__s&&De(n.__H,t)&&(n.__=e,n.u=t,E.__H.__h.push(n))}function pt(e,t){var n=pe(q++,4);!O.__s&&De(n.__H,t)&&(n.__=e,n.u=t,E.__h.push(n))}function $(e){return de=5,Yt(function(){return{current:e}},[])}function Yt(e,t){var n=pe(q++,7);return De(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function Zt(){for(var e;e=dt.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(ue),e.__H.__h.forEach(Ae),e.__H.__h=[]}catch(t){e.__H.__h=[],O.__e(t,e.__v)}}O.__b=function(e){E=null,ot&&ot(e)},O.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),ct&&ct(e,t)},O.__r=function(e){st&&st(e),q=0;var t=(E=e.__c).__H;t&&(Se===E?(t.__h=[],E.__h=[],t.__.forEach(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.forEach(ue),t.__h.forEach(Ae),t.__h=[],q=0)),Se=E},O.diffed=function(e){at&&at(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(dt.push(t)!==1&&nt===O.requestAnimationFrame||((nt=O.requestAnimationFrame)||er)(Zt)),t.__H.__.forEach(function(n){n.u&&(n.__H=n.u),n.u=void 0})),Se=E=null},O.__c=function(e,t){t.some(function(n){try{n.__h.forEach(ue),n.__h=n.__h.filter(function(o){return!o.__||Ae(o)})}catch(o){t.some(function(c){c.__h&&(c.__h=[])}),t=[],O.__e(o,n.__v)}}),it&&it(e,t)},O.unmount=function(e){lt&<(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.forEach(function(o){try{ue(o)}catch(c){t=c}}),n.__H=void 0,t&&O.__e(t,n.__v))};var ut=typeof requestAnimationFrame=="function";function er(e){var t,n=function(){clearTimeout(o),ut&&cancelAnimationFrame(t),setTimeout(e)},o=setTimeout(n,35);ut&&(t=requestAnimationFrame(n))}function ue(e){var t=E,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),E=t}function Ae(e){var t=E;e.__c=e.__(),E=t}function De(e,t){return!e||e.length!==t.length||t.some(function(n,o){return n!==e[o]})}function mt(e,t){return typeof t=="function"?t(e):t}var tr=()=>({host:{sessionId:null,revision:null,state:null,clock:null,wave:null,clockSeq:-1,waveSeq:-1,connected:!1,resyncing:!1},ui:{selectedCueIds:new Set,anchorCueId:null,focusedCueId:null,cueScrollTop:0,cueColumnWidths:[38,105,280,220,50],drawerOpen:!1,drawerHeight:330,modalOpen:!1}});function ft(e,t){if(t.type==="snapshot")return e.host.sessionId=t.state.sessionId,e.host.revision=t.revision,e.host.state=t.state,e.host.clock=t.state.currentClock,e.host.connected=!0,e.host.resyncing=!1,"applied";if(t.type==="patch"){if(!e.host.state||t.baseRevision!==e.host.revision)return e.host.resyncing=!0,"resync";let{logsAppend:n,logsReset:o,nextCueCleared:c,...s}=t.changes;return Object.assign(e.host.state,s),c&&(e.host.state.nextCue=null),o?e.host.state.logs=o:n&&(e.host.state.logs=[...e.host.state.logs,...n].slice(-500)),e.host.revision=t.revision,"applied"}return t.type==="clock"?t.seq<=e.host.clockSeq?"ignored":(e.host.clockSeq=t.seq,e.host.clock=t.clock,"applied"):t.type==="wave"?t.seq<=e.host.waveSeq?"ignored":(e.host.waveSeq=t.seq,e.host.wave=t.wave,"applied"):"ignored"}var xe=class{value=tr();listeners=new Set;subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}notify(){for(let t of this.listeners)t()}updateUi(t){Object.assign(this.value.ui,t),this.notify()}},A=new xe;var rr=0,Re=new Map;function z(e){window.invokeCSharpAction?.(JSON.stringify(e))}function R(e,t,n){let o=`web-${Date.now()}-${++rr}`,c={protocolVersion:1,type:"command",requestId:o,command:e,args:t,...n===void 0?{}:{expectedRevision:n}};return z(c),new Promise((s,u)=>Re.set(o,{resolve:s,reject:u}))}function gt(e){let t=Re.get(e.requestId);t&&(Re.delete(e.requestId),e.ok?t.resolve(e.data):t.reject(e.error))}function bt(e){return typeof HTMLElement<"u"&&e instanceof HTMLElement&&(e.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(e.tagName))}function _t(e,t){if(e.repeat||e.isComposing||t||bt(e.target))return null;let n=e.metaKey||e.ctrlKey,o=e.key.toLowerCase();return n&&o==="n"?"projectNew":n&&o==="o"?"projectOpen":n&&o==="s"?e.shiftKey?"projectSaveAs":"projectSave":n&&o==="z"?e.shiftKey?"redo":"undo":e.ctrlKey&&!e.metaKey&&o==="y"?"redo":e.key===" "?"cueSync":o==="l"?"toggleMute":o==="p"?"toggleDrawer":e.key==="Escape"?"closeDrawer":e.key==="ArrowUp"?"selectPrevious":e.key==="ArrowDown"?"selectNext":e.key==="Enter"?"editCue":e.key==="Delete"||e.key==="Backspace"?"deleteCue":null}var vt=e=>({ok:!0,value:e}),Ie=e=>({ok:!1,message:e});function Ee(e,t=!1){if(!(t?/^[+-]\d{2}:\d{2}:\d{2}[:;]\d{2}$/:/^\d{2}:\d{2}:\d{2}[:;]\d{2}$/).test(e))return Ie(t?"\xB1HH:MM:SS:FF \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044":"HH:MM:SS:FF \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044");let c=(t?e.slice(1):e).replace(";",":").split(":").map(Number);return(c[1]??99)<60&&(c[2]??99)<60?vt(e):Ie("\u5206\u30FB\u79D2\u306F 00\u301C59 \u3067\u3059")}function Oe(e){return/^\/(?:[^\s/#*,?\[\]{}]+(?:\/[^\s/#*,?\[\]{}]+)*)?$/.test(e)?vt(e):Ie("/ \u3067\u59CB\u307E\u308B OSC\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044")}var Z=e=>({name:"",memo:"",triggerTime:"00:00:00:00",frameRate:e,oscAddress:"/",additionalOscAddresses:"",argumentsText:"",targetHostIds:[],enabled:!0,sendTriggerTimeAsSeconds:!1,sendTimecode:"",triggerOffset:"",autoMuteOnFire:!1,autoUnmuteAfter:""}),ht=e=>({...Z(e.frameRate),name:e.name,memo:e.memo,triggerTime:e.triggerTime,oscAddress:e.oscAddress,additionalOscAddresses:e.additionalOscAddresses.join(` +"use strict";(()=>{var le,I,Ke,Wt,K,Le,Je,Xe,Ge,ye,_e,ve,Vt,Y={},qe=[],Kt=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,ce=Array.isArray;function B(e,t){for(var r in t)e[r]=t[r];return e}function we(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Jt(e,t,r){var o,c,s,d={};for(s in t)s=="key"?o=t[s]:s=="ref"?c=t[s]:d[s]=t[s];if(arguments.length>2&&(d.children=arguments.length>3?le.call(arguments,2):r),typeof e=="function"&&e.defaultProps!=null)for(s in e.defaultProps)d[s]===void 0&&(d[s]=e.defaultProps[s]);return se(e,d,o,c,null)}function se(e,t,r,o,c){var s={type:e,props:t,key:r,ref:o,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:c??++Ke,__i:-1,__u:0};return c==null&&I.vnode!=null&&I.vnode(s),s}function M(e){return e.children}function ae(e,t){this.props=e,this.context=t}function G(e,t){if(t==null)return e.__?G(e.__,e.__i+1):null;for(var r;tp&&K.sort(Xe),e=K.shift(),p=K.length,e.__d&&(r=void 0,c=(o=(t=e).__v).__e,s=[],d=[],t.__P&&((r=B({},o)).__v=o.__v+1,I.vnode&&I.vnode(r),Ce(t.__P,r,o,t.__n,t.__P.namespaceURI,32&o.__u?[c]:null,s,c??G(o),!!(32&o.__u),d),r.__v=o.__v,r.__.__k[r.__i]=r,Ze(s,r,d),r.__e!=c&&ze(r)));ie.__r=0}function Qe(e,t,r,o,c,s,d,p,f,u,i){var a,b,h,S,l,_,m,v=o&&o.__k||qe,w=t.length;for(f=Xt(r,t,v,f,w),a=0;a0?se(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d).__=e,d.__b=e.__b+1,p=null,(u=d.__i=Gt(d,r,f,a))!=-1&&(a--,(p=r[u])&&(p.__u|=2)),p==null||p.__v==null?(u==-1&&(c>i?b--:cf?b--:b++,d.__u|=4))):e.__k[s]=null;if(a)for(s=0;s(i?1:0)){for(c=r-1,s=r+1;c>=0||s=0?c--:s++])!=null&&(2&u.__u)==0&&p==u.key&&f==u.type)return d}return-1}function We(e,t,r){t[0]=="-"?e.setProperty(t,r??""):e[t]=r==null?"":typeof r!="number"||Kt.test(t)?r:r+"px"}function oe(e,t,r,o,c){var s,d;e:if(t=="style")if(typeof r=="string")e.style.cssText=r;else{if(typeof o=="string"&&(e.style.cssText=o=""),o)for(t in o)r&&t in r||We(e.style,t,"");if(r)for(t in r)o&&r[t]==o[t]||We(e.style,t,r[t])}else if(t[0]=="o"&&t[1]=="n")s=t!=(t=t.replace(Ge,"$1")),d=t.toLowerCase(),t=d in e||t=="onFocusOut"||t=="onFocusIn"?d.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+s]=r,r?o?r.u=o.u:(r.u=ye,e.addEventListener(t,s?ve:_e,s)):e.removeEventListener(t,s?ve:_e,s);else{if(c=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=r??"";break e}catch{}typeof r=="function"||(r==null||r===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&r==1?"":r))}}function Ve(e){return function(t){if(this.l){var r=this.l[t.type+e];if(t.t==null)t.t=ye++;else if(t.t0?e:ce(e)?e.map(et):B({},e)}function qt(e,t,r,o,c,s,d,p,f){var u,i,a,b,h,S,l,_=r.props,m=t.props,v=t.type;if(v=="svg"?c="http://www.w3.org/2000/svg":v=="math"?c="http://www.w3.org/1998/Math/MathML":c||(c="http://www.w3.org/1999/xhtml"),s!=null){for(u=0;u=r.__.length&&r.__.push({}),r.__[e]}function T(e){return de=1,Qt(mt,e)}function Qt(e,t,r){var o=pe(q++,2);if(o.t=e,!o.__c&&(o.__=[r?r(t):mt(void 0,t),function(p){var f=o.__N?o.__N[0]:o.__[0],u=o.t(f,p);f!==u&&(o.__N=[u,o.__[1]],o.__c.setState({}))}],o.__c=E,!E.__f)){var c=function(p,f,u){if(!o.__c.__H)return!0;var i=o.__c.__H.__.filter(function(b){return!!b.__c});if(i.every(function(b){return!b.__N}))return!s||s.call(this,p,f,u);var a=o.__c.props!==p;return i.forEach(function(b){if(b.__N){var h=b.__[0];b.__=b.__N,b.__N=void 0,h!==b.__[0]&&(a=!0)}}),s&&s.call(this,p,f,u)||a};E.__f=!0;var s=E.shouldComponentUpdate,d=E.componentWillUpdate;E.componentWillUpdate=function(p,f,u){if(this.__e){var i=s;s=void 0,c(p,f,u),s=i}d&&d.call(this,p,f,u)},E.shouldComponentUpdate=c}return o.__N||o.__}function H(e,t){var r=pe(q++,3);!O.__s&&Ae(r.__H,t)&&(r.__=e,r.u=t,E.__H.__h.push(r))}function pt(e,t){var r=pe(q++,4);!O.__s&&Ae(r.__H,t)&&(r.__=e,r.u=t,E.__h.push(r))}function $(e){return de=5,Yt(function(){return{current:e}},[])}function Yt(e,t){var r=pe(q++,7);return Ae(r.__H,t)&&(r.__=e(),r.__H=t,r.__h=e),r.__}function Zt(){for(var e;e=dt.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(ue),e.__H.__h.forEach(Se),e.__H.__h=[]}catch(t){e.__H.__h=[],O.__e(t,e.__v)}}O.__b=function(e){E=null,ot&&ot(e)},O.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),ct&&ct(e,t)},O.__r=function(e){st&&st(e),q=0;var t=(E=e.__c).__H;t&&(ke===E?(t.__h=[],E.__h=[],t.__.forEach(function(r){r.__N&&(r.__=r.__N),r.u=r.__N=void 0})):(t.__h.forEach(ue),t.__h.forEach(Se),t.__h=[],q=0)),ke=E},O.diffed=function(e){at&&at(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(dt.push(t)!==1&&rt===O.requestAnimationFrame||((rt=O.requestAnimationFrame)||en)(Zt)),t.__H.__.forEach(function(r){r.u&&(r.__H=r.u),r.u=void 0})),ke=E=null},O.__c=function(e,t){t.some(function(r){try{r.__h.forEach(ue),r.__h=r.__h.filter(function(o){return!o.__||Se(o)})}catch(o){t.some(function(c){c.__h&&(c.__h=[])}),t=[],O.__e(o,r.__v)}}),it&&it(e,t)},O.unmount=function(e){lt&<(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach(function(o){try{ue(o)}catch(c){t=c}}),r.__H=void 0,t&&O.__e(t,r.__v))};var ut=typeof requestAnimationFrame=="function";function en(e){var t,r=function(){clearTimeout(o),ut&&cancelAnimationFrame(t),setTimeout(e)},o=setTimeout(r,35);ut&&(t=requestAnimationFrame(r))}function ue(e){var t=E,r=e.__c;typeof r=="function"&&(e.__c=void 0,r()),E=t}function Se(e){var t=E;e.__c=e.__(),E=t}function Ae(e,t){return!e||e.length!==t.length||t.some(function(r,o){return r!==e[o]})}function mt(e,t){return typeof t=="function"?t(e):t}var tn=()=>({host:{sessionId:null,revision:null,state:null,clock:null,wave:null,clockSeq:-1,waveSeq:-1,connected:!1,resyncing:!1},ui:{selectedCueIds:new Set,anchorCueId:null,focusedCueId:null,cueScrollTop:0,cueColumnWidths:[38,105,280,220,50],drawerOpen:!1,drawerHeight:330,modalOpen:!1}});function ft(e,t){if(t.type==="snapshot")return e.host.sessionId=t.state.sessionId,e.host.revision=t.revision,e.host.state=t.state,e.host.clock=t.state.currentClock,e.host.connected=!0,e.host.resyncing=!1,"applied";if(t.type==="patch"){if(!e.host.state||t.baseRevision!==e.host.revision)return e.host.resyncing=!0,"resync";let{logsAppend:r,logsReset:o,nextCueCleared:c,...s}=t.changes;return Object.assign(e.host.state,s),c&&(e.host.state.nextCue=null),o?e.host.state.logs=o:r&&(e.host.state.logs=[...e.host.state.logs,...r].slice(-500)),e.host.revision=t.revision,"applied"}return t.type==="clock"?t.seq<=e.host.clockSeq?"ignored":(e.host.clockSeq=t.seq,e.host.clock=t.clock,"applied"):t.type==="wave"?t.seq<=e.host.waveSeq?"ignored":(e.host.waveSeq=t.seq,e.host.wave=t.wave,"applied"):"ignored"}var xe=class{value=tn();listeners=new Set;subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}notify(){for(let t of this.listeners)t()}updateUi(t){Object.assign(this.value.ui,t),this.notify()}},A=new xe;var nn=0,De=new Map;function z(e){window.invokeCSharpAction?.(JSON.stringify(e))}function R(e,t,r){let o=`web-${Date.now()}-${++nn}`,c={protocolVersion:1,type:"command",requestId:o,command:e,args:t,...r===void 0?{}:{expectedRevision:r}};return z(c),new Promise((s,d)=>De.set(o,{resolve:s,reject:d}))}function gt(e){let t=De.get(e.requestId);t&&(De.delete(e.requestId),e.ok?t.resolve(e.data):t.reject(e.error))}function bt(e){return typeof HTMLElement<"u"&&e instanceof HTMLElement&&(e.isContentEditable||/^(INPUT|TEXTAREA|SELECT)$/.test(e.tagName))}function _t(e,t){if(e.repeat||e.isComposing||t||bt(e.target))return null;let r=e.metaKey||e.ctrlKey,o=e.key.toLowerCase();return r&&o==="n"?"projectNew":r&&o==="o"?"projectOpen":r&&o==="s"?e.shiftKey?"projectSaveAs":"projectSave":r&&o==="z"?e.shiftKey?"redo":"undo":e.ctrlKey&&!e.metaKey&&o==="y"?"redo":e.key===" "?"cueSync":o==="l"?"toggleMute":o==="p"?"toggleDrawer":e.key==="Escape"?"closeDrawer":e.key==="ArrowUp"?"selectPrevious":e.key==="ArrowDown"?"selectNext":e.key==="Enter"?"editCue":e.key==="Delete"||e.key==="Backspace"?"deleteCue":null}var vt=e=>({ok:!0,value:e}),Re=e=>({ok:!1,message:e});function Ie(e,t=!1){if(!(t?/^[+-]\d{2}:\d{2}:\d{2}[:;]\d{2}$/:/^\d{2}:\d{2}:\d{2}[:;]\d{2}$/).test(e))return Re(t?"\xB1HH:MM:SS:FF \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044":"HH:MM:SS:FF \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044");let c=(t?e.slice(1):e).replace(";",":").split(":").map(Number);return(c[1]??99)<60&&(c[2]??99)<60?vt(e):Re("\u5206\u30FB\u79D2\u306F 00\u301C59 \u3067\u3059")}function Ee(e){return/^\/(?:[^\s/#*,?\[\]{}]+(?:\/[^\s/#*,?\[\]{}]+)*)?$/.test(e)?vt(e):Re("/ \u3067\u59CB\u307E\u308B OSC\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044")}var Z=e=>({name:"",memo:"",triggerTime:"00:00:00:00",frameRate:e,oscAddress:"/",additionalOscAddresses:"",argumentsText:"",targetHostIds:[],enabled:!0,sendTriggerTimeAsSeconds:!1,sendTimecode:"",triggerOffset:"",autoMuteOnFire:!1,autoUnmuteAfter:""}),ht=e=>({...Z(e.frameRate),name:e.name,memo:e.memo,triggerTime:e.triggerTime,oscAddress:e.oscAddress,additionalOscAddresses:e.additionalOscAddresses.join(` `),argumentsText:e.arguments.map(t=>`${t.type}:${t.value}`).join(` -`),targetHostIds:[...e.targetHostIds],enabled:e.enabled,sendTriggerTimeAsSeconds:e.sendTriggerTimeAsSeconds,sendTimecode:e.sendTimecode??"",triggerOffset:e.triggerOffset??"",autoMuteOnFire:e.autoMuteOnFire,autoUnmuteAfter:e.autoUnmuteAfter??""});function ee(e){let t=[];for(let[n,o]of e.split(/\r?\n/).entries()){if(!o.trim())continue;let c=o.indexOf(":"),s=o.slice(0,c),u=o.slice(c+1);if(c<0||!["int32","float32","string"].includes(s))return{error:`${n+1} \u884C\u76EE\u306F type:value \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044`};if(s==="string")t.push({type:s,value:u});else{let p=Number(u);if(!Number.isFinite(p)||s==="int32"&&!Number.isInteger(p))return{error:`${n+1} \u884C\u76EE\u306E\u5024\u304C\u4E0D\u6B63\u3067\u3059`};t.push(s==="int32"?{type:s,value:p}:{type:"float32",value:p})}}return{value:t}}function me(e){let t={};e.name.trim()||(t.name="\u540D\u524D\u306F\u5FC5\u9808\u3067\u3059");let n=Ee(e.triggerTime);n.ok||(t.triggerTime=n.message);let o=Oe(e.oscAddress);o.ok||(t.oscAddress=o.message);let c=e.additionalOscAddresses.split(/\r?\n|,/).map(u=>u.trim()).filter(Boolean);c.some(u=>!Oe(u).ok)&&(t.additionalOscAddresses="\u5404\u884C\u306B / \u3067\u59CB\u307E\u308B\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044");let s=ee(e.argumentsText);s.error&&(t.arguments=s.error);for(let[u,p,b]of[["sendTimecode",e.sendTimecode,!1],["triggerOffset",e.triggerOffset,!0],["autoUnmuteAfter",e.autoUnmuteAfter,!1]])p&&!Ee(p,b).ok&&(t[u]="\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059");return Object.keys(t).length?{errors:t}:{errors:t,draft:{name:e.name.trim(),memo:e.memo,triggerTime:e.triggerTime,frameRate:e.frameRate,oscAddress:e.oscAddress,additionalOscAddresses:c,arguments:s.value,targetHostIds:e.targetHostIds,enabled:e.enabled,sendTriggerTimeAsSeconds:e.sendTriggerTimeAsSeconds,sendTimecode:e.sendTimecode||null,triggerOffset:e.triggerOffset||null,autoMuteOnFire:e.autoMuteOnFire,autoUnmuteAfter:e.autoUnmuteAfter||null}}}var Pe=(e,t)=>Object.fromEntries(Object.entries(e.fieldErrors??{}).filter(([n])=>n.startsWith(t+".")).map(([n,o])=>[n.slice(t.length+1),o]));var nr=0,Ur=Array.isArray;function r(e,t,n,o,c,s){t||(t={});var u,p,b=t;if("ref"in b)for(p in b={},t)p=="ref"?u=t[p]:b[p]=t[p];var d={type:e,props:b,key:n,ref:u,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--nr,__i:-1,__u:0,__source:c,__self:s};if(typeof e=="function"&&(u=e.defaultProps))for(p in u)b[p]===void 0&&(b[p]=u[p]);return I.vnode&&I.vnode(d),d}function wt(e){return e.replace(/[0-9]/g,t=>String.fromCharCode(t.charCodeAt(0)-65248)).replace(/[^0-9]/g,"")}function Me(e,t=!1){let n=t&&e.startsWith("-")?"-":"+",c=(t&&/^[+-]/.test(e)?e.slice(1):e).split(/[:;]/).slice(0,4).map(wt);return{sign:n,parts:[c[0]??"",c[1]??"",c[2]??"",c[3]??""]}}function fe(e,t="+",n=!1){return e.every(o=>o==="")?"":`${n?t:""}${e.map(o=>o.padStart(2,"0")).join(":")}`}function yt(e,t,n){let o=wt(n),c=[...e];if(o.length<=2)return c[t]=o,{parts:c,focus:o.length===2&&t<3?t+1:null};for(let s=t,u=0;s<4&&u0?t-1:e==="ArrowRight"&&t<3?t+1:e==="Backspace"&&n&&t>0?t-1:null}var te=e=>e==="24"?23:e==="25"?24:29;function re(e,t,n=!1){if(e==="")return e;let{sign:o,parts:c}=Me(e,n);return fe(c.map((s,u)=>Ct(s,u,t)),o,n)}function Ct(e,t,n){if(e==="")return e;let o=t===0?23:t===1||t===2?59:te(n);return String(Math.min(o,Number(e))).padStart(2,"0")}function V({value:e,onChange:t,onCommit:n,frameRate:o,signed:c=!1,required:s=!1,disabled:u=!1,ariaLabel:p,class:b=""}){let d=Me(e,c),[i,a]=T(d.parts),[g,h]=T(d.sign),S=$([]),l=$(null),_=$(d.parts);M(()=>{if(e===l.current){l.current=null;return}let y=Me(e,c);_.current=y.parts,a(y.parts),h(y.sign)},[e,c]);let m=(y,C=g)=>{_.current=y,a(y);let x=fe(y,C,c);l.current=x,t?.(x)},v=y=>{y!==null&&S.current[y]?.focus()},w=y=>{let C=[..._.current];C[y]=Ct(C[y]??"",y,o),m(C),n?.(fe(C,g,c))};return r("span",{class:`timecode-input ${b}`,role:"group","aria-label":p,children:[c&&r("select",{class:"timecode-sign","aria-label":`${p}\u306E\u7B26\u53F7`,disabled:u,value:g,onChange:y=>{let C=y.currentTarget.value;h(C);let x=fe(_.current,C,!0);l.current=x,t?.(x),n?.(x)},children:[r("option",{value:"+",children:"+"}),r("option",{value:"-",children:"\u2212"})]}),i.map((y,C)=>r("span",{class:"timecode-segment",children:[r("input",{ref:x=>{S.current[C]=x},class:"timecode-part",inputMode:"numeric",pattern:"[0-9]*",maxlength:2,required:s,disabled:u,value:y,"aria-label":`${p} ${["\u6642","\u5206","\u79D2","\u30D5\u30EC\u30FC\u30E0"][C]??""}`,onBeforeInput:x=>{let P=x.data;P&&/[^0-9]/.test(P)&&x.preventDefault()},onCompositionStart:x=>{let P=x.currentTarget;P.blur(),window.setTimeout(()=>P.focus(),0)},onInput:x=>{let P=yt(_.current,C,x.currentTarget.value);m(P.parts),v(P.focus)},onPaste:x=>{x.preventDefault();let P=yt(_.current,C,x.clipboardData?.getData("text")??"");m(P.parts),v(P.focus)},onBlur:()=>w(C),onKeyDown:x=>{if(x.key==="ArrowUp"||x.key==="ArrowDown"){x.preventDefault();let W=C===0?23:C===1||C===2?59:te(o),f=[..._.current],D=Number(f[C]||0);f[C]=String(Math.max(0,Math.min(W,D+(x.key==="ArrowUp"?1:-1)))).padStart(2,"0"),m(f);return}let P=or(x.key,C,y==="");P!==null&&(x.preventDefault(),v(P))}}),C<3&&r("span",{class:"timecode-colon","aria-hidden":"true",children:":"})]})),r("small",{class:"timecode-limit",children:["FF 00\u2013",String(te(o)).padStart(2,"0")]})]})}function ne(e){return M(()=>{let t=n=>{n.key==="Escape"&&!n.isComposing&&(n.preventDefault(),e.close())};return window.addEventListener("keydown",t,!0),()=>window.removeEventListener("keydown",t,!0)},[e.close]),r("div",{class:"modal-shade",onMouseDown:t=>{t.target===t.currentTarget&&e.close()},children:r("div",{class:`dialog ${e.wide?"wide":""} ${e.class??""}`,role:"dialog","aria-modal":"true",onKeyDown:t=>t.stopPropagation(),children:[r("div",{class:"dialog-title",children:[r("b",{children:e.title}),e.titleExtra,r("button",{class:"btn",onClick:e.close,children:"\u9589\u3058\u308B"})]}),e.children]})})}var ge=ne,sr=({errors:e,name:t})=>r("small",{class:"field-error",children:e[t]??""}),ar=["24","25","29.97df","30"],U=({label:e,name:t,errors:n,title:o,top:c,children:s})=>r("div",{class:`cue-row ${c?"top":""}`,children:[r("span",{class:"lbl",title:o,children:e}),r("div",{class:"ctl",children:[s,t&&n[t]&&r("small",{class:"field-error",children:n[t]})]})]});function He({cue:e,frameRate:t,hosts:n,close:o,done:c}){let[s,u]=T(()=>e?ht(e):{...Z(t),targetHostIds:n.map(l=>l.id)}),[p,b]=T({}),d=(l,_)=>u(m=>({...m,[l]:_})),i=(l,_,{signed:m=!1,disabled:v=!1}={})=>r(V,{ariaLabel:_,value:s[l],frameRate:s.frameRate,signed:m,disabled:v,required:l==="triggerTime",onChange:w=>d(l,w)}),a=(l,_)=>r("label",{class:"check",children:[r("input",{type:"checkbox",checked:s[l],onChange:m=>d(l,m.currentTarget.checked)}),_]}),g=(l,_,m,v)=>r("div",{class:"check-row",children:[r("label",{class:"check",children:[r("input",{type:"checkbox","aria-label":`${_}\u3092\u6307\u5B9A`,disabled:!m,checked:s[l]!=="",onChange:w=>d(l,w.currentTarget.checked?v:"")}),"\u6307\u5B9A"]}),i(l,_,{disabled:!m||s[l]===""})]}),h=async l=>{l.preventDefault();let _=me(s);if(!_.draft){b(_.errors);return}try{await R(e?"cue.update":"cue.add",e?{id:e.id,cue:_.draft}:{cue:_.draft}),c()}catch(m){b(Pe(m,"cue"))}},S=r("label",{class:"cue-frame-rate",children:[r("span",{children:"\u57FA\u6E96\u30D5\u30EC\u30FC\u30E0\u30EC\u30FC\u30C8"}),r("select",{class:"field",value:s.frameRate,onChange:l=>{let _=l.currentTarget.value;u(m=>({...m,frameRate:_,triggerTime:re(m.triggerTime,_),sendTimecode:re(m.sendTimecode,_),triggerOffset:re(m.triggerOffset,_,!0),autoUnmuteAfter:re(m.autoUnmuteAfter,_)}))},children:ar.map(l=>r("option",{value:l,children:[l," fps"]}))}),r("small",{children:["FF 00\u2013",String(te(s.frameRate)).padStart(2,"0")]})]});return r(ge,{title:e?"\u30AD\u30E5\u30FC\u3092\u7DE8\u96C6":"\u30AD\u30E5\u30FC\u3092\u8FFD\u52A0",close:o,class:"cue",titleExtra:S,children:r("form",{onSubmit:h,class:"cue-edit",children:[r(U,{label:"\u540D\u524D",name:"name",errors:p,children:r("input",{autofocus:!0,class:"field","aria-label":"\u540D\u524D",value:s.name,onInput:l=>d("name",l.currentTarget.value)})}),r(U,{label:"\u30C8\u30EA\u30AC\u30FC\u6642\u9593",name:"triggerTime",errors:p,children:i("triggerTime","\u30C8\u30EA\u30AC\u30FC\u6642\u9593")}),r(U,{label:"\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",name:"triggerOffset",errors:p,title:"\u767A\u706B\u30BF\u30A4\u30DF\u30F3\u30B0\u3092\xB1\u305A\u3089\u3057\u307E\u3059\u3002\u5B9F\u969B\u306E\u767A\u706B\u6642\u523B = \u30C8\u30EA\u30AC\u30FC\u6642\u9593 \xB1 \u3053\u306E\u5024",children:i("triggerOffset","\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",{signed:!0})}),r(U,{label:"OSC\u30A2\u30C9\u30EC\u30B9",errors:p,top:!0,children:[r("input",{class:"field mono","aria-label":"OSC\u30A2\u30C9\u30EC\u30B9",value:s.oscAddress,onInput:l=>d("oscAddress",l.currentTarget.value)}),p.oscAddress&&r("small",{class:"field-error",children:p.oscAddress}),r("span",{class:"hint",children:"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9\uFF081\u884C1\u4EF6\u30FB\u5F15\u6570\u306A\u3057\u3067\u4E00\u7DD2\u306B\u9001\u4FE1\u3055\u308C\u307E\u3059\uFF09"}),r("textarea",{class:"mono","aria-label":"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9",value:s.additionalOscAddresses,onInput:l=>d("additionalOscAddresses",l.currentTarget.value)}),p.additionalOscAddresses&&r("small",{class:"field-error",children:p.additionalOscAddresses})]}),r(U,{label:"TC\u79D2\u6570\u9001\u4FE1",errors:p,children:a("sendTriggerTimeAsSeconds","\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u3092\u79D2\u6570(float)\u3067\u5148\u982D\u5F15\u6570\u306B\u8FFD\u52A0")}),r(U,{label:"\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",name:"sendTimecode",errors:p,title:"\u79D2\u6570\u9001\u4FE1\u3067\u9001\u308B\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u3002\u672A\u6307\u5B9A\u306A\u3089\u30C8\u30EA\u30AC\u30FC\u6642\u9593\u3092\u305D\u306E\u307E\u307E\u9001\u308A\u307E\u3059",children:g("sendTimecode","\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",s.sendTriggerTimeAsSeconds,s.triggerTime||"00:00:00:00")}),r(U,{label:"\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8",errors:p,title:"\u3053\u306E\u30AD\u30E5\u30FC\u306E\u767A\u706B\u3068\u540C\u6642\u306BMUTE\uFF08\u81EA\u52D5\u30AD\u30E5\u30FC\u767A\u706B\u306E\u505C\u6B62\uFF09\u3092\u639B\u3051\u307E\u3059\u3002\u518D\u751F\u4E2D\u306B\u4E0D\u616E\u306ETC\u304C\u98DB\u3093\u3067\u304D\u3066\u3082\u4ED6\u306E\u30AD\u30E5\u30FC\u304C\u8AA4\u767A\u706B\u3057\u307E\u305B\u3093",children:a("autoMuteOnFire","\u767A\u706B\u3068\u540C\u6642\u306B\u81EA\u52D5\u3067\u30DF\u30E5\u30FC\u30C8\u3059\u308B")}),r(U,{label:"\u30DF\u30E5\u30FC\u30C8\u89E3\u9664",name:"autoUnmuteAfter",errors:p,title:"\u6307\u5B9A\u6642\u9593\u306E\u7D4C\u904E\u5F8C\u306BMUTE\u3092\u81EA\u52D5\u89E3\u9664\u3057\u307E\u3059\u3002\u672A\u6307\u5B9A\u306A\u3089\u624B\u52D5\u3067\u89E3\u9664\u3059\u308B\u307E\u3067\u30DF\u30E5\u30FC\u30C8\u3092\u7DAD\u6301\u3057\u307E\u3059",children:g("autoUnmuteAfter","\u30DF\u30E5\u30FC\u30C8\u89E3\u9664\u6642\u9593",s.autoMuteOnFire,"00:00:00:00")}),r(U,{label:"OSC\u5F15\u6570",name:"arguments",errors:p,top:!0,title:"type:value \u3092 1 \u884C 1 \u4EF6\u3002\u4F8B: int32:1 / float32:0.5 / string:GO",children:r("textarea",{class:"mono","aria-label":"OSC\u5F15\u6570",placeholder:`int32:1 +`),targetHostIds:[...e.targetHostIds],enabled:e.enabled,sendTriggerTimeAsSeconds:e.sendTriggerTimeAsSeconds,sendTimecode:e.sendTimecode??"",triggerOffset:e.triggerOffset??"",autoMuteOnFire:e.autoMuteOnFire,autoUnmuteAfter:e.autoUnmuteAfter??""});function ee(e){let t=[];for(let[r,o]of e.split(/\r?\n/).entries()){if(!o.trim())continue;let c=o.indexOf(":"),s=o.slice(0,c),d=o.slice(c+1);if(c<0||!["int32","float32","string"].includes(s))return{error:`${r+1} \u884C\u76EE\u306F type:value \u5F62\u5F0F\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044`};if(s==="string")t.push({type:s,value:d});else{let p=Number(d);if(!Number.isFinite(p)||s==="int32"&&!Number.isInteger(p))return{error:`${r+1} \u884C\u76EE\u306E\u5024\u304C\u4E0D\u6B63\u3067\u3059`};t.push(s==="int32"?{type:s,value:p}:{type:"float32",value:p})}}return{value:t}}function me(e){let t={};e.name.trim()||(t.name="\u540D\u524D\u306F\u5FC5\u9808\u3067\u3059");let r=Ie(e.triggerTime);r.ok||(t.triggerTime=r.message);let o=Ee(e.oscAddress);o.ok||(t.oscAddress=o.message);let c=e.additionalOscAddresses.split(/\r?\n|,/).map(d=>d.trim()).filter(Boolean);c.some(d=>!Ee(d).ok)&&(t.additionalOscAddresses="\u5404\u884C\u306B / \u3067\u59CB\u307E\u308B\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044");let s=ee(e.argumentsText);s.error&&(t.arguments=s.error);for(let[d,p,f]of[["sendTimecode",e.sendTimecode,!1],["triggerOffset",e.triggerOffset,!0],["autoUnmuteAfter",e.autoUnmuteAfter,!1]])p&&!Ie(p,f).ok&&(t[d]="\u6642\u523B\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059");return Object.keys(t).length?{errors:t}:{errors:t,draft:{name:e.name.trim(),memo:e.memo,triggerTime:e.triggerTime,frameRate:e.frameRate,oscAddress:e.oscAddress,additionalOscAddresses:c,arguments:s.value,targetHostIds:e.targetHostIds,enabled:e.enabled,sendTriggerTimeAsSeconds:e.sendTriggerTimeAsSeconds,sendTimecode:e.sendTimecode||null,triggerOffset:e.triggerOffset||null,autoMuteOnFire:e.autoMuteOnFire,autoUnmuteAfter:e.autoUnmuteAfter||null}}}var Oe=(e,t)=>Object.fromEntries(Object.entries(e.fieldErrors??{}).filter(([r])=>r.startsWith(t+".")).map(([r,o])=>[r.slice(t.length+1),o]));var rn=0,jn=Array.isArray;function n(e,t,r,o,c,s){t||(t={});var d,p,f=t;if("ref"in f)for(p in f={},t)p=="ref"?d=t[p]:f[p]=t[p];var u={type:e,props:f,key:r,ref:d,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--rn,__i:-1,__u:0,__source:c,__self:s};if(typeof e=="function"&&(d=e.defaultProps))for(p in d)f[p]===void 0&&(f[p]=d[p]);return I.vnode&&I.vnode(u),u}function wt(e){return e.replace(/[0-9]/g,t=>String.fromCharCode(t.charCodeAt(0)-65248)).replace(/[^0-9]/g,"")}function Pe(e,t=!1){let r=t&&e.startsWith("-")?"-":"+",c=(t&&/^[+-]/.test(e)?e.slice(1):e).split(/[:;]/).slice(0,4).map(wt);return{sign:r,parts:[c[0]??"",c[1]??"",c[2]??"",c[3]??""]}}function fe(e,t="+",r=!1){return e.every(o=>o==="")?"":`${r?t:""}${e.map(o=>o.padStart(2,"0")).join(":")}`}function yt(e,t,r){let o=wt(r),c=[...e];if(o.length<=2)return c[t]=o,{parts:c,focus:o.length===2&&t<3?t+1:null};for(let s=t,d=0;s<4&&d0?t-1:e==="ArrowRight"&&t<3?t+1:e==="Backspace"&&r&&t>0?t-1:null}var te=e=>e==="24"?23:e==="25"?24:29;function ne(e,t,r=!1){if(e==="")return e;let{sign:o,parts:c}=Pe(e,r);return fe(c.map((s,d)=>Ct(s,d,t)),o,r)}function Ct(e,t,r){if(e==="")return e;let o=t===0?23:t===1||t===2?59:te(r);return String(Math.min(o,Number(e))).padStart(2,"0")}function V({value:e,onChange:t,onCommit:r,frameRate:o,signed:c=!1,required:s=!1,disabled:d=!1,ariaLabel:p,class:f=""}){let u=Pe(e,c),[i,a]=T(u.parts),[b,h]=T(u.sign),S=$([]),l=$(null),_=$(u.parts);H(()=>{if(e===l.current){l.current=null;return}let y=Pe(e,c);_.current=y.parts,a(y.parts),h(y.sign)},[e,c]);let m=(y,C=b)=>{_.current=y,a(y);let D=fe(y,C,c);l.current=D,t?.(D)},v=y=>{y!==null&&S.current[y]?.focus()},w=y=>{let C=[..._.current];C[y]=Ct(C[y]??"",y,o),m(C),r?.(fe(C,b,c))};return n("span",{class:`timecode-input ${f}`,role:"group","aria-label":p,children:[c&&n("select",{class:"timecode-sign","aria-label":`${p}\u306E\u7B26\u53F7`,disabled:d,value:b,onChange:y=>{let C=y.currentTarget.value;h(C);let D=fe(_.current,C,!0);l.current=D,t?.(D),r?.(D)},children:[n("option",{value:"+",children:"+"}),n("option",{value:"-",children:"\u2212"})]}),i.map((y,C)=>n("span",{class:"timecode-segment",children:[n("input",{ref:D=>{S.current[C]=D},class:"timecode-part",inputMode:"numeric",pattern:"[0-9]*",maxlength:2,required:s,disabled:d,value:y,"aria-label":`${p} ${["\u6642","\u5206","\u79D2","\u30D5\u30EC\u30FC\u30E0"][C]??""}`,onBeforeInput:D=>{let P=D.data;P&&/[^0-9]/.test(P)&&D.preventDefault()},onCompositionStart:D=>{let P=D.currentTarget;P.blur(),window.setTimeout(()=>P.focus(),0)},onInput:D=>{let P=yt(_.current,C,D.currentTarget.value);m(P.parts),v(P.focus)},onPaste:D=>{D.preventDefault();let P=yt(_.current,C,D.clipboardData?.getData("text")??"");m(P.parts),v(P.focus)},onBlur:()=>w(C),onKeyDown:D=>{if(D.key==="ArrowUp"||D.key==="ArrowDown"){D.preventDefault();let W=C===0?23:C===1||C===2?59:te(o),g=[..._.current],x=Number(g[C]||0);g[C]=String(Math.max(0,Math.min(W,x+(D.key==="ArrowUp"?1:-1)))).padStart(2,"0"),m(g);return}let P=on(D.key,C,y==="");P!==null&&(D.preventDefault(),v(P))}}),C<3&&n("span",{class:"timecode-colon","aria-hidden":"true",children:":"})]})),n("small",{class:"timecode-limit",children:["FF 00\u2013",String(te(o)).padStart(2,"0")]})]})}function re(e){return H(()=>{let t=r=>{r.key==="Escape"&&!r.isComposing&&(r.preventDefault(),e.close())};return window.addEventListener("keydown",t,!0),()=>window.removeEventListener("keydown",t,!0)},[e.close]),n("div",{class:"modal-shade",onMouseDown:t=>{t.target===t.currentTarget&&e.close()},children:n("div",{class:`dialog ${e.wide?"wide":""} ${e.class??""}`,role:"dialog","aria-modal":"true",onKeyDown:t=>t.stopPropagation(),children:[n("div",{class:"dialog-title",children:[n("b",{children:e.title}),e.titleExtra,n("button",{class:"btn",onClick:e.close,children:"\u9589\u3058\u308B"})]}),e.children]})})}var ge=re,sn=({errors:e,name:t})=>n("small",{class:"field-error",children:e[t]??""}),an=["24","25","29.97df","30"],U=({label:e,name:t,errors:r,title:o,top:c,children:s})=>n("div",{class:`cue-row ${c?"top":""}`,children:[n("span",{class:"lbl",title:o,children:e}),n("div",{class:"ctl",children:[s,t&&r[t]&&n("small",{class:"field-error",children:r[t]})]})]});function Me({cue:e,frameRate:t,hosts:r,close:o,done:c}){let[s,d]=T(()=>e?ht(e):{...Z(t),targetHostIds:r.map(l=>l.id)}),[p,f]=T({}),u=(l,_)=>d(m=>({...m,[l]:_})),i=(l,_,{signed:m=!1,disabled:v=!1}={})=>n(V,{ariaLabel:_,value:s[l],frameRate:s.frameRate,signed:m,disabled:v,required:l==="triggerTime",onChange:w=>u(l,w)}),a=(l,_)=>n("label",{class:"check",children:[n("input",{type:"checkbox",checked:s[l],onChange:m=>u(l,m.currentTarget.checked)}),_]}),b=(l,_,m,v)=>n("div",{class:"check-row",children:[n("label",{class:"check",children:[n("input",{type:"checkbox","aria-label":`${_}\u3092\u6307\u5B9A`,disabled:!m,checked:s[l]!=="",onChange:w=>u(l,w.currentTarget.checked?v:"")}),"\u6307\u5B9A"]}),i(l,_,{disabled:!m||s[l]===""})]}),h=async l=>{l.preventDefault();let _=me(s);if(!_.draft){f(_.errors);return}try{await R(e?"cue.update":"cue.add",e?{id:e.id,cue:_.draft}:{cue:_.draft}),c()}catch(m){f(Oe(m,"cue"))}},S=n("label",{class:"cue-frame-rate",children:[n("span",{children:"\u57FA\u6E96\u30D5\u30EC\u30FC\u30E0\u30EC\u30FC\u30C8"}),n("select",{class:"field",value:s.frameRate,onChange:l=>{let _=l.currentTarget.value;d(m=>({...m,frameRate:_,triggerTime:ne(m.triggerTime,_),sendTimecode:ne(m.sendTimecode,_),triggerOffset:ne(m.triggerOffset,_,!0),autoUnmuteAfter:ne(m.autoUnmuteAfter,_)}))},children:an.map(l=>n("option",{value:l,children:[l," fps"]}))}),n("small",{children:["FF 00\u2013",String(te(s.frameRate)).padStart(2,"0")]})]});return n(ge,{title:e?"\u30AD\u30E5\u30FC\u3092\u7DE8\u96C6":"\u30AD\u30E5\u30FC\u3092\u8FFD\u52A0",close:o,class:"cue",titleExtra:S,children:n("form",{onSubmit:h,class:"cue-edit",children:[n(U,{label:"\u540D\u524D",name:"name",errors:p,children:n("input",{autofocus:!0,class:"field","aria-label":"\u540D\u524D",value:s.name,onInput:l=>u("name",l.currentTarget.value)})}),n(U,{label:"\u30C8\u30EA\u30AC\u30FC\u6642\u9593",name:"triggerTime",errors:p,children:i("triggerTime","\u30C8\u30EA\u30AC\u30FC\u6642\u9593")}),n(U,{label:"\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",name:"triggerOffset",errors:p,title:"\u767A\u706B\u30BF\u30A4\u30DF\u30F3\u30B0\u3092\xB1\u305A\u3089\u3057\u307E\u3059\u3002\u5B9F\u969B\u306E\u767A\u706B\u6642\u523B = \u30C8\u30EA\u30AC\u30FC\u6642\u9593 \xB1 \u3053\u306E\u5024",children:i("triggerOffset","\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",{signed:!0})}),n(U,{label:"OSC\u30A2\u30C9\u30EC\u30B9",errors:p,top:!0,children:[n("input",{class:"field mono","aria-label":"OSC\u30A2\u30C9\u30EC\u30B9",value:s.oscAddress,onInput:l=>u("oscAddress",l.currentTarget.value)}),p.oscAddress&&n("small",{class:"field-error",children:p.oscAddress}),n("span",{class:"hint",children:"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9\uFF081\u884C1\u4EF6\u30FB\u5F15\u6570\u306A\u3057\u3067\u4E00\u7DD2\u306B\u9001\u4FE1\u3055\u308C\u307E\u3059\uFF09"}),n("textarea",{class:"mono","aria-label":"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9",value:s.additionalOscAddresses,onInput:l=>u("additionalOscAddresses",l.currentTarget.value)}),p.additionalOscAddresses&&n("small",{class:"field-error",children:p.additionalOscAddresses})]}),n(U,{label:"TC\u79D2\u6570\u9001\u4FE1",errors:p,children:a("sendTriggerTimeAsSeconds","\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u3092\u79D2\u6570(float)\u3067\u5148\u982D\u5F15\u6570\u306B\u8FFD\u52A0")}),n(U,{label:"\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",name:"sendTimecode",errors:p,title:"\u79D2\u6570\u9001\u4FE1\u3067\u9001\u308B\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u3002\u672A\u6307\u5B9A\u306A\u3089\u30C8\u30EA\u30AC\u30FC\u6642\u9593\u3092\u305D\u306E\u307E\u307E\u9001\u308A\u307E\u3059",children:b("sendTimecode","\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",s.sendTriggerTimeAsSeconds,s.triggerTime||"00:00:00:00")}),n(U,{label:"\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8",errors:p,title:"\u3053\u306E\u30AD\u30E5\u30FC\u306E\u767A\u706B\u3068\u540C\u6642\u306BMUTE\uFF08\u81EA\u52D5\u30AD\u30E5\u30FC\u767A\u706B\u306E\u505C\u6B62\uFF09\u3092\u639B\u3051\u307E\u3059\u3002\u518D\u751F\u4E2D\u306B\u4E0D\u616E\u306ETC\u304C\u98DB\u3093\u3067\u304D\u3066\u3082\u4ED6\u306E\u30AD\u30E5\u30FC\u304C\u8AA4\u767A\u706B\u3057\u307E\u305B\u3093",children:a("autoMuteOnFire","\u767A\u706B\u3068\u540C\u6642\u306B\u81EA\u52D5\u3067\u30DF\u30E5\u30FC\u30C8\u3059\u308B")}),n(U,{label:"\u30DF\u30E5\u30FC\u30C8\u89E3\u9664",name:"autoUnmuteAfter",errors:p,title:"\u6307\u5B9A\u6642\u9593\u306E\u7D4C\u904E\u5F8C\u306BMUTE\u3092\u81EA\u52D5\u89E3\u9664\u3057\u307E\u3059\u3002\u672A\u6307\u5B9A\u306A\u3089\u624B\u52D5\u3067\u89E3\u9664\u3059\u308B\u307E\u3067\u30DF\u30E5\u30FC\u30C8\u3092\u7DAD\u6301\u3057\u307E\u3059",children:b("autoUnmuteAfter","\u30DF\u30E5\u30FC\u30C8\u89E3\u9664\u6642\u9593",s.autoMuteOnFire,"00:00:00:00")}),n(U,{label:"OSC\u5F15\u6570",name:"arguments",errors:p,top:!0,title:"type:value \u3092 1 \u884C 1 \u4EF6\u3002\u4F8B: int32:1 / float32:0.5 / string:GO",children:n("textarea",{class:"mono","aria-label":"OSC\u5F15\u6570",placeholder:`int32:1 float32:0.5 -string:GO`,value:s.argumentsText,onInput:l=>d("argumentsText",l.currentTarget.value)})}),r(U,{label:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8",name:"targetHostIds",errors:p,top:!0,children:r("div",{class:"host-pick",children:[r("div",{class:"host-pick-actions",children:[r("button",{type:"button",class:"btn mini",disabled:!n.length,onClick:()=>d("targetHostIds",n.map(l=>l.id)),children:"\u5168\u9078\u629E"}),r("button",{type:"button",class:"btn mini",disabled:!s.targetHostIds.length,onClick:()=>d("targetHostIds",[]),children:"\u5168\u89E3\u9664"})]}),r("div",{class:"host-pick-list",children:n.length?n.map(l=>r("label",{class:"check",children:[r("input",{type:"checkbox",checked:s.targetHostIds.includes(l.id),onChange:_=>d("targetHostIds",_.currentTarget.checked?[...s.targetHostIds,l.id]:s.targetHostIds.filter(m=>m!==l.id))}),l.name]})):r("span",{class:"host-pick-empty",children:["\u30DB\u30B9\u30C8\u304C\u672A\u767B\u9332\u3067\u3059\u3002",r("br",{}),"\u5148\u306B\u300C\u30DB\u30B9\u30C8\u7BA1\u7406\u300D\u304B\u3089\u30DB\u30B9\u30C8\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044"]})})]})}),r(U,{label:"\u30E1\u30E2",name:"memo",errors:p,top:!0,children:r("textarea",{"aria-label":"\u30E1\u30E2",value:s.memo,onInput:l=>d("memo",l.currentTarget.value)})}),r(U,{label:"",errors:p,children:a("enabled","\u6709\u52B9")}),r("div",{class:"dialog-actions",children:[r("button",{type:"button",class:"btn",onClick:o,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn primary",children:e?"\u66F4\u65B0":"\u8FFD\u52A0"})]})]})})}function Tt({cue:e,close:t,done:n}){let[o,c]=T(2),[s,u]=T("00:00:00:00"),[p,b]=T("");return r(ge,{title:"\u9023\u7D9A\u8907\u88FD",close:t,children:r("form",{class:"simple-form",onSubmit:async d=>{d.preventDefault();try{await R("cue.duplicate",{id:e.id,count:o,interval:s}),n()}catch(i){b(i.message)}},children:[r("label",{children:["\u8907\u88FD\u6570",r("input",{class:"field count",type:"number",min:"1",max:"500",value:o,onInput:d=>c(Number(d.currentTarget.value))})]}),r("div",{class:"simple-row",children:[r("span",{children:"\u9593\u9694"}),r(V,{ariaLabel:"\u8907\u88FD\u306E\u9593\u9694",value:s,frameRate:e.frameRate,onChange:u})]}),r("small",{class:"hint",children:["\u300C",e.name,"\u300D\u306E ",e.effectiveTriggerTime," \u304B\u3089\u9593\u9694\u305A\u3064\u5F8C\u308D\u306B\u4E26\u3079\u307E\u3059"]}),p&&r("small",{class:"field-error",children:p}),r("div",{class:"dialog-actions",children:[r("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn primary",children:"\u8907\u88FD"})]})]})})}function kt({ids:e,hosts:t,frameRate:n,close:o,done:c,notice:s}){let[u,p]=T(new Set),[b,d]=T({}),[i,a]=T({}),g={oscAddress:"OSC\u30A2\u30C9\u30EC\u30B9",additionalOscAddresses:"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9\uFF08\u30AB\u30F3\u30DE\u533A\u5207\u308A\uFF09",sendTriggerTimeAsSeconds:"TC\u79D2\u6570\u9001\u4FE1",sendTimecode:"\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",targetHostIds:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8",arguments:"OSC\u5F15\u6570\uFF08type:value\u30011 \u884C 1 \u4EF6\uFF09",triggerOffset:"\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",autoMuteOnFire:"\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8",autoUnmuteAfter:"\u30DF\u30E5\u30FC\u30C8\u89E3\u9664",memo:"\u30E1\u30E2",enabled:"\u6709\u52B9"},h=["sendTimecode","triggerOffset","autoUnmuteAfter"],S=["enabled","sendTriggerTimeAsSeconds","autoMuteOnFire"];return r(ge,{title:`${e.length} \u4EF6\u3092\u4E00\u62EC\u7DE8\u96C6`,close:o,wide:!0,children:r("form",{class:"batch-form",onSubmit:async l=>{l.preventDefault();let _={};for(let m of u){let v=b[m];if(v===void 0&&(v=S.includes(m)?!1:m==="targetHostIds"?[]:""),m==="arguments"){let w=ee(String(v));if(w.error){a({arguments:w.error});return}v=w.value??[]}m==="additionalOscAddresses"&&(v=String(v).split(",").map(w=>w.trim()).filter(Boolean)),h.includes(m)&&v===""&&(v=null),_[m]=v}try{let m=await R("cue.batchUpdate",{ids:e,changes:_});m.offsetSkipped>0&&s(`${m.offsetSkipped} \u4EF6\u306F\u30AA\u30D5\u30BB\u30C3\u30C8\u9069\u7528\u5F8C\u306E\u6642\u523B\u304C\u7BC4\u56F2\u5916\u306E\u305F\u3081\u30AA\u30D5\u30BB\u30C3\u30C8\u3092\u4ED8\u3051\u307E\u305B\u3093\u3067\u3057\u305F`),c()}catch(m){a(Pe(m,"changes"))}},children:[Object.entries(g).map(([l,_])=>r("div",{class:"batch-row",children:[r("label",{children:[r("input",{type:"checkbox",checked:u.has(l),onChange:m=>{let v=m.currentTarget.checked;p(w=>{let y=new Set(w);return v?y.add(l):y.delete(l),y})}}),_]}),r("div",{children:[S.includes(l)?r("input",{type:"checkbox",onChange:m=>{let v=m.currentTarget.checked;d(w=>({...w,[l]:v}))}}):l==="targetHostIds"?r("div",{children:t.map(m=>r("label",{children:[r("input",{type:"checkbox",onChange:v=>{let w=v.currentTarget.checked;d(y=>{let C=y[l]??[];return{...y,[l]:w?[...C,m.id]:C.filter(x=>x!==m.id)}})}}),m.name]}))}):h.includes(l)?r(V,{ariaLabel:_,value:String(b[l]??""),frameRate:n,signed:l==="triggerOffset",onChange:m=>d(v=>({...v,[l]:m}))}):l==="arguments"?r("textarea",{class:"mono","aria-label":_,placeholder:`int32:1 +string:GO`,value:s.argumentsText,onInput:l=>u("argumentsText",l.currentTarget.value)})}),n(U,{label:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8",name:"targetHostIds",errors:p,top:!0,children:n("div",{class:"host-pick",children:[n("div",{class:"host-pick-actions",children:[n("button",{type:"button",class:"btn mini",disabled:!r.length,onClick:()=>u("targetHostIds",r.map(l=>l.id)),children:"\u5168\u9078\u629E"}),n("button",{type:"button",class:"btn mini",disabled:!s.targetHostIds.length,onClick:()=>u("targetHostIds",[]),children:"\u5168\u89E3\u9664"})]}),n("div",{class:"host-pick-list",children:r.length?r.map(l=>n("label",{class:"check",children:[n("input",{type:"checkbox",checked:s.targetHostIds.includes(l.id),onChange:_=>u("targetHostIds",_.currentTarget.checked?[...s.targetHostIds,l.id]:s.targetHostIds.filter(m=>m!==l.id))}),l.name]})):n("span",{class:"host-pick-empty",children:["\u30DB\u30B9\u30C8\u304C\u672A\u767B\u9332\u3067\u3059\u3002",n("br",{}),"\u5148\u306B\u300C\u30DB\u30B9\u30C8\u7BA1\u7406\u300D\u304B\u3089\u30DB\u30B9\u30C8\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044"]})})]})}),n(U,{label:"\u30E1\u30E2",name:"memo",errors:p,top:!0,children:n("textarea",{"aria-label":"\u30E1\u30E2",value:s.memo,onInput:l=>u("memo",l.currentTarget.value)})}),n(U,{label:"",errors:p,children:a("enabled","\u6709\u52B9")}),n("div",{class:"dialog-actions",children:[n("button",{type:"button",class:"btn",onClick:o,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn primary",children:e?"\u66F4\u65B0":"\u8FFD\u52A0"})]})]})})}function Tt({cue:e,close:t,done:r}){let[o,c]=T(2),[s,d]=T("00:00:00:00"),[p,f]=T("");return n(ge,{title:"\u9023\u7D9A\u8907\u88FD",close:t,children:n("form",{class:"simple-form",onSubmit:async u=>{u.preventDefault();try{await R("cue.duplicate",{id:e.id,count:o,interval:s}),r()}catch(i){f(i.message)}},children:[n("label",{children:["\u8907\u88FD\u6570",n("input",{class:"field count",type:"number",min:"1",max:"500",value:o,onInput:u=>c(Number(u.currentTarget.value))})]}),n("div",{class:"simple-row",children:[n("span",{children:"\u9593\u9694"}),n(V,{ariaLabel:"\u8907\u88FD\u306E\u9593\u9694",value:s,frameRate:e.frameRate,onChange:d})]}),n("small",{class:"hint",children:["\u300C",e.name,"\u300D\u306E ",e.effectiveTriggerTime," \u304B\u3089\u9593\u9694\u305A\u3064\u5F8C\u308D\u306B\u4E26\u3079\u307E\u3059"]}),p&&n("small",{class:"field-error",children:p}),n("div",{class:"dialog-actions",children:[n("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn primary",children:"\u8907\u88FD"})]})]})})}function kt({ids:e,hosts:t,frameRate:r,close:o,done:c,notice:s}){let[d,p]=T(new Set),[f,u]=T({}),[i,a]=T({}),b={oscAddress:"OSC\u30A2\u30C9\u30EC\u30B9",additionalOscAddresses:"\u8FFD\u52A0\u30A2\u30C9\u30EC\u30B9\uFF08\u30AB\u30F3\u30DE\u533A\u5207\u308A\uFF09",sendTriggerTimeAsSeconds:"TC\u79D2\u6570\u9001\u4FE1",sendTimecode:"\u9001\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",targetHostIds:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8",arguments:"OSC\u5F15\u6570\uFF08type:value\u30011 \u884C 1 \u4EF6\uFF09",triggerOffset:"\u30C8\u30EA\u30AC\u30FC\u30AA\u30D5\u30BB\u30C3\u30C8",autoMuteOnFire:"\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8",autoUnmuteAfter:"\u30DF\u30E5\u30FC\u30C8\u89E3\u9664",memo:"\u30E1\u30E2",enabled:"\u6709\u52B9"},h=["sendTimecode","triggerOffset","autoUnmuteAfter"],S=["enabled","sendTriggerTimeAsSeconds","autoMuteOnFire"];return n(ge,{title:`${e.length} \u4EF6\u3092\u4E00\u62EC\u7DE8\u96C6`,close:o,wide:!0,children:n("form",{class:"batch-form",onSubmit:async l=>{l.preventDefault();let _={};for(let m of d){let v=f[m];if(v===void 0&&(v=S.includes(m)?!1:m==="targetHostIds"?[]:""),m==="arguments"){let w=ee(String(v));if(w.error){a({arguments:w.error});return}v=w.value??[]}m==="additionalOscAddresses"&&(v=String(v).split(",").map(w=>w.trim()).filter(Boolean)),h.includes(m)&&v===""&&(v=null),_[m]=v}try{let m=await R("cue.batchUpdate",{ids:e,changes:_});m.offsetSkipped>0&&s(`${m.offsetSkipped} \u4EF6\u306F\u30AA\u30D5\u30BB\u30C3\u30C8\u9069\u7528\u5F8C\u306E\u6642\u523B\u304C\u7BC4\u56F2\u5916\u306E\u305F\u3081\u30AA\u30D5\u30BB\u30C3\u30C8\u3092\u4ED8\u3051\u307E\u305B\u3093\u3067\u3057\u305F`),c()}catch(m){a(Oe(m,"changes"))}},children:[Object.entries(b).map(([l,_])=>n("div",{class:"batch-row",children:[n("label",{children:[n("input",{type:"checkbox",checked:d.has(l),onChange:m=>{let v=m.currentTarget.checked;p(w=>{let y=new Set(w);return v?y.add(l):y.delete(l),y})}}),_]}),n("div",{children:[S.includes(l)?n("input",{type:"checkbox",onChange:m=>{let v=m.currentTarget.checked;u(w=>({...w,[l]:v}))}}):l==="targetHostIds"?n("div",{children:t.map(m=>n("label",{children:[n("input",{type:"checkbox",onChange:v=>{let w=v.currentTarget.checked;u(y=>{let C=y[l]??[];return{...y,[l]:w?[...C,m.id]:C.filter(D=>D!==m.id)}})}}),m.name]}))}):h.includes(l)?n(V,{ariaLabel:_,value:String(f[l]??""),frameRate:r,signed:l==="triggerOffset",onChange:m=>u(v=>({...v,[l]:m}))}):l==="arguments"?n("textarea",{class:"mono","aria-label":_,placeholder:`int32:1 float32:0.5 -string:GO`,value:String(b[l]??""),onInput:m=>{let v=m.currentTarget.value;d(w=>({...w,[l]:v}))}}):r("input",{class:"field",value:String(b[l]??""),onInput:m=>{let v=m.currentTarget.value;d(w=>({...w,[l]:v}))}}),r(sr,{errors:i,name:l})]})]})),r("div",{class:"dialog-actions",children:[r("button",{type:"button",class:"btn",onClick:o,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn primary",disabled:!u.size,children:"\u9069\u7528"})]})]})})}function J({message:e,close:t,confirm:n,confirmLabel:o="\u524A\u9664"}){return r(ge,{title:"\u78BA\u8A8D",close:t,children:[r("div",{class:"confirm-body",children:e}),r("div",{class:"dialog-actions",children:[r("button",{class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn danger",onClick:n,children:o})]})]})}var be=(e,t={})=>void R(e,t),ir=e=>be("receive.selectDevice",{deviceId:e||null});function lr(e,t,n=Date.now()){if(e!=="signalLost"&&e!=="freerun")return null;if(!t)return"\u6700\u7D42\u53D7\u4FE1\u306A\u3057";let o=Date.parse(t);return Number.isFinite(o)?`\u6700\u7D42\u53D7\u4FE1 ${Math.max(0,Math.floor((n-o)/1e3))} \u79D2\u524D`:"\u6700\u7D42\u53D7\u4FE1\u306A\u3057"}function St(){let e=A.value.host.state,t=e.receive.selectedDeviceId,[n,o]=T(Date.now()),c=e.transport.status==="signalLost"||e.transport.status==="freerun";M(()=>{if(!c)return;o(Date.now());let u=window.setInterval(()=>o(Date.now()),1e3);return()=>window.clearInterval(u)},[c,e.transport.lastLtcReceivedAtUtc]);let s=lr(e.transport.status,e.transport.lastLtcReceivedAtUtc,n);return r("div",{class:"sec",children:[r("div",{class:"head",children:[r("b",{children:"TC \u5165\u51FA\u529B"}),r("span",{class:"tiny",children:"LTC \u53D7\u4FE1\u8A2D\u5B9A"})]}),r("div",{class:"status",children:[r("span",{children:[r("i",{class:"led"}),"\u5165\u529B ",r("b",{children:e.transport.locked?"\u63A5\u7D9A":"\u672A\u63A5\u7D9A"})]}),r("span",{children:[r("i",{class:"led"}),"LTC ",r("b",{children:e.transport.statusText}),s&&r("span",{class:"tiny muted",children:[" ",s]}),e.transport.signalErrorRatePercent!=null&&r("span",{class:"tiny muted",children:[" \u8AA4\u308A\u7387 ",e.transport.signalErrorRatePercent.toFixed(1),"%"]})]}),r("span",{children:["\u25CB \u51FA\u529B ",r("b",{children:"\u505C\u6B62"})]}),r("span",{children:[r("i",{class:"led"}),"\u9001\u51FA ",r("b",{children:e.transport.triggerMuted?"MUTE":"LIVE"})]})]}),r("div",{class:"settings",children:[r("div",{class:"set",children:[r("span",{class:"tiny",children:"\u5165\u529B\u30C7\u30D0\u30A4\u30B9"}),r("select",{class:"field","aria-label":"\u5165\u529B\u30C7\u30D0\u30A4\u30B9",value:t??"",onChange:u=>ir(u.currentTarget.value),children:[r("option",{value:"",children:"\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044"}),e.receive.devices.map(u=>r("option",{value:u.id,children:u.name}))]}),r("button",{class:"btn",onClick:()=>be("audio.refreshDevices",{direction:"capture"}),children:"\u518D\u30B9\u30AD\u30E3\u30F3"})]}),r("div",{class:"set",children:[r("span",{class:"tiny",children:"\u63A5\u7D9A"}),r("span",{class:`field status-field ${t?"":"device-required"}`,children:t?e.transport.detailText:"\u5165\u529B\u30C7\u30D0\u30A4\u30B9\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044"}),r("button",{class:"btn",disabled:!t,onClick:()=>be("ltc.reconnect",{deviceId:t}),children:"\u518D\u63A5\u7D9A"})]}),r("div",{class:"set",children:[r("span",{class:"tiny",children:"\u30D5\u30EA\u30FC\u30E9\u30F3"}),r("input",{class:"field","aria-label":"\u30D5\u30EA\u30FC\u30E9\u30F3\uFF08\u79D2\uFF09",type:"number",min:"0",step:"0.1",value:e.receive.freerunDurationSeconds,onChange:u=>be("receive.setFreerunDuration",{seconds:Number(u.currentTarget.value)})}),r("span",{class:"tiny unit",children:"\u79D2"})]})]}),r(cr,{})]})}function cr(){let e=A.value.host.wave;if(!e)return r("div",{class:"wave",children:r("span",{class:"tiny",children:"\u6CE2\u5F62\u5F85\u6A5F\u4E2D"})});let t=Math.min(e.min.length,e.max.length),n=[];for(let o=0;o=0;o--)n.push(`${o/Math.max(1,t-1)*360},${15-(e.min[o]??0)*14}`);return r("div",{class:"wave",children:r("svg",{viewBox:"0 0 360 30",preserveAspectRatio:"none",children:r("polygon",{points:n.join(" ")})})})}function At({notice:e}){let t=A.value.host.state.generator,n=()=>({startTime:t.startTime,frameRate:t.frameRate,outputDeviceId:t.selectedOutputDeviceId??"",volume:t.volume}),[o,c]=T(n),s=$(null);M(()=>c(n()),[t.startTime,t.frameRate,t.selectedOutputDeviceId,t.volume]);let u=d=>void R("generator.configure",d).catch(i=>e(i.message)),p=(d,i)=>{let a={...o,[d]:i};c(a),u(a)},b=(d,i=!1)=>{let a={...o,volume:d};c(a),s.current!==null&&window.clearTimeout(s.current),i?u(a):s.current=window.setTimeout(()=>u(a),100)};return r("div",{class:"sec",children:[r("div",{class:"head",children:[r("b",{children:"TC \u5165\u51FA\u529B"}),r("span",{class:"tiny",children:"\u5185\u90E8\u751F\u6210\u8A2D\u5B9A"})]}),r("div",{class:"status",children:[r("span",{children:["\u25CB \u5165\u529B ",r("b",{children:"\u505C\u6B62"})]}),r("span",{children:[r("i",{class:"led"}),"\u751F\u6210 ",r("b",{children:t.running?"\u518D\u751F\u4E2D":"\u505C\u6B62"})]}),r("span",{children:[r("i",{class:"led"}),"\u51FA\u529B ",r("b",{children:t.ltcOutputActive?"\u9001\u51FA\u4E2D":"\u505C\u6B62"})]}),r("span",{children:["\u97F3\u91CF ",r("b",{children:[Math.round(o.volume*100),"%"]})]})]}),r("div",{class:"settings generator-settings",children:[r("label",{class:"wide-control",children:[r("span",{class:"tiny",children:"\u958B\u59CB TC"}),r(V,{value:o.startTime,frameRate:o.frameRate,ariaLabel:"\u958B\u59CB\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",required:!0,onChange:d=>p("startTime",d)})]}),r("label",{children:[r("span",{class:"tiny",children:"FPS"}),r("select",{class:"field",value:o.frameRate,onChange:d=>p("frameRate",d.currentTarget.value),children:["24","25","29.97df","30"].map(d=>r("option",{value:d,children:d}))})]}),r("label",{class:"wide-control",children:[r("span",{class:"tiny",children:"\u51FA\u529B\u30C7\u30D0\u30A4\u30B9"}),r("select",{class:"field",value:o.outputDeviceId,onChange:d=>p("outputDeviceId",d.currentTarget.value),children:[r("option",{value:"",children:"\u306A\u3057\uFF08\u51FA\u529B\u3057\u306A\u3044\uFF09"}),t.outputDevices.map(d=>r("option",{value:d.id,children:d.name}))]})]}),r("label",{class:"wide-control volume-control",children:[r("span",{class:"tiny",children:"\u97F3\u91CF"}),r("input",{type:"range",min:"0",max:"1",step:"0.01",value:o.volume,onInput:d=>b(Number(d.currentTarget.value)),onPointerUp:d=>b(Number(d.currentTarget.value),!0)})]})]}),t.running&&t.settingsPendingReset&&r("small",{class:"pending-note",children:"\u8A2D\u5B9A\u306F\u518D\u751F\u3057\u76F4\u3059\u3068\u53CD\u6620\u3055\u308C\u307E\u3059"}),r("div",{class:"generator-actions",children:[r("button",{class:`btn transport-toggle ${t.running?"danger":""}`,onClick:()=>void R(t.running?"generator.stop":"generator.start",{}).catch(d=>e(d.message)),children:t.running?"\u25A0 \u505C\u6B62":"\u25B6 \u518D\u751F"}),r("button",{class:"btn",onClick:()=>void R("generator.reset",{}).catch(d=>e(d.message)),children:"\u30EA\u30BB\u30C3\u30C8"})]})]})}function ur(e){let t={};e.name.trim()||(t.name="\u540D\u524D\u306F\u5FC5\u9808\u3067\u3059"),e.ipAddress.trim()||(t.ipAddress="IP \u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059");let n=Number(e.port);return(!Number.isInteger(n)||n<1||n>65535)&&(t.port="1\u301C65535 \u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"),t}var dr=e=>Object.fromEntries(Object.entries(e.fieldErrors??{}).filter(([t])=>t.startsWith("host.")).map(([t,n])=>[t.slice(5),n]));function pr({host:e,close:t}){let[n,o]=T({name:e?.name??"",ipAddress:e?.ipAddress??"",port:String(e?.port??53e3)}),[c,s]=T({});M(()=>A.updateUi({modalOpen:!0}),[]);let u=(p,b)=>r("label",{class:"form-field",children:[r("span",{children:b}),r("input",{autofocus:p==="name",class:"field",value:n[p],onInput:d=>{let i=d.currentTarget.value;o(a=>({...a,[p]:i}))}}),r("small",{class:"field-error",children:c[p]??""})]});return r(ne,{title:e?"\u30DB\u30B9\u30C8\u3092\u7DE8\u96C6":"\u30DB\u30B9\u30C8\u3092\u8FFD\u52A0",close:t,children:r("form",{class:"simple-form host-form",onSubmit:async p=>{p.preventDefault();let b=ur(n);if(Object.keys(b).length){s(b);return}try{let d={name:n.name.trim(),ipAddress:n.ipAddress.trim(),port:Number(n.port),enabled:e?.enabled??!0};await R(e?"host.update":"host.add",e?{id:e.id,host:d}:{host:d}),t()}catch(d){s(dr(d))}},children:[u("name","\u540D\u524D"),u("ipAddress","IP \u30A2\u30C9\u30EC\u30B9"),u("port","\u30DD\u30FC\u30C8"),r("div",{class:"dialog-actions",children:[r("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn primary",children:e?"\u66F4\u65B0":"\u8FFD\u52A0"})]})]})})}function mr(e,t,n){let o=t.filter(u=>u.targetHostIds.includes(e.id)).length,c=n.filter(u=>u.targetHostIds.includes(e.id)).length,s=[o?`\u30AD\u30E5\u30FC ${o} \u4EF6`:"",c?`\u30DD\u30F3\u51FA\u3057 ${c} \u4EF6`:""].filter(Boolean).join("\u3068");return s?`\u300C${e.name}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F ${s}\u304C\u9001\u4FE1\u5148\u306B\u3057\u3066\u3044\u307E\u3059\u3002\u524A\u9664\u3059\u308B\u3068\u305D\u308C\u3089\u306F\u9001\u4FE1\u5148\u3092\u5931\u3044\u307E\u3059`:`\u300C${e.name}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`}function Dt({notice:e}){let t=A.value.host.state,[n,o]=T(null),[c,s]=T(null),u=()=>{o(null),s(null),A.updateUi({modalOpen:!1})},p=(b,d)=>void R(b,d).then(i=>{if(b==="host.ping"){let a=i??{};e(a.reachable===void 0?"\u758E\u901A\u78BA\u8A8D\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F":a.reachable?`\u758E\u901A OK \xB7 ${a.latencyMs??"--"} ms`:"\u758E\u901A\u3067\u304D\u307E\u305B\u3093")}}).catch(i=>e(i.message));return r(H,{children:[r("div",{class:"panel-toolbar",children:[r("b",{children:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8"}),r("button",{class:"btn right",onClick:()=>o("new"),children:"\uFF0B \u8FFD\u52A0"})]}),r("div",{class:"host-list",children:t.hosts.map(b=>r("div",{class:`host-row ${b.enabled?"":"disabled"}`,children:[r("span",{class:`reach ${b.reachability}`}),r("b",{class:"host-name",children:b.name}),r("span",{class:"mono host-address",children:[b.ipAddress,":",b.port]}),r("div",{class:"host-actions",children:[r("label",{children:[r("input",{type:"checkbox",checked:b.enabled,onChange:d=>p("host.setEnabled",{id:b.id,enabled:d.currentTarget.checked})}),"\u6709\u52B9"]}),r("button",{class:"btn",onClick:()=>p("host.ping",{id:b.id}),children:"\u758E\u901A\u78BA\u8A8D"}),r("button",{class:"btn",onClick:()=>o(b),children:"\u7DE8\u96C6"}),r("button",{class:"btn danger-link",onClick:()=>{s(b),A.updateUi({modalOpen:!0})},children:"\u524A\u9664"})]})]}))}),n&&r(pr,{host:n==="new"?void 0:n,close:u})," ",c&&r(J,{message:mr(c,t.cues,t.triggerPanel.buttons),close:u,confirm:()=>{p("host.remove",{id:c.id}),u()}})]})}var fr=e=>[...e].sort((t,n)=>Date.parse(n.timestampUtc)-Date.parse(t.timestampUtc));function xt(){let[e,t]=T(!1),n=fr(A.value.host.state.logs),o=e?n.filter(u=>!u.success):n,c=$(null),s=$(!0);return pt(()=>{let u=c.current;u&&s.current&&(u.scrollTop=u.scrollHeight)},[o.length]),r(H,{children:[r("div",{class:"panel-toolbar",children:[r("b",{children:"\u9001\u4FE1\u30ED\u30B0"}),r("span",{class:"tiny",children:"\u65B0\u3057\u3044\u9806"}),r("label",{class:"check tiny log-filter",children:[r("input",{type:"checkbox",checked:e,onChange:u=>t(u.currentTarget.checked)}),"\u5931\u6557\u306E\u307F"]}),r("button",{class:"btn right",onClick:()=>void R("logs.clear",{}),children:"\u30AF\u30EA\u30A2"})]}),r("div",{class:"dock log-dock",ref:c,onScroll:u=>{let p=u.currentTarget;s.current=p.scrollHeight-p.scrollTop-p.clientHeight<4},children:o.length?o.map(u=>r("div",{class:u.success?"log-success":"failure",children:[r("time",{children:new Date(u.timestampUtc).toLocaleTimeString("ja-JP")}),r("span",{children:u.message})]})):r("div",{class:"muted",children:e&&n.length?"\u5931\u6557\u3057\u305F\u9001\u4FE1\u306F\u3042\u308A\u307E\u305B\u3093":"\u9001\u4FE1\u30ED\u30B0\u306F\u3042\u308A\u307E\u305B\u3093"})})]})}function Rt({notice:e}){let t=A.value.host.state,[n,o]=T("logs");return r(H,{children:[r("div",{class:"sec",children:[r("div",{class:"head",children:[r("b",{children:"Cue-Sync \u9001\u4FE1\u8A2D\u5B9A"}),r("span",{class:"tiny mono",children:t.cueSync.oscAddress})]}),r("div",{class:"set",children:[r("span",{class:"tiny",children:"OSC\u30A2\u30C9\u30EC\u30B9"}),r("input",{class:"field mono","aria-label":"Cue-Sync OSC\u30A2\u30C9\u30EC\u30B9",value:t.cueSync.oscAddress,onChange:c=>void R("cueSync.configure",{oscAddress:c.currentTarget.value,targetHostIds:t.cueSync.targetHostIds})}),r("button",{class:"btn",onClick:()=>void R("cueSync.send",{}),children:"\u5358\u767A\u9001\u4FE1"})]}),r("div",{class:"hosts",children:[t.hosts.map(c=>r("label",{children:[r("input",{type:"checkbox",checked:t.cueSync.targetHostIds.includes(c.id),onChange:s=>void R("cueSync.configure",{oscAddress:t.cueSync.oscAddress,targetHostIds:s.currentTarget.checked?[...t.cueSync.targetHostIds,c.id]:t.cueSync.targetHostIds.filter(u=>u!==c.id)})}),c.name]})),r("span",{class:"tiny",children:[t.cueSync.targetHostIds.length," / ",t.hosts.length," \u6709\u52B9"]})]})]}),r("div",{class:"tab-panel",children:[r("div",{class:"tabs",children:[r("button",{class:`tab ${n==="logs"?"on":""}`,onClick:()=>o("logs"),children:"\u9001\u4FE1\u30ED\u30B0"}),r("button",{class:`tab ${n==="hosts"?"on":""}`,onClick:()=>o("hosts"),children:"\u30DB\u30B9\u30C8\u7BA1\u7406"})]}),n==="logs"?r(xt,{}):r(Dt,{notice:e})]})]})}function It(e,t){let n=[];for(let o of t){let c=o.runtime.flashToken,s=e.get(o.id);e.set(o.id,c),s!==void 0&&c>s&&n.push(o.id)}return n}function gr({cell:e,close:t,notice:n}){let o=e.button,[c,s]=T(o?.label??""),[u,p]=T(o?.oscAddress??"/"),[b,d]=T(o?.arguments.map(l=>`${l.type}:${l.value}`).join(` -`)??""),[i,a]=T(o?.targetHostIds??[]),[g,h]=T({}),S=A.value.host.state;return M(()=>A.updateUi({modalOpen:!0}),[]),r(ne,{title:o?"\u30DD\u30F3\u51FA\u3057\u30DC\u30BF\u30F3\u3092\u7DE8\u96C6":"\u30DD\u30F3\u51FA\u3057\u30DC\u30BF\u30F3\u3092\u4F5C\u6210",close:t,children:r("form",{class:"cue-form",onSubmit:async l=>{l.preventDefault();let _={};c.trim()||(_.label="\u30E9\u30D9\u30EB\u306F\u5FC5\u9808\u3067\u3059"),u.startsWith("/")||(_.oscAddress="/ \u3067\u59CB\u3081\u3066\u304F\u3060\u3055\u3044");let m=ee(b);if(m.error&&(_.arguments=m.error),Object.keys(_).length){h(_);return}try{await R("triggerPanel.upsertButton",{button:{...o?{id:o.id}:{},row:e.row,column:e.column,label:c.trim(),oscAddress:u,arguments:m.value??[],targetHostIds:i}}),t()}catch(v){n(v.message)}},children:[r("label",{class:"form-field",children:[r("span",{children:"\u30E9\u30D9\u30EB"}),r("input",{autofocus:!0,class:"field",value:c,onInput:l=>s(l.currentTarget.value)}),r("small",{class:"field-error",children:g.label})]}),r("label",{class:"form-field",children:[r("span",{children:"OSC\u30A2\u30C9\u30EC\u30B9"}),r("input",{class:"field mono",value:u,onInput:l=>p(l.currentTarget.value)}),r("small",{class:"field-error",children:g.oscAddress})]}),r("label",{class:"form-field",children:[r("span",{children:"OSC\u5F15\u6570\uFF08type:value\u30011 \u884C 1 \u4EF6\uFF09"}),r("textarea",{class:"mono",value:b,onInput:l=>d(l.currentTarget.value),placeholder:`int32:1 +string:GO`,value:String(f[l]??""),onInput:m=>{let v=m.currentTarget.value;u(w=>({...w,[l]:v}))}}):n("input",{class:"field",value:String(f[l]??""),onInput:m=>{let v=m.currentTarget.value;u(w=>({...w,[l]:v}))}}),n(sn,{errors:i,name:l})]})]})),n("div",{class:"dialog-actions",children:[n("button",{type:"button",class:"btn",onClick:o,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn primary",disabled:!d.size,children:"\u9069\u7528"})]})]})})}function J({message:e,close:t,confirm:r,confirmLabel:o="\u524A\u9664"}){return n(ge,{title:"\u78BA\u8A8D",close:t,children:[n("div",{class:"confirm-body",children:e}),n("div",{class:"dialog-actions",children:[n("button",{class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn danger",onClick:r,children:o})]})]})}var He=(e,t={})=>void R(e,t),ln=e=>He("receive.selectDevice",{deviceId:e||null});async function cn(e){e(!0);try{await R("audio.refreshDevices",{direction:"capture"})}catch{}finally{e(!1)}}function un(e,t,r=Date.now()){if(e!=="signalLost"&&e!=="freerun")return null;if(!t)return"\u6700\u7D42\u53D7\u4FE1\u306A\u3057";let o=Date.parse(t);return Number.isFinite(o)?`\u6700\u7D42\u53D7\u4FE1 ${Math.max(0,Math.floor((r-o)/1e3))} \u79D2\u524D`:"\u6700\u7D42\u53D7\u4FE1\u306A\u3057"}function St(){let e=A.value.host.state,t=e.receive.selectedDeviceId,[r,o]=T(Date.now()),[c,s]=T(!1),d=e.transport.status==="signalLost"||e.transport.status==="freerun";H(()=>{if(!d)return;o(Date.now());let f=window.setInterval(()=>o(Date.now()),1e3);return()=>window.clearInterval(f)},[d,e.transport.lastLtcReceivedAtUtc]);let p=un(e.transport.status,e.transport.lastLtcReceivedAtUtc,r);return n("div",{class:"sec",children:[n("div",{class:"head",children:[n("b",{children:"TC \u5165\u51FA\u529B"}),n("span",{class:"tiny",children:"LTC \u53D7\u4FE1\u8A2D\u5B9A"})]}),n("div",{class:"status",children:[n("span",{children:[n("i",{class:"led"}),"\u5165\u529B ",n("b",{children:e.transport.locked?"\u63A5\u7D9A":"\u672A\u63A5\u7D9A"})]}),n("span",{children:[n("i",{class:"led"}),"LTC ",n("b",{children:e.transport.statusText}),p&&n("span",{class:"tiny muted",children:[" ",p]}),e.transport.signalErrorRatePercent!=null&&n("span",{class:"tiny muted",children:[" \u8AA4\u308A\u7387 ",e.transport.signalErrorRatePercent.toFixed(1),"%"]})]}),n("span",{children:["\u25CB \u51FA\u529B ",n("b",{children:"\u505C\u6B62"})]}),n("span",{children:[n("i",{class:"led"}),"\u9001\u51FA ",n("b",{children:e.transport.triggerMuted?"MUTE":"LIVE"})]})]}),n("div",{class:"settings",children:[n("div",{class:"set device-set",children:[n("span",{class:"tiny",children:"\u5165\u529B\u30C7\u30D0\u30A4\u30B9"}),n("select",{class:"field","aria-label":"\u5165\u529B\u30C7\u30D0\u30A4\u30B9",value:t??"",onChange:f=>ln(f.currentTarget.value),children:[n("option",{value:"",children:"\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044"}),e.receive.devices.map(f=>n("option",{value:f.id,children:f.name}))]}),n(dn,{scanning:c,onClick:()=>void cn(s)})]}),n("div",{class:"set",children:[n("span",{class:"tiny",children:"\u63A5\u7D9A"}),n("span",{class:`field status-field ${t?"":"device-required"}`,children:t?e.transport.detailText:"\u5165\u529B\u30C7\u30D0\u30A4\u30B9\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044"}),n("button",{class:"btn",disabled:!t,onClick:()=>He("ltc.reconnect",{deviceId:t}),children:"\u518D\u63A5\u7D9A"})]}),n("div",{class:"set",children:[n("span",{class:"tiny",children:"\u30D5\u30EA\u30FC\u30E9\u30F3"}),n("input",{class:"field","aria-label":"\u30D5\u30EA\u30FC\u30E9\u30F3\uFF08\u79D2\uFF09",type:"number",min:"0",step:"0.1",value:e.receive.freerunDurationSeconds,onChange:f=>He("receive.setFreerunDuration",{seconds:Number(f.currentTarget.value)})}),n("span",{class:"tiny unit",children:"\u79D2"})]})]}),n(pn,{})]})}function dn({scanning:e,onClick:t}){return n(M,{children:[n("button",{class:"btn rescan",disabled:e,"aria-busy":e,onClick:t,children:e?"\u30B9\u30AD\u30E3\u30F3\u4E2D\u2026":"\u518D\u30B9\u30AD\u30E3\u30F3"}),e&&n("div",{class:"scan-progress",role:"progressbar","aria-label":"\u30C7\u30D0\u30A4\u30B9\u3092\u30B9\u30AD\u30E3\u30F3\u4E2D",children:n("i",{})})]})}function pn(){let e=A.value.host.wave;if(!e)return n("div",{class:"wave",children:n("span",{class:"tiny",children:"\u6CE2\u5F62\u5F85\u6A5F\u4E2D"})});let t=Math.min(e.min.length,e.max.length),r=[];for(let o=0;o=0;o--)r.push(`${o/Math.max(1,t-1)*360},${15-(e.min[o]??0)*14}`);return n("div",{class:"wave",children:n("svg",{viewBox:"0 0 360 30",preserveAspectRatio:"none",children:n("polygon",{points:r.join(" ")})})})}function At({notice:e}){let t=A.value.host.state.generator,r=()=>({startTime:t.startTime,frameRate:t.frameRate,outputDeviceId:t.selectedOutputDeviceId??"",volume:t.volume}),[o,c]=T(r),s=$(null);H(()=>c(r()),[t.startTime,t.frameRate,t.selectedOutputDeviceId,t.volume]);let d=u=>void R("generator.configure",u).catch(i=>e(i.message)),p=(u,i)=>{let a={...o,[u]:i};c(a),d(a)},f=(u,i=!1)=>{let a={...o,volume:u};c(a),s.current!==null&&window.clearTimeout(s.current),i?d(a):s.current=window.setTimeout(()=>d(a),100)};return n("div",{class:"sec",children:[n("div",{class:"head",children:[n("b",{children:"TC \u5165\u51FA\u529B"}),n("span",{class:"tiny",children:"\u5185\u90E8\u751F\u6210\u8A2D\u5B9A"})]}),n("div",{class:"status",children:[n("span",{children:["\u25CB \u5165\u529B ",n("b",{children:"\u505C\u6B62"})]}),n("span",{children:[n("i",{class:"led"}),"\u751F\u6210 ",n("b",{children:t.running?"\u518D\u751F\u4E2D":"\u505C\u6B62"})]}),n("span",{children:[n("i",{class:"led"}),"\u51FA\u529B ",n("b",{children:t.ltcOutputActive?"\u9001\u51FA\u4E2D":"\u505C\u6B62"})]}),n("span",{children:["\u97F3\u91CF ",n("b",{children:[Math.round(o.volume*100),"%"]})]})]}),n("div",{class:"settings generator-settings",children:[n("label",{class:"wide-control",children:[n("span",{class:"tiny",children:"\u958B\u59CB TC"}),n(V,{value:o.startTime,frameRate:o.frameRate,ariaLabel:"\u958B\u59CB\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9",required:!0,onChange:u=>p("startTime",u)})]}),n("label",{children:[n("span",{class:"tiny",children:"FPS"}),n("select",{class:"field",value:o.frameRate,onChange:u=>p("frameRate",u.currentTarget.value),children:["24","25","29.97df","30"].map(u=>n("option",{value:u,children:u}))})]}),n("label",{class:"wide-control",children:[n("span",{class:"tiny",children:"\u51FA\u529B\u30C7\u30D0\u30A4\u30B9"}),n("select",{class:"field",value:o.outputDeviceId,onChange:u=>p("outputDeviceId",u.currentTarget.value),children:[n("option",{value:"",children:"\u306A\u3057\uFF08\u51FA\u529B\u3057\u306A\u3044\uFF09"}),t.outputDevices.map(u=>n("option",{value:u.id,children:u.name}))]})]}),n("label",{class:"wide-control volume-control",children:[n("span",{class:"tiny",children:"\u97F3\u91CF"}),n("input",{type:"range",min:"0",max:"1",step:"0.01",value:o.volume,onInput:u=>f(Number(u.currentTarget.value)),onPointerUp:u=>f(Number(u.currentTarget.value),!0)})]})]}),t.running&&t.settingsPendingReset&&n("small",{class:"pending-note",children:"\u8A2D\u5B9A\u306F\u518D\u751F\u3057\u76F4\u3059\u3068\u53CD\u6620\u3055\u308C\u307E\u3059"}),n("div",{class:"generator-actions",children:[n("button",{class:`btn transport-toggle ${t.running?"danger":""}`,onClick:()=>void R(t.running?"generator.stop":"generator.start",{}).catch(u=>e(u.message)),children:t.running?"\u25A0 \u505C\u6B62":"\u25B6 \u518D\u751F"}),n("button",{class:"btn",onClick:()=>void R("generator.reset",{}).catch(u=>e(u.message)),children:"\u30EA\u30BB\u30C3\u30C8"})]})]})}function mn(e){let t={};e.name.trim()||(t.name="\u540D\u524D\u306F\u5FC5\u9808\u3067\u3059"),e.ipAddress.trim()||(t.ipAddress="IP \u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059");let r=Number(e.port);return(!Number.isInteger(r)||r<1||r>65535)&&(t.port="1\u301C65535 \u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044"),t}var fn=e=>Object.fromEntries(Object.entries(e.fieldErrors??{}).filter(([t])=>t.startsWith("host.")).map(([t,r])=>[t.slice(5),r]));function gn({host:e,close:t}){let[r,o]=T({name:e?.name??"",ipAddress:e?.ipAddress??"",port:String(e?.port??53e3)}),[c,s]=T({});H(()=>A.updateUi({modalOpen:!0}),[]);let d=(p,f)=>n("label",{class:"form-field",children:[n("span",{children:f}),n("input",{autofocus:p==="name",class:"field",value:r[p],onInput:u=>{let i=u.currentTarget.value;o(a=>({...a,[p]:i}))}}),n("small",{class:"field-error",children:c[p]??""})]});return n(re,{title:e?"\u30DB\u30B9\u30C8\u3092\u7DE8\u96C6":"\u30DB\u30B9\u30C8\u3092\u8FFD\u52A0",close:t,children:n("form",{class:"simple-form host-form",onSubmit:async p=>{p.preventDefault();let f=mn(r);if(Object.keys(f).length){s(f);return}try{let u={name:r.name.trim(),ipAddress:r.ipAddress.trim(),port:Number(r.port),enabled:e?.enabled??!0};await R(e?"host.update":"host.add",e?{id:e.id,host:u}:{host:u}),t()}catch(u){s(fn(u))}},children:[d("name","\u540D\u524D"),d("ipAddress","IP \u30A2\u30C9\u30EC\u30B9"),d("port","\u30DD\u30FC\u30C8"),n("div",{class:"dialog-actions",children:[n("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn primary",children:e?"\u66F4\u65B0":"\u8FFD\u52A0"})]})]})})}function bn(e,t,r){let o=t.filter(d=>d.targetHostIds.includes(e.id)).length,c=r.filter(d=>d.targetHostIds.includes(e.id)).length,s=[o?`\u30AD\u30E5\u30FC ${o} \u4EF6`:"",c?`\u30DD\u30F3\u51FA\u3057 ${c} \u4EF6`:""].filter(Boolean).join("\u3068");return s?`\u300C${e.name}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F ${s}\u304C\u9001\u4FE1\u5148\u306B\u3057\u3066\u3044\u307E\u3059\u3002\u524A\u9664\u3059\u308B\u3068\u305D\u308C\u3089\u306F\u9001\u4FE1\u5148\u3092\u5931\u3044\u307E\u3059`:`\u300C${e.name}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`}function xt({notice:e}){let t=A.value.host.state,[r,o]=T(null),[c,s]=T(null),d=()=>{o(null),s(null),A.updateUi({modalOpen:!1})},p=(f,u)=>void R(f,u).then(i=>{if(f==="host.ping"){let a=i??{};e(a.reachable===void 0?"\u758E\u901A\u78BA\u8A8D\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F":a.reachable?`\u758E\u901A OK \xB7 ${a.latencyMs??"--"} ms`:"\u758E\u901A\u3067\u304D\u307E\u305B\u3093")}}).catch(i=>e(i.message));return n(M,{children:[n("div",{class:"panel-toolbar",children:[n("b",{children:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8"}),n("button",{class:"btn right",onClick:()=>o("new"),children:"\uFF0B \u8FFD\u52A0"})]}),n("div",{class:"host-list",children:t.hosts.map(f=>n("div",{class:`host-row ${f.enabled?"":"disabled"}`,children:[n("span",{class:`reach ${f.reachability}`}),n("b",{class:"host-name",children:f.name}),n("span",{class:"mono host-address",children:[f.ipAddress,":",f.port]}),n("div",{class:"host-actions",children:[n("label",{children:[n("input",{type:"checkbox",checked:f.enabled,onChange:u=>p("host.setEnabled",{id:f.id,enabled:u.currentTarget.checked})}),"\u6709\u52B9"]}),n("button",{class:"btn",onClick:()=>p("host.ping",{id:f.id}),children:"\u758E\u901A\u78BA\u8A8D"}),n("button",{class:"btn",onClick:()=>o(f),children:"\u7DE8\u96C6"}),n("button",{class:"btn danger-link",onClick:()=>{s(f),A.updateUi({modalOpen:!0})},children:"\u524A\u9664"})]})]}))}),r&&n(gn,{host:r==="new"?void 0:r,close:d})," ",c&&n(J,{message:bn(c,t.cues,t.triggerPanel.buttons),close:d,confirm:()=>{p("host.remove",{id:c.id}),d()}})]})}var _n=e=>[...e].sort((t,r)=>Date.parse(r.timestampUtc)-Date.parse(t.timestampUtc));function Dt(){let[e,t]=T(!1),r=_n(A.value.host.state.logs),o=e?r.filter(d=>!d.success):r,c=$(null),s=$(!0);return pt(()=>{let d=c.current;d&&s.current&&(d.scrollTop=d.scrollHeight)},[o.length]),n(M,{children:[n("div",{class:"panel-toolbar",children:[n("b",{children:"\u9001\u4FE1\u30ED\u30B0"}),n("span",{class:"tiny",children:"\u65B0\u3057\u3044\u9806"}),n("label",{class:"check tiny log-filter",children:[n("input",{type:"checkbox",checked:e,onChange:d=>t(d.currentTarget.checked)}),"\u5931\u6557\u306E\u307F"]}),n("button",{class:"btn right",onClick:()=>void R("logs.clear",{}),children:"\u30AF\u30EA\u30A2"})]}),n("div",{class:"dock log-dock",ref:c,onScroll:d=>{let p=d.currentTarget;s.current=p.scrollHeight-p.scrollTop-p.clientHeight<4},children:o.length?o.map(d=>n("div",{class:d.success?"log-success":"failure",children:[n("time",{children:new Date(d.timestampUtc).toLocaleTimeString("ja-JP")}),n("span",{children:d.message})]})):n("div",{class:"muted",children:e&&r.length?"\u5931\u6557\u3057\u305F\u9001\u4FE1\u306F\u3042\u308A\u307E\u305B\u3093":"\u9001\u4FE1\u30ED\u30B0\u306F\u3042\u308A\u307E\u305B\u3093"})})]})}function Rt({notice:e}){let t=A.value.host.state,[r,o]=T("logs");return n(M,{children:[n("div",{class:"sec",children:[n("div",{class:"head",children:[n("b",{children:"Cue-Sync \u9001\u4FE1\u8A2D\u5B9A"}),n("span",{class:"tiny mono",children:t.cueSync.oscAddress})]}),n("div",{class:"set",children:[n("span",{class:"tiny",children:"OSC\u30A2\u30C9\u30EC\u30B9"}),n("input",{class:"field mono","aria-label":"Cue-Sync OSC\u30A2\u30C9\u30EC\u30B9",value:t.cueSync.oscAddress,onChange:c=>void R("cueSync.configure",{oscAddress:c.currentTarget.value,targetHostIds:t.cueSync.targetHostIds})}),n("button",{class:"btn",onClick:()=>void R("cueSync.send",{}),children:"\u5358\u767A\u9001\u4FE1"})]}),n("div",{class:"hosts",children:[t.hosts.map(c=>n("label",{children:[n("input",{type:"checkbox",checked:t.cueSync.targetHostIds.includes(c.id),onChange:s=>void R("cueSync.configure",{oscAddress:t.cueSync.oscAddress,targetHostIds:s.currentTarget.checked?[...t.cueSync.targetHostIds,c.id]:t.cueSync.targetHostIds.filter(d=>d!==c.id)})}),c.name]})),n("span",{class:"tiny",children:[t.cueSync.targetHostIds.length," / ",t.hosts.length," \u6709\u52B9"]})]})]}),n("div",{class:"tab-panel",children:[n("div",{class:"tabs",children:[n("button",{class:`tab ${r==="logs"?"on":""}`,onClick:()=>o("logs"),children:"\u9001\u4FE1\u30ED\u30B0"}),n("button",{class:`tab ${r==="hosts"?"on":""}`,onClick:()=>o("hosts"),children:"\u30DB\u30B9\u30C8\u7BA1\u7406"})]}),r==="logs"?n(Dt,{}):n(xt,{notice:e})]})]})}function It(e,t){let r=[];for(let o of t){let c=o.runtime.flashToken,s=e.get(o.id);e.set(o.id,c),s!==void 0&&c>s&&r.push(o.id)}return r}function vn({cell:e,close:t,notice:r}){let o=e.button,[c,s]=T(o?.label??""),[d,p]=T(o?.oscAddress??"/"),[f,u]=T(o?.arguments.map(l=>`${l.type}:${l.value}`).join(` +`)??""),[i,a]=T(o?.targetHostIds??[]),[b,h]=T({}),S=A.value.host.state;return H(()=>A.updateUi({modalOpen:!0}),[]),n(re,{title:o?"\u30DD\u30F3\u51FA\u3057\u30DC\u30BF\u30F3\u3092\u7DE8\u96C6":"\u30DD\u30F3\u51FA\u3057\u30DC\u30BF\u30F3\u3092\u4F5C\u6210",close:t,children:n("form",{class:"cue-form",onSubmit:async l=>{l.preventDefault();let _={};c.trim()||(_.label="\u30E9\u30D9\u30EB\u306F\u5FC5\u9808\u3067\u3059"),d.startsWith("/")||(_.oscAddress="/ \u3067\u59CB\u3081\u3066\u304F\u3060\u3055\u3044");let m=ee(f);if(m.error&&(_.arguments=m.error),Object.keys(_).length){h(_);return}try{await R("triggerPanel.upsertButton",{button:{...o?{id:o.id}:{},row:e.row,column:e.column,label:c.trim(),oscAddress:d,arguments:m.value??[],targetHostIds:i}}),t()}catch(v){r(v.message)}},children:[n("label",{class:"form-field",children:[n("span",{children:"\u30E9\u30D9\u30EB"}),n("input",{autofocus:!0,class:"field",value:c,onInput:l=>s(l.currentTarget.value)}),n("small",{class:"field-error",children:b.label})]}),n("label",{class:"form-field",children:[n("span",{children:"OSC\u30A2\u30C9\u30EC\u30B9"}),n("input",{class:"field mono",value:d,onInput:l=>p(l.currentTarget.value)}),n("small",{class:"field-error",children:b.oscAddress})]}),n("label",{class:"form-field",children:[n("span",{children:"OSC\u5F15\u6570\uFF08type:value\u30011 \u884C 1 \u4EF6\uFF09"}),n("textarea",{class:"mono",value:f,onInput:l=>u(l.currentTarget.value),placeholder:`int32:1 float32:0.5 -string:GO`}),r("small",{class:"field-error",children:g.arguments})]}),r("fieldset",{children:[r("legend",{children:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8"}),S.hosts.map(l=>r("label",{children:[r("input",{type:"checkbox",checked:i.includes(l.id),onChange:_=>a(_.currentTarget.checked?[...i,l.id]:i.filter(m=>m!==l.id))}),l.name]}))]}),r("div",{class:"dialog-actions",children:[r("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),r("button",{class:"btn primary",children:"\u4FDD\u5B58"})]})]})})}function Et({notice:e}){let t=A.value.host.state,n=A.value.ui,[o,c]=T(!1),[s,u]=T(null),[p,b]=T(null),[d,i]=T(null),[a,g]=T({}),h=(m,v)=>{let w=t.triggerPanel.buttons.filter(y=>y.row>=m||y.column>=v).length;if(w>0){i({rows:m,columns:v,lost:w}),A.updateUi({modalOpen:!0});return}R("triggerPanel.configureGrid",{rows:m,columns:v})},S=()=>{u(null),i(null),b(null),A.updateUi({modalOpen:!1})},l=async m=>{try{let v=await R("triggerPanel.fire",{id:m.id});g(w=>({...w,[m.id]:v.sent===!1?v.reason??"\u9001\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F":"\u9001\u4FE1\u3057\u307E\u3057\u305F"}))}catch(v){g(w=>({...w,[m.id]:v.message}))}},_=Array.from({length:t.triggerPanel.rows*t.triggerPanel.columns},(m,v)=>{let w=Math.floor(v/t.triggerPanel.columns),y=v%t.triggerPanel.columns;return{row:w,column:y,button:t.triggerPanel.buttons.find(C=>C.row===w&&C.column===y)}});return r("section",{class:`drawer ${n.drawerOpen?"open":""}`,style:{height:n.drawerHeight},children:[r("div",{class:"dh",children:[r("h2",{children:"OSC \u30DD\u30F3\u51FA\u3057"}),r("button",{class:`btn ${o?"":"primary"}`,onClick:()=>c(!1),children:"\u5B9F\u884C\u30E2\u30FC\u30C9"}),r("button",{class:`btn ${o?"primary":""}`,onClick:()=>c(!0),children:"\u7DE8\u96C6\u30E2\u30FC\u30C9"}),o&&r(H,{children:[r("label",{class:"grid-size",children:["\u884C ",r("input",{class:"field",type:"number",min:"1",max:"12",value:t.triggerPanel.rows,onChange:m=>h(Number(m.currentTarget.value),t.triggerPanel.columns)})]}),r("label",{class:"grid-size",children:["\u5217 ",r("input",{class:"field",type:"number",min:"1",max:"12",value:t.triggerPanel.columns,onChange:m=>h(t.triggerPanel.rows,Number(m.currentTarget.value))})]})]}),r("span",{class:"muted",children:[t.triggerPanel.rows," \u884C \xD7 ",t.triggerPanel.columns," \u5217"]}),r("button",{class:"btn close",onClick:()=>A.updateUi({drawerOpen:!1}),children:"\u9589\u3058\u308B\u3000Esc"})]}),r("div",{class:`grid ${o?"editing":""}`,style:{gridTemplateColumns:`repeat(${t.triggerPanel.columns},1fr)`,gridTemplateRows:`repeat(${t.triggerPanel.rows},1fr)`},children:_.map(m=>m.button?r("div",{class:"trigger-cell",children:[r("button",{class:"trig",onClick:()=>o?u(m):void l(m.button),children:m.button.label}),a[m.button.id]&&r("small",{class:a[m.button.id]==="\u9001\u4FE1\u3057\u307E\u3057\u305F"?"":"failure",children:a[m.button.id]}),o&&r("button",{class:"cell-remove","aria-label":`${m.button.label}\u3092\u524A\u9664`,onClick:()=>{b(m.button),A.updateUi({modalOpen:!0})},children:"\xD7"})]}):r("button",{class:"empty-cell","aria-label":`${m.row+1}\u884C${m.column+1}\u5217\u306B\u30DC\u30BF\u30F3\u3092\u4F5C\u6210`,disabled:!o,onClick:()=>u(m),children:o?"\uFF0B":""}))}),s&&r(gr,{cell:s,close:S,notice:e})," ",p&&r(J,{message:`\u300C${p.label}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`,close:S,confirm:()=>{R("triggerPanel.removeButton",{id:p.id}),S()}}),d&&r(J,{message:`${d.rows} \u884C \xD7 ${d.columns} \u5217\u306B\u7E2E\u5C0F\u3059\u308B\u3068\u3001\u7BC4\u56F2\u5916\u306E\u30DC\u30BF\u30F3 ${d.lost} \u500B\u304C\u6D88\u3048\u307E\u3059\u3002\u7D9A\u3051\u307E\u3059\u304B\uFF1F`,confirmLabel:"\u7E2E\u5C0F",close:S,confirm:()=>{R("triggerPanel.configureGrid",{rows:d.rows,columns:d.columns}),S()}})]})}var Pt=e=>e.hosts.filter(t=>t.enabled&&e.cueSync.targetHostIds.includes(t.id)).length;function br(e,t){e?.sentCount===0&&t("\u9001\u4FE1\u5148\u30DB\u30B9\u30C8\u304C\u3042\u308A\u307E\u305B\u3093")}function _r(e,t,n){if(Pt(e)===0){t("Cue-Sync \u306E\u9001\u4FE1\u5148\u30DB\u30B9\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044");return}R("cueSync.send",{}).then(()=>n()).catch(o=>t(o.message))}var vr=new Map,Q=new Map,hr=e=>e.split(/[\\/]/).pop()||e,yr=e=>e==null||!Number.isFinite(e)?"-- dBFS":`${e>=0?"":"\u2212"}${Math.abs(e).toFixed(1)} dBFS`,Ot=0;function wr({current:e,frameRate:t,apply:n}){let[o,c]=T(e),[s,u]=T(e);s!==e&&(u(e),c(e));let p=o!==e,b=$({draft:o,current:e});return b.current={draft:o,current:e},r("span",{class:"offset-editor",children:[r(V,{class:"monitor-offset",value:o,frameRate:t,signed:!0,ariaLabel:"\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u30AA\u30D5\u30BB\u30C3\u30C8",onChange:c}),r("button",{class:`btn mini ${p?"primary":""}`,disabled:!p,onClick:()=>{let{draft:i,current:a}=b.current;i!==a&&n(i)},children:"\u78BA\u5B9A"}),p&&r("span",{class:"tiny offset-pending",children:"\u672A\u78BA\u5B9A"})]})}function Mt(){let[,e]=T(0),[t,n]=T(null),[o,c]=T(!1),[s,u]=T(""),[p,b]=T(0),d=A.value,i=d.host.state,a=d.host.clock??i?.currentClock;M(()=>A.subscribe(()=>{for(let f of It(vr,A.value.host.state?.cues??[])){let D=(Q.get(f)??0)+1;Q.set(f,D),window.setTimeout(()=>{Q.get(f)===D&&(Q.delete(f),e(k=>k+1))},700)}e(f=>f+1)}),[]),M(()=>A.updateUi({modalOpen:t!==null}),[t]),M(()=>{if(!s)return;let f=window.setTimeout(()=>u(""),4e3);return()=>window.clearTimeout(f)},[s]),M(()=>{let f=()=>z({protocolVersion:1,type:"viewport",waveformWidth:360,devicePixelRatio:window.devicePixelRatio,visible:document.visibilityState==="visible"});return f(),window.addEventListener("resize",f),document.addEventListener("visibilitychange",f),()=>{window.removeEventListener("resize",f),document.removeEventListener("visibilitychange",f)}},[]);let g=()=>i?.cues.filter(f=>d.ui.selectedCueIds.has(f.id))??[],h=()=>{g().length&&n(g().length===1?"edit":"batch")},S=()=>{g().length&&n("remove")},l=(f,D={})=>void R(f,D).catch(k=>u(k.message)),_=()=>b(f=>f+1),m=()=>{i&&_r(i,u,_)},v=f=>void R("mode.set",{mode:f}).then(D=>{f==="ltc"&&D?.ltcStarted===!1&&u("\u5165\u529B\u30C7\u30D0\u30A4\u30B9\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044")}).catch(D=>u(D.message));if(M(()=>{let f=D=>{let k=_t(D,d.ui.modalOpen);if(k)if(D.preventDefault(),k==="cueSync")m();else if(k==="toggleMute"&&i)l("mute.set",{muted:!i.transport.triggerMuted});else if(k==="toggleDrawer")A.updateUi({drawerOpen:!d.ui.drawerOpen});else if(k==="closeDrawer")A.updateUi({drawerOpen:!1});else if(k==="editCue")h();else if(k==="deleteCue")S();else if(k==="selectPrevious"||k==="selectNext"){if(!i)return;let F=Math.max(0,i.cues.findIndex(j=>j.id===d.ui.focusedCueId)),L=i.cues[Math.max(0,Math.min(i.cues.length-1,F+(k==="selectNext"?1:-1)))];L&&A.updateUi({focusedCueId:L.id,selectedCueIds:new Set([L.id])})}else l({projectNew:"project.new",projectOpen:"project.open",projectSave:"project.save",projectSaveAs:"project.saveAs",undo:"app.undo",redo:"app.redo"}[k])};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)}),!i||!a)return r("main",{class:"app loading",children:"HOST \u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u3059\u2026"});let w=(f,D)=>{let k=new Set(d.ui.selectedCueIds);if(D.metaKey||D.ctrlKey)k.has(f)?k.delete(f):k.add(f);else if(D.shiftKey&&d.ui.anchorCueId){let F=i.cues.findIndex(j=>j.id===d.ui.anchorCueId),L=i.cues.findIndex(j=>j.id===f);k=new Set(i.cues.slice(Math.min(F,L),Math.max(F,L)+1).map(j=>j.id))}else k=new Set([f]);A.updateUi({selectedCueIds:k,focusedCueId:f,anchorCueId:f})},y=f=>{c(!1),l(f)},C=()=>n(null),x=(f,D)=>{let k=D.clientX,F=[...d.ui.cueColumnWidths];D.currentTarget.setPointerCapture(D.pointerId);let L=jt=>{let je=jt.clientX-k,Lt=Math.max(38,(F[f]??38)+je),Bt=Math.max(50,(F[f+1]??50)-je),_e=[...F];_e[f]=Lt,_e[f+1]=Bt,A.updateUi({cueColumnWidths:_e})},j=()=>{window.removeEventListener("pointermove",L),window.removeEventListener("pointerup",j)};window.addEventListener("pointermove",L),window.addEventListener("pointerup",j)},P=i.cues.find(f=>f.id===i.transport.autoMutedCueId),W={"--cue-cols":d.ui.cueColumnWidths.map(f=>`${f}px`).join(" ")};return r(H,{children:[r("main",{class:"app",children:[r("header",{children:[r("b",{children:"TIMEcode BRIDGE 3"}),r("button",{class:"project-button",onClick:()=>c(!o),children:[i.project.displayName,"\u3000\u25CF ",i.project.dirty?"\u672A\u4FDD\u5B58":"\u4FDD\u5B58\u6E08\u307F"]}),o&&r("div",{class:"project-menu",children:[r("button",{onClick:()=>y("project.new"),children:"\u65B0\u898F"}),r("button",{onClick:()=>y("project.open"),children:"\u958B\u304F\u2026"}),r("button",{onClick:()=>y("project.save"),children:"\u4FDD\u5B58"}),r("button",{onClick:()=>y("project.saveAs"),children:"\u540D\u524D\u3092\u4ED8\u3051\u3066\u4FDD\u5B58\u2026"}),i.project.recentFiles.length>0&&r(H,{children:[r("div",{class:"project-menu-sep",children:"\u6700\u8FD1\u4F7F\u3063\u305F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8"}),i.project.recentFiles.map(f=>r("button",{class:"recent",title:f,onClick:()=>{c(!1),l("project.open",{path:f})},children:hr(f)}))]})]}),r("button",{class:"btn","aria-label":"\u53D6\u308A\u6D88\u3059",disabled:!i.project.canUndo,onClick:()=>l("app.undo"),children:"\u21B6"}),r("button",{class:"btn","aria-label":"\u3084\u308A\u76F4\u3059",disabled:!i.project.canRedo,onClick:()=>l("app.redo"),children:"\u21B7"}),r("span",{class:"right muted",children:[r("i",{class:"led"}),"ENGINE ONLINE\u3000 ",a.frameRate," FPS / ",a.dropFrame?"DROP":"NON-DROP"]})]}),r("section",{class:"monitor",children:[r("div",{children:[r("span",{class:"tiny",children:[r("i",{class:"led"}),"\u53D7\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9 / OFFSET\u9069\u7528\u5F8C"]}),r("div",{class:"tc",children:a.display}),r("span",{class:"tiny monitor-offset-line",children:["RAW\u3000",r("span",{class:"mono",children:a.raw}),"\u3000 OFFSET\u3000",r(wr,{current:i.receive.offset,frameRate:a.frameRate,apply:f=>l("receive.setOffset",{value:f})}),"\u3000 ",i.transport.locked?"LOCK":"UNLOCK"," / ",yr(d.host.wave?.levelDbfs)]})]}),r("div",{class:"next",children:[r("span",{class:"tiny",children:["NEXT CUE ",r("b",{class:"right",children:["T\u2212 ",i.nextCue?a.framesUntilNextCue??"--":"--","f"]})]}),r("div",{class:"next-main",children:[r("strong",{children:i.nextCue?.effectiveTriggerTime??"--:--:--:--"}),r("b",{children:i.nextCue?.name??"\u6B21\u306E\u30AD\u30E5\u30FC\u306F\u3042\u308A\u307E\u305B\u3093"})]}),r("span",{class:"tiny mono",children:i.nextCue?i.cues.find(f=>f.id===i.nextCue?.id)?.oscAddress:"\u2014"})]}),r("div",{class:"majorbox",children:[r("button",{class:`major ${p===0?"":p%2?"flash-a":"flash-b"}`,onClick:m,children:["CUE SYNC",r("small",{children:["SPACE \xB7 ",Pt(i),"\u30DB\u30B9\u30C8\u3078\u9001\u4FE1"]})]}),r("button",{class:`major ${i.transport.triggerMuted?"on":""}`,onClick:()=>l("mute.set",{muted:!i.transport.triggerMuted}),children:[i.transport.triggerMuted?"MUTE":"LIVE",r("small",{children:["L \xB7 ",P?.runtime.muteCountdownText?`\u81EA\u52D5\u89E3\u9664\u307E\u3067 ${P.runtime.muteCountdownText}`:`\u30AD\u30E5\u30FC\u9001\u51FA ${i.transport.triggerMuted?"\u505C\u6B62":"\u6709\u52B9"}`]})]})]})]}),r("section",{class:"work",children:[r("section",{class:"cues",style:W,children:[r("div",{class:"tool",children:[r("h2",{children:"\u30AD\u30E5\u30FC\u30EA\u30B9\u30C8"}),r("label",{class:"judge-control muted",title:"\u3053\u306E\u5E45(\u30D5\u30EC\u30FC\u30E0)\u4EE5\u5185\u306E\u524D\u9032\u3084\u5F8C\u9000\u306F\u901A\u5E38\u518D\u751F\u30FB\u30B8\u30C3\u30BF\u30FC\u3068\u3057\u3066\u6271\u3044\u3001\u53D6\u308A\u3053\u307C\u3057\u305F\u9593\u306E\u30AD\u30E5\u30FC\u3082\u767A\u706B\u3057\u307E\u3059\u3002\u8D85\u3048\u3066\u98DB\u3093\u3060\u3089\u982D\u51FA\u3057(\u30B7\u30FC\u30AF)\u3084\u5DFB\u304D\u623B\u3057\u3068\u3057\u3066\u9014\u4E2D\u306E\u30AD\u30E5\u30FC\u306F\u767A\u706B\u3057\u307E\u305B\u3093",children:[r("span",{children:"\u30B8\u30E3\u30F3\u30D7\u5224\u5B9A"}),r("span",{"aria-hidden":"true",children:"\xB1"}),r("input",{class:"inline-input judge-input",type:"number",value:i.receive.triggerWindowFrames,min:"0",step:"1","aria-label":"\u30B8\u30E3\u30F3\u30D7\u5224\u5B9A\uFF08\u30D5\u30EC\u30FC\u30E0\uFF09",onChange:f=>l("receive.setTriggerWindow",{frames:Number(f.currentTarget.value)})}),r("span",{"aria-hidden":"true",children:"f"})]}),r("button",{class:"btn",onClick:()=>n("add"),children:"\uFF0B \u8FFD\u52A0"}),r("button",{class:"btn",disabled:!g().length,onClick:h,children:g().length>1?"\u4E00\u62EC\u7DE8\u96C6":"\u7DE8\u96C6"}),r("button",{class:"btn",disabled:g().length!==1,onClick:()=>l("cue.duplicate",{id:g()[0].id}),children:"\u8907\u88FD"}),r("button",{class:"btn",disabled:g().length!==1,onClick:()=>n("duplicate"),children:"\u9023\u7D9A\u8907\u88FD"}),r("button",{class:"btn",disabled:!g().length,onClick:S,children:"\u524A\u9664"}),r("button",{class:"btn right",onClick:()=>l("cue.sortByTime"),children:"\u6642\u9593\u9806"})]}),r("div",{class:"cols",children:["\u6709\u52B9","\u30C8\u30EA\u30AC\u30FC\u6642\u9593","\u30AD\u30E5\u30FC\u540D","OSC\u30A2\u30C9\u30EC\u30B9","\u624B\u52D5"].map((f,D)=>r("span",{children:[f,D<4&&r("i",{class:"resize-handle",onPointerDown:k=>x(D,k)})]}))}),r("div",{class:"list",children:i.cues.map(f=>r("div",{class:`row ${f.id===a.nextCueId?"current":""} ${f.enabled?"":"disabled"} ${d.ui.selectedCueIds.has(f.id)?"selected":""} ${Q.has(f.id)?Q.get(f.id)%2?"fired-a":"fired-b":""}`,tabIndex:0,"aria-label":`${f.name} ${f.effectiveTriggerTime}`,onPointerDown:()=>{Ot=Date.now()},onFocus:()=>{Date.now()-Ot<300||d.ui.selectedCueIds.has(f.id)||A.updateUi({selectedCueIds:new Set([f.id]),focusedCueId:f.id,anchorCueId:f.id})},onClick:D=>w(f.id,D),onDblClick:D=>{D.target.closest("button,input")||h()},children:[r("span",{children:r("input",{type:"checkbox","aria-label":`${f.name}\u3092${f.enabled?"\u7121\u52B9":"\u6709\u52B9"}\u306B\u3059\u308B`,checked:f.enabled,onChange:D=>l("cue.setEnabled",{id:f.id,enabled:D.currentTarget.checked})})}),r("span",{class:"mono",children:f.effectiveTriggerTime}),r("span",{children:[f.name,i.transport.autoMutedCueId===f.id&&r("b",{class:"mute-tag",children:["MUTE",f.runtime.muteCountdownText?` ${f.runtime.muteCountdownText}`:""]})]}),r("span",{class:"mono",children:f.oscAddress}),r("button",{class:"btn fire","aria-label":`${f.name}\u3092\u767A\u706B`,onClick:D=>{D.stopPropagation(),R("cue.fire",{id:f.id}).then(k=>br(k,u)).catch(k=>u(k.message))},children:"\u25B6"})]},f.id))}),r("div",{class:"footcue",children:[r("span",{class:"tag",children:"NEXT"}),r("b",{class:"mono",children:i.nextCue?.effectiveTriggerTime}),r("span",{children:i.nextCue?.name}),r("label",{class:"right muted check auto-mute-switch",title:"\u30AD\u30E5\u30FC\u767A\u706B\u5F8C\u306E\u81EA\u52D5\u30DF\u30E5\u30FC\u30C8\u6A5F\u80FD\u5168\u4F53\u306E\u6709\u52B9/\u7121\u52B9(\u30AD\u30E5\u30FC\u3054\u3068\u306E\u300C\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8\u300D\u306F\u3053\u308C\u304C ON \u306E\u3068\u304D\u3060\u3051\u50CD\u304F)",children:[r("input",{type:"checkbox",checked:i.transport.autoMuteEnabled,onChange:f=>l("autoMute.setEnabled",{enabled:f.currentTarget.checked})}),"\u767A\u706B\u5F8C\u30AA\u30FC\u30C8\u30DF\u30E5\u30FC\u30C8\u3000",i.transport.autoMuteEnabled?"ON":"OFF"]})]})]}),r("aside",{class:"side",children:[r("div",{class:"sec",children:[r("div",{class:"head",children:[r("b",{children:"\u52D5\u4F5C\u30E2\u30FC\u30C9"}),r("span",{class:"tiny",children:i.transport.statusText})]}),r("div",{class:"mode",children:[r("button",{class:i.mode==="ltc"?"active":"",onClick:()=>v("ltc"),children:"LTC\u53D7\u4FE1"}),r("button",{class:i.mode==="generate"?"active":"",onClick:()=>v("generate"),children:"\u5185\u90E8\u751F\u6210"})]})]}),i.mode==="ltc"?r(St,{}):r(At,{notice:u}),r(Rt,{notice:u})]})]}),r("footer",{children:[r("span",{children:[r("i",{class:"led"}),i.mode==="ltc"?"LTC INPUT":"LTC GENERATOR"," ",i.transport.locked?"LOCK":"UNLOCK",i.transport.detailText?` \xB7 ${i.transport.detailText}`:""]}),r("button",{class:"btn right",onClick:()=>A.updateUi({drawerOpen:!0}),children:"OSC \u30DD\u30F3\u51FA\u3057\u3092\u958B\u304F\u3000P"}),r("span",{children:"SPACE: CUE SYNC\u3000 L: LIVE/MUTE"})]}),s&&r("div",{class:"toast",onClick:()=>u(""),children:s})]}),r(Et,{notice:u}),t==="add"&&r(He,{frameRate:a.frameRate,hosts:i.hosts,close:C,done:C})," ",t==="edit"&&r(He,{cue:g()[0],frameRate:a.frameRate,hosts:i.hosts,close:C,done:C})," ",t==="batch"&&r(kt,{ids:g().map(f=>f.id),hosts:i.hosts,frameRate:a.frameRate,close:C,done:C,notice:u})," ",t==="duplicate"&&r(Tt,{cue:g()[0],close:C,done:C})," ",t==="remove"&&r(J,{message:`\u9078\u629E\u3057\u305F ${g().length} \u4EF6\u306E\u30AD\u30E5\u30FC\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`,close:C,confirm:()=>{l("cue.remove",{ids:g().map(f=>f.id)}),C()}})]})}var X=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),N=e=>typeof e=="number"&&Number.isFinite(e);function Ht(e){return X(e)&&typeof e.raw=="string"&&typeof e.display=="string"&&N(e.totalFrames)&&["24","25","29.97df","30"].includes(String(e.frameRate))&&typeof e.dropFrame=="boolean"&&N(e.receivedAtMonotonicMs)}function Cr(e){return!X(e)||e.protocolVersion!==1||typeof e.type!="string"?!1:e.type==="clock"?N(e.seq)&&Ht(e.clock):e.type==="wave"?N(e.seq)&&X(e.wave)&&N(e.wave.sampleRate)&&N(e.wave.windowMs)&&Array.isArray(e.wave.min)&&Array.isArray(e.wave.max)&&e.wave.min.every(N)&&e.wave.max.every(N)&&(e.wave.levelDbfs===null||N(e.wave.levelDbfs)):e.type==="snapshot"?N(e.revision)&&X(e.state)&&typeof e.state.sessionId=="string"&&Array.isArray(e.state.cues)&&X(e.state.transport)&&Ht(e.state.currentClock):e.type==="patch"?N(e.revision)&&N(e.baseRevision)&&X(e.changes):e.type==="result"?typeof e.requestId=="string"&&typeof e.ok=="boolean"&&(e.ok||X(e.error)):!1}function Ft(e){try{let t=typeof e=="string"?JSON.parse(e):e;return Cr(t)?t:null}catch{return null}}var Fe=(e,t,n,o)=>({id:e,name:t,memo:"",triggerTime:n,effectiveTriggerTime:n,frameRate:"30",oscAddress:o,additionalOscAddresses:[],arguments:[],targetHostIds:["qlab"],enabled:!0,sendTriggerTimeAsSeconds:!1,sendTimecode:null,triggerOffset:null,autoMuteOnFire:!1,autoUnmuteAfter:null,runtime:{lastTriggeredAtUtc:null,flashToken:0,muteCountdownText:""}});function Tr(){return{sessionId:"fake-session",project:{displayName:"show_A.tcb3",filePath:null,dirty:!1,canUndo:!1,canRedo:!1,recentFiles:[]},mode:"ltc",transport:{status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",detailText:"CH 1 \xB7 48 kHz",signalErrorRatePercent:0,locked:!0,triggerMuted:!1,autoMuteEnabled:!1,autoMutedCueId:null,autoUnmuteAtUtc:null,lastLtcReceivedAtUtc:null},receive:{selectedDeviceId:"in",devices:[{id:"in",name:"UR44 \u2014 Input 1 / 2",loopback:!1}],offset:"+00:00:00:08",triggerWindowFrames:3,freerunDurationSeconds:1},generator:{running:!1,startTime:"01:00:00:00",frameRate:"30",selectedOutputDeviceId:"out",outputDevices:[{id:"out",name:"UR44 \u2014 Output 1 / 2",loopback:!1}],volume:.4,ltcOutputActive:!1,settingsPendingReset:!1},currentClock:{raw:"01:24:16:00",display:"01:24:16:08",totalFrames:151688,frameRate:"30",dropFrame:!1,receivedAtMonotonicMs:0,nextCueId:"cue-2",framesUntilNextCue:112},nextCue:{id:"cue-2",name:"\u30BF\u30A4\u30C8\u30EB OUT",triggerTime:"01:24:20:00",effectiveTriggerTime:"01:24:20:00",framesUntil:112},cues:[Fe("cue-1","\u672C\u7DE8 IN","01:23:40:00","/qlab/go"),Fe("cue-2","\u30BF\u30A4\u30C8\u30EB OUT","01:24:20:00","/video/title/out")],hosts:[{id:"qlab",name:"QLab \u2014 Main",ipAddress:"192.168.1.20",port:53e3,enabled:!0,reachability:"reachable"},{id:"reso",name:"Resolume \u2014 Stage",ipAddress:"192.168.1.31",port:7e3,enabled:!0,reachability:"reachable"}],cueSync:{oscAddress:"/cuesync",targetHostIds:["qlab","reso"]},triggerPanel:{rows:4,columns:4,buttons:[{id:"go",row:0,column:0,label:"GO",oscAddress:"/go",arguments:[],targetHostIds:["qlab"]}]},logs:[{id:"log-1",timestampUtc:new Date(Date.now()-1e3).toISOString(),message:"fake-host \u3092\u958B\u59CB\u3057\u307E\u3057\u305F",success:!0}],uiCapabilities:{supportsNativeOpenDialog:!1,supportsNativeSaveDialog:!1,platform:"macos"}}}function Ut(){let e=Tr(),t=1,n=0,o=10,c=!0,s=null,u=d=>queueMicrotask(()=>c&&window.tcb.receive(JSON.stringify(d))),p=d=>{let{logsAppend:i,logsReset:a,...g}=d;e={...e,...g},a&&(e.logs=a),i&&(e.logs=[...e.logs,...i]),u({protocolVersion:1,type:"patch",baseRevision:t,revision:++t,changes:d})},b=d=>{let i=JSON.parse(d);if(i.type==="ready"||i.type==="resync"){u({protocolVersion:1,type:"snapshot",revision:t,state:e}),s===null&&(s=window.setInterval(()=>u({protocolVersion:1,type:"clock",seq:++n,clock:{...e.currentClock,receivedAtMonotonicMs:performance.now()}}),100));return}if(i.type!=="command")return;let a=i.args,g=l=>u({protocolVersion:1,type:"result",requestId:i.requestId,ok:!0,data:l,revision:t}),h=l=>u({protocolVersion:1,type:"result",requestId:i.requestId,ok:!1,error:{code:"validation",message:"\u5165\u529B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",retryable:!1,fieldErrors:l}}),S=(l,_=!0)=>p({logsAppend:[{id:`log-${++o}`,timestampUtc:new Date().toISOString(),message:l,success:_}]});if(i.command==="mode.set"){let l=a.mode,_=l==="ltc"&&!!e.receive.selectedDeviceId;p({mode:l,transport:{...e.transport,status:_?"receiving":"stopped",statusText:_?"\u53D7\u4FE1\u4E2D":l==="ltc"?"\u505C\u6B62\u4E2D":"\u751F\u6210\u5F85\u6A5F",locked:_}}),g({ltcStarted:_});return}if(i.command==="ltc.stop"){p({transport:{...e.transport,status:"stopped",statusText:"\u505C\u6B62",locked:!1}}),g();return}if(i.command==="receive.selectDevice"&&!(a.deviceId??"")){p({receive:{...e.receive,selectedDeviceId:null},transport:{...e.transport,status:"stopped",statusText:"\u505C\u6B62\u4E2D",locked:!1}}),g();return}if(i.command==="receive.selectDevice"){p({receive:{...e.receive,selectedDeviceId:a.deviceId},transport:{...e.transport,status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",locked:!0}}),g();return}if(i.command==="ltc.start"||i.command==="ltc.reconnect"){p({receive:{...e.receive,selectedDeviceId:a.deviceId??e.receive.selectedDeviceId},transport:{...e.transport,status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",locked:!0}}),g();return}if(i.command==="receive.setFreerunDuration"){p({receive:{...e.receive,freerunDurationSeconds:Number(a.seconds)}}),g();return}if(i.command==="audio.refreshDevices"){p(a.direction==="capture"?{receive:{...e.receive}}:{generator:{...e.generator}}),g();return}if(i.command==="generator.configure"){p({generator:{...e.generator,startTime:a.startTime,frameRate:a.frameRate,selectedOutputDeviceId:a.outputDeviceId||null,volume:a.volume,settingsPendingReset:e.generator.running}}),g();return}if(i.command==="generator.start"||i.command==="generator.stop"){let l=i.command.endsWith("start");p({generator:{...e.generator,running:l,ltcOutputActive:l&&!!e.generator.selectedOutputDeviceId,settingsPendingReset:!1},transport:{...e.transport,status:l?"receiving":"stopped",statusText:l?"\u751F\u6210\u4E2D":"\u505C\u6B62"}}),g();return}if(i.command==="generator.reset"){p({generator:{...e.generator,running:!1,ltcOutputActive:!1,settingsPendingReset:!1},currentClock:{...e.currentClock,raw:e.generator.startTime,display:e.generator.startTime}}),g();return}if(i.command==="host.add"||i.command==="host.update"){let l=a.host,_={...l.name?{}:{"host.name":"\u5FC5\u9808"},...l.ipAddress?{}:{"host.ipAddress":"\u5FC5\u9808"},...l.port>=1&&l.port<=65535?{}:{"host.port":"1\u301C65535"}};if(Object.keys(_).length){h(_);return}let m=i.command==="host.add"?`host-${++o}`:String(a.id),v={id:m,name:l.name,ipAddress:l.ipAddress,port:l.port,enabled:l.enabled??!0,reachability:"unknown"};p({hosts:i.command==="host.add"?[...e.hosts,v]:e.hosts.map(w=>w.id===m?v:w)}),g({id:m});return}if(i.command==="host.remove"){p({hosts:e.hosts.filter(l=>l.id!==a.id)}),g();return}if(i.command==="host.setEnabled"){p({hosts:e.hosts.map(l=>l.id===a.id?{...l,enabled:!!a.enabled}:l)}),g();return}if(i.command==="host.ping"){p({hosts:e.hosts.map(l=>l.id===a.id?{...l,reachability:"reachable"}:l)}),g({reachable:!0,latencyMs:2});return}if(i.command==="triggerPanel.configureGrid"){p({triggerPanel:{...e.triggerPanel,rows:Number(a.rows),columns:Number(a.columns),buttons:e.triggerPanel.buttons.filter(l=>l.rowm.id!==l),_]}}),g({id:l});return}if(i.command==="triggerPanel.removeButton"){p({triggerPanel:{...e.triggerPanel,buttons:e.triggerPanel.buttons.filter(l=>l.id!==a.id)}}),g();return}if(i.command==="triggerPanel.fire"){S(`\u30DD\u30F3\u51FA\u3057: ${e.triggerPanel.buttons.find(l=>l.id===a.id)?.label??a.id}`),g({sent:!0,sentCount:1,skippedHostIds:[],reason:"None"});return}if(i.command==="logs.clear"){p({logsReset:[]}),g();return}if(i.command==="cueSync.configure"){p({cueSync:{...e.cueSync,...a}}),g();return}if(i.command==="cueSync.send"){S("Cue-Sync \u3092\u9001\u4FE1\u3057\u307E\u3057\u305F"),g({sent:!0,sentCount:2,skippedHostIds:[]});return}if(i.command==="cue.add"||i.command==="cue.update"){let l=a.cue,_={...Z(l.frameRate??"30"),...l,additionalOscAddresses:(l.additionalOscAddresses??[]).join(` +string:GO`}),n("small",{class:"field-error",children:b.arguments})]}),n("fieldset",{children:[n("legend",{children:"\u9001\u4FE1\u5148\u30DB\u30B9\u30C8"}),S.hosts.map(l=>n("label",{children:[n("input",{type:"checkbox",checked:i.includes(l.id),onChange:_=>a(_.currentTarget.checked?[...i,l.id]:i.filter(m=>m!==l.id))}),l.name]}))]}),n("div",{class:"dialog-actions",children:[n("button",{type:"button",class:"btn",onClick:t,children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),n("button",{class:"btn primary",children:"\u4FDD\u5B58"})]})]})})}function Et({notice:e}){let t=A.value.host.state,r=A.value.ui,[o,c]=T(!1),[s,d]=T(null),[p,f]=T(null),[u,i]=T(null),[a,b]=T({}),h=(m,v)=>{let w=t.triggerPanel.buttons.filter(y=>y.row>=m||y.column>=v).length;if(w>0){i({rows:m,columns:v,lost:w}),A.updateUi({modalOpen:!0});return}R("triggerPanel.configureGrid",{rows:m,columns:v})},S=()=>{d(null),i(null),f(null),A.updateUi({modalOpen:!1})},l=async m=>{try{let v=await R("triggerPanel.fire",{id:m.id});b(w=>({...w,[m.id]:v.sent===!1?v.reason??"\u9001\u4FE1\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F":"\u9001\u4FE1\u3057\u307E\u3057\u305F"}))}catch(v){b(w=>({...w,[m.id]:v.message}))}},_=Array.from({length:t.triggerPanel.rows*t.triggerPanel.columns},(m,v)=>{let w=Math.floor(v/t.triggerPanel.columns),y=v%t.triggerPanel.columns;return{row:w,column:y,button:t.triggerPanel.buttons.find(C=>C.row===w&&C.column===y)}});return n("section",{class:`drawer ${r.drawerOpen?"open":""}`,style:{height:r.drawerHeight},children:[n("div",{class:"dh",children:[n("h2",{children:"OSC \u30DD\u30F3\u51FA\u3057"}),n("button",{class:`btn ${o?"":"primary"}`,onClick:()=>c(!1),children:"\u5B9F\u884C\u30E2\u30FC\u30C9"}),n("button",{class:`btn ${o?"primary":""}`,onClick:()=>c(!0),children:"\u7DE8\u96C6\u30E2\u30FC\u30C9"}),o&&n(M,{children:[n("label",{class:"grid-size",children:["\u884C ",n("input",{class:"field",type:"number",min:"1",max:"12",value:t.triggerPanel.rows,onChange:m=>h(Number(m.currentTarget.value),t.triggerPanel.columns)})]}),n("label",{class:"grid-size",children:["\u5217 ",n("input",{class:"field",type:"number",min:"1",max:"12",value:t.triggerPanel.columns,onChange:m=>h(t.triggerPanel.rows,Number(m.currentTarget.value))})]})]}),n("span",{class:"muted",children:[t.triggerPanel.rows," \u884C \xD7 ",t.triggerPanel.columns," \u5217"]}),n("button",{class:"btn close",onClick:()=>A.updateUi({drawerOpen:!1}),children:"\u9589\u3058\u308B\u3000Esc"})]}),n("div",{class:`grid ${o?"editing":""}`,style:{gridTemplateColumns:`repeat(${t.triggerPanel.columns},1fr)`,gridTemplateRows:`repeat(${t.triggerPanel.rows},1fr)`},children:_.map(m=>m.button?n("div",{class:"trigger-cell",children:[n("button",{class:"trig",onClick:()=>o?d(m):void l(m.button),children:m.button.label}),a[m.button.id]&&n("small",{class:a[m.button.id]==="\u9001\u4FE1\u3057\u307E\u3057\u305F"?"":"failure",children:a[m.button.id]}),o&&n("button",{class:"cell-remove","aria-label":`${m.button.label}\u3092\u524A\u9664`,onClick:()=>{f(m.button),A.updateUi({modalOpen:!0})},children:"\xD7"})]}):n("button",{class:"empty-cell","aria-label":`${m.row+1}\u884C${m.column+1}\u5217\u306B\u30DC\u30BF\u30F3\u3092\u4F5C\u6210`,disabled:!o,onClick:()=>d(m),children:o?"\uFF0B":""}))}),s&&n(vn,{cell:s,close:S,notice:e})," ",p&&n(J,{message:`\u300C${p.label}\u300D\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`,close:S,confirm:()=>{R("triggerPanel.removeButton",{id:p.id}),S()}}),u&&n(J,{message:`${u.rows} \u884C \xD7 ${u.columns} \u5217\u306B\u7E2E\u5C0F\u3059\u308B\u3068\u3001\u7BC4\u56F2\u5916\u306E\u30DC\u30BF\u30F3 ${u.lost} \u500B\u304C\u6D88\u3048\u307E\u3059\u3002\u7D9A\u3051\u307E\u3059\u304B\uFF1F`,confirmLabel:"\u7E2E\u5C0F",close:S,confirm:()=>{R("triggerPanel.configureGrid",{rows:u.rows,columns:u.columns}),S()}})]})}var Pt=e=>e.hosts.filter(t=>t.enabled&&e.cueSync.targetHostIds.includes(t.id)).length;function hn(e,t){e?.sentCount===0&&t("\u9001\u4FE1\u5148\u30DB\u30B9\u30C8\u304C\u3042\u308A\u307E\u305B\u3093")}function yn(e,t,r){if(Pt(e)===0){t("Cue-Sync \u306E\u9001\u4FE1\u5148\u30DB\u30B9\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044");return}R("cueSync.send",{}).then(()=>r()).catch(o=>t(o.message))}var wn=new Map,Q=new Map,Cn=e=>e.split(/[\\/]/).pop()||e,Tn=e=>e==null||!Number.isFinite(e)?"-- dBFS":`${e>=0?"":"\u2212"}${Math.abs(e).toFixed(1)} dBFS`,Ot=0;function kn({current:e,frameRate:t,apply:r}){let[o,c]=T(e),[s,d]=T(e);s!==e&&(d(e),c(e));let p=o!==e,f=$({draft:o,current:e});return f.current={draft:o,current:e},n("span",{class:"offset-editor",children:[n(V,{class:"monitor-offset",value:o,frameRate:t,signed:!0,ariaLabel:"\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9\u30AA\u30D5\u30BB\u30C3\u30C8",onChange:c}),n("button",{class:`btn mini ${p?"primary":""}`,disabled:!p,onClick:()=>{let{draft:i,current:a}=f.current;i!==a&&r(i)},children:"\u78BA\u5B9A"}),p&&n("span",{class:"tiny offset-pending",children:"\u672A\u78BA\u5B9A"})]})}function Mt(){let[,e]=T(0),[t,r]=T(null),[o,c]=T(!1),[s,d]=T(""),[p,f]=T(0),u=A.value,i=u.host.state,a=u.host.clock??i?.currentClock;H(()=>A.subscribe(()=>{for(let g of It(wn,A.value.host.state?.cues??[])){let x=(Q.get(g)??0)+1;Q.set(g,x),window.setTimeout(()=>{Q.get(g)===x&&(Q.delete(g),e(k=>k+1))},700)}e(g=>g+1)}),[]),H(()=>A.updateUi({modalOpen:t!==null}),[t]),H(()=>{if(!s)return;let g=window.setTimeout(()=>d(""),4e3);return()=>window.clearTimeout(g)},[s]),H(()=>{let g=()=>z({protocolVersion:1,type:"viewport",waveformWidth:360,devicePixelRatio:window.devicePixelRatio,visible:document.visibilityState==="visible"});return g(),window.addEventListener("resize",g),document.addEventListener("visibilitychange",g),()=>{window.removeEventListener("resize",g),document.removeEventListener("visibilitychange",g)}},[]);let b=()=>i?.cues.filter(g=>u.ui.selectedCueIds.has(g.id))??[],h=()=>{b().length&&r(b().length===1?"edit":"batch")},S=()=>{b().length&&r("remove")},l=(g,x={})=>void R(g,x).catch(k=>d(k.message)),_=()=>f(g=>g+1),m=()=>{i&&yn(i,d,_)},v=g=>void R("mode.set",{mode:g}).then(x=>{g==="ltc"&&x?.ltcStarted===!1&&d("\u5165\u529B\u30C7\u30D0\u30A4\u30B9\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044")}).catch(x=>d(x.message));if(H(()=>{let g=x=>{let k=_t(x,u.ui.modalOpen);if(k)if(x.preventDefault(),k==="cueSync")m();else if(k==="toggleMute"&&i)l("mute.set",{muted:!i.transport.triggerMuted});else if(k==="toggleDrawer")A.updateUi({drawerOpen:!u.ui.drawerOpen});else if(k==="closeDrawer")A.updateUi({drawerOpen:!1});else if(k==="editCue")h();else if(k==="deleteCue")S();else if(k==="selectPrevious"||k==="selectNext"){if(!i)return;let F=Math.max(0,i.cues.findIndex(j=>j.id===u.ui.focusedCueId)),L=i.cues[Math.max(0,Math.min(i.cues.length-1,F+(k==="selectNext"?1:-1)))];L&&A.updateUi({focusedCueId:L.id,selectedCueIds:new Set([L.id])})}else l({projectNew:"project.new",projectOpen:"project.open",projectSave:"project.save",projectSaveAs:"project.saveAs",undo:"app.undo",redo:"app.redo"}[k])};return window.addEventListener("keydown",g),()=>window.removeEventListener("keydown",g)}),!i||!a)return n("main",{class:"app loading",children:"HOST \u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u3059\u2026"});let w=(g,x)=>{let k=new Set(u.ui.selectedCueIds);if(x.metaKey||x.ctrlKey)k.has(g)?k.delete(g):k.add(g);else if(x.shiftKey&&u.ui.anchorCueId){let F=i.cues.findIndex(j=>j.id===u.ui.anchorCueId),L=i.cues.findIndex(j=>j.id===g);k=new Set(i.cues.slice(Math.min(F,L),Math.max(F,L)+1).map(j=>j.id))}else k=new Set([g]);A.updateUi({selectedCueIds:k,focusedCueId:g,anchorCueId:g})},y=g=>{c(!1),l(g)},C=()=>r(null),D=(g,x)=>{let k=x.clientX,F=[...u.ui.cueColumnWidths];x.currentTarget.setPointerCapture(x.pointerId);let L=jt=>{let je=jt.clientX-k,Lt=Math.max(38,(F[g]??38)+je),Bt=Math.max(50,(F[g+1]??50)-je),be=[...F];be[g]=Lt,be[g+1]=Bt,A.updateUi({cueColumnWidths:be})},j=()=>{window.removeEventListener("pointermove",L),window.removeEventListener("pointerup",j)};window.addEventListener("pointermove",L),window.addEventListener("pointerup",j)},P=i.cues.find(g=>g.id===i.transport.autoMutedCueId),W={"--cue-cols":u.ui.cueColumnWidths.map(g=>`${g}px`).join(" ")};return n(M,{children:[n("main",{class:"app",children:[n("header",{children:[n("b",{children:"TIMEcode BRIDGE 3"}),n("button",{class:"project-button",onClick:()=>c(!o),children:[i.project.displayName,"\u3000\u25CF ",i.project.dirty?"\u672A\u4FDD\u5B58":"\u4FDD\u5B58\u6E08\u307F"]}),o&&n("div",{class:"project-menu",children:[n("button",{onClick:()=>y("project.new"),children:"\u65B0\u898F"}),n("button",{onClick:()=>y("project.open"),children:"\u958B\u304F\u2026"}),n("button",{onClick:()=>y("project.save"),children:"\u4FDD\u5B58"}),n("button",{onClick:()=>y("project.saveAs"),children:"\u540D\u524D\u3092\u4ED8\u3051\u3066\u4FDD\u5B58\u2026"}),i.project.recentFiles.length>0&&n(M,{children:[n("div",{class:"project-menu-sep",children:"\u6700\u8FD1\u4F7F\u3063\u305F\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8"}),i.project.recentFiles.map(g=>n("button",{class:"recent",title:g,onClick:()=>{c(!1),l("project.open",{path:g})},children:Cn(g)}))]})]}),n("button",{class:"btn","aria-label":"\u53D6\u308A\u6D88\u3059",disabled:!i.project.canUndo,onClick:()=>l("app.undo"),children:"\u21B6"}),n("button",{class:"btn","aria-label":"\u3084\u308A\u76F4\u3059",disabled:!i.project.canRedo,onClick:()=>l("app.redo"),children:"\u21B7"}),n("span",{class:"right muted",children:[n("i",{class:"led"}),"ENGINE ONLINE\u3000 ",a.frameRate," FPS / ",a.dropFrame?"DROP":"NON-DROP"]})]}),n("section",{class:"monitor",children:[n("div",{children:[n("span",{class:"tiny",children:[n("i",{class:"led"}),"\u53D7\u4FE1\u30BF\u30A4\u30E0\u30B3\u30FC\u30C9 / OFFSET\u9069\u7528\u5F8C"]}),n("div",{class:"tc",children:a.display}),n("span",{class:"tiny monitor-offset-line",children:["RAW\u3000",n("span",{class:"mono",children:a.raw}),"\u3000 OFFSET\u3000",n(kn,{current:i.receive.offset,frameRate:a.frameRate,apply:g=>l("receive.setOffset",{value:g})}),"\u3000 ",i.transport.locked?"LOCK":"UNLOCK"," / ",Tn(u.host.wave?.levelDbfs)]})]}),n("div",{class:"next",children:[n("span",{class:"tiny",children:["NEXT CUE ",n("b",{class:"right",children:["T\u2212 ",i.nextCue?a.framesUntilNextCue??"--":"--","f"]})]}),n("div",{class:"next-main",children:[n("strong",{children:i.nextCue?.effectiveTriggerTime??"--:--:--:--"}),n("b",{children:i.nextCue?.name??"\u6B21\u306E\u30AD\u30E5\u30FC\u306F\u3042\u308A\u307E\u305B\u3093"})]}),n("span",{class:"tiny mono",children:i.nextCue?i.cues.find(g=>g.id===i.nextCue?.id)?.oscAddress:"\u2014"})]}),n("div",{class:"majorbox",children:[n("button",{class:`major ${p===0?"":p%2?"flash-a":"flash-b"}`,onClick:m,children:["CUE SYNC",n("small",{children:["SPACE \xB7 ",Pt(i),"\u30DB\u30B9\u30C8\u3078\u9001\u4FE1"]})]}),n("button",{class:`major ${i.transport.triggerMuted?"on":""}`,onClick:()=>l("mute.set",{muted:!i.transport.triggerMuted}),children:[i.transport.triggerMuted?"MUTE":"LIVE",n("small",{children:["L \xB7 ",P?.runtime.muteCountdownText?`\u81EA\u52D5\u89E3\u9664\u307E\u3067 ${P.runtime.muteCountdownText}`:`\u30AD\u30E5\u30FC\u9001\u51FA ${i.transport.triggerMuted?"\u505C\u6B62":"\u6709\u52B9"}`]})]})]})]}),n("section",{class:"work",children:[n("section",{class:"cues",style:W,children:[n("div",{class:"tool",children:[n("h2",{children:"\u30AD\u30E5\u30FC\u30EA\u30B9\u30C8"}),n("label",{class:"judge-control muted",title:"\u3053\u306E\u5E45(\u30D5\u30EC\u30FC\u30E0)\u4EE5\u5185\u306E\u524D\u9032\u3084\u5F8C\u9000\u306F\u901A\u5E38\u518D\u751F\u30FB\u30B8\u30C3\u30BF\u30FC\u3068\u3057\u3066\u6271\u3044\u3001\u53D6\u308A\u3053\u307C\u3057\u305F\u9593\u306E\u30AD\u30E5\u30FC\u3082\u767A\u706B\u3057\u307E\u3059\u3002\u8D85\u3048\u3066\u98DB\u3093\u3060\u3089\u982D\u51FA\u3057(\u30B7\u30FC\u30AF)\u3084\u5DFB\u304D\u623B\u3057\u3068\u3057\u3066\u9014\u4E2D\u306E\u30AD\u30E5\u30FC\u306F\u767A\u706B\u3057\u307E\u305B\u3093",children:[n("span",{children:"\u30B8\u30E3\u30F3\u30D7\u5224\u5B9A"}),n("span",{"aria-hidden":"true",children:"\xB1"}),n("input",{class:"inline-input judge-input",type:"number",value:i.receive.triggerWindowFrames,min:"0",step:"1","aria-label":"\u30B8\u30E3\u30F3\u30D7\u5224\u5B9A\uFF08\u30D5\u30EC\u30FC\u30E0\uFF09",onChange:g=>l("receive.setTriggerWindow",{frames:Number(g.currentTarget.value)})}),n("span",{"aria-hidden":"true",children:"f"})]}),n("button",{class:"btn",onClick:()=>r("add"),children:"\uFF0B \u8FFD\u52A0"}),n("button",{class:"btn",disabled:!b().length,onClick:h,children:b().length>1?"\u4E00\u62EC\u7DE8\u96C6":"\u7DE8\u96C6"}),n("button",{class:"btn",disabled:b().length!==1,onClick:()=>l("cue.duplicate",{id:b()[0].id}),children:"\u8907\u88FD"}),n("button",{class:"btn",disabled:b().length!==1,onClick:()=>r("duplicate"),children:"\u9023\u7D9A\u8907\u88FD"}),n("button",{class:"btn",disabled:!b().length,onClick:S,children:"\u524A\u9664"}),n("button",{class:"btn right",onClick:()=>l("cue.sortByTime"),children:"\u6642\u9593\u9806"})]}),n("div",{class:"cols",children:["\u6709\u52B9","\u30C8\u30EA\u30AC\u30FC\u6642\u9593","\u30AD\u30E5\u30FC\u540D","OSC\u30A2\u30C9\u30EC\u30B9","\u624B\u52D5"].map((g,x)=>n("span",{children:[g,x<4&&n("i",{class:"resize-handle",onPointerDown:k=>D(x,k)})]}))}),n("div",{class:"list",children:i.cues.map(g=>n("div",{class:`row ${g.id===a.nextCueId?"current":""} ${g.enabled?"":"disabled"} ${u.ui.selectedCueIds.has(g.id)?"selected":""} ${Q.has(g.id)?Q.get(g.id)%2?"fired-a":"fired-b":""}`,tabIndex:0,"aria-label":`${g.name} ${g.effectiveTriggerTime}`,onPointerDown:()=>{Ot=Date.now()},onFocus:()=>{Date.now()-Ot<300||u.ui.selectedCueIds.has(g.id)||A.updateUi({selectedCueIds:new Set([g.id]),focusedCueId:g.id,anchorCueId:g.id})},onClick:x=>w(g.id,x),onDblClick:x=>{x.target.closest("button,input")||h()},children:[n("span",{children:n("input",{type:"checkbox","aria-label":`${g.name}\u3092${g.enabled?"\u7121\u52B9":"\u6709\u52B9"}\u306B\u3059\u308B`,checked:g.enabled,onChange:x=>l("cue.setEnabled",{id:g.id,enabled:x.currentTarget.checked})})}),n("span",{class:"mono",children:g.effectiveTriggerTime}),n("span",{children:[g.name,i.transport.autoMutedCueId===g.id&&n("b",{class:"mute-tag",children:["MUTE",g.runtime.muteCountdownText?` ${g.runtime.muteCountdownText}`:""]})]}),n("span",{class:"mono",children:g.oscAddress}),n("button",{class:"btn fire","aria-label":`${g.name}\u3092\u767A\u706B`,onClick:x=>{x.stopPropagation(),R("cue.fire",{id:g.id}).then(k=>hn(k,d)).catch(k=>d(k.message))},children:"\u25B6"})]},g.id))}),n("div",{class:"footcue",children:[n("span",{class:"tag",children:"NEXT"}),n("b",{class:"mono",children:i.nextCue?.effectiveTriggerTime}),n("span",{children:i.nextCue?.name}),n("label",{class:"right muted check auto-mute-switch",title:"\u30AD\u30E5\u30FC\u767A\u706B\u5F8C\u306E\u81EA\u52D5\u30DF\u30E5\u30FC\u30C8\u6A5F\u80FD\u5168\u4F53\u306E\u6709\u52B9/\u7121\u52B9(\u30AD\u30E5\u30FC\u3054\u3068\u306E\u300C\u767A\u706B\u6642\u30DF\u30E5\u30FC\u30C8\u300D\u306F\u3053\u308C\u304C ON \u306E\u3068\u304D\u3060\u3051\u50CD\u304F)",children:[n("input",{type:"checkbox",checked:i.transport.autoMuteEnabled,onChange:g=>l("autoMute.setEnabled",{enabled:g.currentTarget.checked})}),"\u767A\u706B\u5F8C\u30AA\u30FC\u30C8\u30DF\u30E5\u30FC\u30C8\u3000",i.transport.autoMuteEnabled?"ON":"OFF"]})]})]}),n("aside",{class:"side",children:[n("div",{class:"sec",children:[n("div",{class:"head",children:[n("b",{children:"\u52D5\u4F5C\u30E2\u30FC\u30C9"}),n("span",{class:"tiny",children:i.transport.statusText})]}),n("div",{class:"mode",children:[n("button",{class:i.mode==="ltc"?"active":"",onClick:()=>v("ltc"),children:"LTC\u53D7\u4FE1"}),n("button",{class:i.mode==="generate"?"active":"",onClick:()=>v("generate"),children:"\u5185\u90E8\u751F\u6210"})]})]}),i.mode==="ltc"?n(St,{}):n(At,{notice:d}),n(Rt,{notice:d})]})]}),n("footer",{children:[n("span",{children:[n("i",{class:"led"}),i.mode==="ltc"?"LTC INPUT":"LTC GENERATOR"," ",i.transport.locked?"LOCK":"UNLOCK",i.transport.detailText?` \xB7 ${i.transport.detailText}`:""]}),n("button",{class:"btn right",onClick:()=>A.updateUi({drawerOpen:!0}),children:"OSC \u30DD\u30F3\u51FA\u3057\u3092\u958B\u304F\u3000P"}),n("span",{children:"SPACE: CUE SYNC\u3000 L: LIVE/MUTE"})]}),s&&n("div",{class:"toast",onClick:()=>d(""),children:s})]}),n(Et,{notice:d}),t==="add"&&n(Me,{frameRate:a.frameRate,hosts:i.hosts,close:C,done:C})," ",t==="edit"&&n(Me,{cue:b()[0],frameRate:a.frameRate,hosts:i.hosts,close:C,done:C})," ",t==="batch"&&n(kt,{ids:b().map(g=>g.id),hosts:i.hosts,frameRate:a.frameRate,close:C,done:C,notice:d})," ",t==="duplicate"&&n(Tt,{cue:b()[0],close:C,done:C})," ",t==="remove"&&n(J,{message:`\u9078\u629E\u3057\u305F ${b().length} \u4EF6\u306E\u30AD\u30E5\u30FC\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F`,close:C,confirm:()=>{l("cue.remove",{ids:b().map(g=>g.id)}),C()}})]})}var X=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),N=e=>typeof e=="number"&&Number.isFinite(e);function Ht(e){return X(e)&&typeof e.raw=="string"&&typeof e.display=="string"&&N(e.totalFrames)&&["24","25","29.97df","30"].includes(String(e.frameRate))&&typeof e.dropFrame=="boolean"&&N(e.receivedAtMonotonicMs)}function Sn(e){return!X(e)||e.protocolVersion!==1||typeof e.type!="string"?!1:e.type==="clock"?N(e.seq)&&Ht(e.clock):e.type==="wave"?N(e.seq)&&X(e.wave)&&N(e.wave.sampleRate)&&N(e.wave.windowMs)&&Array.isArray(e.wave.min)&&Array.isArray(e.wave.max)&&e.wave.min.every(N)&&e.wave.max.every(N)&&(e.wave.levelDbfs===null||N(e.wave.levelDbfs)):e.type==="snapshot"?N(e.revision)&&X(e.state)&&typeof e.state.sessionId=="string"&&Array.isArray(e.state.cues)&&X(e.state.transport)&&Ht(e.state.currentClock):e.type==="patch"?N(e.revision)&&N(e.baseRevision)&&X(e.changes):e.type==="result"?typeof e.requestId=="string"&&typeof e.ok=="boolean"&&(e.ok||X(e.error)):!1}function Ft(e){try{let t=typeof e=="string"?JSON.parse(e):e;return Sn(t)?t:null}catch{return null}}var Fe=(e,t,r,o)=>({id:e,name:t,memo:"",triggerTime:r,effectiveTriggerTime:r,frameRate:"30",oscAddress:o,additionalOscAddresses:[],arguments:[],targetHostIds:["qlab"],enabled:!0,sendTriggerTimeAsSeconds:!1,sendTimecode:null,triggerOffset:null,autoMuteOnFire:!1,autoUnmuteAfter:null,runtime:{lastTriggeredAtUtc:null,flashToken:0,muteCountdownText:""}});function An(){return{sessionId:"fake-session",project:{displayName:"show_A.tcb3",filePath:null,dirty:!1,canUndo:!1,canRedo:!1,recentFiles:[]},mode:"ltc",transport:{status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",detailText:"CH 1 \xB7 48 kHz",signalErrorRatePercent:0,locked:!0,triggerMuted:!1,autoMuteEnabled:!1,autoMutedCueId:null,autoUnmuteAtUtc:null,lastLtcReceivedAtUtc:null},receive:{selectedDeviceId:"in",devices:[{id:"in",name:"UR44 \u2014 Input 1 / 2",loopback:!1}],offset:"+00:00:00:08",triggerWindowFrames:3,freerunDurationSeconds:1},generator:{running:!1,startTime:"01:00:00:00",frameRate:"30",selectedOutputDeviceId:"out",outputDevices:[{id:"out",name:"UR44 \u2014 Output 1 / 2",loopback:!1}],volume:.4,ltcOutputActive:!1,settingsPendingReset:!1},currentClock:{raw:"01:24:16:00",display:"01:24:16:08",totalFrames:151688,frameRate:"30",dropFrame:!1,receivedAtMonotonicMs:0,nextCueId:"cue-2",framesUntilNextCue:112},nextCue:{id:"cue-2",name:"\u30BF\u30A4\u30C8\u30EB OUT",triggerTime:"01:24:20:00",effectiveTriggerTime:"01:24:20:00",framesUntil:112},cues:[Fe("cue-1","\u672C\u7DE8 IN","01:23:40:00","/qlab/go"),Fe("cue-2","\u30BF\u30A4\u30C8\u30EB OUT","01:24:20:00","/video/title/out")],hosts:[{id:"qlab",name:"QLab \u2014 Main",ipAddress:"192.168.1.20",port:53e3,enabled:!0,reachability:"reachable"},{id:"reso",name:"Resolume \u2014 Stage",ipAddress:"192.168.1.31",port:7e3,enabled:!0,reachability:"reachable"}],cueSync:{oscAddress:"/cuesync",targetHostIds:["qlab","reso"]},triggerPanel:{rows:4,columns:4,buttons:[{id:"go",row:0,column:0,label:"GO",oscAddress:"/go",arguments:[],targetHostIds:["qlab"]}]},logs:[{id:"log-1",timestampUtc:new Date(Date.now()-1e3).toISOString(),message:"fake-host \u3092\u958B\u59CB\u3057\u307E\u3057\u305F",success:!0}],uiCapabilities:{supportsNativeOpenDialog:!1,supportsNativeSaveDialog:!1,platform:"macos"}}}function Ut(){let e=An(),t=1,r=0,o=10,c=!0,s=null,d=u=>queueMicrotask(()=>c&&window.tcb.receive(JSON.stringify(u))),p=u=>{let{logsAppend:i,logsReset:a,...b}=u;e={...e,...b},a&&(e.logs=a),i&&(e.logs=[...e.logs,...i]),d({protocolVersion:1,type:"patch",baseRevision:t,revision:++t,changes:u})},f=u=>{let i=JSON.parse(u);if(i.type==="ready"||i.type==="resync"){d({protocolVersion:1,type:"snapshot",revision:t,state:e}),s===null&&(s=window.setInterval(()=>d({protocolVersion:1,type:"clock",seq:++r,clock:{...e.currentClock,receivedAtMonotonicMs:performance.now()}}),100));return}if(i.type!=="command")return;let a=i.args,b=l=>d({protocolVersion:1,type:"result",requestId:i.requestId,ok:!0,data:l,revision:t}),h=l=>d({protocolVersion:1,type:"result",requestId:i.requestId,ok:!1,error:{code:"validation",message:"\u5165\u529B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",retryable:!1,fieldErrors:l}}),S=(l,_=!0)=>p({logsAppend:[{id:`log-${++o}`,timestampUtc:new Date().toISOString(),message:l,success:_}]});if(i.command==="mode.set"){let l=a.mode,_=l==="ltc"&&!!e.receive.selectedDeviceId;p({mode:l,transport:{...e.transport,status:_?"receiving":"stopped",statusText:_?"\u53D7\u4FE1\u4E2D":l==="ltc"?"\u505C\u6B62\u4E2D":"\u751F\u6210\u5F85\u6A5F",locked:_}}),b({ltcStarted:_});return}if(i.command==="ltc.stop"){p({transport:{...e.transport,status:"stopped",statusText:"\u505C\u6B62",locked:!1}}),b();return}if(i.command==="receive.selectDevice"&&!(a.deviceId??"")){p({receive:{...e.receive,selectedDeviceId:null},transport:{...e.transport,status:"stopped",statusText:"\u505C\u6B62\u4E2D",locked:!1}}),b();return}if(i.command==="receive.selectDevice"){p({receive:{...e.receive,selectedDeviceId:a.deviceId},transport:{...e.transport,status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",locked:!0}}),b();return}if(i.command==="ltc.start"||i.command==="ltc.reconnect"){p({receive:{...e.receive,selectedDeviceId:a.deviceId??e.receive.selectedDeviceId},transport:{...e.transport,status:"receiving",statusText:"\u53D7\u4FE1\u4E2D",locked:!0}}),b();return}if(i.command==="receive.setFreerunDuration"){p({receive:{...e.receive,freerunDurationSeconds:Number(a.seconds)}}),b();return}if(i.command==="audio.refreshDevices"){p(a.direction==="capture"?{receive:{...e.receive}}:{generator:{...e.generator}}),b();return}if(i.command==="generator.configure"){p({generator:{...e.generator,startTime:a.startTime,frameRate:a.frameRate,selectedOutputDeviceId:a.outputDeviceId||null,volume:a.volume,settingsPendingReset:e.generator.running}}),b();return}if(i.command==="generator.start"||i.command==="generator.stop"){let l=i.command.endsWith("start");p({generator:{...e.generator,running:l,ltcOutputActive:l&&!!e.generator.selectedOutputDeviceId,settingsPendingReset:!1},transport:{...e.transport,status:l?"receiving":"stopped",statusText:l?"\u751F\u6210\u4E2D":"\u505C\u6B62"}}),b();return}if(i.command==="generator.reset"){p({generator:{...e.generator,running:!1,ltcOutputActive:!1,settingsPendingReset:!1},currentClock:{...e.currentClock,raw:e.generator.startTime,display:e.generator.startTime}}),b();return}if(i.command==="host.add"||i.command==="host.update"){let l=a.host,_={...l.name?{}:{"host.name":"\u5FC5\u9808"},...l.ipAddress?{}:{"host.ipAddress":"\u5FC5\u9808"},...l.port>=1&&l.port<=65535?{}:{"host.port":"1\u301C65535"}};if(Object.keys(_).length){h(_);return}let m=i.command==="host.add"?`host-${++o}`:String(a.id),v={id:m,name:l.name,ipAddress:l.ipAddress,port:l.port,enabled:l.enabled??!0,reachability:"unknown"};p({hosts:i.command==="host.add"?[...e.hosts,v]:e.hosts.map(w=>w.id===m?v:w)}),b({id:m});return}if(i.command==="host.remove"){p({hosts:e.hosts.filter(l=>l.id!==a.id)}),b();return}if(i.command==="host.setEnabled"){p({hosts:e.hosts.map(l=>l.id===a.id?{...l,enabled:!!a.enabled}:l)}),b();return}if(i.command==="host.ping"){p({hosts:e.hosts.map(l=>l.id===a.id?{...l,reachability:"reachable"}:l)}),b({reachable:!0,latencyMs:2});return}if(i.command==="triggerPanel.configureGrid"){p({triggerPanel:{...e.triggerPanel,rows:Number(a.rows),columns:Number(a.columns),buttons:e.triggerPanel.buttons.filter(l=>l.rowm.id!==l),_]}}),b({id:l});return}if(i.command==="triggerPanel.removeButton"){p({triggerPanel:{...e.triggerPanel,buttons:e.triggerPanel.buttons.filter(l=>l.id!==a.id)}}),b();return}if(i.command==="triggerPanel.fire"){S(`\u30DD\u30F3\u51FA\u3057: ${e.triggerPanel.buttons.find(l=>l.id===a.id)?.label??a.id}`),b({sent:!0,sentCount:1,skippedHostIds:[],reason:"None"});return}if(i.command==="logs.clear"){p({logsReset:[]}),b();return}if(i.command==="cueSync.configure"){p({cueSync:{...e.cueSync,...a}}),b();return}if(i.command==="cueSync.send"){S("Cue-Sync \u3092\u9001\u4FE1\u3057\u307E\u3057\u305F"),b({sent:!0,sentCount:2,skippedHostIds:[]});return}if(i.command==="cue.add"||i.command==="cue.update"){let l=a.cue,_={...Z(l.frameRate??"30"),...l,additionalOscAddresses:(l.additionalOscAddresses??[]).join(` `),argumentsText:(l.arguments??[]).map(y=>`${y.type}:${y.value}`).join(` -`),sendTimecode:l.sendTimecode??"",triggerOffset:l.triggerOffset??"",autoUnmuteAfter:l.autoUnmuteAfter??""},m=me(_);if(!m.draft){h(Object.fromEntries(Object.entries(m.errors).map(([y,C])=>[`cue.${y}`,C])));return}let v=i.command==="cue.add"?`cue-${++o}`:String(a.id),w={...Fe(v,m.draft.name,m.draft.triggerTime,m.draft.oscAddress),...m.draft,id:v,effectiveTriggerTime:m.draft.triggerTime};p({cues:i.command==="cue.add"?[...e.cues,w]:e.cues.map(y=>y.id===v?w:y),project:{...e.project,dirty:!0,canUndo:!0}}),g({id:v});return}if(i.command==="cue.remove"){p({cues:e.cues.filter(l=>!a.ids.includes(l.id))}),g();return}if(i.command==="cue.duplicate"){let l=e.cues.find(m=>m.id===a.id),_=[];if(l){let m=Array.from({length:Number(a.count??1)},(v,w)=>{let y=`cue-${++o}`;return _.push(y),{...l,id:y,name:`${l.name} (\u30B3\u30D4\u30FC${w?` ${w+1}`:""})`}});p({cues:[...e.cues,...m]})}g({ids:_});return}if(i.command==="cue.batchUpdate"){p({cues:e.cues.map(l=>a.ids.includes(l.id)?{...l,...a.changes}:l)}),g({updated:a.ids.length,offsetSkipped:0});return}if(i.command.startsWith("project.")){p({...i.command==="project.new"?{cues:[]}:{},project:{...e.project,displayName:i.command==="project.new"?"\u540D\u79F0\u672A\u8A2D\u5B9A.tcb3":e.project.displayName,dirty:!1,canUndo:!1,canRedo:!1}}),g({cancelled:!1});return}if(i.command==="app.undo"||i.command==="app.redo"){p({project:{...e.project,canUndo:i.command==="app.redo",canRedo:i.command==="app.undo"}}),g();return}i.command==="mute.set"&&p({transport:{...e.transport,triggerMuted:!!a.muted}}),i.command==="cue.setEnabled"&&p({cues:e.cues.map(l=>l.id===a.id?{...l,enabled:!!a.enabled}:l)}),g()};return window.invokeCSharpAction=b,{dispose:()=>{c=!1,s!==null&&window.clearInterval(s),window.invokeCSharpAction===b&&delete window.invokeCSharpAction}}}var Ue=!1,Ne=!1;function kr(){Ne=!0,!Ue&&(Ue=!0,requestAnimationFrame(()=>{Ue=!1,Ne&&(Ne=!1,A.notify())}))}var $e=()=>z({protocolVersion:1,type:"ready",clientVersion:"3.0.0",capabilities:["clock-latest-only","wave-minmax","m1","m2a","m2b","m3"]});function Nt(e=800){let t=!1,n=null,o=window.setTimeout(()=>{t||window.invokeCSharpAction||(n=Ut(),$e())},e);return window.tcb={receive:c=>{let s=Ft(c);if(!s)return;if(s.type==="result"){gt(s);return}ft(A.value,s)==="resync"&&z({protocolVersion:1,type:"resync",currentRevision:A.value.host.revision,reason:"patch baseRevision mismatch"}),kr()},hostAttached:()=>{t=!0,window.clearTimeout(o),n?.dispose(),n=null,$e()}},$e(),{dispose:()=>{t=!0,window.clearTimeout(o),n?.dispose(),n=null}}}Nt();var $t=document.getElementById("app");if(!$t)throw new Error("Missing #app mount point");rt(r(Mt,{}),$t);})(); +`),sendTimecode:l.sendTimecode??"",triggerOffset:l.triggerOffset??"",autoUnmuteAfter:l.autoUnmuteAfter??""},m=me(_);if(!m.draft){h(Object.fromEntries(Object.entries(m.errors).map(([y,C])=>[`cue.${y}`,C])));return}let v=i.command==="cue.add"?`cue-${++o}`:String(a.id),w={...Fe(v,m.draft.name,m.draft.triggerTime,m.draft.oscAddress),...m.draft,id:v,effectiveTriggerTime:m.draft.triggerTime};p({cues:i.command==="cue.add"?[...e.cues,w]:e.cues.map(y=>y.id===v?w:y),project:{...e.project,dirty:!0,canUndo:!0}}),b({id:v});return}if(i.command==="cue.remove"){p({cues:e.cues.filter(l=>!a.ids.includes(l.id))}),b();return}if(i.command==="cue.duplicate"){let l=e.cues.find(m=>m.id===a.id),_=[];if(l){let m=Array.from({length:Number(a.count??1)},(v,w)=>{let y=`cue-${++o}`;return _.push(y),{...l,id:y,name:`${l.name} (\u30B3\u30D4\u30FC${w?` ${w+1}`:""})`}});p({cues:[...e.cues,...m]})}b({ids:_});return}if(i.command==="cue.batchUpdate"){p({cues:e.cues.map(l=>a.ids.includes(l.id)?{...l,...a.changes}:l)}),b({updated:a.ids.length,offsetSkipped:0});return}if(i.command.startsWith("project.")){p({...i.command==="project.new"?{cues:[]}:{},project:{...e.project,displayName:i.command==="project.new"?"\u540D\u79F0\u672A\u8A2D\u5B9A.tcb3":e.project.displayName,dirty:!1,canUndo:!1,canRedo:!1}}),b({cancelled:!1});return}if(i.command==="app.undo"||i.command==="app.redo"){p({project:{...e.project,canUndo:i.command==="app.redo",canRedo:i.command==="app.undo"}}),b();return}i.command==="mute.set"&&p({transport:{...e.transport,triggerMuted:!!a.muted}}),i.command==="cue.setEnabled"&&p({cues:e.cues.map(l=>l.id===a.id?{...l,enabled:!!a.enabled}:l)}),b()};return window.invokeCSharpAction=f,{dispose:()=>{c=!1,s!==null&&window.clearInterval(s),window.invokeCSharpAction===f&&delete window.invokeCSharpAction}}}var Ue=!1,Ne=!1;function xn(){Ne=!0,!Ue&&(Ue=!0,requestAnimationFrame(()=>{Ue=!1,Ne&&(Ne=!1,A.notify())}))}var $e=()=>z({protocolVersion:1,type:"ready",clientVersion:"3.0.0",capabilities:["clock-latest-only","wave-minmax","m1","m2a","m2b","m3"]});function Nt(e=800){let t=!1,r=null,o=window.setTimeout(()=>{t||window.invokeCSharpAction||(r=Ut(),$e())},e);return window.tcb={receive:c=>{let s=Ft(c);if(!s)return;if(s.type==="result"){gt(s);return}ft(A.value,s)==="resync"&&z({protocolVersion:1,type:"resync",currentRevision:A.value.host.revision,reason:"patch baseRevision mismatch"}),xn()},hostAttached:()=>{t=!0,window.clearTimeout(o),r?.dispose(),r=null,$e()}},$e(),{dispose:()=>{t=!0,window.clearTimeout(o),r?.dispose(),r=null}}}Nt();var $t=document.getElementById("app");if(!$t)throw new Error("Missing #app mount point");nt(n(Mt,{}),$t);})(); diff --git a/src/TimecodeBridge.Web/dist/styles/m3.css b/src/TimecodeBridge.Web/dist/styles/m3.css index d9cc68d..2082a68 100644 --- a/src/TimecodeBridge.Web/dist/styles/m3.css +++ b/src/TimecodeBridge.Web/dist/styles/m3.css @@ -3,3 +3,4 @@ .next{overflow:hidden}.next>*{min-width:0}.next-main{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:12px;overflow:hidden}.next strong{margin-right:0}.next-main b,.next>.mono{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.footcue{overflow:hidden}.footcue>span:nth-child(3){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hosts label{display:flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trig{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .log-filter{margin-left:8px}.auto-mute-switch{cursor:pointer} .offset-editor{display:inline-flex;align-items:center;gap:6px}.offset-pending{color:#c5a66d} +.device-set{position:relative}.device-set .rescan{min-width:calc(6em + 20px)}.scan-progress{position:absolute;left:78px;right:0;bottom:-4px;height:2px;overflow:hidden;background:var(--l);border-radius:1px}.scan-progress>i{display:block;width:30%;height:100%;background:var(--a);animation:scan-slide 1s ease-in-out infinite}@keyframes scan-slide{from{transform:translateX(-100%)}to{transform:translateX(340%)}}@media (prefers-reduced-motion:reduce){.scan-progress>i{width:100%;animation:none;opacity:.6}} diff --git a/src/TimecodeBridge.Web/src/components/receive-panel.tsx b/src/TimecodeBridge.Web/src/components/receive-panel.tsx index 27dfdef..e271cd5 100644 --- a/src/TimecodeBridge.Web/src/components/receive-panel.tsx +++ b/src/TimecodeBridge.Web/src/components/receive-panel.tsx @@ -5,6 +5,8 @@ import {command} from "../commands"; const run=(name:Parameters[0],args:unknown={})=>void command(name,args); export const selectReceiveDevice=(value:string):void=>run("receive.selectDevice",{deviceId:value||null}); +/** 再スキャン。Host は裏のスレッドで列挙し終えてから result を返すので、それまでスキャン中を表示する(失敗は Host のエラー表示に出る) */ +export async function rescanDevices(setScanning:(scanning:boolean)=>void):Promise{setScanning(true);try{await command("audio.refreshDevices",{direction:"capture"})}catch{/* 表示は Host 側 */}finally{setScanning(false)}} export function lastLtcText(status:string,lastReceived:string|null,now=Date.now()):string|null { if(status!=="signalLost"&&status!=="freerun") return null; @@ -17,6 +19,7 @@ export function lastLtcText(status:string,lastReceived:string|null,now=Date.now( export function ReceivePanel():JSX.Element { const s=appStore.value.host.state!,deviceId=s.receive.selectedDeviceId; const[now,setNow]=useState(Date.now()); + const[scanning,setScanning]=useState(false); const showLast=s.transport.status==="signalLost"||s.transport.status==="freerun"; useEffect(()=>{ if(!showLast) return; @@ -25,7 +28,10 @@ export function ReceivePanel():JSX.Element { return()=>window.clearInterval(timer); },[showLast,s.transport.lastLtcReceivedAtUtc]); const lastText=lastLtcText(s.transport.status,s.transport.lastLtcReceivedAtUtc,now); - return
TC 入出力LTC 受信設定
入力 {s.transport.locked?"接続":"未接続"}LTC {s.transport.statusText}{lastText&& {lastText}}{s.transport.signalErrorRatePercent!=null&& 誤り率 {s.transport.signalErrorRatePercent.toFixed(1)}%}○ 出力 停止送出 {s.transport.triggerMuted?"MUTE":"LIVE"}
入力デバイス
接続{deviceId?s.transport.detailText:"入力デバイスを選んでください"}
フリーランrun("receive.setFreerunDuration",{seconds:Number(e.currentTarget.value)})}/>秒
; + return
TC 入出力LTC 受信設定
入力 {s.transport.locked?"接続":"未接続"}LTC {s.transport.statusText}{lastText&& {lastText}}{s.transport.signalErrorRatePercent!=null&& 誤り率 {s.transport.signalErrorRatePercent.toFixed(1)}%}○ 出力 停止送出 {s.transport.triggerMuted?"MUTE":"LIVE"}
入力デバイスvoid rescanDevices(setScanning)}/>
接続{deviceId?s.transport.detailText:"入力デバイスを選んでください"}
フリーランrun("receive.setFreerunDuration",{seconds:Number(e.currentTarget.value)})}/>秒
; } +/** 再スキャンのボタンと、スキャン中に入力デバイス欄の下へ流す帯(進み具合は測れないので不定の帯) */ +export function RescanButton({scanning,onClick}:{scanning:boolean;onClick:()=>void}):JSX.Element {return <>{scanning&&
}} + export function Wave():JSX.Element {const wave=appStore.value.host.wave;if(!wave)return
波形待機中
;const n=Math.min(wave.min.length,wave.max.length),p:string[]=[];for(let i=0;i=0;i--)p.push(`${i/Math.max(1,n-1)*360},${15-(wave.min[i]??0)*14}`);return
} diff --git a/src/TimecodeBridge.Web/src/styles/m3.css b/src/TimecodeBridge.Web/src/styles/m3.css index d9cc68d..2082a68 100644 --- a/src/TimecodeBridge.Web/src/styles/m3.css +++ b/src/TimecodeBridge.Web/src/styles/m3.css @@ -3,3 +3,4 @@ .next{overflow:hidden}.next>*{min-width:0}.next-main{display:grid;grid-template-columns:max-content minmax(0,1fr);align-items:center;gap:12px;overflow:hidden}.next strong{margin-right:0}.next-main b,.next>.mono{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.footcue{overflow:hidden}.footcue>span:nth-child(3){min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hosts label{display:flex;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trig{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .log-filter{margin-left:8px}.auto-mute-switch{cursor:pointer} .offset-editor{display:inline-flex;align-items:center;gap:6px}.offset-pending{color:#c5a66d} +.device-set{position:relative}.device-set .rescan{min-width:calc(6em + 20px)}.scan-progress{position:absolute;left:78px;right:0;bottom:-4px;height:2px;overflow:hidden;background:var(--l);border-radius:1px}.scan-progress>i{display:block;width:30%;height:100%;background:var(--a);animation:scan-slide 1s ease-in-out infinite}@keyframes scan-slide{from{transform:translateX(-100%)}to{transform:translateX(340%)}}@media (prefers-reduced-motion:reduce){.scan-progress>i{width:100%;animation:none;opacity:.6}} diff --git a/src/TimecodeBridge.Web/tests/receive-panel.test.ts b/src/TimecodeBridge.Web/tests/receive-panel.test.ts index 6187a09..c51e202 100644 --- a/src/TimecodeBridge.Web/tests/receive-panel.test.ts +++ b/src/TimecodeBridge.Web/tests/receive-panel.test.ts @@ -1,5 +1,16 @@ import assert from "node:assert/strict"; import test from "node:test"; -import {selectReceiveDevice} from "../src/components/receive-panel"; +import {h} from "preact"; +import render from "preact-render-to-string"; +import {acceptResult} from "../src/commands"; +import {RescanButton,rescanDevices,selectReceiveDevice} from "../src/components/receive-panel"; -test("selecting the empty receive device sends receive.selectDevice with null",()=>{const messages:string[]=[];Object.defineProperty(globalThis,"window",{value:{invokeCSharpAction:(json:string)=>messages.push(json)},writable:true,configurable:true});selectReceiveDevice("");assert.equal(messages.length,1);const message=JSON.parse(messages[0]!);assert.equal(message.command,"receive.selectDevice");assert.deepEqual(message.args,{deviceId:null})}); +function captureMessages():string[]{const messages:string[]=[];Object.defineProperty(globalThis,"window",{value:{invokeCSharpAction:(json:string)=>messages.push(json)},writable:true,configurable:true});return messages} + +test("selecting the empty receive device sends receive.selectDevice with null",()=>{const messages=captureMessages();selectReceiveDevice("");assert.equal(messages.length,1);const message=JSON.parse(messages[0]!);assert.equal(message.command,"receive.selectDevice");assert.deepEqual(message.args,{deviceId:null})}); + +test("rescan shows scanning until the host returns the result",async()=>{const messages=captureMessages();const states:boolean[]=[];const done=rescanDevices(v=>states.push(v));assert.deepEqual(states,[true]);const message=JSON.parse(messages[0]!);assert.equal(message.command,"audio.refreshDevices");assert.deepEqual(message.args,{direction:"capture"});acceptResult({protocolVersion:1,type:"result",requestId:message.requestId,ok:true});await done;assert.deepEqual(states,[true,false])}); + +test("rescan stops showing scanning when the host reports an error",async()=>{const messages=captureMessages();const states:boolean[]=[];const done=rescanDevices(v=>states.push(v));const message=JSON.parse(messages[0]!);acceptResult({protocolVersion:1,type:"result",requestId:message.requestId,ok:false,error:{code:"audioError",message:"x",retryable:true}});await done;assert.deepEqual(states,[true,false])}); + +test("rescan button is disabled with an indeterminate progress bar while scanning",()=>{const idle=render(h(RescanButton,{scanning:false,onClick:()=>{}}));assert.match(idle,/再スキャン/);assert.doesNotMatch(idle,/progressbar/);assert.doesNotMatch(idle,/disabled/);const busy=render(h(RescanButton,{scanning:true,onClick:()=>{}}));assert.match(busy,/スキャン中…/);assert.match(busy,/disabled/);assert.match(busy,/role="progressbar"/)}); diff --git a/tests/TimecodeBridge.Host.Tests/CommandRouterTests.cs b/tests/TimecodeBridge.Host.Tests/CommandRouterTests.cs index 7601ee2..13efe6b 100644 --- a/tests/TimecodeBridge.Host.Tests/CommandRouterTests.cs +++ b/tests/TimecodeBridge.Host.Tests/CommandRouterTests.cs @@ -283,11 +283,11 @@ public void HostAddValidatesAndRegisters() } [AvaloniaFact] - public void HostPingReportsReachabilityAndLatency() + public async Task HostPingReportsReachabilityAndLatency() { var h = new HostHarness(); h.Hosts.AddHost(new OscHost { Id = "lo", Name = "Local", IpAddress = "127.0.0.1", Port = 9000 }); - var r = h.Run("host.ping", """{"id":"lo"}"""); + var r = await h.RunAsync("host.ping", """{"id":"lo"}"""); Assert.True(r.Ok, r.Error?.Message); var data = System.Text.Json.JsonSerializer.SerializeToElement(r.Data, Protocol.Json); Assert.True(data.GetProperty("reachable").GetBoolean()); @@ -328,4 +328,99 @@ public void GeneratorConfigureAndStart() Assert.True(h.Run("generator.stop").Ok); Assert.False(h.State.GeneratorRunning); } + + [AvaloniaFact] + public async Task DeviceListsAreEnumeratedOnceUntilRescan() + { + var h = new HostHarness(); + h.State.BuildSnapshot(); + var afterFirst = h.Devices.Enumerations; + h.State.BuildSnapshot(); + h.Run("mode.set", """{"mode":"generate"}"""); + h.State.SelectedInputDeviceId = "in-1"; + h.Run("mode.set", """{"mode":"ltc"}"""); + h.State.BuildSnapshot(); + Assert.Equal(afterFirst, h.Devices.Enumerations); // 状態を組み立てても、切り替えても列挙し直さない + + h.Devices.Capture.Add(new("in-2", "UR22C", false)); + Assert.DoesNotContain(h.State.BuildSnapshot().Receive.Devices, d => d.Id == "in-2"); + Assert.True((await h.RunAsync("audio.refreshDevices", """{"direction":"capture"}""")).Ok); // 再スキャンで反映 + Assert.Contains(h.State.BuildSnapshot().Receive.Devices, d => d.Id == "in-2"); + } + + [AvaloniaFact(Timeout = 10_000)] + public async Task RescanRunsInBackgroundAndKeepsOldListUntilDone() + { + var h = new HostHarness(); + h.State.BuildSnapshot(); + using var gate = new ManualResetEventSlim(false); + h.Devices.Gate = gate; // 以後の列挙は gate が開くまで終わらない + h.Devices.Capture.Add(new("in-2", "UR22C", false)); + + var rescan = h.RunAsync("audio.refreshDevices", """{"direction":"capture"}"""); + Assert.False(rescan.IsCompleted); // 列挙が終わるまで result を返さない(Web はその間スキャン中を表示する) + Assert.DoesNotContain(h.State.BuildSnapshot().Receive.Devices, d => d.Id == "in-2"); // 列挙中も止まらず古い一覧を返す + Assert.True(h.Run("mode.set", """{"mode":"generate"}""").Ok); // ほかの command も止まらない + + gate.Set(); + Assert.True((await rescan).Ok); + Assert.Contains(h.State.BuildSnapshot().Receive.Devices, d => d.Id == "in-2"); + } + + [AvaloniaFact] + public void SelectingDeviceMissingFromCachedListRescansOnce() + { + var h = new HostHarness(); + h.State.BuildSnapshot(); + h.Devices.Capture.Add(new("in-2", "UR22C", false)); // 起動後に挿したデバイス(再スキャン前) + Assert.True(h.Run("receive.selectDevice", """{"deviceId":"in-2"}""").Ok); + Assert.Contains("StartLtc:in-2:False", h.Engine.Calls); + Assert.Equal(ErrorCode.DeviceNotFound, h.Run("receive.selectDevice", """{"deviceId":"none"}""").Error!.Code); + } + + [AvaloniaFact] + public void GeneratorStartAfterPauseAppliesPendingSettingsInsteadOfResuming() + { + var h = new HostHarness(); + h.Run("generator.configure", """{"startTime":"01:00:00:00","frameRate":"30"}"""); + h.Run("generator.start"); + h.Engine.ActiveSource = TimecodeSourceType.Generator; + h.Engine.CurrentRawTimecode = new TimecodeValue(1, 0, 5, 0, FrameRate.Fps30); + h.Run("generator.stop"); + + Assert.True(h.Run("generator.start").Ok); // 設定を変えていなければ止めた位置から再開 + Assert.Equal("ResumeGenerator", h.Engine.Calls[^1]); + + h.Run("generator.stop"); + h.Run("generator.configure", """{"startTime":"02:00:00:00","frameRate":"25"}"""); + Assert.True(h.State.GeneratorSettingsPending); + Assert.True(h.Run("generator.start").Ok); // 保留中の変更があれば新しい設定で最初から + Assert.Equal("StartGenerator:02:00:00:00", h.Engine.Calls[^1]); + Assert.False(h.State.GeneratorSettingsPending); + } + + [AvaloniaFact] + public void GeneratorResetRebuildsWhenFrameRateChanged() + { + var h = new HostHarness(); + h.Run("generator.configure", """{"startTime":"01:00:00:00","frameRate":"30"}"""); + h.Run("generator.start"); + h.Engine.ActiveSource = TimecodeSourceType.Generator; + h.Engine.FrameRate = FrameRate.Fps30; + + h.Run("generator.configure", """{"startTime":"03:00:00:00"}"""); + Assert.True(h.Run("generator.reset").Ok); // レートが同じなら開始 TC だけ差し替える + Assert.Equal("ResetGenerator:03:00:00:00", h.Engine.Calls[^1]); + + h.Run("generator.configure", """{"frameRate":"24"}"""); + Assert.True(h.Run("generator.reset").Ok); // レートが変われば生成器ごと作り直す + Assert.Equal("StartGenerator:03:00:00:00", h.Engine.Calls[^1]); + Assert.False(h.State.GeneratorSettingsPending); + + h.Run("generator.stop"); + h.Engine.FrameRate = FrameRate.Fps24; + h.Run("generator.configure", """{"frameRate":"25"}"""); + Assert.True(h.Run("generator.reset").Ok); // 一時停止中なら作り直したあと開始位置で止める + Assert.Equal(new[] { "StartGenerator:03:00:00:00", "StopGenerator" }, h.Engine.Calls.TakeLast(2)); + } } diff --git a/tests/TimecodeBridge.Host.Tests/CueEditingCommandTests.cs b/tests/TimecodeBridge.Host.Tests/CueEditingCommandTests.cs index d94713e..4b2a5ca 100644 --- a/tests/TimecodeBridge.Host.Tests/CueEditingCommandTests.cs +++ b/tests/TimecodeBridge.Host.Tests/CueEditingCommandTests.cs @@ -173,7 +173,7 @@ public void SaveThenOpenRoundTripsProjectAndSourceSettings() Assert.False(h.Project.HasUnsavedChanges); var h2 = new HostHarness(); - var r = h2.Run("project.open", $$"""{"path":"{{path.Replace("\\", "/")}}"}"""); + var r = h2.Run("project.open", $$"""{"path":{{JsonSerializer.Serialize(path)}}}"""); Assert.True(r.Ok, r.Error?.Message); Assert.Equal("false", Data(r, "cancelled")); Assert.Single(h2.Cues.Cues); @@ -200,7 +200,7 @@ public void SaveWithExplicitPathWritesWithoutDialog() { var h = new HostHarness(); h.Run("cue.add", """{"cue":{"name":"A","triggerTime":"00:00:01:00","oscAddress":"/a"}}"""); - var r = h.Run("project.saveAs", $$"""{"path":"{{path.Replace("\\", "/")}}"}"""); + var r = h.Run("project.saveAs", $$"""{"path":{{JsonSerializer.Serialize(path)}}}"""); Assert.True(r.Ok, r.Error?.Message); Assert.Equal("false", Data(r, "cancelled")); Assert.True(File.Exists(path)); diff --git a/tests/TimecodeBridge.Host.Tests/Fakes.cs b/tests/TimecodeBridge.Host.Tests/Fakes.cs index 96c0853..db02b10 100644 --- a/tests/TimecodeBridge.Host.Tests/Fakes.cs +++ b/tests/TimecodeBridge.Host.Tests/Fakes.cs @@ -72,8 +72,14 @@ public void Complete(string address, string host, bool success, string? error = public sealed class FakeDeviceService : IAudioDeviceService { - public List Capture { get; } = new() { new("in-1", "Audio Bridge 2-A", false) }; - public List Render { get; } = new() { new("out-1", "Speakers", false), new("loop-1", "Loopback", true) }; - public IReadOnlyList GetCaptureDevices() => Capture; - public IReadOnlyList GetRenderDevices() => Render; + // IAudioDeviceService の契約どおり、ループバック取り込みはキャプチャ側に並ぶ(WindowsAudioDeviceService と同じ) + public List Capture { get; } = new() { new("in-1", "Audio Bridge 2-A", false), new("loop-1", "Loopback", true) }; + public List Render { get; } = new() { new("out-1", "Speakers", false) }; + /// 列挙した回数(実機では 1 回が重い) + public int Enumerations { get; private set; } + /// 設定すると、列挙はこれが開くまで終わらない(実機の遅い列挙の代わり) + public ManualResetEventSlim? Gate { get; set; } + // 実機の列挙と同じく、その時点の写しを返す + public IReadOnlyList GetCaptureDevices() { Gate?.Wait(); Enumerations++; return Capture.ToArray(); } + public IReadOnlyList GetRenderDevices() { Gate?.Wait(); Enumerations++; return Render.ToArray(); } } diff --git a/tests/TimecodeBridge.Host.Tests/HostHarness.cs b/tests/TimecodeBridge.Host.Tests/HostHarness.cs index 3e3a82d..13d7f7e 100644 --- a/tests/TimecodeBridge.Host.Tests/HostHarness.cs +++ b/tests/TimecodeBridge.Host.Tests/HostHarness.cs @@ -26,16 +26,23 @@ public HostHarness() Osc.HostRegistry = Hosts; Cues = new CueManager(Engine, Osc); Panel = new OscTriggerPanelManager(Osc, Hosts); - State = new HostState(Engine, Cues, Hosts, Osc, Panel, Project, Devices, Recent); + State = new HostState(Engine, Cues, Hosts, Osc, Panel, Project, new CachedAudioDeviceService(Devices), Recent); // 本番(App)と同じく一覧を使い回す Projects = new ProjectCoordinator(State, Engine, Cues, Hosts, Panel, Project, Recent); Router = new CommandRouter(State, Engine, Cues, Hosts, Panel, Projects); } - public ResultMessage Run(string command, string argsJson = "{}", string requestId = "r1", long? expectedRevision = null) + public ResultMessage Run(string command, string argsJson = "{}", string requestId = "r1", long? expectedRevision = null) => + RunAsync(command, argsJson, requestId, expectedRevision).GetAwaiter().GetResult(); + + /// + /// 本当に非同期で完了する command(host.ping など)用。 は UI スレッドで同期待ちするため、 + /// 継続が UI スレッドへ戻る command ではデッドロックする(Windows の Ping.SendPingAsync は必ず非同期で完了する)。 + /// + public Task RunAsync(string command, string argsJson = "{}", string requestId = "r1", long? expectedRevision = null) { var revision = expectedRevision is null ? "" : $$""", "expectedRevision":{{expectedRevision}}"""; var json = $$"""{"protocolVersion":1,"type":"command","requestId":"{{requestId}}","command":"{{command}}","args":{{argsJson}}{{revision}}}"""; var msg = JsonSerializer.Deserialize(json, Protocol.Json)!; - return Router.ExecuteAsync(msg).GetAwaiter().GetResult(); + return Router.ExecuteAsync(msg); } } diff --git a/tests/TimecodeBridge.Host.Tests/ProtocolTests.cs b/tests/TimecodeBridge.Host.Tests/ProtocolTests.cs index b9e1a42..c49de2f 100644 --- a/tests/TimecodeBridge.Host.Tests/ProtocolTests.cs +++ b/tests/TimecodeBridge.Host.Tests/ProtocolTests.cs @@ -62,8 +62,9 @@ public void SnapshotSerializesWithoutThrowing() using var doc = JsonDocument.Parse(json); var state = doc.RootElement.GetProperty("state"); Assert.Equal("ltc", state.GetProperty("mode").GetString()); - Assert.Equal(3, state.GetProperty("receive").GetProperty("devices").GetArrayLength()); - Assert.Equal("macos", state.GetProperty("uiCapabilities").GetProperty("platform").GetString()); + var inputs = state.GetProperty("receive").GetProperty("devices").EnumerateArray().Select(d => d.GetProperty("id").GetString()).ToArray(); + Assert.Equal(new[] { "in-1", "loop-1" }, inputs); // 出力専用デバイス(out-1)は入力候補に出さない + Assert.Equal(OperatingSystem.IsWindows() ? "windows" : "macos", state.GetProperty("uiCapabilities").GetProperty("platform").GetString()); Assert.Equal("停止中", state.GetProperty("transport").GetProperty("statusText").GetString()); } } diff --git a/tests/TimecodeBridge.Host.Tests/RecentProjectsStoreTests.cs b/tests/TimecodeBridge.Host.Tests/RecentProjectsStoreTests.cs index 8b3a794..b4225b6 100644 --- a/tests/TimecodeBridge.Host.Tests/RecentProjectsStoreTests.cs +++ b/tests/TimecodeBridge.Host.Tests/RecentProjectsStoreTests.cs @@ -16,7 +16,7 @@ public void AddsToFrontDeduplicatesAndCapsAtTen() for (int i = 0; i < 12; i++) store.Add($"/p/show{i}.json"); store.Add("/p/show3.json"); Assert.Equal(RecentProjectsStore.Max, store.Items.Count); - Assert.Equal("/p/show3.json", store.Items[0]); + Assert.Equal(Path.GetFullPath("/p/show3.json"), store.Items[0]); // Windows では C:\p\show3.json Assert.Equal(1, store.Items.Count(p => p.EndsWith("show3.json"))); var reloaded = new RecentProjectsStore(path, Path.Combine(Path.GetTempPath(), "none.json")); diff --git a/tools/tcb3/smoke-windows.ps1 b/tools/tcb3/smoke-windows.ps1 new file mode 100644 index 0000000..784ce31 --- /dev/null +++ b/tools/tcb3/smoke-windows.ps1 @@ -0,0 +1,178 @@ +<# +.SYNOPSIS + TimecodeBridge v3(Host)の Windows 通し確認。smoke.sh の Windows 版で、jq・pgrep・python は要らない(pwsh 7)。 + 起動 → Web が ready → OSC を発火して受信 → 送信ログと画面を確認 → 撮影、を行い SMOKE PASS / SMOKE FAIL で終わる。 + -LoopbackDevice を付けると 2 つ目の Host でその出力へ LTC を生成し、1 つ目がその出力のループバック取り込みで受信して、 + LTC の時刻でキューが発火し OSC が届くところまで確かめる(VB-Audio Virtual Cable などの聞こえない出力を指定すること)。 +.EXAMPLE + pwsh tools/tcb3/smoke-windows.ps1 # Debug ビルド + pwsh tools/tcb3/smoke-windows.ps1 -Exe publish\TimecodeBridge3.exe # 発行物(CI) + pwsh tools/tcb3/smoke-windows.ps1 -LoopbackDevice "CABLE In 16ch" # LTC 生成 → ループバック受信も +#> +param( + [string]$Exe = (Join-Path $PSScriptRoot '..\..\src\TimecodeBridge.Host\bin\Debug\net8.0\TimecodeBridge3.exe'), + [int]$Port = 47310, + [int]$OscPort = 9110, + [string]$RunDir = (Join-Path ([IO.Path]::GetTempPath()) 'tcb3-smoke'), + [string]$LoopbackDevice = '', + [int]$ReadyTimeoutSec = 60, + [switch]$Keep +) +$ErrorActionPreference = 'Stop' +$script:fails = 0 +function Ok([string]$m) { Write-Host "ok $m" } +function Ng([string]$m) { Write-Host "NG $m"; $script:fails++ } + +function Api([int]$port, [string]$method, [string]$path, [string]$body = $null, [string]$contentType = 'application/json; charset=utf-8') { + $uri = "http://127.0.0.1:$port$path" + if ($null -eq $body) { return Invoke-RestMethod -Uri $uri -Method $method -TimeoutSec 30 } + return Invoke-RestMethod -Uri $uri -Method $method -TimeoutSec 30 -ContentType $contentType -Body ([Text.Encoding]::UTF8.GetBytes($body)) +} +function Cmd([int]$port, [string]$name, $arguments = $null) { + $msg = @{ command = $name } + if ($null -ne $arguments) { $msg.args = $arguments } + return Api $port POST /command ($msg | ConvertTo-Json -Depth 10 -Compress) +} +function Eval([int]$port, [string]$js) { return (Api $port POST /eval $js 'text/plain; charset=utf-8').result } +function State([int]$port) { return (Api $port GET /state).state } +function Clock([int]$port) { return Api $port GET /clock } + +$script:hosts = @() +function Start-Host([int]$port, [string]$name) { + $dir = Join-Path $RunDir $name + New-Item -ItemType Directory -Force (Join-Path $dir 'data') | Out-Null + # 試験用インスタンスが利用者の設定(最近使ったプロジェクト)を書き換えないよう、設定保存先を分ける + $env:TIMECODEBRIDGE_AUTOMATION_PORT = "$port" + $env:TIMECODEBRIDGE_DATA_DIR = Join-Path $dir 'data' + $env:TIMECODEBRIDGE_BRIDGE_TRACE = '1' + try { + $p = Start-Process -FilePath $exePath -WorkingDirectory (Split-Path $exePath) -PassThru ` + -RedirectStandardOutput (Join-Path $dir 'host.log') -RedirectStandardError (Join-Path $dir 'host.err.log') + } + finally { + Remove-Item Env:TIMECODEBRIDGE_AUTOMATION_PORT, Env:TIMECODEBRIDGE_DATA_DIR, Env:TIMECODEBRIDGE_BRIDGE_TRACE -ErrorAction SilentlyContinue + } + $script:hosts += $p + $deadline = (Get-Date).AddSeconds($ReadyTimeoutSec) + while ((Get-Date) -lt $deadline) { + if ($p.HasExited) { + Get-Content (Join-Path $dir 'host.log'), (Join-Path $dir 'host.err.log') -ErrorAction SilentlyContinue | Select-Object -Last 40 | Write-Host + throw "$name(port $port)が起動直後に終了しました(exit $($p.ExitCode))" + } + try { if ((Api $port GET /health).webReady) { Ok "$name 起動 pid=$($p.Id) port=$port"; return $p } } catch { } + Start-Sleep -Milliseconds 500 + } + Get-Content (Join-Path $dir 'host.log') -ErrorAction SilentlyContinue | Select-Object -Last 40 | Write-Host + throw "$name(port $port)が $ReadyTimeoutSec 秒待っても Web ready になりません" +} + +# ウィンドウを PrintWindow(PW_RENDERFULLCONTENT)で撮る。前面化しないので、他のウィンドウに隠れていても WebView の中身が写る +Add-Type -AssemblyName System.Drawing +Add-Type @' +using System; +using System.Runtime.InteropServices; +public static class Tcb3Window { + [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; } + [DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr v); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); + [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags); +} +'@ +function Save-Shot([Diagnostics.Process]$p, [string]$out) { + [void][Tcb3Window]::SetProcessDpiAwarenessContext([IntPtr]-4) + $p.Refresh() + $h = $p.MainWindowHandle + if ($h -eq [IntPtr]::Zero) { Ng "撮影: ウィンドウが見つかりません"; return } + $r = New-Object Tcb3Window+RECT + [void][Tcb3Window]::GetWindowRect($h, [ref]$r) + $bmp = New-Object Drawing.Bitmap ([Math]::Max(1, $r.R - $r.L)), ([Math]::Max(1, $r.B - $r.T)) + $g = [Drawing.Graphics]::FromImage($bmp) + $hdc = $g.GetHdc() + $ok = [Tcb3Window]::PrintWindow($h, $hdc, 2) + $g.ReleaseHdc($hdc); $g.Dispose() + $bmp.Save($out, [Drawing.Imaging.ImageFormat]::Png); $bmp.Dispose() + if ($ok) { Ok "撮影 $out" } else { Ng "撮影: PrintWindow が失敗しました" } +} + +# OSC を 1 通受けてアドレスを返す(届かなければ $null)。ループバックだけに束縛するのでファイアウォールの確認は出ない +function Receive-Osc([Net.Sockets.UdpClient]$udp, [int]$timeoutMs) { + $udp.Client.ReceiveTimeout = $timeoutMs + $from = New-Object Net.IPEndPoint ([Net.IPAddress]::Any), 0 + try { $bytes = $udp.Receive([ref]$from) } catch [Net.Sockets.SocketException] { return $null } + $end = [Array]::IndexOf($bytes, [byte]0) + return [Text.Encoding]::ASCII.GetString($bytes, 0, [Math]::Max(0, $end)) +} + +$exePath = (Resolve-Path $Exe).Path +# WebView2 のユーザーデータが実行ファイルの隣に作られていないか(Program Files に入れると書けず表示できない)。以前の実行の残りは数えない +$besideExe = "$exePath.WebView2" +$besideExeExisted = Test-Path $besideExe +New-Item -ItemType Directory -Force $RunDir | Out-Null +$udp = New-Object Net.Sockets.UdpClient (New-Object Net.IPEndPoint ([Net.IPAddress]::Loopback), $OscPort) +try { + $main = Start-Host $Port 'main' + + $s = State $Port + if ($s.uiCapabilities.platform -eq 'windows') { Ok 'uiCapabilities.platform=windows' } else { Ng "platform=$($s.uiCapabilities.platform)" } + if ((Eval $Port "document.body.innerText.includes('キューリスト')") -eq 'true') { Ok 'Web UI が描画されている' } else { Ng 'Web UI に「キューリスト」がありません' } + if ($besideExeExisted -or -not (Test-Path $besideExe)) { Ok 'WebView2 のユーザーデータを実行ファイルの隣に作らない' } else { Ng "WebView2 のユーザーデータが実行ファイルの隣にできた: $besideExe" } + + # ホストとキューを登録し、手動発火した OSC が届くか + $hid = (Cmd $Port host.add @{ host = @{ name = 'smoke'; ipAddress = '127.0.0.1'; port = $OscPort } }).data.id + if ($hid) { Ok "host.add $hid" } else { Ng 'host.add 失敗' } + $ping = Cmd $Port host.ping @{ id = $hid } + if ($ping.ok -and $ping.data.reachable) { Ok "host.ping $($ping.data.latencyMs)ms" } else { Ng "host.ping: $($ping | ConvertTo-Json -Compress)" } + $cue = @{ name = 'smoke-manual'; triggerTime = '00:00:10:00'; oscAddress = '/smoke/manual'; targetHostIds = @($hid); arguments = @(@{ type = 'int32'; value = 1 }, @{ type = 'string'; value = '日本語' }) } + $cid = (Cmd $Port cue.add @{ cue = $cue }).data.id + if ($cid) { Ok "cue.add $cid" } else { Ng 'cue.add 失敗' } + $fire = Cmd $Port cue.fire @{ id = $cid } + if ($fire.ok -and $fire.data.sentCount -eq 1) { Ok 'cue.fire sentCount=1' } else { Ng "cue.fire: $($fire | ConvertTo-Json -Compress)" } + $addr = Receive-Osc $udp 5000 + if ($addr -eq '/smoke/manual') { Ok "OSC 受信 $addr" } else { Ng "OSC が届かない(受信: $addr)" } + Start-Sleep -Milliseconds 500 + $s = State $Port + if ($s.logs | Where-Object { $_.success -and $_.message -like '/smoke/manual*' }) { Ok '送信ログに成功が残る' } else { Ng '送信ログに /smoke/manual がありません' } + if ((Eval $Port "document.body.innerText.includes('smoke-manual')") -eq 'true') { Ok 'キューが画面に出ている' } else { Ng 'キューが画面に出ていません' } + + if ($LoopbackDevice) { + # 2 つ目の Host で LTC を生成してその出力へ流し、1 つ目はその出力のループバック取り込みで受ける + $gen = Start-Host ($Port + 1) 'gen' + $out = (State ($Port + 1)).generator.outputDevices | Where-Object { $_.name -like "*$LoopbackDevice*" } | Select-Object -First 1 + $in = (State $Port).receive.devices | Where-Object { $_.loopback -and $_.name -like "*$LoopbackDevice*" } | Select-Object -First 1 + if (-not $out -or -not $in) { throw "デバイス '$LoopbackDevice' の出力またはループバック入力が見つかりません" } + [void](Cmd ($Port + 1) mode.set @{ mode = 'generate' }) + [void](Cmd ($Port + 1) generator.configure @{ startTime = '01:00:00:00'; frameRate = '30'; outputDeviceId = $out.id; volume = 0.8 }) + [void](Cmd ($Port + 1) generator.start) + [void](Cmd $Port receive.selectDevice @{ deviceId = $in.id }) + $deadline = (Get-Date).AddSeconds(15) + do { Start-Sleep -Milliseconds 500; $t = (State $Port).transport } while ($t.status -ne 'receiving' -and (Get-Date) -lt $deadline) + if ($t.status -eq 'receiving') { Ok "LTC 受信 ($($in.name))" } else { Ng "LTC を受信できない: $($t.statusText) $($t.detailText)" } + $c1 = Clock $Port; Start-Sleep -Seconds 2; $c2 = Clock $Port + if ($c2.totalFrames - $c1.totalFrames -ge 45 -and $c2.frameRate -eq '30') { Ok "時計が進む $($c1.display) → $($c2.display) ($($c2.frameRate)fps)" } else { Ng "時計: $($c1.display) → $($c2.display) $($c2.frameRate)" } + # 誤り率は受信開始からの累計で、開始直後に弾かれる数フレームが最初の数秒は大きく効くので、5 秒以上受けてから見る + Start-Sleep -Seconds 3 + $err = (State $Port).transport.signalErrorRatePercent + if ($null -ne $err -and $err -lt 2) { Ok "誤り率 $([Math]::Round($err, 2))%" } else { Ng "誤り率 $err%" } + + # 受信した LTC の時刻でキューが発火するか(3 秒後) + $f = (Clock $Port).totalFrames + 90 + $trig = '{0:00}:{1:00}:{2:00}:{3:00}' -f [Math]::Floor($f / 108000), ([Math]::Floor($f / 1800) % 60), ([Math]::Floor($f / 30) % 60), ($f % 30) + $lcue = @{ name = 'smoke-ltc'; triggerTime = $trig; frameRate = '30'; oscAddress = '/smoke/ltc'; targetHostIds = @($hid) } + [void](Cmd $Port cue.add @{ cue = $lcue }) + $addr = Receive-Osc $udp 10000 + if ($addr -eq '/smoke/ltc') { Ok "LTC $trig で発火した OSC を受信" } else { Ng "LTC 発火の OSC が届かない(受信: $addr)" } + Save-Shot $gen (Join-Path $RunDir 'smoke-gen.png') + } + + Start-Sleep -Milliseconds 500 + Save-Shot $main (Join-Path $RunDir 'smoke.png') +} +catch { + Ng $_.Exception.Message +} +finally { + $udp.Dispose() + if (-not $Keep) { foreach ($p in $script:hosts) { if (-not $p.HasExited) { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } } } +} + +if ($script:fails -eq 0) { Write-Host 'SMOKE PASS'; exit 0 } else { Write-Host "SMOKE FAIL ($script:fails)"; exit 1 }