🎸

Pythonで楽器の音を可視化する

に公開

はじめに

データソリューション事業部の関田です。
今回は、ベースで「A(ラ)」の音を弾いて録音し、その音響データをPythonを使って様々な角度から可視化してみました。
音響信号処理の基礎である「波形」表示に加え、周波数解析を用いた「スペクトル」「メルスペクトログラム」の描画について、コードと実際の出力結果を交えて紹介します。

本記事はDAL Tech Blog Advent Calendar 2025として投稿しました。全ての記事は以下からご確認いただけます。

https://adventar.org/calendars/12288

音響処理の基礎知識

まずは、音をデジタルデータとして扱うための基本を簡単におさらいします。

音とは?

物理的には空気の振動です。振動数によって音の高さが決まり、振動数が大きいほど音が高くなります。
この振動をマイクで電気信号に変換し、さらにAD変換(アナログ-デジタル変換)を行うことで、コンピュータで扱える数値配列になります。

サンプリング周波数(Sample Rate: sr)

1秒間に何回音のデータを記録するかという指標です。単位はHz(ヘルツ)です。
例えば、CD音質なら44.1kHz(1秒間に44,100回)です。この数値が高いほど、高い周波数の音まで正確に記録できます。

音階と周波数

音階と周波数の関係について、少し掘り下げてみます。

基準周波数 A=440Hz

一般的にチューニングの基準となる「A4(ラ)」は 440Hz です。
音階が1オクターブ下がると、周波数は半分になります。つまり、「A4(ラ)」よりも1オクターブ低いA3の周波数は220Hz、3オクターブ低いA1の周波数は55Hzです。

ベースにおける「A」

ベースの3弦開放弦(A1)の基音は55Hzですが、実際にはその整数倍の周波数を持つ倍音が多く含まれています。 そのため、1つの音を弾いただけでも、スペクトル上では以下のような周波数の成分が観測されます。

  • 55Hz (A1): 基音(一番低い基本の音)
  • 110Hz (A2): 第2倍音(1オクターブ上のラ)
  • 220Hz (A3): 第4倍音(2オクターブ上のラ)

今回の可視化では、この「55Hz, 110Hz...」という倍音成分がどう見えるかに注目します。

補足:CDEとドレミの関係について

音楽に馴染みがない方向けに、アルファベット(音名)と一般的な「ドレミ」(階名)の対応表を載せておきます。

音名 C D E F G A B
階名 ド レ ミ ファ ソ ラ シ

実装コードと結果

それでは、実際のコードと出力結果を順に見ていきます。

⓪ 音響データ読み込み

librosa.load を使うと、WAVファイルの読み込み、モノラル変換、正規化(-1.0〜1.0)、サンプリングレートの取得を一括で行ってくれます。

y, sr = librosa.load("wavefile.wav", sr=None)

① 波形

時間ごとの振幅(音の大きさ)の変化を表します。
librosa.display.waveshow を使うと、時間軸の計算や描画の最適化を自動で行ってくれます。

plt.figure(figsize=(12, 4))
# librosaを使って波形を描画
librosa.display.waveshow(y, sr=sr, color="blue")
plt.title("Waveform")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.tight_layout()
plt.show()

▼ 出力結果

  • 0.8秒付近で弦が弾かれ、その後徐々に音が減衰していく様子が見て取れます。
  • 波形を見るだけでは「何の音程か」までは判別が難しいですが、音の立ち上がりや長さを確認するのに適しています。

② 周波数スペクトル

FFT(高速フーリエ変換)を行い、音響を周波数成分に分解します。「どの高さの音が、どれくらい含まれているか」がわかります。 ここには librosa ではなく、シンプルで高速な numpy のFFTを使います。

N = len(y)
# FFT計算(実数部分のみ)
fft_values = np.fft.rfft(y)
# 周波数軸の作成
freqs = np.fft.rfftfreq(N, d=1/sr)
# 振幅スペクトル(絶対値)
magnitude = np.abs(fft_values)

plt.figure(figsize=(12, 4))
plt.plot(freqs, magnitude)
plt.xscale("log") # 周波数は対数軸で表示
plt.xlabel("Frequency [Hz]")
plt.ylabel("Magnitude")
plt.title("Frequency Spectrum")
plt.grid(True, which="both", linestyle="--", alpha=0.7)
plt.xlim(10, 2000)
# ベース(A)の倍音ライン (55Hz, 110Hz, 220Hz...)
plt.vlines([55, 110, 220, 440, 880, 1760], 0, np.max(magnitude)*1.1, 
           linestyle="dashed", colors="gray", alpha=0.8)
plt.tight_layout()
plt.show()

▼ 出力結果

  • グレーの点線は左から 55Hz, 110Hz, 220Hz... を示しています。
  • グラフのいくつかのピークが、この点線と綺麗に重なっていることがわかります。これにより、この音が正確にチューニングされたA(ラ)の音であることが視覚的に確認できます。
  • 特に110Hz(第2倍音)の成分が強く出ているのが見て取れます。

③ メルスペクトログラム

スペクトルに「時間変化」の情報を加え(スペクトログラム)、さらに周波数軸を人間の聴覚特性に合わせた「メル尺度」に変換したものです。 librosa.feature.melspectrogram で計算し、librosa.display.specshow で描画します。

n_fft = 2048
hop_length = 512

# メルスペクトログラムの計算
S = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=n_fft, hop_length=hop_length)
# 振幅をデシベル(dB)に変換
S_db = librosa.power_to_db(S, ref=np.max)

plt.figure(figsize=(12, 5))
# y_axis="mel" とすることでメル尺度で表示
librosa.display.specshow(S_db, sr=sr, hop_length=hop_length, x_axis="time", y_axis="mel")
plt.colorbar(format="%+2.0f dB")
plt.title("Mel Spectrogram")
plt.tight_layout()
plt.show()

▼ 出力結果

  • 横軸が時間、縦軸が周波数(メル尺度)、色が強さを表します。
  • 人間の聴覚特性に合わせて、低周波数帯域(画像の下の方)が拡大されて表示されています。
  • 時間が経つにつれて高音成分(上の方)から消えていく、弦楽器の減衰特性がよく見えます。

おわりに

Librosaを使うと、非常に少ないコード量でリッチな可視化が可能になります。 今回はベースの「A」音を例に、以下の3つの視点で分析しました。

  • 波形: 音のアタック感や減衰といった「形状」を見る。
  • スペクトル: 55Hz, 110Hzといった「周波数成分」を見る。
  • メルスペクトログラム: 人間の耳に近い感覚で「時間変化と周波数成分」を同時に見る。

楽器の音作りや、音響解析の前処理としてぜひ活用してみてください!

DAL Tech Blog

Discussion