これは何か
Onsa(音叉)は、Windows と Linux 向けのデスクトップ音楽プレーヤーです。自分で音楽ファイルを持っていて、音に実際に何が起きているかを知りたい人に向けています。見た目のコンセプトは計器パネルです。アプリは正直な測定器のように見え、ウィンドウ下部の信号経路のストリップが、音が本当に通るすべての段階(元の形式、リサンプル、ReplayGain、EQ、リミッター、出力バックエンドまで)を示します。各段階はそのストリップから切ることができます。
自分で有効にしない限り、インターネットには一切つながりません。ネットワーク機能はすべて初期設定でオフです。
v1.4.0 にあるもの:ギャップレス再生とクロスフェード、並べ替えできる再生キュー、手動プレイリストとスマートプレイリスト、目に見える DSP チェーン、全文検索ができ、元フォルダを外したり移動したりできるライブラリ、1曲ずつと一括のタグ編集、3つの情報源からの歌詞、パネルを調整できる再生中画面、6つの標準テーマ、アプリ内ヘルプ、2つの表示言語。yt-dlp によるダウンロードは複数を同時に実行できるようになりました。必要なプログラム(yt-dlp、ffmpeg、Deno、fpcalc)は、承認を得たあとで、公式リリースのチェックサムと一致した場合にだけ、Onsa 自身が取得します。
インストーラーは
v1.4.0 のリリースページ
にあり、CI がビルドしてアップロードしています。Windows 向けの Onsa_1.4.0_x64-setup.exe(11.79 MB)、Linux 向けの .deb(18.93 MB)と AppImage(89.92 MB)。Linux 向けパッケージは Onsa として初めてのもので、まだ WSL で試しただけで、実際の Linux マシンでは試していません。
Onsa のコードは GitHub で見られます。リポジトリにはあえてまだライセンスを付けていません。ライセンスの判断は保留中です。
次に
v1.4.1 で M10b の残りを片付けます。Onsa を閉じても消えないダウンロードキューと、選べるダウンロード先フォルダです。コードはすでにリポジトリにありますが、リリースのタグはまだです。
その後は M16(v1.5「表示」)。6つの標準テーマを、それぞれ独自のレイアウトを持つように全面的に作り直します。最初のテーマは、ヴィンテージのアナログ航空機のコックピット。6つのテーマのコンセプトは承認済みで、テーマの仕組みを色の外まで広げています。書体、表面の質感、枠、メーターとビジュアライザーの様式、アニメーション。
このページのスクリーンショットはまだ古い見た目です。M16 のリリース後に差し替えます。
オーディオエンジンは画面から完全に分離
アーキテクチャの一番の原則:オーディオエンジンは専用のスレッドで動き、画面のことは何も知りません。画面は、Tauri のコマンドとイベントで話しかけるリモコンにすぎません。画面がどれだけ重くなっても、再生が途切れてはいけません。
仕事は、境界を守った6つの Rust クレートに分かれています。
| クレート | 内容 |
|---|---|
onsa-audio | 音源、リサンプル、クロスフェード、DSP、出力、解析用タップ |
onsa-library | SQLite、スキャナー、フォルダ監視、タグ、ジャケット、プレイリスト、検索 |
onsa-downloader | バイナリの管理とダウンロードの実行 |
onsa-lyrics | LRC パーサー、歌詞の情報源、キャッシュ |
onsa-scrobble | Last.fm とオフライン用キュー — M9 待ち |
onsa-cli | 画面なしでオーディオエンジンを動かす |
ルール:onsa-audio は他のどの Onsa クレートにも依存せず、機能モジュールどうしも互いに依存しません。まとめるのは src-tauri だけです。たとえばダウンローダーはライブラリのことを何も知らず、できたファイルのパスを返すだけで、src-tauri がそれを渡します。
通信の向きも決めてあります。画面からバックエンドへはコマンド(play、seek、set_eq、library_query)、バックエンドから画面へはイベント(再生位置は 10 Hz 以下、解析フレームは 60 Hz 以下、進捗、エラー)。ジャケット画像はイベントの中の base64 ではなく、カスタムプロトコルで配信します。
コードを書く前に固定した判断
仕様は最初の1行のコードより先に書きました。一部の判断は固定扱いで、プロジェクトの持ち主の承認なしには変えません。
| 分野 | 判断 |
|---|---|
| プラットフォーム | Windows 10/11 と Linux x86_64。macOS は対象外ですが、移植を不可能にするものをあえて使うことはしません。 |
| シェル | Tauri 2 |
| バックエンド | 安定版 Rust、モジュールごとにクレートを分けた Cargo ワークスペース |
| フロントエンド | SvelteKit(Svelte 5、厳格な TypeScript)、adapter-static、SPA |
| スタイル | 素の CSS とテーマシステムの CSS 変数。Tailwind もコンポーネントライブラリもなし。 |
| データベース | rusqlite 経由の SQLite、バージョン付きマイグレーション |
| 音声 | symphonia、rubato、cpal、rtrb、realfft |
| HTTP | プロジェクト全体で reqwest のクライアントを一つ。タイムアウト、サイズ上限つきで、失敗は値として返す |
設計の判断と、その理由
コードから明らかでない判断は、日付、背景、判断、理由とともに記録しています。アプリの形を決めたものをいくつか:
- 制御スレッドとデコードスレッドを統合。 仕様では別々に描いていましたが、作ったのは、バッファを満たす合間にコマンドを処理する一つのエンジンスレッドです。理由は、いちばん単純でスレッド間のロックが要らないこと。一時停止中はコマンドのチャネルでスレッドが眠るので、待機中の CPU はゼロです。
- リサンプルを伴うギャップレス再生は一本のレーンで。 サンプルレートが同じ曲が続くときは、一つのリサンプラーを使う一本のレーンの中でつなぐので、リサンプラーには途切れのない信号が見えます。シーク、スキップ、サンプルレートの変化があると、新しいレーンを始めます。
- 一時停止とシークには出力コールバックで 60 ms のマイクロフェード、再生中のスキップにはストリーム内のクロスフェードを使います。一時停止はすぐに効き、スキップでも仕様どおりクロスフェードがかかります。
- タグの編集はまず
overridesに入り、「ファイルに書き込む」は別の操作。 Onsa に保存、ファイルに書き込む、ファイルを移動する、の三層で、取り消しも同じはしごを下ります。いちばんよく使う層が、いちばん安く取り消せます。 - タグの書き込みは、元のファイルを置き換える前に読み直す。 一時コピーに書き込んで読み戻し、中身が合わなければコピーを捨て、元のファイルには一切触れません。これは理論上の用心ではありません。lofty は、2回続けて書き込んだとき、WAV の中の ID3v2 タグを失ったのです。
- 近いものは読み直し、遠いものは覚えておく。 曲の隣にある
.lrcやタグの中の歌詞は、曲が変わるたびに読みます。キャッシュするのはインターネットからの答えだけです。小さなテキストファイルを読むほうが、覚えていたことがまだ正しいかを判断するより安上がりです。 - 「歌詞はない」は覚えるが、つながらないサービスは答えではない。 HTTP のステータスを握りつぶすと、落ちているサービスと「知らない」と答えたサービスの区別がつかなくなります。そして、たった1分のネットワーク不調で、曲が歌詞なしと記録されてしまいます。
- バイナリのリリース元は、ダウンロードしたものからではなくコードに書く。 URL、チェックサム一覧の中のファイル名、想定サイズは固定です。まずチェックサムを取り、それからファイルを取ります。一致しないものは、見るためでさえ、どこにも書き出しません。
破ってはならないルール
cpal の出力コールバックと DSP のホットパスでは、メモリの確保、ロック、I/O、ログ出力、パニックを一切しません。パラメーターはアトミック変数かロックフリーのリングバッファで受け取り、スムージングをかけて反映します。バッファはすべて事前に確保します。リアルタイムで安全か迷ったら、安全ではないとみなして別のスレッドに移します。
人のファイルに何かが触れる前に効く安全策:
- 取り消しは手順ごとではなく、まとまりごと。各手順は変更前と変更後の値とともに記録し、取り消しは最後の手順から逆向きに進みます。
- フォルダの範囲は正規化したパスで判定するので、
salinan/../asli/lagu.mp3がすり抜けることはなく、似た名前の隣のフォルダも範囲に含まれません。 - 1回の実行で50ファイルまで、絶対的な上限は500。決め手は機械の能力ではなく、間違いがあったときに人が読み返して取り消せるかどうかです。
- 確信度が 0.85 未満の提案も読めるように表示しますが、どの項目にもチェックは入りません。
- 名前の変更には必ず試行(ドライラン)があり、移動元、移動先、移動できないファイルの理由を示します。
テスト
リポジトリに著作権のある音楽ファイルはありません。音声のテスト素材はテストの実行中に作ります。正弦波、スイープ、ノイズ、無音、そしてテスト自身が書いたタグ。オーディオエンジンはオフラインのシンクでテストします。一本の連続した正弦波から切り出した二つのファイルを続けて再生し、元の正弦波と比べて、途切れがあってはいけません。バイクアッドの応答は、いくつかの試験周波数で ±0.1 dB の許容差で確かめます。
タグ v1.4.0 時点のソースツリーにあるテスト関数の数(#[test] と #[tokio::test] から数えたもの):
| 部分 | テスト関数 |
|---|---|
src-tauri | 126 |
onsa-library | 112 |
onsa-audio | 82 |
onsa-downloader | 58 |
onsa-lyrics | 36 |
onsa-cli | 2 |
onsa-scrobble | 0 — M9 待ち |
| Rust 合計 | 416 |
フロントエンド(node --test) | 49 |
これはソースにあるテスト関数の数で、1回の実行結果ではありません。CI は Windows と Ubuntu のマトリクスで、cargo fmt --check、cargo clippy -- -D warnings、cargo test、svelte-check、アプリのビルドを実行します。
自動テストのほかに、各マイルストーンには docs/PROGRESS.md に手動検証の記録があります。たとえば M8 では、コマンドからの確認が43件、ウィンドウからが22件、最小ウィンドウでが6件、完了条件専用が8件です。
マイルストーンと状況
| マイルストーン | 状況 |
|---|---|
| M0 プロジェクトの骨組み | 9月11日 完了 |
| M1 中核のオーディオエンジン | 9月11日 完了 |
| M2 DSP チェーン | 9月11日 完了 |
| M3 ライブラリ | 9月11日 完了 |
| M4 基本の UI と OS 連携 | 9月12日 完了 |
| M5 ビジュアライザー、メーター、音色の色 | 9月13日 完了 |
| M6 プレイリスト | 9月13日 完了 |
| M7a メタデータ | 9月18日 完了 |
| M10a バイナリ取得と yt-dlp | 9月18日 完了 |
| M8 歌詞と1曲ずつのタグ編集 | 9月19日 完了 |
| v1.0.0 — M0〜M8 と M10a | 2026年9月19日 タグ — 533a2b0 |
| v1.0.1 — 監査セッションからの6件の修正 | 2026年9月19日 タグ — c21e923 |
| v1.1.0 — アプリ内ヘルプ | 2026年9月20日 タグ — 4af6879 |
| v1.2.0 — テスター用パッケージ、CI による Windows インストーラー | 2026年9月21日 タグ — 26132f9 |
| M13 元フォルダの管理 | 9月27日 完了 |
| M14 再生中画面のパネル | 9月27日 完了 |
| v1.3.0「整頓」— M13、M14、テストで見つかった4つのバグ、6テーマすべてで音色の色 | 2026年9月27日 タグ — d51afdd |
| M10b ffmpeg、Deno、fpcalc、並列ダウンロード | 9月29日 一部 — 残りは v1.4.1 |
| M15 ヘルプ内のダウンロード元一覧 | 9月29日 完了 |
| v1.4.0「フルダウンロード」— M10b、M15、CI による Linux パッケージ | 2026年10月9日 タグ — 27aefb8 |
| v1.4.1 M10b の残り | 作業中、タグ未設定 |
| M16 表示(v1.5)— 6つの新テーマ、コンセプトは10月10日に承認 | 次、v1.4.1 の後 |
| M17、M7b(v1.6)· M11、M18(v1.7) | 予定 |
| M9 スクロブル | 見送り、日付未定 |
順番は一度だけ変えました。M7a の後に M10 の一部、次に M8 を進め、M9 は見送りました。その理由と、それで生じた負債は記録してあります。M8 の後は v1 に新機能を入れず、両方のシステム向けのリリース用バイナリをまっさらな状態から動かしてから v1.0.0 のタグを付けました。
その後の三つのリリースも新機能なしです。v1.0.1 では監査セッションで見つかった6件を修正。v1.1.0 ではアプリ内ヘルプを加え、初めて Onsa を開いた人が持ち主に聞かなくても使えるようにしました。v1.2.0 ではテスター用のパッケージを用意しました。インターネットを使う経路はすべて一度本物のサービスで証明し、Windows のインストーラーは CI でビルドするようにしました。持ち主のマシンでビルドすると、個人の AcoustID キーがバイナリに埋め込まれてしまうとわかったからです。
新機能は v1.3.0 と v1.4.0 から再び入りました。どちらも9月25日に決めたリリースの方針に沿っています。テスターの報告はリリースの関門にはせず、届いたものは次のパッチリリースに入れます。
わかっている限界
README とリリースノートに、隠さずそのまま書いています。v1.4.0 について:
- ffmpeg と Deno は任意。 ffmpeg がなくてもダウンロードはできますが、音声はそのままの形で取得され、タグやジャケットは埋め込まれず、MP3 や FLAC への変換もできません。Deno がないと、一部の YouTube の形式が使えないことがあります。ダウンロード画面には両方とも書いてあります。
- ダウンロードキューはまだ保存されない。 Onsa を閉じると未完了のダウンロードは消え、保存先は常に最初のライブラリフォルダの
Unduhanです。どちらも v1.4.1 で解決します。 - Opus しか提供しない情報源は、メッセージを出して断る。 まだ Opus のデコーダーがないからです。ファイルが置かれたのにライブラリに一度も現れないより、最初に断るほうがましです。
- Last.fm のスクロブルはまだない。
- Linux は WSL で試しただけで、実際の Linux マシンではまだ試していません。
- 単語ごとのタイミングは読み込んで保存していますが、まだ単語ごとには描いていません。光るのは行で、単語ではありません。
- 最終的なアイコンはまだなく、プロジェクトのライセンスはあえて保留しています。