Wizard Notes

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

和音の緊張度を算出する緊張度曲線 (Tension Curve) のPython実装

前回に引き続き、“The Psychophysics of Harmony Perception: Harmony is a Three-Tone Phenomenon” より和音の心理数理モデルPython実装を行います。

今回は、和音の緊張度を分析する数理モデルを扱います。

用途としては、減3和音や増3和音のような緊張感のある和音・ヴォイシングを分析・検出したり、コード進行を分析するのに利用できる可能性があります。

www.wizard-notes.com

和音の緊張度

参考文献では、和音の緊張度を音程の等しさに基づいて算出しています。

緊張度の高い和音としては、減3和音や増3和音などが知られています。

この3和音の低い音高(Low tone)と真ん中の音高 (Middle tone) の周波数差を第1音程、また、真ん中の音高と高い音高 (High tone) の周波数差を第2音程とすると、第1音程と第2音程が等しいという特徴があります。

この第1,2音程が等しいことが和音の緊張感を生み、和音進行で未解決的な響きとして知覚されると仮定されています。

藤澤 隆史, ノーマン D クック,和音性の計算法と曲線の描き方 : 不協和度・緊張 度・モダリティより

このような特徴を利用し、参考文献では3つ音高の各周波数から第1,2音程を算出して緊張度を導出するモデルを提案しています。

3つの純音における緊張度の算出

不協和度と同様に、まずは倍音のない純音を考えます。

純音の場合、以下の数式から緊張度を求めることができます*1

x12, x23がそれぞれ第1,2音程であり、不協和度の時と同様に周波数比の対数を取ることで音程を線形な軸で扱います。

すなわち、x12=1であれば第1音程は半音(例えば、CとC#の音程)になります。

なお、γはt123の値の大きさを調整する係数であり、論文ではγ=0.6となっています。

以上をクラスとしてPythonで実装したのが以下になります。

注意点として、f1, f2, f3 は f1≦f2≦f3 であることが要求されます

class Tension():
    def __init__(
            self,
            n_semitone=12,
            gamma=0.6,
            p=0.88
        ):
        self.n_semitone = n_semitone
        self.gamma = gamma
        self.p = p
    
    def calc_velocity(self, v1, v2, v3):
        return v1 * v2 * v3

    def calc_pitch_distance(self, f1, f2):
        return np.abs(self.n_semitone * np.log2(f2/f1))
        
    def calc_puretones(self, f1, f2, f3, v1, v2, v3):
        x12  = self.calc_pitch_distance(f1, f2)
        x23  = self.calc_pitch_distance(f2, f3)
        v123 = self.calc_velocity(v1, v2, v3)
        np.set_printoptions(suppress=True, precision=4)
        
        t = v123 * np.exp( -( (x23-x12)/self.gamma )**2 ) 
        return t

このコードを使い、3音の内の2音の音高を変えてプロットしました。

下図は音高がちょうど12平均律の各半音の時の値だけプロットしています。

f2とf3の入れ替えた結果は等しいため、対称的な形状となっています。

少し分かりにくいですが、第1,2音程に注目すると、目的としてた第1,2音程が等しい3和音では大きな値となっていることが確認できます。

倍音を考慮した緊張度の算出

次に、倍音を考慮した緊張度 T の算出を行います。

基本的には不協和度の算出と同じ、3つの音の周波数比をN倍音まで全パターン足すことで算出します。

注意点としては、各緊張度 t を算出する際に渡す3つの音高はソートしてから渡す必要があります

Python実装は、先ほどのTensionクラスにメソッドを追加しました。

    # class Tension():
    def calc(self, f1, f2, f3, v1=1.0, v2=1.0, v3=1.0, n_overtones=2):
        T = 0.0
        f, v = np.zeros(3), np.zeros(3)
        
        for k in range(1, n_overtones+1):
            for m in range(1, n_overtones+1):
                for n in range(1, n_overtones+1):
                    f[:] = f1 * k, f2 * m, f3 * n
                    v[:] = v1*(self.p**(k-1)), v2*(self.p**(m-1)), v3*(self.p**(n-1))
                    args = np.argsort(f)
                    f[:], v[:] = f[args], v[args]
                    T += self.calc_puretones(f[0], f[1], f[2], v[0], v[1], v[2])

        return T / n_overtones
    
    def surface(self, f_lower, f_centers, f_uppers, v1=1.0, v2=1.0, v3=1.0, n_overtones=2):
        # f_centers, f_uppers は複数の音高を格納したnumpy配列
        T_mx = np.zeros((f_centers.shape[0], f_uppers.shape[0]))
        for k, fc in enumerate(f_centers):
            for m, fu in enumerate(f_uppers):
                
                T_mx[k,m] = self.calc(f_lower, fc, fu, v1, v2, v3)
    
        return T_mx

なお、このコードはナイーブな実装のため3重ループが出てきており計算速度は遅いです。

高速化のためには、例えば Numba などを使って実装するのがよいと思われます。

www.wizard-notes.com

以下が計算する倍音数の上限値 n_orvertonesを変えたときの結果です。

先ほどの純音の場合(すなわちn_orvertones==1)だと第1,2音程が等しい場合以外はほぼ値を持たない結果となっていましたが、計算する倍音数を大きくすると倍音の影響で第1,2音程が異なっていても緊張度の値が大きくなる3和音が出てきます。

また、傾向として増三和音で緊張度が大きな値を取るようです。

■2倍音まで計算

■3倍音まで計算

■4倍音まで計算

■6倍音まで計算

プロット用のコード

まとめ

和音の緊張度を算出する緊張度曲線 (Tension Curve) Python実装を行いました。

和音の特性を音高や音程に対して連続的に算出できるので、よく知られた和音の分析だけでなく微分音の活用などを探るのによいかもしれません。

ただし、綺麗な数理モデルに基づいているとはいえ、あくまでも数理モデルであり実際に知覚される緊張度とは乖離があることに注意が必要です。

参考文献

*1:12平均律の場合