音程から半音数への変換
概要
この記事に記したようにプログラムで作曲をする中で和音を扱いたく思いました。音楽理論の資料では和音の定義に音程が使われており(例:ブラックアダーコード:ルートと増4度(減5度)・短7度・長9度)、一方で実際にMIDI音源を発音させるためには半音数(ドの場合48や60など)に変換する必要がありました。この音程表記から半音数への変換ロジックがわかりやすい記事が無く、プログラムコードとしても無いようでしたので調査検証した結果をここに残しておきます。HaskellのコードとしてMaybeモナド、そして論理学の含意の良い用例ともなりました。
音程とは?
世間一般には音程=音高(周波数の高さ)のように用いられていますが、音楽理論において厳密には音高と音高との距離を表す相対値です。以下のようにクオリティ:{完全,長,短,増,減,(重増,重減)}と度数:自然数の組で記されます。以下の例の楽譜の上段はドから、下段はレからの音程を示しています。この記事を書き終わった私ですら譜面と音程との結びつきが瞬時には分からないくらい難解かと思います。
変換ロジック
音楽理論の資料を読み解き、音程から半音数への変換は以下の様にフローチャートで表すことが出来るということが分かりました。checkで示している部分は理論上存在する音程名称になり得るか確認する処理です。∈
は一項が二項で示される要素中にあるかをBoolで返します。//
と%
は整数除算の結果と余りです。→
は後述する論理学の含意です。
含意の用法
フローチャートの以下の箇所は音楽理論的に存在しない音程を退ける意図で入れており、論理学の含意が良く適用出来ています。含意A→B
は(not A) or B
と等価であり、Aが真であればBも真である
ということそれ自体を真偽判定します。andやor単体と比べ少々トリッキーですね。
(q=完全)→dIsPerfect
は、クオリティが完全であるならば、その度数は完全となり得る度数(即ちmod7結果が1,4,5度のいずれか)である
ということを言っており、成立しなければそれは存在しない音程であると言えます。例えば完全2度
は完全と着いているものの1,4,5度では無いため偽です。短3度
はq=完全
では無いため3度が完全となり得なくても真となります。
(q∈{長,短}) → not dIsPerfect
も同様に、クオリティが長か短であれば、その度数は完全となり得ない度数である
ということを示しています。例えば長8度
は偽となり実際存在しません。完全8度
や減8度
は存在します。
プログラム
上記のフローチャートで示した処理をHaskellで記述しました。Maybeモナド上での処理とすることで、存在しない音程であると判断出来た場合にNothingを返し処理を中断する操作が平易に書けています。
以下はクオリティ及び音程の型の定義です。
data IntervalQuality = IPerf | IMajor | IMinor | IAug | IDim deriving (Eq, Show)
data Interval = Interval IntervalQuality Int
以下は既に紹介した論理学の含意の定義です。Haskellでは標準実装されていないため自分で定義する必要がありました。
(~>) :: Bool -> Bool -> Bool
(~>) x1 x2 = not x1 || x2
以下はクオリティによる加減算の基点とする半音数を計算するMaybeモナド上の関数です。guardにより自然数ではない度数はNothingを返します。なお、このguardは参照元の関数も同内容の処理をしているのですが、関数単体としてこれが無いと成立しないと判断したため記述を残しています。なお、AbsPitch
はInt
です。
majorInterval :: Int -> Maybe AbsPitch
majorInterval x = do
guard $ x > 0
let
(oct, n) = divMod (x - 1) 7 -- 整数での除算と余りの計算
p = [0, 2, 4, 5, 7, 9, 11] !! n
return $ 12 * oct + p
以下はメインの処理を行うMaybeモナド上の関数です。
intervalPitch :: Interval -> Maybe AbsPitch
intervalPitch (Interval q x) = do
guard (x > 0)
let
n = x `mod` 7 -- 13度など、大きな度数を指定された場合に備えた正規化
isPerfect = n `elem` [1, 4, 5] -- 完全となり得る度数かの判断
guard $ (q == IPerf) ~> isPerfect
guard $ elem q [IMajor, IMinor] ~> not isPerfect
let
diff = case q of -- クオリティの種類による加減算の量の判定
IPerf -> 0
IMajor -> 0
IAug -> 1
IMinor -> -1
IDim -> if isPerfect then -1 else -2
mi <- majorInterval x
return $ mi + diff
フローチャートで説明したcheckはguardと先に定義した含意(~>)で実装しています。guardだけだとPythonなどのif not condition : return None
という記述と変わりませんが、mi <- majorInterval x
では以下のような処理と同等の内容となります。その上do記法であるため以降の記述ではmi
をMaybeの付かないAbsPitch
型であるとして平易に扱えるようになります。
mi = majorInterval(x)
if mi is None:
return None
return $ mi + diff
は、AbsPitch
同士の加算を行った後でMaybeモナドで包むような記述が出来ています。
以下はプログラム全行
Interval.hs
module Interval where
import Euterpea
import Control.Monad (guard)
data IntervalQuality = IPerf | IMajor | IMinor | IAug | IDim deriving (Eq, Show)
data Interval = Interval IntervalQuality Int
iPerf = Interval IPerf
iMaj = Interval IMajor
iMin = Interval IMinor
iAug = Interval IAug
iDim = Interval IDim
-- Logical imply
(~>) :: Bool -> Bool -> Bool
(~>) x1 x2 = not x1 || x2
majorInterval :: Int -> Maybe AbsPitch
majorInterval x = do
guard $ x > 0
let
(oct, n) = divMod (x - 1) 7
p = [0, 2, 4, 5, 7, 9, 11] !! n
return $ 12 * oct + p
intervalPitch :: Interval -> Maybe AbsPitch
intervalPitch (Interval q x) = do
guard (x > 0)
let
n = x `mod` 7
isPerfect = n `elem` [1, 4, 5]
guard $ (q == IPerf) ~> isPerfect
guard $ elem q [IMajor, IMinor] ~> not isPerfect
let
diff = case q of
IPerf -> 0
IMajor -> 0
IAug -> 1
IMinor -> -1
IDim -> if isPerfect then -1 else -2
mi <- majorInterval x
return $ mi + diff
main :: IO () -- お試し出力用
main = do
let f interval x = show interval ++ " "
++ show x ++ ":"
++ show (intervalPitch $ Interval interval x)
mapM_ (\i -> mapM_ (putStrLn . f i) [1..16] >> putStrLn "")
[IPerf, IMajor, IMinor, IAug, IDim]
以下の様に出力出来ます。
お試し出力結果
IPerf 1:Just 0
IPerf 2:Nothing
IPerf 3:Nothing
IPerf 4:Just 5
IPerf 5:Just 7
IPerf 6:Nothing
IPerf 7:Nothing
IPerf 8:Just 12
IPerf 9:Nothing
IPerf 10:Nothing
IPerf 11:Just 17
IPerf 12:Just 19
IPerf 13:Nothing
IPerf 14:Nothing
IPerf 15:Just 24
IPerf 16:Nothing
IMajor 1:Nothing
IMajor 2:Just 2
IMajor 3:Just 4
IMajor 4:Nothing
IMajor 5:Nothing
IMajor 6:Just 9
IMajor 7:Just 11
IMajor 8:Nothing
IMajor 9:Just 14
IMajor 10:Just 16
IMajor 11:Nothing
IMajor 12:Nothing
IMajor 13:Just 21
IMajor 14:Just 23
IMajor 15:Nothing
IMajor 16:Just 26
IMinor 1:Nothing
IMinor 2:Just 1
IMinor 3:Just 3
IMinor 4:Nothing
IMinor 5:Nothing
IMinor 6:Just 8
IMinor 7:Just 10
IMinor 8:Nothing
IMinor 9:Just 13
IMinor 10:Just 15
IMinor 11:Nothing
IMinor 12:Nothing
IMinor 13:Just 20
IMinor 14:Just 22
IMinor 15:Nothing
IMinor 16:Just 25
IAug 1:Just 1
IAug 2:Just 3
IAug 3:Just 5
IAug 4:Just 6
IAug 5:Just 8
IAug 6:Just 10
IAug 7:Just 12
IAug 8:Just 13
IAug 9:Just 15
IAug 10:Just 17
IAug 11:Just 18
IAug 12:Just 20
IAug 13:Just 22
IAug 14:Just 24
IAug 15:Just 25
IAug 16:Just 27
IDim 1:Just (-1)
IDim 2:Just 0
IDim 3:Just 2
IDim 4:Just 4
IDim 5:Just 6
IDim 6:Just 7
IDim 7:Just 9
IDim 8:Just 11
IDim 9:Just 12
IDim 10:Just 14
IDim 11:Just 16
IDim 12:Just 18
IDim 13:Just 19
IDim 14:Just 21
IDim 15:Just 23
IDim 16:Just 24
まとめ
まあまあ難解なロジックですよね?ジャズの奏法解説の動画など見ると音楽家の方は普通にこの音程名を使いこなし、目的の鍵盤を探したりしているので、「この計算を頭でしているのか、、、」と驚きます。しかしこの資料に示したロジックの様に音程が存在するのかの判定をやる必要が無く(音楽家の参考資料の音程名がそもそも間違いが無い)、13度を超えるような度数の計算は頻度が低かったりするため、音楽家の脳内では慣れも含めて正常運用出来ているのかも知れません。ギターの指板などは物理的に音程計算ができるようになっている、、、とか?
なお音楽理論はここ数ヶ月で覚えた限りなので、内容間違いがあったらコメントで教えていただけると助かります、、、音楽家的には微妙な表現があったりなども。
Discussion