あらゆる理論で音楽を作成・分析できるPythonライブラリ Neuma を作成する (3) - 音の調律 -
今回は、Neuma の音の調律について書いていきます。
Neuma では、音の表示方法を Scale
として表現していますが、同様に 音の調律もクラスとして表現 しています。
前回 ->
2023/9/28 追記
TunningSystem 周りを大幅に変更したため、記事の内容を更新しました。
音の調律
皆さんが普段使っている・聞き慣れている音は、 12平均律 という調律で調律されています。
12平均律は、 1オクターブを12等分 したものです。
1オクターブは周波数で2倍になるので、12平均律では、1オクターブを2の12乗根で割った周波数の音を1音階としています。
12平均律は非常に便利です。
なぜなら、 全ての音が等間隔 になっているからです。
全ての音が等間隔なら、カラオケでいうところのキー変更が自由にできます。
キー変更について
当然ながら、クラシックの人たちは、キー変更なんてしません。
音の絶対的な高さも重要視される要素だからです。
じゃあ、なぜ全ての音が等間隔というのが重要視されたのかと言うと、響きの問題です。
詳しくは調べてみて欲しいのですが、この調律に至るまでいくつもの調律がありました。
この流れは全て、全てのキーでコードの響きが美しい ということを目指していたのです。
平均律によって、全てのキーでコードの響きが美しいということが実現されました。
実は、平均律のコードの響きは美しくないんですけどね。
他にも、 純正律 や ピタゴラス律 、一部界隈で流行っている? 31平均律 などがあります。
困ったことにここまで説明した調律は、音階では「ドレミファソラシ」を使う西洋の音階で表示します。
だからこそ、 音の名前と音の高さを一致させることができない のです。
実装
調律抽象クラス
ことによって、抽象クラスを作成しています。
これを継承することで、音の調律を表現するクラスを作成することができます。
from abc import ABC, abstractmethod
from dataclasses import dataclass
from ..note.note import Note
@dataclass(frozen=True)
class TunningSystem(ABC):
base: float
@abstractmethod
def get_freq(self, note: Note) -> float:
pass
@abstractmethod
def fine_freq(self, freq: float, fine: float) -> float:
pass
base
は、基準となる周波数です。
get_freq
は、音を表す Note
クラスを受け取り、その音の周波数を返します。
fine_freq
は、周波数を受け取り、その周波数を微調整した周波数を返します。
これらを実装することで、音の調律を表現するクラスを作成することができます。
n平均律クラス
Neuma では、音の調律を表現するクラスとして n平均律クラス
を用意しています。
当然、12平均律も n平均律クラス
の一つです。
class EqualTemperament(TunningSystem):
"""平均律クラス
Description:
平均律の調律クラス。
基本倍音を等分する。
Attributes:
base_tone (int): 基準となる Note.tone。
base_freq (float): 基準となる周波数。
divisions (int): 基本倍音の分割数。
overtone (float): オクターブを表す倍音。
key (list[int]): 音階のリスト。
"""
divisions: int
overtone: float
key: list[int]
def __init__(
self,
base_tone: int = 6,
base_freq: float = 440.0,
divisions: int = 12,
overtone: float = 2,
key: Optional[list[int]] = None,
) -> None:
if key is None:
key = [i for i in range(divisions)]
super().__init__(base_tone, base_freq, key)
kwargs: dict[str, Any] = {
"base_tone": base_tone,
"divisions": divisions,
"overtone": overtone,
}
for key, value in kwargs.items():
object.__setattr__(self, key, value)
def get_freq(self, note: Note) -> float:
tone: int = self.get_liner_tone(note)
freq: float = self.base_freq * self.overtone ** (tone / self.divisions)
return freq
def __get_diff(self, diff: Fraction) -> int:
rtn: float = int(diff)
return rtn
def fine_freq(self, freq: float, fine: float) -> float:
fine /= 100
freq *= self.overtone ** (fine / self.divisions)
return freq
def get_liner_tone(self, note: Note) -> int:
tone: int = self.key[note.tone - 1]
tone -= self.key[self.base_tone - 1]
tone += note.octave * self.divisions
tone += self.__get_diff(note.diff)
return tone
class EDO12(EqualTemperament):
"""12平均律クラス
Description:
12平均律の調律クラス。
"""
def __init__(
self,
base_freq: float = 440,
) -> None:
base_tone: int = 6
divisions: int = 12
overtone: float = 2
key: list[int] = [0, 2, 4, 5, 7, 9, 11]
super().__init__(base_tone, base_freq, divisions, overtone, key)
引数のデフォルトは、440HzのAを基準とした12平均律です。
base_tone
は、基準となる音の音階です。
ここでは、Aである 6 を基準としています。
base_freq
は、基準となる音の周波数です。
divisions
は、1オクターブの音階の数です。
n平均律での1オクターブの音階の数は、 n
なので、12平均律では12です。
key
は、音階の配列です。
先頭から順に、ドレミファソラシの音階を表しています。
正確には、 Note
クラスの tone
に対応しています。
base_tone もこの配列に対応しているので、 base_tone
が 6
なら、基準が A
になります。
get_freq
の fine
はこのクラスではセントを単位としています。
セントは、 1オクターブを1200分割した単位 ですがここでは「半音100セント」を基準にしています。
そのため、 division
が 20 の場合、「1オクターブを2000分割した単位」になります。
セント単位での音高調整はシンセサイザーなどで、よく見ます。
どうやって使う?
調律クラスは基本的に音そのものに関わるときに用いられます。
例えば、楽器クラスといった音を出すものや、周波数を分析に用いるときなどです。
ぶっちゃけ使い道は少ないですが、音を出すことを考えると必須級なので、作りました。
平均律と純正律で音階の違いを見せる、とかやりたかったのですが優先度が低く12平均律でしか動作を確認していません。
他の平均律ならできると思いますが、面倒なので…… (実装済みです。(2023/9/28 追記))
比較を作ったら、追記します。
まとめ
今回は、内容が薄いですね。
というのも話すことがあまりないんです……
調律の歴史を辿るのも面白いのですが、 tech カテゴリで実装さえしていない調律の話を3回分やるのも……
と言うことで、実装中心ですが説明しました。
次回は、音を鳴らす関連で楽器クラスについて、の前に音クラスについて説明します。
音そのものを処理し、保存するクラスです。
Discussion