DyadにおけるHarmonic Entropy
概要
音がいかにハモっているかという指標に「Harmonic Entropy」というのがあります。
基本的な考え方は以下の通りです。
The general idea of Harmonic Entropy is to first develop a discrete probability distribution quantifying how strongly an arbitrary incoming dyad "matches" every element in a set of basis rational intervals, and then seeing how evenly distributed the resulting probabilities are. If the distribution for some dyad is spread out very evenly, such that there is no clear "victor" basis interval that dominates the distribution, the dyad is considered to be more discordant; on the other extreme, if the distribution tends to concentrate on one or a small set of dyads, the dyad is considered to be more concordant.
つまり、入力音程がある有理音程(=純正律の音程)であると強く知覚できる場合に「協和している」、突出した有理音程がなくどの有理音程ともつかない場合に「不協和である」というわけです。
アプローチとしては、あらゆる有理音程に対する入力音程がその音程に聞こえる確率を定義し、その確率分布のシャノン情報量を計算します。シャノン情報量が多いということは、無数にある有理音程の中から特定の有理音程であると強く判断できることを意味します。
台が有限集合の確率空間
として定義されます。
定義
まず
この確率
-
がc に近いほど大きいj -
が簡単な分数であるほど大きいj
この時、
def calcDyadHE(c):
js = rationals()
return shannonEntropy(js, c)
def shannonEntropy(js, c):
s = 0
for j in js:
a = p(j, c, js)
s -= a * np.log1p(a)
return s
ちなみにnp.log1p
を用いているのは、確率がほとんど0に張り付くからです。
有理数の生成と「高さ」
ここで、rationals()
で任意の有理数を生成していますが、プログラム上は有限個に区切らなければなりません。そのため複雑性に上限を設けるのですが、有理数に「高さ」の概念を導入する必要があります。
有理数
境界となる
def rationals(o=50, interval=(0,3)):
rs = []
for i in range(interval[0], interval[1]):
rs.extend(farey(o, i))
rs.pop(0) # remove 0/1
print(rs)
return rs
def farey(n, k):
if n == 1:
return [(k,1), (k+1,1)]
else:
return deepen(farey(n-1, k) , n)
def deepen(arr, k):
n = len(arr)
for i in range(n - 1):
l = arr[n - i - 2]
r = arr[n - i - 1]
num = l[0] + r[0]
denom = l[1] + r[1]
if(denom < k + 1):
arr.insert(n - i - 1, (num, denom))
return arr
確率分布
次に、ある音が他の音に聞こえるかどうかの確率分布を考えます。これは、先ほどの
-
がc に近いほど大きいj
を満たす必要があります。
ガウス分布で、標準偏差が約17セントほどと設定することが多いようです。
分布はガウス分布とするのが一般的ですが、ラプラス分布などを設定する例もあるようなので、念の為一般化ガウス分布(GGD)で記述しておきます。
def s(x, c):
return ggd(frac2cent(x), c, 17*1.414, 2)
def frac2cent(x):
return 1200 * np.log2(x)
from scipy.special import gamma
def ggd(x, mu, alpha, beta):
return beta / (2 * alpha * gamma(1/beta)) * np.exp(-np.power(np.abs(x - mu)/alpha, beta))
Domain-Integral Probabilities
さて、いよいよ任意の音程に対するスコアを計算します。このスコアは、先ほどの
-
が簡単な分数であるほど大きいj
を満たす必要があります。
まず、任意の音程を有理音程
こうすると、簡単な有理数は隣との間隔が広いので大きな領域を獲得し、複雑な有理数は領域が狭くなります。
そして、ある音程
これは、先ほどの
-
が簡単な分数であるほど大きいj
を満たしています。
Simple Weighted Probabilities
しかしながら、積分は解析解を求めるのが大変なので、プログラム上では近似を行います。有理数群の「高さ」にTenney height を用いた場合は、
同じようにWeil heightを用いた場合は
def q(j, c):
jn = j[0]
jd = j[1]
return s(jn / jd, c) / h(jn, jd)
def h(jn, jd, t="Weil"):
if t == "Tenney":
return np.sqrt(jn * jd)
elif t == "Weil":
return max(jn, jd)
ちなみにこの
def p(j, c, js):
s = 0
for ji in js:
s += q(ji, c)
return q(j, c) / s
プロット
matplotlibで描画してみます。1オクターブとちょっと(¢1500)で計算しました。
import numpy as np
import matplotlib.pyplot as plt
from he import calcDyadHE
C = 1000
x = np.linspace( 0, 1500, C)
y = calcDyadHE(x)
plt.plot(x, y)
plt.show()
plt.savefig('fig.png')
log1p
で計算したので負になっていますが、本来ならばエントロピーは非負の値になります。
¢0は完全なユニゾンなので当然として、¢1200は1オクターブ上のユニゾンなので突出していますね。
¢700あたりは完全5度(3/2)、¢500あたりは完全4度(4/3)です。その他、¢400の長三度(4/5)、¢900の短七度(7/4)も目立っています。
まとめ
全てのコードはここに置いてあります。
次回は和音に拡張しようと思います。
Discussion