2011年12月9日金曜日

Prolog で楽典クイズ

我々が日ごろ聞いてるふつうの音楽は、1オクターブの中に高さの異なる音が12個あって、それぞれの音は「音名」という名前を持っている。

実は、ほとんどの音はそれぞれ三つの音名を持っている。例えば、ミ♯、ファ、ソ♭♭の組み合わせや、レx、ミ、ファ♭の組み合わせは同じ音高であったりする。

ところが、ある音だけは二つの音名しか持っていない。この音が何かわかるだろうか。

ダブルシャープとかダブルフラットを使ってるから、小学校で習うかどうかは分からないけど、たぶん義務教育の範囲には入ってる気がするくらいの基礎的な楽典知識だけで構成された問いだけど、面白いことに、意外と音楽をやってる人でも即座には答えられなかったりする。

ピアノの鍵盤を思い浮かべて単なる図形として捉えたら簡単なんだけど、今日は、これを「寝る前ちょこっとプログラミング」のお題にしてみようと、帰りの電車で思いついた。Prolog でやってみる。

====

note(0, 'C').
note(2, 'D').
note(4, 'E').
note(5, 'F').
note(7, 'G').
note(9, 'A').
note(11, 'B').

notes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).

alteration('x', -2).
alteration('♯',  -1).
alteration('♮',   0).
alteration('♭',   1).
alteration('♭♭',  2).

noteWithThreeNames(Names)   :- findNoteNamesByCount(Names, 3).
noteWithOnlyTwoNames(Names) :- findNoteNamesByCount(Names, 2).

findNoteNamesByCount(Names, Count) :-
   notes(Notes), member(Note, Notes),
   collectNoteNames(Note, Names),
   length(Names, Count).

collectNoteNames(Note, Names) :-
  findall(Name, findName(Note, Name), Names).

findName(Note, Names) :- 
  alteration(Alteration, Diff), 
  getNote(Note + Diff, NaturalTone),
  concat_atom([NaturalTone, Alteration], Names).

getNote(N, X) :- N2 is N mod 12, note(N2, X).

素朴な実装だけど、以下のように正しい結果が得られる。
?- noteWithThreeNames(Names).
Names = ['B♯', 'C♮', 'D♭♭'] ;
Names = ['Bx', 'C♯', 'D♭'] ;
Names = ['Cx', 'D♮', 'E♭♭'] ;
Names = ['D♯', 'E♭', 'F♭♭'] ;
Names = ['Dx', 'E♮', 'F♭'] ;
Names = ['E♯', 'F♮', 'G♭♭'] ;
Names = ['Ex', 'F♯', 'G♭'] ;
Names = ['Fx', 'G♮', 'A♭♭'] ;
Names = ['Gx', 'A♮', 'B♭♭'] ;
Names = ['A♯', 'B♭', 'C♭♭'] ;
Names = ['Ax', 'B♮', 'C♭'].

?- noteWithOnlyTwoNames(Names).
Names = ['G♯', 'A♭'] ;
false.

====

音楽の話題でいえば、厳格対位法という、こんなのとは比較にならないくらいパズルっぽいやつがある。これは与えられた音の配列(定旋律)に対して、たくさんの「禁則」をくぐり抜けてごく限られた音の配列(対旋律)を見つけ出していくという、古典音楽作曲の伝統的なトレーニングなんだけど、視点を変えるとパズルの「川渡り問題」に似てなくもない。

たぶん、とっくにどこかのプログラマが挑戦してるだろうけど、もうちょいヒマができたら自分でもやってみたい気がする。

0 件のコメント:

コメントを投稿