Python/Librosaを使ってAmazonで売ってるキーボード静音化リング(O-ring)の効果を検証してみた。
概要
使った機材など
- キーボード:Keychron K2
- 静音化リング
- 撮影・音収集:Pixel 4
サイズ Cherry MX軸対応 静音化リング MXORDP
- 発売日: 2014/09/02
- メディア: Personal Computers
こんなふうに使います。
キーボード布教はこっち ossyaritoori.hatenablog.com
メカニカルキーボードの静音化
メカニカルキーボードはよくある他の方式のものと違って音が大きめに出る事が多いです。 後付でキーボードの打鍵音を改善するにはざっくりと3つの手法があるようです。
- 静音化リングをつける (所要10分)
- 潤滑油をさす (所要60分)
- 底に詰め物をする (要分解・所要120分)
2と3はきちんとした知識と装備を持って臨むべきですが初心者でも1の静音化リングの取り付けは直ぐにできます。
キートップを外して,裏の突起部分にリングをはめるだけです。
音がどんな感じに変わるのか
理論上,キートップが下の面に当たる底打ち音というのが低減されるらしいですがわからないのでスマホでShiftキー付近を押したときの音を録音して比較してみました。
リングなし(通常)
リング有り(施工後)
こうして聞くとカチャカチャとした甲高い音が減衰しているのがわかります。
ですがどの程度というのはよくわかりませんね。ということで評価パートに行きます。
Python/librosa を用いて定量的に評価
LibrosaというPythonのライブラリがあるので簡単に音声の解析ができそうです。
使うライブラリは以下の通りです。
import sys import scipy.io.wavfile import numpy as np import matplotlib.pyplot as plt import librosa import librosa.display
mp4からwavファイルの抽出
どうもmp4をそのまま読ませるよりもwavを読ませるといいみたいです。
幸いffmpegをインストールしていたので
ffmpeg -i hoge.mp4 -ac 2 -ar 44100 -vn hoge_audio.wav
のように変換します。
音声を取得して波形を見る
まずは波形を見ます。打撃音なのでなんとなくインパルス応答のような音になっているのがわかります。
なお,左右で微妙に音のタイミングがずれているので音速との関係からうまくやれば距離を測ることができそうです。
コード
#音声ファイル読み込み 静音化なしの場合 wav_filename = "videos/shift_noring_audio.wav" rate1, data1 = scipy.io.wavfile.read(wav_filename) #縦軸(振幅)の配列を作成 #16bitの音声ファイルのデータを-1から1に正規化 data1 = data1 / 32768 #横軸(時間)の配列を作成 #np.arange(初項, 等差数列の終点, 等差) time1 = np.arange(0, data1.shape[0]/rate1, 1/rate1) #データプロット plt.figure(figsize=(12,4)) plt.subplot(1,2,1) plt.plot(time1, data1[:, 0], label="Left channel") plt.plot(time1, data1[:, 1], label="Right channel") plt.legend() plt.grid() # 拡大 Plot plt.subplot(1,2,2) plt.plot(time1, data1[:, 0], label="Left channel") plt.plot(time1, data1[:, 1], label="Right channel") plt.legend() plt.grid() plt.xlim(39000/rate1, 40000/rate1) plt.show()
音波形を比較する
音の波形で比較してみましょう。 ただし,ここで問題になるのが音を鳴らしても必ずしも同じタイミングになるとは限らない点です。
音の発生位置を特定
音の発生位置はOnsetと呼ばれるのでOnset detectionという問題になるようです。
音の開始地点(オンセット)の検出 - KIEFERworld -音楽情報処理研究所-
理論的な話は抜きにするとLibrosaではほぼ一行でこれを実行することが出来ます。
# Onset Frameの推定 onset_frames = librosa.onset.onset_detect(data1[:,0],rate1, wait=1, pre_avg=1, post_avg=1, pre_max=1, post_max=1) print(onset_frames) # 時間への変換 onset_times = librosa.frames_to_time(onset_frames) print(onset_times) # サンプルへの変換(こっちをよく使うかも) onset_samples =librosa.frames_to_samples(onset_frames) print(onset_samples)
これで
発生位置をあわせて比較
最初の波の立ち上がり部分で比較をします。 波形を見てもこの通り,差があることがわかります。
コード
onset1=librosa.frames_to_samples(librosa.onset.onset_detect(data1[:,0],rate1, wait=1, pre_avg=1, post_avg=1, pre_max=1, post_max=1)) onset2=librosa.frames_to_samples(librosa.onset.onset_detect(data2[:,0],rate1, wait=1, pre_avg=1, post_avg=1, pre_max=1, post_max=1)) winlen = 800 #figure plt.figure() plt.plot(data1[onset1[0]:onset1[0]+winlen,0],label='w/o ring') plt.plot(data2[onset2[0]:onset2[0]+winlen,0],label='w ring') plt.legend() plt.grid()
周波数領域(FFT)で比較
先程までは時間領域で見てきましたが,FFTで周波数領域を見るのも鉄板でしょう。 Librosaに関数があるはずですが,ひとまずScipyにて書いてみました。
余談ですがmatlabのFFT関数はかなり使いづらかった記憶がありますが今はもっと良くなったのでしょうか。
コード
##### 周波数成分を表示する ##### def showfft(data,rate): #縦軸:dataを高速フーリエ変換する(時間領域から周波数領域に変換する) fft_data = np.abs(np.fft.fft(data)) #横軸:周波数の取得 #np.fft.fftfreq(データ点数, サンプリング周期) freqList = np.fft.fftfreq(data.shape[0], d=1.0/rate) #データプロット plt.plot(freqList, fft_data) plt.xlim(0, 8000) #0~8000Hzまで表示 plt.show() return [freqList, fft_data] # 図示 plt.figure() plt.plot(freqList1,fft_data1,alpha = 1,label="w/o ring") plt.plot(freqList2,fft_data2,alpha = 0.5,label="w/ ring") plt.xlim(0, 8000) #0~8000Hzまで表示 plt.xlabel("Freq [Hz]") plt.ylabel("Magnitude") plt.legend() plt.grid()
以下の図の通り,2000Hz付近の音が大きく減衰していることがわかります。撮ったときの距離が若干ことなる可能性はありますが他の波形との関係性から概ね静音化に成功したと見て良さそうです。
まとめ
メカニカルキーボード,買おう。そして静音化しよう。
HHKBやNizは初手で買うにはちょっと高いのでRK61あたりから始めてもいいのかなと思います。
音声処理に興味を持った方は音声処理x本ノックを見てみてもいいかも。
おまけメモ空間
- 最初音を区切るのに包絡線を用いようとしていた。
- ヒルベルト変換を用いた包絡線抽出はScipyで簡単にできる。
- かなりきれいにとれるが,ここからひとまとまりの音として区切りを推定するのに困った。(TODO)
# 1.包絡線をヒルベルト変換から得る # https://org-technology.com/posts/Hilbert-transform.html hdata1=scipy.signal.hilbert(data1[:,0]) hdata2=scipy.signal.hilbert(data2[:,0])
- Spectrometerでの比較
- よくみる図としてSpectrometerがある。
- カラーマップとスケールを合わせればこの図でも比較できそう
- 超余談だがこの画像をCNNに打ち込んでどうこうしようという系の論文はあまり好きではないです
Spectrometer図示コード
def showspectro(data,rate): # フレーム長 fft_size = 1024 # フレームシフト長 hop_length = int(fft_size / 4) # 短時間フーリエ変換実行 amplitude = np.abs(librosa.core.stft(data, n_fft=fft_size, hop_length=hop_length)) # 振幅をデシベル単位に変換 log_power = librosa.core.amplitude_to_db(amplitude) # グラフ表示 librosa.display.specshow(log_power, sr=rate, hop_length=hop_length, x_axis='time', y_axis='hz', cmap='magma') plt.colorbar(format='%+2.0f dB') plt.show()