Suzuna — STATE Ph3.x 録音品質UI
概要
Phase 1〜3で構築した変換・学習・TTSパイプラインに、 録音品質を担保するための前処理UIを追加する。
「失敗しない録音」を提供することで、学習データの品質を底上げし、 最終的なカスタムモデルの完成度を高める。
音声処理パイプライン
録音 / インポート
↓
① 無音トリミング (自動)
↓
② ノイズリダクション (自動 / 強度調整可)
↓
③ ローカット / ハイカット(デフォルト値あり / スライダー調整可)
↓
④ ゲイン調整 (オートノーマライズ or 手動)
↓
⑤ 品質スコア表示 (自動算出)
↓
学習データとして保存 / RVC変換へ
UI構成
録音パネル(ConvertPage / RecordPage)
┌─ 録音 ────────────────────────────────────────┐
│ [🎤 録音開始] │
│ │
│ 波形: ▁▂▄▆▄▂▁▃▅▃▁ (リアルタイム) │
│ │
│ 音量: [████████░░] -12dB ← 適正 │
│ ⚠ クリッピング警告 / ⚠ 小さすぎ警告 │
│ │
│ 環境音: 静か ✅ / ノイズ多め ⚠ / 大きい ❌ │
└─────────────────────────────────────────────────┘
録音前チェック(マイク接続時に自動実行)
- 3秒間の環境音を測定
- ノイズレベルを判定 → UI上にバッジ表示
- ✅ 静か(-50dB以下)
- ⚠ ノイズあり(-50〜-35dB)
- ❌ 大きい(-35dB以上)
音声処理パネル(録音後 / インポート後)
┌─ 音声処理 ─────────────────────────────────────┐
│ 入力: voice_001.wav [▶試聴] │
│ │
│ [✅ 無音トリミング] 自動検出・除去 │
│ │
│ ノイズリダクション 強度: 0.5 │
│ [──────●──────────] │
│ │
│ ローカット 80 Hz │
│ [●───────────────] 20〜500Hz │
│ │
│ ハイカット 16000 Hz │
│ [──────────────●─] 8k〜24kHz │
│ │
│ ゲイン調整 │
│ ○ オートノーマライズ(ピーク -3dB) │
│ ○ 手動 [──●──────] +0.0 dB (-12〜+12) │
│ │
│ [▶ 処理を適用] [↩ リセット] │
│ │
│ 出力: voice_001_processed.wav [▶試聴] │
└─────────────────────────────────────────────────┘
品質スコアパネル
┌─ 録音品質スコア ───────────────────────────────┐
│ │
│ 87 / 100 良好 ✅ │
│ │
│ ノイズレベル ████████░░ 良好 │
│ 音量安定度 ███████░░░ 普通 │
│ クリッピング ██████████ 問題なし │
│ 無音比率 █████████░ 良好 │
│ │
│ 💡 音量安定度を上げるにはマイクとの距離を │
│ 一定に保ってください │
└─────────────────────────────────────────────────┘
スコア算出ロジック
| 項目 | 配点 | 算出方法 |
|---|---|---|
| ノイズレベル | 30点 | SNR(信号対雑音比)から換算 |
| 音量安定度 | 25点 | RMS音量の分散から換算 |
| クリッピング | 25点 | サンプル値が0.99以上の割合 |
| 無音比率 | 20点 | 無音区間が全体の20〜40%が理想 |
Pythonバックエンド
依存ライブラリ
librosa # 波形解析・特徴量抽出 (ISC License)
scipy # ローカット/ハイカットフィルター (BSD)
noisereduce # ノイズリダクション (MIT)
soundfile # WAV読み書き (BSD)
numpy # 数値演算 (BSD)
API追加
POST /audio/process
body: {
input_path: string,
trim_silence: bool, // 無音トリミング (default: true)
noise_reduce: float, // ノイズリダクション強度 0〜1 (default: 0.5, 0=無効)
lowcut_hz: int, // ローカット周波数 (default: 80)
highcut_hz: int, // ハイカット周波数 (default: 16000)
gain_mode: "auto" | "manual",
gain_db: float // 手動ゲイン -12〜+12 (default: 0)
}
response: {
output_path: string,
score: QualityScore
}
POST /audio/score
body: { input_path: string }
response: QualityScore
GET /audio/env-check
response: {
noise_level_db: float,
status: "quiet" | "noisy" | "loud"
}
# WebSocket: リアルタイム波形・音量レベル
WS /audio/monitor
stream: {
waveform: float[], // 波形データ (128サンプル/フレーム)
rms_db: float, // 現在のRMS音量
clipping: bool // クリッピング検出
}
型定義
interface QualityScore {
total: number // 0〜100
grade: "excellent" | "good" | "fair" | "poor"
noise: number // 0〜30
stability: number // 0〜25
clipping: number // 0〜25
silence_ratio: number // 0〜20
advice?: string // 改善アドバイス
}
Python実装スケルトン
# python/routers/audio_process.py
import numpy as np
import librosa
import soundfile as sf
from scipy.signal import butter, sosfilt
import noisereduce as nr
def process_audio(input_path, output_path, config):
y, sr = librosa.load(input_path, sr=None)
# ① 無音トリミング
if config.trim_silence:
y, _ = librosa.effects.trim(y, top_db=30)
# ② ノイズリダクション
if config.noise_reduce > 0:
y = nr.reduce_noise(y=y, sr=sr, prop_decrease=config.noise_reduce)
# ③ ローカット / ハイカット
if config.lowcut_hz > 0:
sos = butter(4, config.lowcut_hz, btype='high', fs=sr, output='sos')
y = sosfilt(sos, y)
if config.highcut_hz < sr // 2:
sos = butter(4, config.highcut_hz, btype='low', fs=sr, output='sos')
y = sosfilt(sos, y)
# ④ ゲイン調整
if config.gain_mode == "auto":
peak = np.max(np.abs(y))
if peak > 0:
target = 10 ** (-3 / 20) # -3dBFS
y = y * (target / peak)
else:
gain_linear = 10 ** (config.gain_db / 20)
y = np.clip(y * gain_linear, -1.0, 1.0)
sf.write(output_path, y, sr)
return calc_quality_score(y, sr)
def calc_quality_score(y, sr):
# ノイズレベル (SNR推定)
rms = np.sqrt(np.mean(y**2))
noise_floor = np.percentile(np.abs(y), 10)
snr = 20 * np.log10(rms / (noise_floor + 1e-10))
noise_score = min(30, max(0, (snr - 10) / 30 * 30))
# 音量安定度
frame_rms = librosa.feature.rms(y=y)[0]
stability = 1 - min(1, np.std(frame_rms) / (np.mean(frame_rms) + 1e-10))
stability_score = stability * 25
# クリッピング
clip_ratio = np.mean(np.abs(y) > 0.99)
clipping_score = max(0, 25 - clip_ratio * 1000)
# 無音比率
silence_ratio = np.mean(np.abs(y) < 0.01)
silence_score = 20 if 0.2 <= silence_ratio <= 0.4 else max(0, 20 - abs(silence_ratio - 0.3) * 50)
total = int(noise_score + stability_score + clipping_score + silence_score)
return {
"total": total,
"grade": "excellent" if total >= 90 else "good" if total >= 70 else "fair" if total >= 50 else "poor",
"noise": int(noise_score),
"stability": int(stability_score),
"clipping": int(clipping_score),
"silence_ratio": int(silence_score),
"advice": get_advice(noise_score, stability_score, clipping_score)
}
デフォルト値まとめ
| パラメーター | デフォルト | 推奨範囲 | 備考 |
|---|---|---|---|
| 無音トリミング | ON | — | 常時推奨 |
| ノイズリダクション | 0.5 | 0.3〜0.7 | 強すぎると声質劣化 |
| ローカット | 80Hz | 60〜200Hz | ファン・エアコン除去 |
| ハイカット | 16000Hz | 12k〜20kHz | 高周波ノイズ除去 |
| ゲインモード | オート | — | ピーク -3dBFS |
完了条件
- リアルタイム波形・音量メーター(WebSocket)
- 録音前環境音チェック(3秒計測)
- 無音トリミング
- ノイズリダクション(強度スライダー)
- ローカット / ハイカットフィルター
- ゲイン調整(オート / 手動)
- 品質スコア表示(4項目 + アドバイス)
- 処理前後の試聴比較