🌊

プログラミングで音を描いてみよう

2024/12/04に公開

この記事では、プログラミングはそこそこできるが音のデータになじみがない方に向けて、短いPythonスクリプトで音を作ってみる方法を紹介します!これを読んでもDTMができるようになったりするわけではありません。でも楽しいと思います。

音は波です。デジタル化された波は、ようするに配列です。つまり、自分で適当な配列を作れば、オリジナルの音を作ることができます。

サンプルスクリプトはこちらに置いたので、いろいろパラメータを変えて遊んでみてください。

https://github.com/thousanda/draw-wave

さっそくですが波形です。今回は以下のような正弦波、いわゆる三角関数の sin (サイン) / cos (コサイン) で表現できる波を扱ってみます。

音源はZennに貼ることができなかったので、実際にこのスクリプトを実行してできあがったものを再生してみてください。

スクリプト全体像

正弦波の音データを作成するスクリプトは draw-wave/sine/main-wav.py にあります。

import numpy as np
from scipy.io.wavfile import write

# サンプリングレート
sample_rate = 48000  # 48kHz

# 周波数(440Hz = A音)
frequency = 440

# 音の長さ(秒)
duration = 2  # 2秒

# 時間軸データ
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)

# 正弦波を生成
amplitude = 32767  # 16ビットPCMの最大値
wave_data = (amplitude * np.sin(2 * np.pi * frequency * t)).astype(np.int16)

# WAVファイルに書き出し
write("output/440Hz_A.wav", sample_rate, wave_data)

print("WAVファイルが生成されました!")

スクリプトの部分解説

import

まずはimport。numpyという有名な数値計算のライブラリを使います。使わなくてもできますが、ちょっとシンプルに書けて、処理も速いので使います。scipyはWAVオーディオデータの書き出しのためだけに使います。

import numpy as np
from scipy.io.wavfile import write

サンプリングレート

次にサンプリングレート。これは1秒間をいくつのサンプルで表現するかを表す値です。つまり、48000 Hzに設定すると、配列の要素48000個が1秒間を表現します。

# サンプリングレート
sample_rate = 48000  # 48kHz

作る音の音程

お次は生成する音の音程です。「ラ」の音の周波数が440 Hzです。この値を大きくするとより高い音に、小さくするとより低い音に変わります。単位に同じHzを使っていますが、サンプリングレートは音をデジタル化するときに使うパラメータで、こちらは音程なのでアナログ/デジタル関係ない音自体の特性です。

# 周波数(440Hz = A音)
frequency = 440

作る音の長さ

2秒間の音を作ってみます。さきほど、サンプリングレートを1秒あたり48000サンプルとしました。つまり、2秒間で96000サンプルになります。len(音の配列) = 96000 ということです。

# 音の長さ(秒)
duration = 2  # 1秒

時間軸データ

ここが一番難しいと思います。音のサンプルをとる時刻の配列を作っています。

# 時間軸データ
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)

話を簡単にするために、サンプリングレートを5 Hz、つまり1秒間に5サンプル用意することにして、2秒間の音源を作るとします。そうすると、以下のようなデータを用意していることになります。

t = [0.0 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8]

正弦波を生成

では実際に正弦波を作ってみましょう。amplitudeは振幅、音の大きさです。1つのサンプルを16 bitで表現する場合、-32768から32767までの値を使えるので、限界ぎりぎりにしています。

# 正弦波を生成
amplitude = 32767  # 16ビットPCMの最大値
wave_data = (amplitude * np.sin(2 * np.pi * frequency * t)).astype(np.int16)

そして最後に波形を生成します。高校数学の弧度法を覚えていますでしょうか。角度の表現方法のひとつで、2π = 360° と表すものです。 sin(2π * t) とすると、tが0から1まで進む間が1周期となります。

WAVファイルに書き出し

できた配列を、scipyを使って音データとしてファイルに書き出します。./output/440Hz_A.wav というパスに作成されます。output/ ディレクトリは事前に作っておいてください。

# WAVファイルに書き出し
write("output/440Hz_A.wav", sample_rate, wave_data)

print("WAVファイルが生成されました!")

このあとの遊び方

まずは、音程を変えたり、音の長さを変えたりしてみて、生成される音が変わるのを確認してみてください。音程の変化を感じるだけでも楽しいです。どれくらいの音程まで聞こえるか試してみたりできます。

注意: 高音や低音はけっこうな音量にしても聞こえづらいので、音量を上げすぎてしまいがちです。耳を傷めないように音量には注意してください。

次はサンプリングレートを変えてみましょう。48000 Hzは十分に大きいので、これより大きくしても聞こえ方は変わらないはずです。ハイレゾ音源は96000 Hzだったりするので、全く何も変わらないとまでは言い切れませんが、こと純粋な正弦波においては変化は感じ取れないと僕は思います。

逆にサンプリングレートを下げていくと、だんだん音が劣化していくのを感じられると思います。そして、あるラインを境に別の音程になってしまいます。これについて興味を持ったら、「サンプリング定理」、「ナイキスト周波数」、「エイリアシング」といった言葉を調べてみてください。この記事の続編としてこれを体験できる記事も書こうと思っています。

また、正弦波ではない波も作ることができます。全然違う配列を作れば、全然違う音が鳴ります。以下のような形の波は三角波といいます。

三角波を生成するスクリプトは draw-wave/triangle/ に置いてあります。使い方は正弦波用のものと同じです。

正弦波は丸みのある音がしますが、それと比べると見た目の通り、三角波はすこし尖った音がします。

おもしろいですね。

Discussion