純正律と平均律というものがあります。
簡単に言うと、ピアノのような楽器(というかほとんどの楽器)は、移調がしやすいようにオクターブに対して音階を均等に割り当てています。その弊害で、本来は単純な整数比でハモるはずの和音が、微妙にズレてしまう……とは言っても、ヘルツレベルの話なので、聴いてわかる人はほとんどいないです。
音楽的な話はおいといて、「シンプルな整数比を重ねる」という発想を元に、
- 純正律を再現できるか?
- 再現できたとして、その純正律は本当に極限までシンプルさを追求したものなのか?
を検証していきたいと思います。
ルール
- 整数倍した時にどれだけ鍵盤が移動するかを決める
- 音階を、互いに素な整数 $p,q$ からなる有理数 $\frac{p}{q}$ と表現したとき、$p+q$ の合計が小さい方が、より シンプル であるとする
以上のルールをもとに、解析により純正律を定めていきます。
なお、解析においては、独自の大小関係を持つカスタム有理数の定義した上で、ダイクストラ法により行いましたが、コードとかは長いので最後に載せます。
レベル1:3倍音
あるドの $3$ 倍音は次のオクターブのソになります。(また、逆から見れば、ある音の $\frac{1}{3}$ 倍は、前の前のオクターブのファになります)
このルールを基に解析したレベル $1$ 純正律は以下の通りになります。
C | C♯ | D | E♭ | E | F | F♯ | G | A♭ | A | B♭ | B |
---|---|---|---|---|---|---|---|---|---|---|---|
$1$ | $\frac{256}{243}$ | $\frac{9}{8}$ | $\frac{32}{27}$ | $\frac{81}{64}$ | $\frac{4}{3}$ | $\frac{729}{512}$ | $\frac{3}{2}$ | $\frac{128}{81}$ | $\frac{27}{16}$ | $\frac{16}{9}$ | $\frac{243}{128}$ |
音楽に詳しい人なら気がついたでしょうが、これは ピタゴラス音律 と呼ばれるものとほぼ一致しています(考え方は全く同じです)。
ピタゴラス音律だと F♯ にあたる音が $\frac{1024}{729}$ になっていますが、分子と分母の合計が小さい方を優先する というルールにより、この表では $\frac{729}{512}$ を採用しています。
レベル2:5倍音
あるドの $5$ 倍音は次の次のオクターブのミになります。
このルールを追加したレベル $2$ 純正律は以下の通りになります。
C | C♯ | D | E♭ | E | F | F♯ | G | A♭ | A | B♭ | B |
---|---|---|---|---|---|---|---|---|---|---|---|
$1$ | $\frac{16}{15}$ | $\frac{9}{8}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{36}{25}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{9}{5}$ | $\frac{15}{8}$ |
だいぶシンプルになりました。そして、これはいわゆる通常使われる純正律とほぼ同じになります。
ほぼ同じ、というのは、♯や♭がつくような細かい音については正しい純正律が存在しないからで、これは 12 個すべての音階を無理に決めても実用的な意味がないからです。
実用はさておき、次のレベルに行ってみましょう。
レベル3:7倍音
ここからアカペラか管楽器でしか使わないような倍音の話になっていきます。
あるドの $7$ 倍音は次の次のオクターブのシ♭になります。
このルールを追加したレベル $3$ 純正律は以下の通りになります。
C | C♯ | D | E♭ | E | F | F♯ | G | A♭ | A | B♭ | B |
---|---|---|---|---|---|---|---|---|---|---|---|
$1$ | $\frac{15}{14}$ | $\frac{8}{7}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{7}{5}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{7}{4}$ | $\frac{15}{8}$ |
更に(見かけは)シンプルになりました。
しかし、このスケールのB♭は平均律とのズレがかなり大きいので、違和感を覚える人が多いと思います。実際にこの音を使った和音を聞くと、キレイというよりは圧があって怖いです。詳しくは自然七度で検索してください。
レベル4:11倍音
次の素数は $11$ です。あるドに対して次の次の次のオクターブのファ♯です。
C | C♯ | D | E♭ | E | F | F♯ | G | A♭ | A | B♭ | B |
---|---|---|---|---|---|---|---|---|---|---|---|
$1$ | $\frac{12}{11}$ | $\frac{8}{7}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{7}{5}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{7}{4}$ | $\frac{11}{6}$ |
これがレベル $4$ 純正律です。わかりづらいですが、C♯ と B がより「シンプル」になっています。
レベル5以上:無意味
$13$ 以上の素数を使っても、「分子と分母の合計が小さくなるようにする」という目的を満たせないため、何も変化がありません。よって、これ以上のレベルを考えるのは無意味です。
最終的な結果をまとめると以下になります。
スケール | C | C♯ | D | E♭ | E | F | F♯ | G | A♭ | A | B♭ | B |
---|---|---|---|---|---|---|---|---|---|---|---|---|
平均律 | $2^0$ | $2^\frac{1}{12}$ | $2^\frac{1}{6}$ | $2^\frac{1}{4}$ | $2^\frac{1}{3}$ | $2^\frac{5}{12}$ | $2^\frac{1}{2}$ | $2^\frac{7}{12}$ | $2^\frac{2}{3}$ | $2^\frac{3}{4}$ | $2^\frac{5}{6}$ | $2^\frac{11}{12}$ |
レベル$1$ | $1$ | $\frac{256}{243}$ | $\frac{9}{8}$ | $\frac{32}{27}$ | $\frac{81}{64}$ | $\frac{4}{3}$ | $\frac{729}{512}$ | $\frac{3}{2}$ | $\frac{128}{81}$ | $\frac{27}{16}$ | $\frac{16}{9}$ | $\frac{243}{128}$ |
レベル$2$ | $1$ | $\frac{16}{15}$ | $\frac{9}{8}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{36}{25}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{9}{5}$ | $\frac{15}{8}$ |
レベル$3$ | $1$ | $\frac{15}{14}$ | $\frac{8}{7}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{7}{5}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{7}{4}$ | $\frac{15}{8}$ |
レベル$4$ | $1$ | $\frac{12}{11}$ | $\frac{8}{7}$ | $\frac{6}{5}$ | $\frac{5}{4}$ | $\frac{4}{3}$ | $\frac{7}{5}$ | $\frac{3}{2}$ | $\frac{8}{5}$ | $\frac{5}{3}$ | $\frac{7}{4}$ | $\frac{11}{6}$ |
以上の議論により、レベル $4$ 純正律が「究極の」純正律であることが示されました。もちろんこれは冗談です。
余談:√2の近似
平均律におけるドとファ♯は $2^\frac{1}{2}=\sqrt{2}$ の関係があります。よって、純正律のファ♯は $\sqrt{2}=1.414\cdots$ の近似になります。
- $\frac{729}{512}=1.423\cdots$
- $\frac{36}{25}=1.44$
- $\frac{7}{5}=1.4$
コードとか
from fractions import Fraction
import heapq
class CustomFraction(Fraction):
def __lt__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) < (other.numerator + other.denominator)
return super().__lt__(other)
def __le__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) <= (other.numerator + other.denominator)
return super().__le__(other)
def __gt__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) > (other.numerator + other.denominator)
return super().__gt__(other)
def __ge__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) >= (other.numerator + other.denominator)
return super().__ge__(other)
def __eq__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) == (other.numerator + other.denominator)
return super().__eq__(other)
def __ne__(self, other):
if isinstance(other, CustomFraction):
return (self.numerator + self.denominator) != (other.numerator + other.denominator)
return super().__ne__(other)
def __mul__(self, other):
if isinstance(other, CustomFraction):
return CustomFraction(self.numerator * other.numerator, self.denominator * other.denominator)
return super().__mul__(other)
def __truediv__(self, other):
if isinstance(other, CustomFraction):
return CustomFraction(self.numerator * other.denominator, self.denominator * other.numerator)
return super().__truediv__(other)
q = []
heapq.heapify(q)
heapq.heappush(q,(CustomFraction(1,1),0))
dp = [CustomFraction(1,99999)]*12
move = []
move.append((CustomFraction(3,2),7))
move.append((CustomFraction(5,4),4))
move.append((CustomFraction(7,4),10))
move.append((CustomFraction(11,8),6))
move.append((CustomFraction(13,8),8))
for i in range(len(move)):
fraq, pos = move[i]
move.append((1/fraq,-pos))
while q:
fraq, pos = heapq.heappop(q)
if fraq >= dp[pos]:
continue
dp[pos] = fraq
for multiply,step in move:
next_fraq = fraq*multiply
next_pos = pos + step
while next_pos >= 12:
next_fraq /= 2
next_pos -= 12
while next_pos < 0:
next_fraq *= 2
next_pos += 12
next_fraq = CustomFraction(next_fraq)
if next_fraq >= dp[next_pos]:
continue
heapq.heappush(q,(next_fraq,next_pos))
print(*dp)