メインコンテンツへスキップ

setParamの操作感をブラウザとデスクトップへ。UTAU原音設定エディター「Avennota」の設計と実装

setParamの操作感をWebとデスクトップへ。UTAU原音設定エディター「Avennota」の設計判断や、ブラウザのファイルアクセス制約、波形描画の負荷対策、SHIROによる自動推定の統合などを解説します。

setParamの操作感をブラウザとデスクトップへ。UTAU原音設定エディター「Avennota」の設計と実装

歌声合成ソフトUTAUの音源制作に使う原音設定エディター「Avennota(アヴェンノータ)」を開発しています。

UTAUの原音設定ツールとしては、長年にわたりWindows向けに提供され、キーボード主体の素早い編集フローが確立されているsetParamが広く知られています。また近年では、多機能でクロスプラットフォームに対応したvLabelerや、Webブラウザやスマートフォンから手軽に原音設定を行えるLABERUなど、制作者の環境やニーズに合わせたツールが登場しています。

Avennotaは、setParamで親しまれてきた「フォルダを開いて波形を見ながら直接数値を調整していく」作業感をベースに、Webブラウザ(Chrome / Edge)とデスクトップ(Windows / macOS / Linux)の両方で同じ編集ロジックが動くエディターを目指して作りました。

本記事では、Avennotaの設計判断や、ブラウザでのローカルファイルアクセスの制約、波形ドラッグとテーブル描画のパフォーマンス対策、SHIROによる自動推定の統合など、開発中に直面した技術的な課題と実装内容を紹介します。

1. 独自形式を作らず「フォルダとoto.ini」を直接扱う

Avennotaの設計で最初に決めたのは、アプリ独自のプロジェクトファイルを用意しないことでした。

多機能な音声ラベラーでは、プロジェクトファイル(.lbpなど)を作成して作業し、最終的にoto.iniへエクスポートする形式を採ることがあります。一方、Avennotaでは「音源フォルダを開き、波形とスペクトログラムを確認しながら数値を調整し、手元のoto.iniへそのまま保存する」という、setParamの直接的な操作感を重視しました。

編集の基本単位は「1つのフォルダと1つのoto.ini」です。UTAU音源では1つのWAVファイルに対して複数のエイリアス(単独音、連続音、語尾音など)を割り当てることが多いため、一覧で選択・操作する対象も、音声ファイル単位ではなく原音設定の各行(Oto)にしています。

2. Web版とデスクトップ版のファイルアクセス

File System Access APIとTauriの使い分け

デスクトップ版にはTauri v2(React + TypeScript + Vite)を採用しています。一方、Web版ではChromium系ブラウザが備えるFile System Access APIを使い、端末上の音源フォルダを直接読み書きします。

音声デコード、波形描画、スペクトログラム解析、原音設定の計算ロジックはすべてWeb側に集約し、プラットフォーム固有の処理はVoiceFolderという抽象インターフェースで分離しました。

  • Web版: File System Access APIで取得したディレクトリハンドル経由でファイルを操作。
  • デスクトップ版: Tauriのtauri-plugin-fsやdialogプラグインを呼び出し、ネイティブのファイルパスを操作。

この構成により、OSや実行環境ごとにoto.iniの解釈や編集処理を作り直す必要がなくなります。Rust側の実装は、ウィンドウ制御やOS固有の初期化・権限設定など、ネイティブ専用の最小限の処理にとどめています。

Windows Chromiumで.iniファイルが拒否される問題

File System Access APIの導入過程で、「Windows版のChromium系ブラウザにおいて、ディレクトリハンドル経由で.iniファイルを取得・走査できない」という制約に直面しました。

Chromiumのセキュリティ機構により、Windows環境では.iniがシステム保護の対象ファイルとみなされ、フォルダ一覧から除外されたりアクセスが拒否されたりします。そのため、フォルダ走査の結果だけを見て「oto.iniが存在しない」と判断して新規作成へ進んでしまうと、既存の原音設定を上書きしてしまうリスクがありました。

