music occult

音楽と怪文書

Python + Opycleid でコード進行を分析する(ネオ・リーマン理論)

概要

Python ライブラリの Opycleid を使用して, コード進行からネオ・リーマン理論の変換を表示するプログラムを実装します.

ネオ・リーマン理論とは

1980年代以降, アメリカの研究者らを中心に形作られてきた, 比較的新しい音楽理論です.
一連の研究をゆるく包括する概念のため, 明確に定義するのは難しいですが, コードの機能や進行ではなく, コード間の相対的な変換 (Transformation) に着目する点を特徴としています.

例えば, C → Am というコード進行は, ネオ・リーマン理論的な見方をすると Cに変換Rが作用してAmになる みたいな感じです.

ここでは詳細は割愛させていただき, 参考リンクのみの記載とします.

ネオ・リーマン理論 - Wikipedia
An introduction to neo-Riemannian theory (1)
An introduction to neo-Riemannian theory (6)
ネオ・リーマン理論のリーマン受容にみる概念変容
SoundQuest - ネオ・リーマン理論❶ PLR操作とトネッツ

Opycleid とは

音楽理論研究者の Alexandre Popoff 氏が開発した Python 用のライブラリです.

コードに限らず音楽的オブジェクト(musical object) 一般に対して, オブジェクト間の変換を定義し, 分析することができます.

今回は, その中の一機能を利用して, 24種類の長短三和音の間のネオ・リーマン理論的な分析を行います.

サイト URL
Github https://github.com/AlexPof/opycleid
公式ドキュメント https://alexpof.github.io/opycleid/
チュートリアル https://alexpof.github.io/opycleid/gettingstarted/


(チュートリアルが分かりやすいので, RPL変換と Python の知識がある方はこちらを直接読んでも大丈夫と思います)

環境構築

Python3 と NumPy が動いて Opycleid をインストールできるなら何でも良いのですが, 今回は Google Colaboratory を使用します(Googleアカウントが必要).

colab.research.google.com


なお動作確認時のバージョンは下記の通りです.
Python: 3.10.11
NumPy: 1.22.4
Opycleid: 0.4.2

  1. ノートブックの新規作成 で, 適当なノートブックを作成します.
  2. !pip install opycleidを実行し, Opycleid をインストールします (Successfully installed opycleid が表示されればOK).

以上で準備完了です. なお, 無料プランでは一定時間でセッションが切断され環境もリセットされるため, その都度 再インストールが必要です.

2つのコード間の変換を求める

「+ コード」をクリックし, 下記を挿入します.

from opycleid.musicmonoids import PRL_Group

_monoid = PRL_Group()
print(_monoid.get_operation('C_M', 'C_m')) # P
print(_monoid.get_operation('C_M', 'A_m')) # R
print(_monoid.get_operation('C_M', 'E_m')) # L

実行すると, 各コード間の変換を表す記号が表示されます.

['P']
['R']
['L']

上記の例では, opycleid.musicmonoids 名前空間に予め用意されている PRL_Group クラスの get_operation() メソッドを使用して, ネオ・リーマン理論の最も基本的な変換である「P」「R」「L」によってコードがどのように変換されるかを分析しています.
get_operation(変換前のコード名, 変換後のコード名)で, 変換を表す記号のリストを取得できます.


PRLを組み合わせたもっと複雑な変換も分析できます.

print(_monoid.get_operation('C_M', 'Bb_m')) # RLRLP

実行結果:

['RLRLP']

使用可能なコード名の一覧は下記で表示できます(異名同音を無視した24種類).

for obj in PRL_Group().get_objects():
  print(obj[1].get_elements())

実行結果:

['A_M', 'A_m', 'B_M', 'B_m', 'Bb_M', 'Bb_m', 'C_M', 'C_m', 'Cs_M', 'Cs_m', 'D_M', 'D_m', 'E_M', 'E_m', 'Eb_M', 'Eb_m', 'F_M', 'F_m', 'Fs_M', 'Fs_m', 'G_M', 'G_m', 'Gs_M', 'Gs_m']

コードを変換する

「+ コード」をクリックし, 下記を挿入します.

from opycleid.musicmonoids import PRL_Group

_monoid = PRL_Group()
print(_monoid.apply_operation('P','C_M')) # C_m
print(_monoid.apply_operation('R','C_M')) # A_m
print(_monoid.apply_operation('L','C_M')) # E_m
print(_monoid.apply_operation('RLRLP','C_M')) # Bb_m

実行すると, 各変換後のコード名が表示されます

['C_m']
['A_m']
['E_m']
['Bb_m']

