forked from donya/Kulitta
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathConstraints.lhs
211 lines (149 loc) · 6.75 KB
/
Constraints.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
Module for musical constraints
Donya Quick
Last modified: 18-Aug-2014
> module Constraints where
> import ChordSpaces
> import Data.List
> import System.Random
> import Data.Maybe
Musical constraints are modeled as predicates over some number of
chords. These are "hard" constraints such that a piece of music
either does or does not satisfy the constraints.
======== PREDICATES =========
First we defint the hProg function that turns a pairwise predicate
into a progression predicate.
> hProg :: Predicate (a,a) -> Predicate [a]
> hProg f xs = and $ map f $ zip xs $ tail xs
> foldPreds :: [Predicate a] -> Predicate a
> foldPreds fs xs = and $ map ($xs) fs
Here we define the notion of voices not crossing to mean that if there
is a permutation that sorts both chords, the voices do not cross. This
allows for voices that may be in unison.
hNotCross :: Predicate (AbsChord, AbsChord)
hNotCross (c1,c2) =
let ps1 = findAllP c1 (sort c1) -- find permutation, p1, to sort c1
ps2 = findAllP c2 (sort c2) -- find permutation, p2, to sort c2
in not $ null [p | p<-ps1, elem p ps2]
> hNotCross :: Predicate (AbsChord, AbsChord)
> hNotCross (c1,c2) =
> let sn = permutations [0..length c1-1]
> ps1 = filter (\s -> p s c1 == sort c1) sn -- find permutation, p1, to sort c1
> ps2 = filter (\s -> p s c2 == sort c2) sn -- find permutation, p2, to sort c2
> in not $ null [p | p<-ps1, elem p ps2]
Alternatively, we may wish to not allow voices to touch either. If
we know the chords are both sets (no duplicated pitches), then ranks
can be compared.
> hNotCross' :: Predicate (AbsChord, AbsChord)
> hNotCross' (c1,c2) = rank c1 == rank c2
The function above needs to generate all permutations (as indices)
of a chord.
> findAllP :: AbsChord -> AbsChord -> [[Int]]
> findAllP c1 c2 =
> let n = length c1
> f i = filter (\j -> c2 !! j == c1 !! i) [0..n - 1]
> g [] = [[]]
> g (is:t) = [(a:b) | a<-is, b<-g t]
> in filter (\x -> x == nub x) (g $ map f [0..n - 1])
> hNotCrossP :: Predicate Prog
> hNotCrossP = hProg hNotCross
Now we define a predicate for avoiding parallel motion (all voices
moving in the same direction). We will consider parallel motion in
the context of vectors, so this is not the strictest possible case.
We don't care about intervals of 0, since that does not constitute
voice movement.
> hNotPar1 :: Predicate (AbsChord, AbsChord)
> hNotPar1 (c1, c2) =
> let diffs = zipWith subtract c1 c2
> in not $ hasDups $ filter (/= 0) diffs
> hasDups :: (Eq a) => [a] -> Bool
> hasDups [] = False
> hasDups (a:as) = elem a as || hasDups as
Now we define a stricter version that considers parallel motion
in MULTISETS rather than vectors. On more than 2 voices, this
will become rather hard to satisfy unless the chords are
quite strange.
> hNotParStrict1 :: Predicate (AbsChord, AbsChord)
> hNotParStrict1 (c1,c2) = hNotPar1 (sort c1, sort c2)
And finally, progression level predicates of each.
> hNotPar2, hNotParStrict2 :: Predicate Prog
> hNotPar2 = hProg hNotPar1
> hNotParStrict2 = hProg hNotParStrict1
And now a "fill in the blanks" predicate. With this predicate,
we can set start and end points, middle points, etc. if we
want them. Of course, there will be no valid solutions unless we
pick points within the intended quotient space.
> fillBlanks :: [Maybe AbsChord] -> Predicate Prog
> fillBlanks (m:ms) (p:ps) =
> case m of Just c -> p == c && fillBlanks ms ps
> Nothing -> fillBlanks ms ps
> fillBlanks [] [] = True
> fillBlanks _ _ = False
Now some distance metric-type approaches.
> type DistMeasure = AbsChord -> AbsChord -> Double
> type Threshold = Double
> simpleDist, eucDist, maxDist :: DistMeasure
> simpleDist a b = fromIntegral $ sum $ map abs $ zipWith subtract a b
> eucDist a b = sqrt $ sum $ map fromIntegral $ zipWith subtract a b
> maxDist a b = fromIntegral $ maximum $ map abs $ zipWith subtract a b
> distClass :: DistMeasure -> Predicate Double -> Predicate (AbsChord, AbsChord)
> distClass d ft (x,y) = ft $ d x y
> simpleClass t = distClass simpleDist (<=t)
> eucClass t = distClass eucDist (<=t)
> maxClass t = distClass maxDist (<=t)
> noCPL :: Double -> Predicate (AbsChord, AbsChord)
> noCPL i x = maxClass i x && hNotPar1 x && hNotCross x
> noCL :: Double -> Predicate (AbsChord, AbsChord)
> noCL i x = maxClass i x && hNotCross x
> progL t = hProg (maxClass t)
===========================
Contour equivalence
> rank :: [PitchNum] -> [Int]
> rank xs =
> let vals = sort $ nub xs
> ranks = zip vals [0..length vals - 1]
> in map (\x -> fromJust $ lookup x ranks) xs
============================
ADDITIONAL THESIS PREDICATES
--- Single chords ---
> sorted :: Predicate AbsChord
> sorted x = x == sort x
> spaced :: [(Int, Int)] -> Predicate AbsChord
> spaced lims x = and $
> zipWith (\(l,u) diff -> l <= diff && diff <= u) lims $
> zipWith subtract x (tail x)
> triads :: [AbsChord]
> triads = [[0,0,4,7], [0,4,7,7], [0,0,3,7], [0,3,7,7], [0,0,3,6]]
> doubled :: [AbsChord] -> Predicate AbsChord
> doubled templates x = elem (normOP x) allTriads where
> allTriads = concatMap (\c -> map (normOP . t c) templates) [0..11]
> satbFilter x = and $ map ($x) [sorted, spaced satbLimits, doubled triads]
> satbFilter2 x = and $ map ($x) [sorted, spaced satbLimits]
> satbLimits = repeat (3,12)
> satbRanges = [(40,60), (47,67), (52,76), (60,81)]
> satbChords = filter satbFilter (makeRange satbRanges)
> satbOP :: QSpace AbsChord
> satbOP = satbChords // opEq where
> satbOP' :: StdGen -> QSpace AbsChord
> satbOP' g = randomize g satbChords // opEq where
> satbR :: StdGen -> Predicate AbsChord -> EqRel AbsChord -> QSpace AbsChord
> satbR g f r = randomize g (filter f $ makeRange satbRanges) // r
> pianoChord :: Predicate AbsChord
> pianoChord x = length x <= 5 && maximum x - minimum x <= 12
--- Pairs ---
> notParallel :: Predicate (AbsChord, AbsChord)
> notParallel (x,y) = let diff = zipWith subtract x y in nub diff == diff
> voiceLeading :: [Predicate (PitchNum, PitchNum)] -> Predicate (AbsChord, AbsChord)
> voiceLeading preds (x,y) = and $ zipWith ($) preds $ zip x y
> vl7 :: Predicate (AbsChord, AbsChord)
> vl7 = voiceLeading (repeat f) where f (a,b) = abs(a - b) <= 7
Note: the version of noCrossing below assumes that voices cannot overlap.
In other words, the pitches must all be unique.
> noCrossing :: Predicate (AbsChord, AbsChord)
> noCrossing (x,y) = rank x == rank y
> pairProg2 :: (Eq a, Show a) => QSpace a -> EqRel a -> Predicate (a,a) -> [a] -> [[a]]
> pairProg2 qs r c [] = [[]]
> pairProg2 qs r c (x:xs) =
> let newSolns = [(y:ys) | y<-eqClass qs r x, ys<-pairProg2 qs r c xs, c (y, head ys)]
> in if not $ null newSolns then newSolns
> else error "No solutions that satisfy the consraints!"
===============