基本上,您应该存储具有这些属性的和弦的结果 properties,而不是存储给定的属性。一个简单(但在音乐上不是很好)的解决方案是,只存储最终音高:
newtype Pitch = Pitch {midiNote :: Int}
a, as, bb, b, bs, c, cs, db, d, ds, eb, e, es, f, fs, gb, g, gs, ab :: Pitch
[ a, as,bb, b,bs, c,cs,db, d,ds,eb, e,es, f,fs,gb, g,gs,ab] = map Pitch
[55,56,56,57,58,58,59,59,60,61,61,62,63,63,64,64,65,66,66]
type Chord = [Pitch]
minor :: Pitch -> Chord
minor (Pitch fund) = map (Pitch . (fund+)) [0, 3, 7]
seventh :: Pitch -> Chord
seventh (Pitch fund) = map (Pitch . (fund+)) [0, 4, 7, 10]
spread :: Chord -> Chord
spread = sort
. zipWith (\octShift (Pitch note) -> Pitch $ note + 12 * octShift) $ cycle [0,1]
用作例如
chords :: [Chord]
chords = [ minor e, seventh d, minor e, minor a, seventh b, spread $ minor e ]
一种更复杂的方法实际上可能以一种更具音乐意义的方式存储有关和弦的信息:
data Chord = Chord { fundamental :: Pitch
, gender :: Maybe ChordGender
, ExtraNotes :: [AddNote]
, OctaveShifts :: [Int]
}
data ChordGender = Major | Minor
data AddNote = AddNote { roughInterval :: Int, intervalIsMajor :: Bool }
major :: Pitch -> Chord
major fund = Chord fund (Just Major) [] []
sus4 :: Pitch -> Chord
sus4 fund = Chord fund Nothing [AddNote 4 False] []
spread :: Chord -> Chord
spread ch@(Chord _ _ _ shifts)
= ch{shifts = cycle [0,1]}
这可以以几乎相同的方式使用,但更通用。
如果您不喜欢将属性作为前缀函数,您可以使用as the diagrams package, with
infixl 8 #
(#) :: a -> (a -> b) -> b
(#) = flip ($)
写
chords = [ c # major
, g # sus4
, g # major
, a # minor
, f # major # spread
, g # sus4 # spread
, g # major # spread
, c # major # spread
]