Essaytial Machine Learning

機械学習は随想だ

サウンドプログラミング 音律編

 Python で音律を構成しよう。

概要

 前回記事の続き。音程の基礎と近代音楽のスタンダードになっている平均律と呼ばれる音律の構成法を解説する。

おさらい

 前回は単位波形、周波数、音の長さをパラメータとした単音データの生成法を述べ、単音データを結合したり足し合わせたりして音データを作れることを解説した。結合や足し合わせには辞書データを作ってループを回すとサウンドプログラミング感が出る。

import numpy as np

# 単音を生成する
def makewave(unit, f, T, rate):
    n = int(T*rate)
    x = f*np.linspace(0, T, n, endpoint=False)%1
    return unit(x)

# 波形関数
def unit_sin(x):
    return np.sin(2*np.pi*x)

unit = unit_sin
rate = 44100
dct1 = [{'unit': unit, 'f': 300, 'T': 2}, 
        {'unit': unit, 'f': 375, 'T': 2}, 
        {'unit': unit, 'f': 450, 'T': 2}]

wave1 = np.sum([makewave(**d, rate=rate) for d in dct1], axis=0)

 作成した音声データの聴覚的確認には Jupyter Notebook を使うといい。

# Jupyter Notebookに音声データを埋め込む
from IPython.display import Audio
Audio(wave1, rate=rate)

音程・音律

 音の高さの間隔を音程と呼び、曲中で使う音の高さを基準音との音程によって定めたものを音律と呼ぶ。

 音程は周波数の比で表せる。倍音だとかうなりだとかの関係で周波数が整数比(=有理数倍)の2音は協和音と呼ばれて心地良い音程となることになっているが、感覚的な話が交ざるので詳しくは書かない。周波数が2倍となる音程はオクターブと単位付けられており、1オクターブの2音は高さが違う同じ種類の音と認識されるようだ。

 整数比の音程からなる音律としてはピタゴラス音律純正律が代表的だが、いずれも1つの楽器から有限種類の高さの音しか出ないことを前提にしているので、サウンドプログラミングではあまり気にせず自由に音程を与えるといいだろう。心地良い音程が整数比の関係にあることを逆手に取り、周波数を整数比からずらすことで不気味さを演出する、といった技法も可能だ。

 コードでは基準音の周波数を与え、それを音毎に定数倍することで音程を実現するといい。

# サンプリングレートと基準音の周波数を大局的に定義して
# 波形、音程(interval、周波数比)、音長の辞書リストを作る
# 有理数倍 = 整数 / 整数なら協和音となるが任意の正実数を指定できる
rate = 44100
f0 = 300
dct2 = [{'unit': unit_sin, 'interval': 1/1, 'T': 2}, 
        {'unit': unit_sin, 'interval': 5/4, 'T': 2}, 
        {'unit': unit_sin, 'interval': 3/2, 'T': 2}]

# ** で辞書を展開すると interval でエラーになるのでちょっと冗長に書く
# 音程を指定することを前提にするなら makewave を書き直すのも良い
wave2 = np.sum([makewave(unit=d['unit'], f=f0*d['interval'], T=d['T'], rate=rate) for d in dct2], axis=0)

平均律

 整数比からなる音律では主に利便性の問題がいくつかあり、近代音楽では1オクターブを等分する平均律を利用することが多い。ただし、ここでの等分は比による等分であり、基準音の周波数  f_0 \in \mathbb{R}_{\gt 0} と1オクターブの分割数  m \in \mathbb{N} に対して  \sqrt[m]{2}^i f_0 i \in \mathbb{Z}) の周波数を持つ音で構成される。そのため音程は整数比でなくなるが、どうせ聞いても分からないし*1平均律を利用する上では誤差として納得されるようだ。分割数  m平均律は特に  m 平均律と呼ばれ、12平均律が最もよく知られているが*2、これも作りたい曲に合わせて自由に決めるといいだろう。53とか*3

# サンプリングレート、基準音の周波数と分割数を大局的に定義する
# 辞書リストには周波数(または音程)の代わりに周波数比の指数部を入れる
# この指数部も基本は整数だが任意の実数で良い
rate = 44100
f0 = 300
m = 12
dct3 = [{'unit': unit_sin, 'character': 0, 'T': 2}, 
        {'unit': unit_sin, 'character': 4, 'T': 2}, 
        {'unit': unit_sin, 'character': 7, 'T': 2}]

wave3 = np.sum([makewave(unit=d['unit'], f=f0*2**(d['character']/m), T=d['T'], rate=rate) for d in dct3], axis=0)

音階

 音律を定める場合、基準音から1オクターブ上までの範囲内で音程を決め、それらを  2 ^ i i \in \mathbb{Z})倍して全周波数域に拡張すると楽だ。この時、1オクターブ内の音を周波数が小さい順に並べたものを音階と呼ぶ。音階を周波数比のリストで保持しておくと音律の切り替えにも利用できる。例えば前述のピタゴラス音律純正律、12平均律は互いに音階について対応関係を持つので切り替え可能。

# ピタゴラス音律
# 第6成分は 1024/729 と択一
scale_pythagorean = [1, 256/243, 9/8, 32/27, 81/64, 4/3, 729/512, 3/2, 128/81, 27/16, 16/9, 243/128]

# 純正律
# 0, 2, 4, 5, 7, 9, 11 以外の成分は諸説ある
scale_just = [1, 16/15, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 8/5, 5/3, 9/5, 15/8]

# 12平均律
# numpy の数列で記述できる
scale_12equal = 2**(np.arange(12)/12)

# 音階を大局的に定義して辞書にオクターブと度数(リストのインデクス)を保持
rate = 44100
f0 = 300
scale = scale_pythagorean
dct4 = [{'unit': unit_sin, 'octave': 0, 'degree': 0, 'T': 2}, 
        {'unit': unit_sin, 'octave': 0, 'degree': 4, 'T': 2}, 
        {'unit': unit_sin, 'octave': 0, 'degree': 7, 'T': 2}]

wave4 = np.sum([makewave(unit=d['unit'], f=f0*2**d['octave']*scale[d['degree']], T=d['T'], rate=rate) for d in dct4], axis=0)

*1:記事内のコードで生成される和音を聞き比べてみよう。

*2:むしろ12平均律以外は一般にはほとんど知られていない。

*3:53平均律…周波数比が3/2倍に極めて近い音程を含むことで有名。wikipediaによると紀元前には発見されていたらしい。