粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

Python/Librosaを使ってAmazonで売ってるキーボード静音化リング(O-ring)の効果を検証してみた。

概要

  • カニカルキーボードの打鍵音を和らげたい
  • 1000円くらいのシリコン製のリングをキーボードにつけて静音化
  • どうせなのでPythonの音声処理ライブラリを使って効果を見てみる

使った機材など

  • キーボード:Keychron K2
  • 静音化リング
  • 撮影・音収集:Pixel 4

サイズ Cherry MX軸対応 静音化リング MXORDP

サイズ Cherry MX軸対応 静音化リング MXORDP

  • 発売日: 2014/09/02
  • メディア: Personal Computers

こんなふうに使います。

f:id:ossyaritoori:20200924232516p:plain
自分でも撮ったけどAmazonの商品紹介の写真が一番わかり易い

キーボード布教はこっち ossyaritoori.hatenablog.com




カニカルキーボードの静音化

カニカルキーボードはよくある他の方式のものと違って音が大きめに出る事が多いです。 後付でキーボードの打鍵音を改善するにはざっくりと3つの手法があるようです。

hbish.com

  1. 静音化リングをつける (所要10分)
  2. 潤滑油をさす (所要60分)
  3. 底に詰め物をする (要分解・所要120分)

2と3はきちんとした知識と装備を持って臨むべきですが初心者でも1の静音化リングの取り付けは直ぐにできます。

キートップを外して,裏の突起部分にリングをはめるだけです。

f:id:ossyaritoori:20200924233710p:plain
キートップを外した跡。この軸の色ごとに推し心地が違っており,「赤軸」などのように呼ばれます。

音がどんな感じに変わるのか

理論上,キートップが下の面に当たる底打ち音というのが低減されるらしいですがわからないのでスマホでShiftキー付近を押したときの音を録音して比較してみました。

f:id:ossyaritoori:20200924235405p:plain
うぉぉ!

リングなし(通常)

リング有り(施工後)

こうして聞くとカチャカチャとした甲高い音が減衰しているのがわかります。

ですがどの程度というのはよくわかりませんね。ということで評価パートに行きます。



Python/librosa を用いて定量的に評価

LibrosaというPythonのライブラリがあるので簡単に音声の解析ができそうです。

jorublog.site

使うライブラリは以下の通りです。

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

のように変換します。

音声を取得して波形を見る

まずは波形を見ます。打撃音なのでなんとなくインパルス応答のような音になっているのがわかります。

なお,左右で微妙に音のタイミングがずれているので音速との関係からうまくやれば距離を測ることができそうです。

f:id:ossyaritoori:20200925000203p:plain
左:音全体の波形,右:一つの打鍵音にズームインした波形

コード

#音声ファイル読み込み 静音化なしの場合
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() 

音波形を比較する

音の波形で比較してみましょう。 ただし,ここで問題になるのが音を鳴らしても必ずしも同じタイミングになるとは限らない点です。

f:id:ossyaritoori:20200925002302p:plain
一応これでも波形がおとなしめになった事がわかるといえばそうなのですが…

音の発生位置を特定

音の発生位置はOnsetと呼ばれるのでOnset detectionという問題になるようです。

musicinformationretrieval.com

音の開始地点(オンセット)の検出 - 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)

これで

発生位置をあわせて比較

最初の波の立ち上がり部分で比較をします。 波形を見てもこの通り,差があることがわかります。

f:id:ossyaritoori:20200925004726p:plain
最初の波の立ち上がりを重ね合わせた結果がこれです。

コード

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にて書いてみました。

余談ですがmatlabFFT関数はかなり使いづらかった記憶がありますが今はもっと良くなったのでしょうか。

コード

##### 周波数成分を表示する #####
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付近の音が大きく減衰していることがわかります。撮ったときの距離が若干ことなる可能性はありますが他の波形との関係性から概ね静音化に成功したと見て良さそうです。

f:id:ossyaritoori:20200925002359p:plain
青:Before,黄色:After

まとめ

カニカルキーボード,買おう。そして静音化しよう。

HHKBやNizは初手で買うにはちょっと高いのでRK61あたりから始めてもいいのかなと思います。

音声処理に興味を持った方は音声処理x本ノックを見てみてもいいかも。

github.com

おまけメモ空間

  • 最初音を区切るのに包絡線を用いようとしていた。
    • ヒルベルト変換を用いた包絡線抽出はScipyで簡単にできる。
    • かなりきれいにとれるが,ここからひとまとまりの音として区切りを推定するのに困った。(TODO)
# 1.包絡線をヒルベルト変換から得る
# https://org-technology.com/posts/Hilbert-transform.html

hdata1=scipy.signal.hilbert(data1[:,0])
hdata2=scipy.signal.hilbert(data2[:,0])

f:id:ossyaritoori:20200925003012p:plain

  • Spectrometerでの比較
    • よくみる図としてSpectrometerがある。
    • カラーマップとスケールを合わせればこの図でも比較できそう
    • 超余談だがこの画像をCNNに打ち込んでどうこうしようという系の論文はあまり好きではないです

f:id:ossyaritoori:20200925003829p:plain
こういう図よく見ますよね。

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