音声分析器で難しいのは動く線を描くことではありません。すべての線が同じ信号を同じ時点のものとして示し、しかもオーディオコールバックを UI スケジューラにせず、画面が一時的に遅くなっても待ち行列を増やさないことです。
SignalMetric は最初に一つの判断を置きました。Monitor、Spectrum、Timeline、Scope は、別々に競争する四つの器具ではなく、一つの分析フレームの四つの読み方であるべきです。
横にスワイプして図全体を表示
図を生成できませんでした。下でソースを確認できます。
図のソースを表示
flowchart LR
A[ライブマイク入力] --> D[オーディオ取得]
B[読み込んだファイルまたはローカル録音] --> C[AVAudioPlayerNode]
C --> E[メインミキサーの Tap]
D --> F[2,048 サンプル取得窓]
E --> F
F --> G[直列 ScopeAnalyzer]
G --> H[最新 ScopeFrame]
H --> I[ViewModel が 30 FPS で公開]
I --> J[Monitor]
I --> K[Spectrum]
I --> L[Timeline]
I --> M[Scope]
D --> N[上限付き録音ライター]
図自体は一般的です。計器の信頼性を決めるのは、その境界の扱いです。
ソースが変わっても分析契約は一つ
SignalMetric には、権限不要の Demo、Live Mic、読み込んだ音声、明示的なローカル録音という四つのソース状態があります。AVAudioEngine に入る場所は同一ではありません。
- マイクのセッションは input node に tap を入れます。
- 読み込んだファイルと完成したローカル録音は
AVAudioPlayerNodeから再生し、main mixer tap で実際の再生経路を分析します。 - 録音はマイクの input tap をライブ分析と共有し、受け取った buffer をライターへ渡します。
- Demo は権限を求めず、決定的な
ScopeFrameを生成します。
重要なのは transport が完全に同じであることではなく、実際の音声が最終的に同じ分析契約へ届くことです。契約は Float32 のモノラル窓、sample rate、route fingerprint、channel count です。
マルチチャンネル入力は明示したモノラル分析信号へ fold-down されます。これによりスペクトルとラウドネスは一貫しますが、マルチチャンネル納品メーターを装うものではありません。UI は channel count を隠しません。ルートまたは sample rate が変わると分析器は状態をリセットし、帯域範囲を再構成します。古い Integrated loudness や FFT 範囲を新しい入力へ持ち込めば、連続して見えても一貫したセッションではなくなるからです。
render thread の仕事を限定する
AVAudioEngine tap は音声レンダリング経路の近くにあります。ビュー更新を待つ、ネットワークを始める、無制限の履歴を追加する、セッション長に比例する buffer を確保する、といった仕事を置く場所ではありません。
SignalMetric は固定の 2,048 サンプル窓を集め、二つの事前確保済み sample slot を使います。一つは直列分析中、もう一つは待機できます。窓が満ちたら取得経路は利用可能な slot にコピーし、一度だけ分析をスケジュールします。すでに待機フレームがあれば、その後ろに追加しません。表示を何秒も過去のものにしないため、遅延の借金を作らない設計です。
横にスワイプして図全体を表示
図を生成できませんでした。下でソースを確認できます。
図のソースを表示
sequenceDiagram
participant Tap as Audio tap
participant Capture as 固定取得スロット
participant Analyzer as 直列分析器
participant Store as 最新フレームロック
participant UI as Main actor 30 FPS
Tap->>Capture: 2,048 サンプル窓を満たす
alt 待機フレームなし
Capture->>Analyzer: 一つの窓を予約
Analyzer->>Analyzer: FFT、レベル、ラウドネス、波形
Analyzer->>Store: 最新 ScopeFrame を置換
else 待機フレームあり
Capture-->>Capture: backlog を増やさない
end
UI->>Store: 最新の完了フレームを読む
Store-->>UI: 一貫した一つの snapshot
UI->>UI: 四つの workspace を描画
UI は main actor で 30 FPS に最新の完了フレームを読みます。オーディオコールバックが SwiftUI を直接呼ぶことはありません。表示 cadence を安定させながら、callback cadence はデバイスのルートや I/O buffer に従えます。
最新フレームは lock で保護され、値全体として置換されます。Monitor の数値が一つの FFT 窓、Scope の波形が別の窓、という状態にはなりません。画面は一フレーム遅れることがあっても、内部では整合します。
フレームが持つもの
ScopeFrame は信号処理と表示の境界です。そこには次が含まれます。
- raw RMS dBFS、smoothed display level、instantaneous Sample Peak、peak hold、4x True Peak estimate;
- K-weighted Momentary、Short-Term、Integrated loudness、LRA;
- 24 の表示帯域、64 の対数 dBFS trace、peak hold とスペクトル統計;
- 強いスペクトル成分、centroid、bandwidth、85% roll-off、flatness;
- トリガーされた 256 bin waveform envelope;
- DC offset、zero-crossing rate、channel count、clip state、dominant frequency、beat confidence;
- sample rate、source fingerprint、timestamp、analysis window の時間。
単位の整形、色、画面ごとのルールはこのモデルに入れません。分析器は信号の証拠を持ち、ViewModel は整形済み単位、選択中の workspace、アクセシビリティ要約、response preference、session reset、上限付き 30 秒履歴を扱います。UI を変えても測定そのものを変えないためです。
FFT だけが仕事ではない
分析器は Hanning 窓の後に Accelerate/vDSP で 2,048 point DFT を実行します。Hanning 窓は leakage を抑えますが、目立つ bin がただちに正確な中心周波数の完全な正弦波を意味するわけではありません。
強い成分の表示は次の工程を取ります。
- 絶対・相対の両方の floor を超える局所最大を見つける。
- 分解可能な Hanning main-lobe 近傍の候補を抑え、一成分を複数の side-lobe card にしない。
- log magnitude の放物線補間で周波数を細かくする。
- Hanning coherent gain を補正してレベルを表示する。
- 二つ以上のピークが候補 fundamental を支える場合だけ harmonic label を付ける。
- 低い公開 cadence で成分を対応付けて平滑化し、順位のちらつきを避ける。
したがって Spectrum は周波数の証拠を読みやすくしますが、六つの正弦波がソース全体を再構成したとは言いません。生の分解能は sampleRate / 2048 として明示されます。
レベルとラウドネスにも同じ節度があります。RMS と Sample Peak は実用的なデジタル範囲に収め、True Peak は estimate と明示します。ラウドネスは持続する K-weighting と時間重み付きの窓を使います。Momentary は 400 ms、Short-Term は 3 秒、Integrated は 400 ms block を 100 ms hop で扱い、絶対・相対ゲートを適用します。BS.1770-style のモノラル分析であり、認証済み納品コンプライアンスを主張しません。
履歴にも上限が必要
良い Timeline は連続して感じられるべきですが、メモリー予算は固定でなければなりません。SignalMetric はスペクトル履歴を毎秒 8 フレーム公開し、30 秒で最大 240 列にします。セッションの最初から全フレームを保持しません。
Scope も同じです。隠れたフル解像度録音ではなく、上昇 zero-crossing trigger の後に現在の窓を固定 256 bin の min/max/average envelope に縮約します。細いトランジェントも各 bin の min/max で残しつつ、表示コストは予測可能です。
横にスワイプして図全体を表示
図を生成できませんでした。下でソースを確認できます。
図のソースを表示
flowchart TD
A[生のソースフレーム] --> B[固定 2,048 サンプル分析窓]
B --> C[64 対数帯域スペクトル]
C --> D[毎秒 8 フレームの履歴公開]
D --> E[240 列の Timeline]
B --> F[トリガー後の縮約]
F --> G[256 bin waveform envelope]
E --> H[一定のセッションメモリー]
G --> H
これは性能最適化だけではありません。無制限の履歴はメモリーと描画コストを時間とともに変え、長いセッションを別の器具にします。計器は一時間後にも同じ計器であるべきです。
録音は二つ目のリアルタイム責務
録音は時間に敏感な入力経路へ disk I/O を追加します。元の AVAudioPCMBuffer を遅い writer に直接渡せば、保存の圧力が取得へ結び付きます。SignalMetric は受け入れた buffer をコピーし、専用の直列 queue で書きます。
待機できる recording buffer は 64 個までです。保存が追い付かなければ、無制限にメモリーを増やすのではなく録音を停止します。Stop、interruption、route change、background の際には、新しい buffer を先に拒否し、受け入れ済みの書き込みを排出してからローカル M4A を閉じます。main thread は待ちません。
横にスワイプして図全体を表示
図を生成できませんでした。下でソースを確認できます。
図のソースを表示
stateDiagram-v2
[*] --> LiveAnalysis
LiveAnalysis --> Recording: Record をタップ
Recording --> Finalizing: Stop / interruption / background
Finalizing --> LocalFile: 受理済み書込みを排出して M4A を閉じる
Finalizing --> Failed: 空のファイルまたは書込み失敗
LocalFile --> FileAnalysis: ローカル録音を開く
FileAnalysis --> LiveAnalysis: ソースを切替
通常の Live Mic はメモリー内分析のままです。ファイルが作られるのは Record をタップした後だけで、ユーザーが共有先を選ぶまでローカルに残ります。
限界も設計の一部
SignalMetric は都合のよい近道を選びません。
- オーディオ callback から SwiftUI を呼ばない。
- queue や history をセッション時間とともに増やさない。
- マイクや読み込んだ音声を処理のために upload しない。
- 未校正のマイクを SPL として見せない。
- 4x の sample 間 estimate を認証結果と呼ばない。
- confidence が低いテンポやピッチを強制的に表示しない。
結果は無関係な四つのアニメーションではありません。一つのソース、一つの分析境界、そしてより良い問いのための四つの見方です。利用者向けの全体像は SignalMetric 製品ガイド を参照してください。