Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

和音の協和度を算出する不協和度曲線 (Dissonance Curve) のPython実装

和音の響きはその音楽の雰囲気を分析する重要な要素です。

長/短3和音のような明るい/悲しいといった響きから、ジャジーな和音の複雑で豊かな響きまで様々です。

このような和音の響きを計算機で分析する方法はいくつかあります。

今回は、和音がどのくらい協和的かを示すモデルとして知られている不協和度曲線を紹介します。

2つの純音の不協和度

参考文献では、基本周波数がそれぞれ f1, f2 (f1<f2)である2つの純音の不協和度曲線は以下のように定義されています*1

v12は2つの純音の音量 v1, v2から算出される値です。

そのため、v12の定義は

  • v12=v1*v2
  • v12=0.5*(v1+v2)
  • v12=(v1*v2)**0.5

などいろいろ考えられますが、参考文献では単純な積が使われています。

3種類のalphaとbetaは曲線の形状を決定するパラメタであり、参考文献では以下の値が使われています。

以上より、Pythonでクラスとして実装しました。

import numpy as np
import matplotlib.pyplot as plt


class Dissonance():
    def __init__(
            self,
            n_semitone=12,
            alphas = [0.7, 1.4, 4.0],
            beta = 1.25
        ):
        self.n_semitone = n_semitone
        self.alphas = np.array(alphas)
        self.beta = beta
    
    def calc_velocity(self, v1, v2):
        return v1 * v2

    def calc_pitch_distance(self, f1, f2):
        return np.abs(self.n_semitone * np.log2(f2/f1))
        
    def calc_puretones(self, f1, f2, v1=1.0, v2=1.0):
        """
        f1, v1は値を与える。f1は基準となる純音の基本周波数
        f2, v2は値もしくはnd.arrayを与える
        """
        f2 = np.array(f2) if type(f2) in [int, float] else f2
        
        v12 = self.calc_velocity(v1, v2)
        x12 = self.calc_pitch_distance(f1, f2)
        x12beta = x12 ** self.beta
        
        d12 = v12 * self.alphas[2] * (\
              np.exp(-self.alphas[0] * x12beta) \
            - np.exp(-self.alphas[1] * x12beta) )
               
        return d12

注意として、calc_pitch_distance()でのf1>f2となる場合への対処です。

f1<f2の場合の関数とf1>f2の場合の関数はx軸で対称になることから絶対値を取ればよいことになります。

この calc_puretones()の返り値 d12 をプロットすると、2つの純音の不協和度を算出することができます。

試しに、f1=440として、f2を440~880Hzまで変化させてプロットしてみます。

_x = np.arange(0, 12+0.1, 0.1)
x = 440 * 2**(_x/12)
dissonance = Dissonance()
y = dissonance.calc_puretones(x[0], x, 1.0, 1.0)

この純音のモデルは、私たちが普段感じるように半音で隣り合う2音が鳴った時に不協和となることが反映されています。

一方で、2つの音高が離れるほど不協和を感じなくなるというモデルになっています。

後者を修正するために、倍音を考慮した不協和度を考えます。

倍音を考慮した不協和度曲線

倍音を考慮した不協和度のアイディアは単純です。

先ほどは2つの音高の基音のみを考えていました。

倍音を考慮した不協和度曲線では、2つの音高の倍音の不協和度を全て算出し合計します。

具体的には、純音の不協和度算出関数をd(f1, f2, v1, v2) とすると、2つの音の不協和度 D12は、

と表すことができます。

ここで、g(v, i)は、基音に対する i 番目の倍音の音量 v_i を算出する関数です。

例えば、v1=1.0に対して、v1_2=0.8v1_3=0.64というように倍音の音量が小さくなるというような音をモデル化することができます。

この D12Python実装が以下になります。先ほどのDissonance()クラスにメソッドを追加しています。

