※ISO 532 (Zwicker method) による推定ラウドネスおよびラウドネスレベルの算出は以下の記事をご覧ください。
www.wizard-notes.com
先日、下記の記事で統合ラウドネス値(Integrated loudness) LKFS/LUFS のPython実装 pyloudnorm をご紹介しました。
pyloudnormで平均ラウドネス値 (LUFS) 算出/ラウドネス正規化 - Wizard Notes
こちらのライブラリのコードは、Rec. ITU-R BS.1770-4 に沿って実装されているため、勧告の内容・数式と具体的な算出/実装方法の勉強に役立ちます。
勉強のため、pyloudnorm](https://github.com/csteinmetz1/pyloudnorm)での計算処理の流れを日本語コメントで解説したコードを作成しましたので、具体的な算出/実装方法が気になる方はご覧ください。
LUFSの算出のための処理を整理すると、以下の4つの処理に分けることができます。
それぞれの処理の要点を以下にまとめました。
- K-weighting filterの適用
- 各チャネルのMean Squareの算出
- 処理ブロックの時間長は 400ms
- オーバーラップは75%, つまり100msごとに計算
- ゲートなしラウドネスレベル 算出
- 全チャネルのMean Squareを集約(重み付けて総和)した値
- チャネルごとにGで重み付けする
- 平均ラウドネス値 (Integrated loudness) 算出のためのゲート処理
- 絶対値ゲート
- ゲートなしラウドネスレベルの値が-70未満の処理は除去
- 無音や極端に小さいブロックを除去するため
- 相対値ゲート
- 絶対値ゲートを通った信号の(3)を見て、(3)の平均値と比べて-10LU (dB) 低いブロックは除去
コード
import numpy as np
import scipy.signal
from future.utils import iteritems
"""
Rec. ITU-R BS.1770-4 Integrated Loudness
ゲート処理適応後のラウドネス値計算の解説 by Kurene
Original: https://github.com/csteinmetz1/pyloudnorm/blob/master/pyloudnorm/meter.py
License: MIT
"""
def integrated_loudness(x, meter):
if x.ndim == 1:
x = np.reshape(x, (x.shape[0], 1))
numSamples, numChannels = x.shape[0], x.shape[1]
rate = meter.rate
block_size = meter.block_size
y = x.copy()
for (filter_class, filter_stage) in iteritems(meter._filters):
for ch in range(numChannels):
y[:,ch] = filter_stage.apply_filter(y[:,ch])
G = [1.0, 1.0, 1.0, 1.41, 1.41]
T_g = block_size
Gamma_a = -70.0
overlap = 0.75
step = 1.0 - overlap
T = numSamples / rate
numBlocks = int(np.round(((T - T_g) / (T_g * step)))+1)
j_range = np.arange(0, numBlocks)
z = np.zeros(shape=(numChannels,numBlocks))
for i in range(numChannels):
for j in j_range:
l = int(T_g * (j * step ) * rate)
u = int(T_g * (j * step + 1) * rate)
z[i,j] = (1.0 / (T_g * rate)) * np.sum(np.square(y[l:u, i]))
l_all = [0.0 for _ in j_range]
for j in j_range:
tmp = 0.0
for i in range(numChannels):
tmp += G[i] * z[i,j]
with np.errstate(divide='ignore'):
l_all[j] = -0.691 + 10.0 * np.log10(tmp)
J_g = [j for j,l_j in enumerate(l_all) if l_j >= Gamma_a]
z_avg_gated = [ np.mean([z[i,j] for j in J_g]) for i in range(numChannels)]
Gamma_r = -0.691 + 10.0 * np.log10(np.sum([G[i] * z_avg_gated[i] for i in range(numChannels)])) - 10.0
J_g = [j for j,l_j in enumerate(l_all) if (l_j > Gamma_r and l_j > Gamma_a)]
z_avg_gated = np.nan_to_num(np.array([np.mean([z[i,j] for j in J_g]) for i in range(numChannels)]))
with np.errstate(divide='ignore'):
LUFS = -0.691 + 10.0 * np.log10(np.sum([G[i] * z_avg_gated[i] for i in range(numChannels)]))
return LUFS
pyloudnormのオリジナルのコードはこちら
参考文献