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))