NumPyとSciPyでサイン波とドレミ

May 2, 2020

またまたDeep Learningではありませんが。

音のことを何も知らないので、この本を読んでいます。

https://www.kspub.co.jp/book/detail/1565296.html

ソースコードがない本なのですが、どうやら同じ著者による以下の本と内容が似ているらしく、こちらのソースコードが参考になります。

https://gihyo.jp/book/2013/978-4-7741-5522-7

今回は入門編ということで、Pythonでサイン波とドレミを鳴らしてみます。

Pythonの実装方法は以下のサイトを参考にさせていただきました。

http://www.non-fiction.jp/2015/08/17/sin_wave/

サイン波

まずはサイン波。

numpyとscipyが便利なので、とても短いコードで済みます。

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
import numpy as np
from scipy.io import wavfile

A = 1
fs = 44100
duration = 2
f0 = 500

t = np.arange(0, fs * duration) / fs
y = A * np.sin(2 * np.pi * f0 * t)

wavfile.write('output/sine.wav', fs, (y * 32767).astype(np.int16))

これで生成できます。

https://soundcloud.com/user-255261297-724174099/sine-no-fade

ただ、聞いていただくとわかるようにこの状態だと再生開始時と終了時にプツプツという雑音が入ります。

これはクリックノイズと呼ばれるもので、周波数の急激な変化が原因のようです。

先程のデータの冒頭をグラフにしてみると確かに急激に始まっていますね。

周波数を徐々に変化するようにしてみた音声がこちらです。

https://soundcloud.com/user-255261297-724174099/sine

コードはこのような形で実装。(開始・終了それぞれ20msを徐々に変化させている)

1
2
3
4
fade_ms = 20
fade_size = int(len(y) * fade_ms / (duration * 1000))
fade_filter = np.concatenate([np.linspace(0, 1, fade_size), np.ones(len(y) - fade_size * 2), np.linspace(1, 0, fade_size)])
faded = y * fade_filter

グラフでも、徐々に変化していることがわかります。

ドレミ

最後にサイン波を応用してドレミファソラシドを鳴らしてみます。

フェード無し

https://soundcloud.com/user-255261297-724174099/doremi-no-fade

単純なサイン波よりノイズが気になります。

フェード有り

https://soundcloud.com/user-255261297-724174099/doremi

ノイズがなくなりました。

グラフはすこし分かりづらいですが、フェード有りだと音高の切り替わりのときに隙間ができています。 (フェード処理により周波数が低くなるので)

フェード無し

フェード有り

ソースコードはこちらです。(フェード有りのもの)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import os
import numpy as np
from functools import reduce
from scipy.io import wavfile

A = 1
fs = 44100

def y(i, f0):
    duration = 0.4
    offset = i * duration
    t = np.arange(offset, offset + fs * duration) / fs
    y = A * np.sin(2 * np.pi * f0 * t)

    fade_ms = 20
    fade_size = int(len(y) * fade_ms / (duration * 1000))
    fade_filter = np.concatenate([np.linspace(0, 1, fade_size), np.ones(len(y) - fade_size * 2), np.linspace(1, 0, fade_size)])
    faded = y * fade_filter

    return faded

f0_list = [261.63, 293.66, 329.63, 349.23, 392.0, 440.0, 493.88, 524.25]
y = reduce(lambda x, f0: np.append(x, y(len(x), f0)), f0_list, [])

wavfile.write('output/doremi.wav', fs, (y * 32767).astype(np.int16))

こんな感じで音響プログラミングに入門してみました。