なお、gammag(v, i)の代わりの役目をしています。

    def curve(self, f1, f2, v1=1.0, v2=1.0, gamma=1.0, n_overtones=8):
        f2 = np.array(f2) if type(f2) in [int, float] else f2
        
        d12 = np.zeros(f2.shape)
        for k in range(1, n_overtones+1):
            for m in range(1, n_overtones+1):
                d12[:] += self.calc_puretones(f1*k, f2*m, v1*(gamma**(k-1)), v2*(gamma**(m-1)))
                
        return d12

この倍音を考慮した不協和度曲線を、何倍音まで考慮するかを変えてプロットしてみました。

_x = np.arange(0, 12+0.1, 0.1)
x = 440 * 2**(_x/12)
dissonance = Dissonance()
for n_overtones in [1,2,3,4,5,6]:
    y = dissonance.curve(x[0], x, 0.88, 0.88, n_overtones=n_overtones)
    plt.plot(x, y, label=f"n_overtones={n_overtones}")

また、不協和度曲線は1オクターブ以上でも算出することができます。

_x = np.arange(0, 24+0.1, 0.1)
x = 440 * 2**(_x/12)
dissonance = Dissonance()
y = dissonance.curve(x[0], x, 0.88, 0.88)

以上で、2つの音高の不協和度を算出することができました。

3つ以上の音の不協和度の算出

不協和度曲線の興味深いところは、2つの音高に対する不協和度を利用することで3つ以上の音の不協和度のような値を算出できるところです。

具体的には、3つの音高(周波数) f1, f2, f3 について、

として2つの不協和度を合計することで算出することができます*2

この3つの音高の不協和度を算出する関数を、Dissonanceクラスに追加します。

    def surface(self, f1, f2, f3, v1=1.0, v2=1.0, v3=1.0, gamma=1.0, n_overtones=6):
        f2 = np.array(f2) if type(f2) in [int, float] else f2
        f3 = np.array(f3) if type(f3) in [int, float] else f3
        
        d23  = np.zeros((f2.shape[0], f3.shape[0]))
        d123 = np.zeros((f2.shape[0], f3.shape[0]))
        
        d12 = self.curve(f1, f2, v1, v2, gamma, n_overtones)
        d13 = self.curve(f1, f3, v1, v3, gamma, n_overtones)
        
        for k in range(f2.shape[0]):
            d23[k,:] = self.curve(f2[k], f3, v2, v3, gamma, n_overtones)
            for m in range(f3.shape[0]):
                d123[k,m] = (d12[k] + d13[m] + d23[k,m])/3
                
        return d123

f1は固定で、f2, f3を1オクターブ上前で変えてみた結果をプロットしました。

_x = np.arange(0, 12, 0.1)
x = 440 * 2**(_x/12)
dissonance = Dissonance()
z = dissonance.surface(x[0], x, x, 0.88, 0.88, 0.88)

赤色に近いほど、不協和度が高いことを表しています。

3つの音高であるため、例えば長/短/減/増三和音がどの位置にあるか探してみるとおもしろいです。

なお、協和度に注目する場合は、以下のように不協和度曲線(曲面)を反転させた方が分かりやすくなります。

d123[k,m] = -(d12[k] + d13[m] + d23[k,m])/3

最後に、D12, D13, D23を個別にプロットすると以下のようになります。

まとめ

和音の協和度を算出する不協和度曲線 (Dissonance Curve) Pythonで実装しました。

本記事では3つの音高までしか扱っていませんが、4つ以上の音高にも対応可能である*3ため、セブンスコードやよりテンションコードのような複雑な和音の分析や、ヴォイシングの分析にも利用できると考えられます。

ただし、実用では曲線(曲面)の形状や値域が、適用する音色(周波数特性)に合わせて主観に合うように調整する必要があります。

参考文献

*1:12音平均律の場合

*2:合計の計算方法としては相加平均や相乗平均などがあり得ると思います

*3:音高数が異なる場合に不協和度を比較する場合、算出された不協和度のスケールを合わせる必要があります