全体像
このページを一通り読んだら, waveファイル から1秒毎の \(L_{eq}\) をCSV形式で出力できるようになる。
おまけで,波形をグラフとして表示できるようになる。
まえがき
<- クリックして展開
誤解を恐れずに言うなら,プログラミングするということは,
| ( Input ) | : なにか入力して, |
| ( Function ) | : それを処理して, |
| ( Output ) | : それを出力する。 |
これしかない。なにも難しいことはない。
初学者が一番最初に習うプログラムとしてHello Worldがある。
print('Hello World!!')
| ( In ) | : 自分が打ち込んだ文字列が, |
| ( Fn ) | : print関数を介して, |
| ( Out ) | : コンソールに出力される。 |
これだけで立派なプログラムだ。
次に,計算させてみる。
print(1 + 2)
当然3が出力される。
でも,これだと現実の物事はなにも解決してくれない。データはなにも変わらない。
テキストファイルや音声,動画ファイルがあって,
人間がやっていた作業を代わりに命令して自動化できる。
これが,本来プログラミングする目的であって,モチベーションにつながる。
準備
なにはともあれ,まずは環境構築。
Python
- ダウンロードページに進んで,"Windows x86-64 executable installer"をダウンロードする。
- 環境変数を通すようにチェックして,ダイアログにしたがってインストールする。
column どうしてPython?
大方のコンピュータはC言語で動いている。それが基準だから。やっぱり,はじめては偉い。
もっというと,Cの前にアセンブリ言語があって,プロセッサが処理するために機械語になる。
でもそんなの気にしなくていい。裏で勝手にやってくれる。
Python(Cython)はC言語の代わりに書いたコードをC言語に変換してくれる。
あとは同じ。
じゃあなんでPythonを使うのか。
書きやくなるように工夫されていて,
使用者が多いから情報が集めやすい。
それだけ。
Visual Studio Code
- 作業フォルダを作る。
- Pythonのパスを通す。
- 拡張機能のインストール。
シンタックスハイライトからエラーチェック,
ライブラリが入ってないと警告してくれたり,とにかく便利。
2019/05/23時点最新版のPython3.7.3とVS Code1.34.0 zip版を使用する。
以下テストコード。これが動いたら今日から君もPythonist。
"好きなファイル名.py"にして保存,F5で実行。
calc.py
print("Hello World!!")
print(1 + 1) # 四則演算
print(2 - 1)
print(2 * 3)
print(4 / 3)
print(8 % 3)
import numpy # import: "このライブラリを使うよ"の宣言
arrFoo = numpy.asarray([1, 2, 3])
print(arrFoo)
import numpy as np # as: "この略で呼べるよ"の宣言
arrFoo = numpy.asarray([4, 5, 6])
arrBar = np.asarray([7, 8, 9])
print(arrFoo)
print(arrFoo[0])
print(arrFoo[1])
print(arrBar)
初回はnumpyがないよって怒られるはず。
Ctrl + @ でコンソールに移動。
pip install "インストールしたいライブラリ名"
でライブラリ追加。
pipが古いとか言われたら,指示に従ってインストール。
実現したいこと
A特性補正済みのwaveファイルから1秒毎の\(L_{eq}\)が知りたい。
分解する
| ( In ) | : waveファイルを読み込む |
| ( Fn ) | : \(L_{eq}\)を計算する |
| ( Out ) | : CSVに出力する |
( In ) waveファイル
-
構造理解
音を記録するには?
A-D Convert アナログ-デジタル変換
PCM Palse Code Modulation パルス符号変調
2つのパラメータ
- sampling rate サンプリング周波数[Hz]
1秒間に何回書き込むか。
- quantization bit rate 量子化ビット数 (encoding bit rate 符号化ビット数)
どのくらいの度数に分けるか?
- sampling rate サンプリング周波数[Hz]
-
取り込み方法
Python nativeでいけるか,外部ライブラリはどれが使いやすそうか。
標準ライブラリのwaveは拡張性がなさそう。
モノラル/ステレオ以上の対応は書いてない.4chとかのアンビソニックス音源は読めない?
読み出しても格納が面倒そう.24bit96kHzとかややこしげ。
ライブラリはPySoundFileが良さげ。
簡単,これに尽きる。
PySoundFileの使い方,matplotlibでグラフ表示。
plotWave.py
import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf
# 各自書き換える
path = 'D:/documents/labo/pythonLeq/16bit1kHzMono.wav'
info = sf.SoundFile(path) # ファイル情報の確認
print('samplerate: ', info.samplerate)
print('channels: ', info.channels)
print('subtype: ', info.subtype)
print('format: ', info.format)
# データの読み込み
data, samplerate = sf.read(path) # data: 振幅(-1:1), samplerate: サンプリング周波数[Hz]
print(samplerate)
time = np.arange(0, len(data)) / samplerate # time: ファイルの長さ[s]
print('time: ', time)
print(time[-1]) # array[-1]: 最後の要素
fractionTime = len(data) % samplerate # 1秒未満の長さ
print('fractionTime: ', fractionTime)
plt.plot(time, data, label='wave')
plt.xlim(0, 0.003) # 表示する範囲
plt.ylim(-1, 1)
plt.xlabel('time(s)') # 軸ラベル
plt.ylabel('amplitude')
plt.show()
plt.close()
SPL sound plessure level, 音圧レベル[dB]を計算する
\[ \begin{eqnarray} L &=& 10\log\frac{{p_1}^2}{{p_0}^2} \\ &=& 10\log\left(\frac{p_1}{p_0}\right)^2 \\ &=& 20\log\frac{p_1}{p_0} \end{eqnarray} \]\(p_0\)は基準音圧[Pa], (20 µPaのとき, dB SPL という。)
\(p_1\)は瞬時値[Pa],
\(\log\) は \(\log_{10}\) : 常用対数を表す。
「ただし\(0\lt p_0\),\(0\lt p_1\)」あるいは絶対値をとる。
column 式の意味
[dB]=10[B]だから10倍してる。二乗がついてるのは分散と同じ考え方。
\(\sin\)よろしく-1~1の範囲をとる。
誰も書かなかったデシベルのお話し STUDIO OUR HOUSEいい読み物。
calcSPL.py
import math
p0 = 20 * 10 ** -6
p1 = 20 * 10 ** -6 * 50000
print( 10 * math.log10(p1 ** 2 / p0 ** 2) ) # 数式と同じ意味
print( 10 * math.log10( (p1 / p0) ** 2 ) )
print( 20 * math.log10(p1 / p0) )
def calc(p0, p1): # 繰り返し使えるように関数宣言 define
print(20 * math.log10(p1 / p0), 'dB')
print('twice')
calc(p0, 20 * 10 ** -6 * 2) # 2倍は?
print('10 times')
calc(p0, 20 * 10 ** -6 * 10) # 10倍は?
for i in range(1, 11): # 1から10倍
print(i, 'time(s)')
calc(p0, 20 * 10 ** -6 * i)
dBの感覚
feeling.py
p0 = 20 * 10 ** -6 # = 0.00002 = 20 * 10 ^ -6
print('decimal point')
calc(p0, 20)
calc(p0, 2)
calc(p0, .2)
calc(p0, .02)
calc(p0, .002)
calc(p0, .0002)
print('bug?')
calc(p0, .00002)
print('use "for statement"')
for i in range(7):
calc(p0, 20 * 10 ** -i)
print('test:', 20 * 10 ** -5)
print('test:', 20 * 10 ** -6) # != .00002
p0 = .00002
calc(p0, .00002)
信用できないPython native
明示的な型宣言がないと,こういう弊害がある。
挙動をみると8bitの型が暗黙で動いてるっぽい。
あふれたからもっと大きい型にいれて,そのときにはもう桁落ちしてる。
きっとどこかに書いてあるんだろうけど,読むのめんどくさい。
リファレンスは困ってから読むもの。
今回の問題は"16bitが何dBか"ということ。
グラフを交えて実際に計算してみる。
graph.py
import math
import matplotlib.pyplot as plt
quantity = 2 ** 16
print((20 * math.log10(quantity / 1)))
x = []
y = []
for i in range(1, quantity):
x.append(i)
y.append(20 * math.log10(i / 1))
plt.plot(x, y)
# plt.xscale('log')
plt.grid(which='major', color='black', linestyle='-')
plt.xlabel('quantization bit rate')
plt.ylabel('SPL(dB)')
plt.show()
plt.close()
16bitが96dBとすると,これを超える音を録音できなくなる。
なので,\(p_0\)を"通常 20 μPa"ではなく可変にして,dB SPLに合わせる。
このとき, Calibration Signal 校正信号 から\(p_0\)を求める。
校正信号を基準にdB SPLを計算する。
dBを入力してもらって \(RMS\) から,
\(p_0=EU\): Engineering Unitとする。
\[L_{cal}=20\log_{10}\frac{p_{cal}}{EU}\] \[L_{cal}/20=\log_{10}\frac{p_{cal}}{EU}\]底の変換
\[3=\log_28\] \[8=2^3\]leqSimu.py
import math pCal = 0.5 # 校正信号のRMS値(計算結果) lCal = 94 # 想定する校正信号の強度 eu = pCal / 10 ** (lCal / 20) print(eu) wavRms = 0.1 # 入力信号のRMS値(計算結果) leq = 20 * math.log10( wavRms / eu ) print(leq)
何bit必要?
音の単位「dB(デシベル)」ってなに? Healthy Hearing180dB?
第43回宇宙産業・科学技術基盤部会ロケットでもせいぜい150dBらしいけど,
オトナの中学理科講座【物理】人類の観測史上最大の『音』は? IPROS製造業雑学。
地球を3周した「世界で最も大きな音」とは? Gigazine194dBが上限みたい。
dB SPLから逆算してみる。
X=194[dB]として
\[L=10\log_{10}X^2\] \[L=20\log_{10}X\] \[\frac{L}{20}=\log_{10}X\] \[X=10^\frac{L}{20}\]\(X\)が2の何乗かがわかればいいから,
\[\log_2X=\log_210^\frac{L}{20}\]import math print(math.log2(10 ** (194 / 20))) # 音の限界
( Fn ) \(L_{eq}\)
音圧レベルの時間当たりの平均値。
わかるひとには,交流の利得の音バージョンっていえばわかる。
- 計算方法
\[L_{eq}=10\log\left[\frac{1}{t_1-t_0}
\int_{t_0}^{t_1}\frac{{p_1}^2}{{p_0}^2}dt\right]\]
\(p_0\) : 基準音圧レベル \(p_1\) : 収集した音圧 \(t_0\) : 測定の開始時間 \(t_1\) : 測定の終了時間 参考
Leq音圧レベルとは何ですか? NATIONAL INSTRUMENTS
\[ L_{eq}=\class{mathbg}{10\log}\left[\frac{1}{t_1-t_0} \int_{t_0}^{t_1}\class{mathbg}{\frac{{p_1}^2}{{p_0}^2}}dt\right] \]
さっきの音圧レベル[dB]を,その時間分全部足してあげて,
\[ L_{eq}=10\log\left[\frac{1}{t_1-t_0} \class{mathbg}{\int_{t_0}^{t_1}}\frac{{p_1}^2}{{p_0}^2}\class{mathbg}{dt}\right] \]計測時間で割る。
\[ L_{eq}=10\log\left[\class{mathbg}{\frac{1}{t_1-t_0}} \int_{t_0}^{t_1}\frac{{p_1}^2}{{p_0}^2}dt\right] \]
\(RMS\): Root Mean Square value 二乗平均平方根
= effective value 実効値
\[RMS[x]=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(x_i)^2}\]testRMS.py
import math def rms(array): ans = 0 for i in range(len(array)): ans += array[i] ** 2 ans = 1 / len(array) * ans ans = math.sqrt(ans) return ans x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print('calc:', rms(x)) y = [1, 2, 3, -4, 5, 6, 7, 8, -9, 10] print('calc;', rms(y)) # マイナスがついてもokファイルを取り込んで計算してみる。
amplitude.py
import numpy as np import soundfile as sf path = 'D:/documents/labo/pythonLeq/16bit1kHzMono.wav' data, samplerate = sf.read(path) time = np.arange(0, len(data)) / samplerate # RMSの計算 # sqrt: 平方根 # mean: 平均 # [a: b]: aからbまで,ここでは1秒間 rms1st = np.sqrt(np.mean(data[0: info.samplerate] ** 2)) rms2nd = np.sqrt(np.mean(data[info.samplerate: info.samplerate * 2] ** 2)) rms3rd = np.sqrt(np.mean(data[info.samplerate * 2: info.samplerate * 3] ** 2)) print(rms1st, rms2nd, rms3rd)
少し癖のあるnumpyの範囲指定
setRange.py
import math import numpy as np x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # print(x[0:(len(x) - 1)] ** 2) これはエラー y = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print(y, y ** 2) z = np.array(x) print(z, z[3:7] ** 2) print(z, z[0:9] ** 2) print(z, z[1:10] ** 2) print(z, z[0:10] ** 2) print(z, z[0:-1] ** 2) # 配列最後尾指定の常套句 -1 は使えない print(z, z[0:len(z)] ** 2) # 0番目から len(z) - 1 番目まで
使えないnative mean
uselessMean.py
import math from statistics import mean import numpy as np x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print(math.sqrt(mean(x[0:len(x)] ** 2))) print(np.sqrt(np.mean(x[0:len(x)] ** 2))) print(sum(x[0:len(x)] ** 2)) print(mean(x[0:len(x)] ** 2)) # 見事に小数点切り捨て print(np.mean(x[0:len(x)] ** 2))
参考
「収録した騒音計やマイクロホンからの音データをオースコープベーシックを使って音圧レベルとして表示する方法」 小野測器 - DR-7100 FAQ
( Out ) CSV
tips
Character Separated Values
文字列を文字で区切って列にして,改行で行にする。
い,ろ,は\n
に,ほ,へ\n
と,ち,り\n
ぬ,る,を\n
\n は改行
ルールのあるテキストファイル,文字だけのExcelって感じ。
","を使うからcomma-separated valuesとも呼ばれる。というか9割はこれ。
一部 tab 区切りで.tsv とか space で区切ったりもするらしい。
列数を揃えなきゃいけないわけじゃなかったり,","をどう表現するかだったりの
細かい決まりはなくて,方言があるみたい。
.xml .json 等,行列でデータを表す形式はたくさんある。
行列形式のテキストファイル。
今回の出力でお世話になる。
outputCsv.py
import datetime
import math
import numpy as np
testArray = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# csv出力
filename = 'leq_' + datetime.datetime.today().strftime("%Y%m%dT%H%M%S") + '.csv'
np.savetxt(filename, testArray, delimiter=',', fmt='%d')
組み立てる
複数引数を取るためにArgumentParserを使用。
leq.py
import argparse
import datetime
import math
import numpy as np
import soundfile as sf
# 引数の設定
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('inputFile')
parser.add_argument('calibrationFile')
parser.add_argument('SPL', type=float)
args = parser.parse_args()
# ( In ) データの読み込み
data, samplerate = sf.read(args.inputFile)
calData, calSR = sf.read(args.calibrationFile)
time = np.array(np.arange(0, (len(data) / samplerate)), dtype='int32')
print('time: ', time[-1])
# ( Fn ) 計算
def rms(amp, sec):
return np.sqrt(np.mean(amp[samplerate*sec:samplerate*(sec+1)] ** 2))
rmsCal = rms(calData, 1)
leq = [20*math.log10(rms(data, i)/(rmsCal/10**(args.SPL/20))) for i in range(time[-1])]
# ( Out ) csv出力
filename = 'leq_' + datetime.datetime.today().strftime("%Y%m%dT%H%M%S") + '.csv'
np.savetxt(filename, leq, delimiter=',', fmt='%d')
あとがき
TODO
jupyter notebookとかgoogle colaboratoryにコードを移す
構成を考える。amplitude -> EU: rms, spl
csvはヘッダーと秒数くらいつける
複数ch対応
note Lmaxはnumpyのarray.max()とかですぐ出せる。