apply_operation(作用させる変換, コード名)で, 変換後のコード名を取得できます.

使用可能な変換の一覧は下記で表示できます.

for m in PRL_Group().get_morphisms():
  print(m[0])

実行結果:

L
LP
LR
LRL
LRLP
P
PL
PLP
PLRL
PR
PRL
PRP
R
RL
RLP
RLR
RLRL
RLRLP
RP
RPL
RPR
RPRL
RPRP
id_.

注1:結果が同じとなる変換は, 代表する一つの名称のみ定義されています(例:PLPとLPLは同じ結果となるので, PLPのみ定義され, LPLは使用できない).
注2:id_.は恒等射(自分自身への変換, 何も変換しない)を表します.

コード進行から変換を求める

一つずつコード間の変換を求めるのは面倒なので, コード進行をリストで渡すと, まとめて分析してくれるプログラムを実装してみます.
「+ コード」をクリックし, 下記を挿入します.

from opycleid.musicmonoids import PRL_Group

def print_operations(monoid, elms):
  elm_pairs = list(zip(elms, elms[1:] + elms[:1]))
  print('-----------------------')
  for pair in elm_pairs:
    ops = monoid.get_operation(pair[0], pair[1])
    print(f'{pair[0]:4} -> {pair[1]:4} : {"".join(ops):5}')

_monoid = PRL_Group()
print_operations(_monoid, ['C_M', 'A_m', 'D_m', 'G_M'])

実行すると, いわゆる1625進行の隣接するコード間の変換が全て表示されます(循環進行とみなして, 末尾から先頭への変換も表示します).

-----------------------
C_M  -> A_m  : R    
A_m  -> D_m  : RL   
D_m  -> G_M  : PRL  
G_M  -> C_M  : LR 

コード進行から変換を求める(変換名の置換)

ネオ・リーマン理論では, RPL 以外の特徴的な変換に対して固有の名称が付けられているものがあります.
先ほどのプログラムを少し改造して, 固有の変換名へ置換して表示するようにしてみます.

from opycleid.musicmonoids import PRL_Group

def print_operations(monoid, elms):
  elm_pairs = list(zip(elms, elms[1:] + elms[:1]))
  print('-----------------------')
  for pair in elm_pairs:
    ops = monoid.get_operation(pair[0], pair[1])
    print(f'{pair[0]:4} -> {pair[1]:4} : {conv_op("".join(ops)):8}')

def conv_op(op):
  match op:
    case 'PLP':
      return 'H' # Hexatonic Pole
    case 'PRL':
      return 'F' # Far Fifth
    case 'RLP':
      return 'N' # Nebenverwandt (Near Fifth)
    case 'RPL':
      return 'S' # Slide
    case 'RPR':
      return 'near-T6' # Near T6
    case _:
      return op

_monoid = PRL_Group()
print_operations(_monoid, ['C_M', 'Gs_m', 'Cs_M', 'Fs_m', 'F_M', 'B_m'])

実行結果:

-----------------------
C_M  -> Gs_m : H       
Gs_m -> Cs_M : F       
Cs_M -> Fs_m : N       
Fs_m -> F_M  : S       
F_M  -> B_m  : near-T6 
B_m  -> C_M  : LRL  

N(トニック→サブドミナントマイナーを一般化したような変換)や, S(3rdを保持したままルートと5thが半音ズレる)などの変換を表示できるようになります.

注: Opycleid は自分でオリジナルの MonoidAction を定義することができるので, 上記のような細工なしでも自由な名称を付けることが可能です. MonoidAction の定義方法はチュートリアルを参照.


複数のコード進行を比較し, コードを一箇所変えるとどうなるか観察してみると面白いかもしれません.

print_operations(_monoid, ['F_M', 'E_m', 'A_m'])
print_operations(_monoid, ['F_M', 'E_M', 'A_m'])
-----------------------
F_M  -> E_m  : LRL     
E_m  -> A_m  : RL      
A_m  -> F_M  : L       
-----------------------
F_M  -> E_M  : PLRL    
E_M  -> A_m  : N       
A_m  -> F_M  : L     

最後に

opycleid.musicmonoids名前空間には他にも, ピッチクラス・セットの移高(Transposition)と転回(Inversion)で三和音を変換する TI_Group_Triadsクラスや, 増三和音を交えた変換に対応する UPL_Monoidクラスなどが用意されています. もちろんオリジナルの変換を定義することも可能です.

ネオ・リーマン理論に興味を持たれた方は, Alexandre Popoff 氏のブログも是非チェックしてみて下さい.
以上