この問題に対応するため、Avennotaでは以下のフォールバック経路を実装しました(コミット67fbefc)。

  1. フォルダ走査で.iniが見つからない場合、ユーザーに「既存のoto.iniを個別に選択する(showOpenFilePicker)」か「新規に保存先を指定する(showSaveFilePicker)」かを選んでもらう。
  2. ユーザーがファイルピッカーで明示的に指定したファイルハンドルであれば、Chromiumでも.iniの直接読み書きが許可される。
  3. 取得したハンドルをフォルダの文脈と紐付け、以降の読み込み・保存・復元処理で共通して利用する。

3. oto.iniの非破壊保存と「右ブランクの符号」

既存の音源を安全に編集するためには、パースした数値を正しく書き戻すだけでなく、変更していない箇所を元の状態のまま保つ必要があります。

未編集行のバイト完全一致

たとえば、375.0という数値をアプリが勝手に375と書き換えてしまうと、実際に編集した場所以外にも無用な差分が発生してしまいます。また、Shift_JIS特有の文字や、アプリ側で想定していない形式の行が含まれていた場合でも、勝手に削除してはなりません。

Avennotaでは、パースした行データとは別に、元の文字列・バイト列・改行コード(CRLF / LF)を保持しています。編集されていない行は元のバイト列をそのまま書き戻し、編集された行についても変更があった数値欄のみを書き直します。

文字コードはUTF-8とShift_JISに対応し、Shift_JISへの変換時に文字化けや情報落ちが発生する場合は、保存前に対象行を提示して保存を停止する設計にしています。

右ブランク(Cutoff)の符号の意味

原音設定のパラメータにおいて、発音終了位置を決める「右ブランク」は、符号によって意味が異なります。

  • 0以上: 原音(WAV)の末尾からの距離(ms)
  • 負の値: 左ブランク(Offset)から発音終了位置までの長さ(ms)

例えば、長さ1,000msの原音で左ブランクが100msの場合、「右ブランク200ms」と「右ブランク-700ms」は、どちらも波形上の終了位置(800ms)としては一致します。

画面上は同じ位置にマーカーを表示できますが、保存時にどちらかの表現へ一律に変換してしまうと、制作者が意図して設定していた符号形式が変わってしまいます。そのため、マーカー移動の内部計算では「波形上の絶対位置」と「原音設定値への変換」を分離し、元の符号を維持したまま数値を計算・更新するようにしています。

二重のデータ保護

保存処理では、以下のステップを踏むことで安全性を確保しています。

  1. 元ファイルの外部変更がないか確認
  2. 保存前の内容をoto.ini.bakへ退避
  3. 新しい内容を書き込み、読み戻して照合

さらに、編集中の予期せぬブラウザクラッシュや誤操作に備え、未保存の編集内容は編集停止の1秒後に復元データ(Web版はIndexedDB、デスクトップ版はアプリデータ領域のJSONファイル)へ自動記録されます。

4. 波形ドラッグとテーブル再描画の分離

UIコンポーネントにはReact Aria Components、波形描画にはwavesurfer.jsを使用しています。

開発初期、1,000行を超える大規模なoto.iniを読み込んだ際に、「波形上のマーカーをドラッグすると画面の描画がもたつく」という問題が発生しました。React AriaのTableコンポーネントは、行データが変更されるとテーブル全体のコレクションを再構築するため、マーカーの移動に合わせて本体の編集状態(Store)を更新してしまうと、ドラッグ中に大量の再計算が走ってしまうことが原因でした。

この負荷を抑えるため、ドラッグ中の状態と確定した編集状態を分離しました。

  1. 仮の値を一時ストアへ逃がす: ドラッグ中のマーカー位置は本体のステートには渡さず、軽量なプレビュー専用ストア(markerPreviewStore)へ格納する。
  2. TanStack Pacerによるスロットル: プレビュー値の更新頻度を16ms間隔(約60fps)に間引き、波形描画のみを滑らかに追従させる。
  3. ドロップ時の1回コミット: マウスを離した時点で初めて本体ステートへ反映する。これにより、アンドゥ履歴も「1回のドラッグにつき1回」という自然な単位にまとまる。
  4. テーブル行のメモ化: 変更のない行オブジェクトはキャッシュを再利用し、useDeferredValueを用いて一覧テーブルの更新を波形描画よりも遅延させる。

この構成により、行数の多い音源であっても波形操作時のレスポンスを維持できるようになりました。

5. SHIROによる自動推定の統合

