はじめに(前回までのあらすじ)
前回の記事では、iTunes APIのプレビュー音源をFFT解析し、そこにAutoEqの補正データを合成することで「同じ曲がイヤホンごとにどう出力されるか」を可視化しました。
- 数値上もっとも低音を出していたのは、意外にも「低音の王様」Boseではなく qdc SUPERIOR だったこと
- Boseは低域そのものより、中高域を「引き算」することで低音の存在感を演出していたこと
- Final A5000は1〜5kHzの盛り上がりが解像度感の正体らしいこと
- AirPods ProとAirPods 4の差は、カナル型かオープン型かという物理構造がそのまま重低音の限界としてデータに刻まれていたこと
手元の4〜5機種を比較しただけでも、グラフの裏にある設計思想の違いが見えてきて面白かったのですが、書き終えて残った疑問がありました。
自分が持っている機種だけでなく、もっとたくさんのイヤホンを並べたら、結局「値段の割に良い音」を出しているのはどれなんだろう?
数千円のイヤホンと数万円のイヤホンを同じ土俵に乗せて、客観的に「コスパ」を計算できないか。それが今回のテーマです。
※前回に引き続き、オーディオに関してはまだ勉強中です。今回はデータ処理・インフラ寄りの話が中心になります。
今回のアプローチ:精密検査から健康診断へ
前回は手元の4〜5機種を精密に見る「精密検査」でした。今回は逆に、200機種以上を横断的にスコアリングする「健康診断」に近いアプローチです。
バッチ処理の量が増えるので、ちょうど組み直したばかりの自宅Kubernetes環境—-k3s(軽量Kubernetes)をRaspberry Pi 4上でシングルノード稼働させたクラスタ——を、このバッチジョブの実行基盤として使うことにしました。もとはローカルLLM(Ollama)を動かしていたPiを一度クリーンにして作った、できたてほやほやのクラスタです。実用データで動かす初めての本番(?)ジョブが、まさかのイヤホン分析になりました。
なお今回は、前回使った「AutoEqの補正値」ではなく、生の周波数特性(FR)実測データを直接使う方式に変えています。補正値は「ターゲットに近づけるための逆算値」なので、曲のスペクトルに掛け合わせたときにそれが「実機の色付け」を表しているのか「補正後の理想形」を表しているのか、解釈に悩む余地があります。生の実測データとターゲットカーブの差分を直接見る方式なら、その解釈のブレがありません。この点は前回の検証方法を見直すきっかけにもなりました。
全体構成
[GitHub: 公開FR測定データ] [Kaggle: crinacleランキング(価格付き)]
│ │
▼ ▼
k3s Job: analyze.py (xlsx → CSV変換)
FR実測 vs Harmanターゲット │
との乖離スコアを算出 │
│ │
└──────────┬───────────────────┘
▼
k3s Job: join_price.py
ブランド名+モデル名であいまい一致
cospa_score = 1 / (技術スコア × 価格)
│
▼
cospa_ranking.csv(ランキング結果)
- クラスタ: k3s v1.36.4+k3s1(シングルノード、Raspberry Pi 4 / Debian 13)
- 実行基盤: Kubernetes
Job(python:3.12-slimイメージ、ホストパスマウントでデータ授受) - 言語: Python 3.12(標準ライブラリの
csv/difflib+numpyのみ)
データソース
① 周波数特性(FR)の実測データ
Crinacle氏らが公開している測定を、有志がGitHubにミラーしている非公式データベースを使わせてもらいました。
Frequency, dB のタブ区切りテキストが、ブランド/モデルごとにディレクトリで整理されています。前回登場したBoseやqdc、Finalのような有名機種だけでなく、聞いたこともない中華イヤホンまで、桁違いの物量でカバーされています。ターゲットカーブ(Harman IE 2019 v2など)も同梱されているのが便利でした。
② 価格データ
FR測定データには価格情報がないため、価格付きのKaggleデータセットを別途使用しました。
こちらはxlsx形式で971モデル分の価格(USD)と格付けが入っています。
Step 1: 技術スコアの算出(analyze.py)
やっていることはシンプルです。
- 対象モデルのFRファイル(L/R、複数の装着バリエーション)を全部読み込み、対数周波数グリッドに補間
- SPLオフセットを揃えて平均し、そのモデルの「代表カーブ」を作る
- Harman IE 2019 v2ターゲットとのRMS偏差を「技術スコア」とする(低いほどターゲットに忠実)
def interp_to_grid(freqs, dbs):
order = np.argsort(freqs)
freqs, dbs = freqs[order], dbs[order]
return np.interp(FREQ_GRID, freqs, dbs, left=dbs[0], right=dbs[-1])
# モデルごとに全バリエーションのカーブを平均してから、
# ターゲットカーブとのRMS偏差を technical_score とする
avg_curve = np.mean(curves, axis=0)
deviation = float(np.sqrt(np.mean((avg_curve - target_curve) ** 2)))
これをk3sのJobとして実行し、593個のFRファイル・71ブランドから223モデル分の技術スコアを算出しました。
apiVersion: batch/v1
kind: Job
metadata:
name: otomatch-fr-analysis
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: analyze
image: python:3.12-slim
command: ["sh", "-c", "pip install --no-cache-dir numpy >/dev/null 2>&1 && python /data/analyze.py"]
volumeMounts:
- {name: workdir, mountPath: /data}
volumes:
- name: workdir
hostPath: {path: /home/yoshidam/otomatch-analysis, type: Directory}
Step 2: 価格との突き合わせ(join_price.py)
ここが今回一番苦労した部分です。FR側の「ブランド + モデル名」とKaggle側の表記は完全には一致しません。最初はdifflibの文字列類似度だけで緩くマッチさせていたのですが、それだと誤マッチが安いモデルに引っ張られてコスパスコアが不当に跳ね上がるという問題が起きました。
例: 「Truthear Zero TE S2」が、全く別モデルの「Truthear Hola」($20)に類似度0.625で誤って紐付き、価格の安さだけでコスパ上位に浮上してしまった
対策として、
- 文字列類似度の候補は緩めに集めつつ(
cutoff=0.5) - 意味のある単語(3文字以上)が2つ以上共通しているかをゲート条件として追加
- 採用閾値を
0.86まで引き上げ、届かないものはunmatched.csvに隔離してランキングには入れない
という形で厳格化しました。
shared = query_tokens & cand_tokens
if len(shared) < 2 and not (query_tokens <= cand_tokens or cand_tokens <= query_tokens):
continue # 単語レベルの根拠がない類似度の高さは信用しない
結果:現時点でのコスパランキング(上位10件)
223モデル中、信頼できる価格一致が取れたのは102モデル(46%)。それらをcospa_score = 1 / (技術スコア × 価格[USD])(値が大きいほど高コスパ)で並べたものが以下です。
| 順位 | モデル | 価格(USD) | cospa_score | 一致度 |
|---|---|---|---|---|
| 1 | Moondrop Spaceship | $20 | 15.67 | 1.00 |
| 2 | Tanchjim Tanya RM | $25 | 15.33 | 0.90 |
| 3 | BLON BL01 | $23 | 15.03 | 1.00 |
| 4 | BLON BL05 | $43 | 10.55 | 1.00 |
| 5 | BLON BL-Max | $40 | 9.01 | 1.00 |
| 6 | Tin Hifi T1 Plus | $30 | 8.85 | 1.00 |
| 7 | Moondrop Sparks | $90 | 8.51 | 1.00 |
| 8 | Tin Hifi T1 | $36 | 8.47 | 1.00 |
| 9 | BLON BL03 | $38 | 8.39 | 1.00 |
| 10 | Tanchjim Cora | $50 | 8.27 | 1.00 |
前回登場したBose・qdc・Final・AirPodsはいずれも数千円〜数万円のハイエンド〜ミドル帯だったので、この物量ランキングには(今のところ価格突合の都合もあり)顔を出していません。代わりにBLONやTin HiFi、Moondropの低価格帯モデルが上位を占めました。これらはオーディオ愛好家コミュニティでも「コスパ神」として名前が挙がる常連なので、少なくとも方向性としては直感と大きく矛盾しない結果になっています。
まだ残っている課題
正直に書いておきます。
-
モデル名の重複バリエーション問題: FR測定データ側には、同じ実機を「イヤーチップ違い」で複数回測定したものが、別モデルのように名前に残っているケースがあります(例:
Zero TE S1〜S10)。今回は幸いマッチング精度を上げた副作用でこれらが弾かれましたが、根本的にはモデル名の正規化が必要です。 - 未マッチの121モデル: 表記ゆれで価格が見つからなかったものが半分以上あります。ブランド名の別記法や旧モデル名などが原因とみられ、精度を上げる余地があります。
- 価格はUSD・データは2023年時点: 日本の実売価格や現在の相場とはズレがあります。
- 「同じ曲」の要素がまだ入っていない: 前回の記事の核心だった「曲のスペクトルに特性を合成する」ステップは、今回のバッチにはまだ組み込んでいません。今のtechnical_scoreは汎用的なターゲット追従度でしかなく、特定の楽曲でどう聴こえるかは反映できていません。
次回予告
次は、前回の手法(曲のスペクトル × イヤホン特性の合成)をこの大規模バッチに統合します。曲ごとにエネルギーが集中する帯域を重み付けすることで、「その曲を聴いたときに体感差が出やすいイヤホンはどれか」まで踏み込んだコスパスコアに拡張する予定です。あわせて、前回焦点を当てたBoseやqdc、Finalのようなハイエンド機もこのランキングに合流させ、「安いイヤホンで妥協すべきか、それとも高いイヤホンには本当に理由があるのか」という前回の結論をデータで検証してみたいと思います。
編集後記
前回「高いイヤホンには高いだけの理由がある」と結論づけたばかりなのに、今回は真逆の「安いイヤホンのコスパがすごい」という結果が並んでしまいました。数値と体感、そして今度は数値と数値がまた矛盾しはじめていて、オーディオの魔境はまだ底が見えません。