2.2.4.1. Volume level¶
オーディオを扱うすべてのプログラムには、何らかのボリューム スライダーがあります。 そして、それらはそれぞれ異なる意味を持っています! Blender では、サウンド ストリップのプロパティとして見つけることができます。 これは 0 ~ 100 の数値です。値 > 1 はサウンドを増幅します。値が 1 未満の場合、サウンド レベルが減少します。 シンプルですね。しかし、音量レベル 0.5 はどれくらい大きいのでしょうか? 1.0の半分くらいの音量でしょうか?最大値の 100 はどうでしょうか?本当に?100?
2.2.4.1.1. Volume level value と loudness(音量) の関係¶
次の式を使用して、ゲインまたは損失をデシベル (dB) 単位で計算できます。
dB = 20 x log (volume level)
volume level = antilog (dB/20)
したがって、ボリューム レベルを 0.5 に下げると、-6 dB の損失が生じます。音量レベルを 100 に上げると、20 x log(100) = 40 db のゲインになります。電卓の逆対数関数 (10<sup>x</sup>) を使用すると、antilog(-6dB/20) = 0.5012 という逆計算を行うことができます。
db loss/gain |
-40 |
-30 |
-20 |
-10 |
-6 |
0 |
+6 |
+10 |
+20 |
+30 |
+40" |
---|---|---|---|---|---|---|---|---|---|---|---|
Volume Level |
0.01 |
0.0316 |
0.1 |
0.316 |
0.501 |
1 |
1.995 |
3.162 |
10 |
31.62 |
100 |
心理学の研究によると、知覚される音量は 10 dB ごとに 2 倍になります。したがって、サウンドを 2 倍大きくするには、音量レベルを antilog(+10dB/20) = 3.16 に設定する必要があります。知覚される音を半分に減らすには、音量レベルをantilog(-10dB/20) = 0.316まで下げる必要があります。
デシベルは線形ではなく対数スケールであることに注意してください。これが、ボリューム スライダーが 0 ~ 1 ではなく非対称 (0 ~ 100) になっている理由です。スライダーを +100 に設定すると、音量が 40 dB 増加します。同じ量で音量を下げるには、音量を 0.01 に設定する必要があります。便利な変換表が Wikipedia にあります。
音がデジタル化されると、アナログ音波が離散的な瞬間にサンプリングされ、波の振幅 (音量) が保存されます。次の 2 つの概念が非常に重要です。
サンプル レート: 一般的なサンプル レートは 44.100 Hz (オーディオ CD 品質)、つまり 1 秒あたり 44100 個のサンプルが取得されることを意味します。サンプルが多いほど品質は向上しますが、必要なストレージも増えます。
ビット深度: 各サンプルで、アナログ音波の振幅が電圧に変換され、数値 (通常は 16 ビットまたは 24 ビットの整数) で保存されます。 16 ビット整数には 2<sup>16</sup> = 65536 個の値しかないため、表現できる最大振幅が存在します。これらの数値は、-1 から +1 までの浮動小数点数に変換されます。これらのしきい値 (-1 または +1) を超える振幅は、-1 または +1 でクリップされ、歪みが生じます。
音の音量や大きさは、知覚的/心理的現象です。そのため、音の大きさは多くの要因によって影響されます。音波の振幅だけでなく、周波数(低音にはより多くのパワーが必要です)、環境(音源までの距離など)、リスナーの慣れなどです。以下の議論を簡単にするために、ラウドネス (心理的現象) を音波の振幅または電圧 (物理的現象) に換算します。
dbという尺度は相対的な尺度です。それはあるサウンドと基準サウンドとの比です。
デジタル サウンドの場合、基準は +1V の振幅、または選択したビット深度で保存できる最大振幅のサウンドです。この定義( dB = log(sample voltage/1 V) `` )のため 、サンプル電圧 = 1V の dB 値はゼロです。 ``log(1/1) = 0
であるためです。
したがって、ゼロ dB は、何もせずに得られる最大音響レベルです。サウンドレベルを下げるとマイナスのdBになります。
2.2.4.1.2. Blender Python で dB 値を計算するにはどうすればよいですか?¶
添付のブレンド ファイルには、サウンド ストリップの dB 値とその他のサウンド パラメータを計算する Python スクリプトが含まれています。コードは Snuq's VSEQF-addon (vu_meter.py) に基づいています。 例として、Blender のオープン ムービー Spring を使用します。
まず、選択したオーディオ ストリップとそれに含まれるサウンド データへのハンドルが必要です。ストリップのプロパティ (ボリュームなど) はアニメーション化できるため、現在のシーンおよびビュー レイヤの依存関係グラフで継続的に更新されるアニメーション化されたデータ ブロックにアクセスする必要があります。
import bpy
import math
strip = bpy.context.scene.sequence_editor.active_strip
depsgraph = bpy.context.evaluated_depsgraph_get()
sound = strip.sound.evaluated_get(depsgraph).factory
2.2.4.1.2.1. ストリップ全体のdB値¶
対策はかなりあります。最もよく使用されるのは次のとおりです。
ピーク値: ストリップ内の最高/最低 dB 値。これはボリュームメーター(VUメーター)で表示されるものです。
RMS (二乗平均平方根): ストリップ全体の平均 dB。
まず、サンプリングされたデータにアクセスする必要があります。これは 2 次元の numpy float 配列です。最初の次元はサンプル数です。 2 番目はチャンネル数です (モノラルの場合は 1、ステレオの場合は 2)。値は 0 ~ 1 の浮動小数点数です。
max = sound.data().max()
min = sound.data().min()
if abs(max) > abs(min):
peak = abs(max)
else:
peak = abs(min)
db = 20 * math.log10(peak)
Sound オブジェクトの data() メソッドは、ストリップがトリミングされ、ボリューム レベルが適用されていない場合でも、ストリップ全体のサンプリングされたデータを返します。言い換えれば、これらはサンプリングされた生のデータです。ただし、サウンドのピーク値は、サウンドの大きさの適切な推定値ではない場合があります。ある種の平均値が必要です。音波には正と負のサンプル値が含まれているため、単純な平均ではほぼゼロレベルに打ち消されます。 RMS 値が解決策です。各サンプル値は 2 乗され (負の数が削除され)、これらの 2 乗の平均 (平均) が計算され、平方根で元のレベルに再び減らされます。
サンプリングされたデータは numpy 配列に含まれているため、コードは比較的単純です。
samples = sound.data()
m = np.mean(samples**2)
rms = np.sqrt(m)
db = 20 * math.log10(rms)
2.2.4.1.2.2. 現在のフレームまたはストリップのセクションの dB 値¶
タイムラインのPlayhead/カーソルの下にあるサウンド サンプルの dB 値が必要だとします。ただし、オーディオ ストリップはフレームではなくタイムコードで機能します。これが多くの誤解の原因となっています。たとえば、MP4 ファイル (ビデオ + オーディオ) があり、シーンのフレーム/秒パラメータを変更すると、ビデオの長さは変わりますが、オーディオ ストリップの長さは変わりません。オーディオ ストリップの長さは、サンプル レートとサンプル数に基づいて計算できます。
sample_rate = sound.specs[0]
nr_of_samples = sound.length
duration = nr_of_samples/sample_rate
sound.length が len(sound.data()) に等しいと期待するかもしれませんが、どうやらそうではありません。オープン ムービー スプリングの場合: sound.length = 20466685 および len(sound.data() = 20466688)。
サンプル数はフレームよりもはるかに多いため、各フレームのサウンド データには複数のサンプルが含まれます。
nr_of_frames = strip.frame_final_end
nr_of_samples_per_frame = nr_of_samples/nr_of_frames
公開中のムービー "Spring" の場合: duration = 464.1s、nr_of_frames = 11139、nr_of_sample_per_frame = 1837.38 です。 したがって、各フレームには 1837 個のサンプルがあります。 Playhead の下の dB 値を計算するには、これらの 1837 サンプルのピーク値または rms 値が必要です。
これらのサンプルには、limit メソッドを使用してアクセスできます。この方法はタイムコードで機能します。したがって、現在のフレームの開始タイムコードと終了タイムコードが必要になります。これらの開始時間と終了時間は、選択したストリップを基準としています。選択したストリップの最初のフレームはゼロです。ただし、現在のフレームはタイムラインを基準としています。したがって、現在のフレームからstrip.frame_startを減算する必要があります。 db または RMS 値の計算は上記と同じです。
cur_frame = bpy.context.scene.frame_current
fps = bpy.context.scene.render.fps / bpy.context.scene.render.fps_base
time_from = (cur_frame - 1 - strip.frame_start) / fps
time_to = (cur_frame - strip.frame_start) / fps
sound_cur_frame = sound.limit(time_from, time_to)
2.2.4.1.2.3. トリミングされたストリップのデシベル値¶
Sound オブジェクトの Data メソッドは常にストリップ全体のすべてのサンプルを返すため、トリミングされたストリップに対しては、limit メソッド (上記のセクション 2.2 を参照) も使用する必要があります。ストリップの [トリミングとカット](../video/trimming.md) については、別の投稿で詳しく説明します。図 3 は、さまざまなアンカー ポイントをまとめたものです。
time_from = (strip.frame_final_start - strip.frame_start)/fps
time_to = (strip.frame_final_end - strip.frame_start)/fps
sound_trimmed_strip = sound.limit(time_from, time_to)
2.2.4.1.2.4. アニメーションストリップのデシベル値¶
サウンド データには、生のサンプリング データが含まれています。ストリップ全体のボリューム レベルの変更を考慮するには、このレベルと生データを単純に乗算します。
max = sound.data().max() * strip.volume
ただし、ストリップのボリューム レベルは、フレームごとに変更したりアニメーション化したりできます。セクション 2.2 では、1 フレームの dB 値を計算しました。依存関係グラフのおかげで、strip.volume 値がその特定のフレームに対して更新されるため、この値を使用できます。したがって、アニメーション ストリップ全体の dB 値を計算するには、ストリップをフレームごとにループし、Playheadをそのフレームに設定し、そのフレームの sound.data を取得するのが最も簡単です (ただし、最も効率的ではないかもしれません)。 、それに音量を乗算し、これらのデータを配列に累積します。
def get_rms(samples):
m = np.mean(samples**2)
rms = np.sqrt(m)
return 20 * math.log10(rms)
animated_samples = np.empty(shape=[0, 1])
for f in range (strip.frame_final_start, strip.frame_final_end):
bpy.context.scene.frame_set(f)
time_from = (f - strip.frame_start - 1)/fps
time_to = (f - strip.frame_start)/fps
chunk = sound.limit(time_from, time_to).data()
chunk = chunk * strip.volume
animated_samples = np.append(animated_samples, chunk, axis=0)
print("frame ", bpy.context.scene.frame_current,"time:", time_from, "-", time_to, "-",
"size:", len(chunk), "cum", len(animated_samples),
"RMS:", get_rms(chunk), "volume:" ,strip.volume)
print("total rms: ", get_rms(animated_samples))