手動編集の基盤を整えた後、自動推定(オートラベリング)機能の統合を行いました。

shiro-rsとWASM化

音素境界の自動推定には、機械学習ベースのツール「SHIRO」を利用しています。Avennotaでは、SHIROの処理系をRustへ移植したshiro-rsを開発し、WebAssembly化した@shiro-rs/wasmをWeb Worker上で実行しています。

計算処理をWorkerでバックグラウンド実行することで、UIの操作をブロックせず、Web版・デスクトップ版の双方で同じ推定ロジックを利用できます。

音素境界から原音設定への変換規則

SHIROが出力するのは「各音素の開始・終了時刻」です。これをもとに、UTAUの単独音(CV)、連続音(VCV)、CVVCそれぞれの原音設定パラメータを算出する必要があります。

  • 連続音(VCV): 収録音声を解析してテンポ(BPM)を自動推定し、拍の長さに応じて先行発声、オーバーラップ、固定範囲を適切な比率で配置する。
  • 単独音(CV): 音声の立ち上がりや有声開始のタイミングを検出し、子音の長さに合わせた補正を行う。
  • CVVC: 直前の母音との接続(VC)や語尾の無音(R)への移行エイリアスを生成する。

これらの変換処理は、UTAUコミュニティで長年共有されてきた原音設定の考え方や制作ガイドの計算手順を参考に実装しました。

推定結果はいきなりファイルへ書き込むのではなく、差分確認ダイアログで変更点を確認した上で反映します。適用した推定結果は、1回の「元に戻す(Undo)」でまとめて取り消すことが可能です。また、NNSVSなどの外部学習で利用できるように、推定した音素境界をHTK単音素ラベル(.lab)として音源フォルダへ書き出す機能も備えています。

6. 定番の一括処理を標準機能として内包する

setParamの大きな強みの一つに、有志によって制作された多様な外部プラグイン(連続音の一括展開、重複エイリアスの整理、先行発声・オーバーラップの調整など)の存在があります。

しかし、これらのプラグインはWindows環境の実行ファイルとして作られているものが多く、MacやLinux、あるいはブラウザ上ではそのまま動かすことができません。

そこでAvennotaでは、制作現場で頻繁に使われる定番のワークフローを調査し、エディター本体の標準機能(一括処理)として再実装しました。

  • 対応表によるエイリアスの複製・置換: a かから単独音のかを生成するなど、規則テーブルに基づいた一括変換。
  • 重複エイリアスの整理: 同一エイリアスを持つ行をリストアップし、波形確認や試聴を行いながら残す行を選択・削除。
  • 先行発声・オーバーラップの比率調整: 先行発声とオーバーラップを1/3の比率に整えたり、収録BPMに基づいた値へ揃える。
  • 柔軟な検索・置換・抽出: 正規表現を用いたエイリアスやコメントの編集、条件に一致した行の別oto.iniへの書き出し。

これらの一括処理もすべてプレビュー画面で事前確認ができ、誤って適用した場合も1回のアンドゥで元の状態へ戻せる設計にしています。

7. マルチプラットフォーム配布とCI/CD

デスクトップ版の配布は、以下の7つのターゲットを対象にGitHub Actionsで自動ビルドを行っています。

  • Windows: x64, ARM64, x86(NSISインストーラー / ポータブルexe)
  • macOS: Universal(Apple Silicon / Intel両対応, app / DMG)
  • Linux: x64, ARM64, x86(deb / AppImage)

配布パイプラインの構築にあたっては、Linux 32bit(x86)環境向けにDebian 12のコンテナ内でWebKitGTKとRustツールチェーンをセットアップしてビルドしたり、Release Pleaseの自動リリース時にWindows環境特有の改行コード(CRLF)が混入してフォーマット検査が停止する問題に対応するなど、細かな調整を重ねました。

おわりに

Avennotaは、setParamが確立した優れた操作感と、UTAUコミュニティが長年蓄積してきた原音設定のワークフローを、現代のWeb技術とクロスプラットフォーム環境で安全に使えるようにすることを目指して開発しています。

現在、Web版の公開および各OS向けのデスクトップ版バイナリの配布を行っています。

実際の音源を使った編集の操作感や、自動推定の使い勝手など、フィードバックをいただきながら継続して改善を進めていきたいと考えています。