1 """Semantics module for the music_halfspan formalism.
2
3 Lambda calculus semantics for the halfspan formalism.
4 This is the form of the semantics described in our paper appendix and in
5 my 2nd-year review.
6
7 """
8 """
9 ============================== License ========================================
10 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
11
12 This file is part of The Jazz Parser.
13
14 The Jazz Parser is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 The Jazz Parser is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
26
27 ============================ End license ======================================
28
29 """
30 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
31
32 import copy, re, math
33 from ...base.semantics.lambdacalc import \
34 Semantics as SemanticsBase, LambdaAbstraction as LambdaAbstractionBase, \
35 FunctionApplication as FunctionApplicationBase, \
36 Variable as VariableBase, Literal, LogicalForm, \
37 DummyLogicalForm as DummyLogicalFormBase, \
38 next_unused_variable, multi_apply as multi_apply_base, \
39 multi_abstract as multi_abstract_base, Terminal
40 from ...base.semantics.temporal import Temporal, TemporalSemantics, \
41 earliest_time
42 from jazzparser.utils.base import group_pairs
43 from jazzparser.utils.tonalspace import root_to_et_coord, \
44 coordinate_to_roman_name, coordinate_to_et_2d, \
45 coordinate_to_alpha_name_c
46 from jazzparser.settings import OPTIONS
47 from itertools import izip
48
49
50
51
52
53
54 FORMALISM_NAME = "music_halfspan"
55
56 -class Semantics(SemanticsBase, TemporalSemantics):
63
77
78
79 INTERVAL_TO_NN = {
80 0 : (0,0),
81 1 : (-1,-1),
82 2 : (2,0),
83 3 : (1,-1),
84 4 : (0,1),
85 5 : (-1,0),
86
87 6 : (-2,-1),
88 7 : (1,0),
89 8 : (0,-1),
90 9 : (-1,1),
91 10 : (-2,0),
92 11 : (1,1)
93 }
96 """
97 A four-dimensional coordinate. Stores the coordinate (x,y) within
98 an enharmonic (4x3) block, and the coordinate (X,Y) that locates
99 the enharmonic block within the infinite space.
100
101 Note that if x and y are set outside the range, they'll just be taken
102 mod 4 and 3 respectively. If you want to set x,y and X,Y from a 2D
103 coordinate, use from_harmonic_coord or the + operator.
104
105 This is used in the semantics as a tonic point.
106
107 """
108 timed_object = True
109 delta = False
110
111 - def __init__(self, coord=(0,0), block=(0,0), time=None, duration=None, delta=False, *args, **kwargs):
112 """
113 @type delta: bool
114 @param delta: whether this represents a vector, rather than a
115 point, in the tonal space. A delta coordinate will never show in
116 roman numeral format
117
118 """
119 Terminal.__init__(self, *args, **kwargs)
120 Temporal.__init__(self, time=time, duration=duration)
121
122 self.delta = delta
123 self.x, self.y = coord
124 self.X, self.Y = block
125
145
153
156
159
161 """
162 A string representation of the coordinate that's like the normal
163 __str__, but displays the harmonic coordinate, rather than the
164 enharmonic 4-tuple.
165
166 """
167 strng = "(%d,%d)" % self.harmonic_coord
168 if self.time is not None:
169 strng = "%s@%d" % (strng,self.time)
170 return strng
171
173 return type(self) == type(other) and \
174 self.x == other.x and \
175 self.y == other.y and \
176 self.X == other.X and \
177 self.Y == other.Y and \
178 self.time == other.time
179
184
187
188 @property
190 """
191 Returns the 2D coordinate within an enharmonic block.
192 Equivalently, locates this coordinate in the zeroth enharmonic
193 block.
194
195 """
196 return (self.x, self.y)
197
198 @property
200 """
201 Returns the 2D location of the enharmonic block in which this
202 coordinate lies.
203
204 """
205 return (self.X, self.Y)
206
207 @property
209 """
210 Returns the 2D coordinate (relative to the origin block) that
211 this enharmonic coordinate represents.
212
213 """
214 return (4*self.X + self.x,
215 3*self.Y - self.X + self.y)
216
217 @staticmethod
219 """
220 Creates an enharmonic coordinate from a harmonic coordinate,
221 given as a 2-tuple.
222
223 """
224
225 x,y = coord
226 encoord = EnharmonicCoordinate()
227
228 encoord += (x,y)
229 return encoord
230
235 x = property(_get_x, _set_x)
236
241 y = property(_get_y, _set_y)
242
247 X = property(_get_X, _set_X)
248
253 Y = property(_get_Y, _set_Y)
254
256 """
257 Given a 2D coord within an enharmonic block, returns an
258 L{EnharmonicCoordinate} for the instance of that point that is
259 closest to this enharmonic coordinate.
260
261 @type coord: 2-tuple or L{EnharmonicCoordinate}
262 @param coord: if given as a tuple, should be a coordinate within
263 a block (i.e. between (0,0) and (4,3)); if an EC, the EC's zero
264 coord will be used
265
266 """
267 if type(coord) == EnharmonicCoordinate:
268 coord = coord.zero_coord
269 else:
270 assert 0 <= coord[0] <= 4
271 assert 0 <= coord[1] <= 3
272
273 base_x, base_y = self.harmonic_coord
274
275 base_note = 7*base_x + 4*base_y
276
277 candidate_note = 7*coord[0] + 4*coord[1]
278
279
280 interval = (candidate_note - base_note) % 12
281
282
283
284
285
286 rel_coord = INTERVAL_TO_NN[interval]
287
288 return EnharmonicCoordinate.from_harmonic_coord(
289 (base_x+rel_coord[0], base_y+rel_coord[1]))
290
291 - def add_coord(self, coord=(0,0), delta=False):
292 """
293 Returns an enharmonic coordinate that results from adding the
294 2D harmonic coordinates to this enharmonic coordinate.
295
296 E.g.
297 <(0,1)/(0,0)> + (-1,0) = <(3,0)/(-1,0)>
298 and
299 <(1,0)/(0,0)> + (-1,0) = <(0,0)/(0,0)>
300
301 """
302 absx = self.x + coord[0]
303 absy = self.y + coord[1]
304
305 blockshiftx = (absx / 4)
306 blockshifty = (absy + blockshiftx) / 3
307
308 newx = absx % 4
309
310
311 newy = (absy+blockshiftx) % 3
312
313 newX = self.X + blockshiftx
314 newY = self.Y + blockshifty
315 return EnharmonicCoordinate((newx, newy), (newX, newY), delta=delta)
316
318 """
319 Define addition for 2D coordinates (same as L{add_coord})
320 and C{EnharmonicCoordinate}s.
321
322 """
323 if type(other) == tuple:
324 if len(other) == 2:
325 return self.add_coord(other)
326 else:
327 raise TypeError, "tuples added to an "\
328 "EnharmonicCoordinate must be 2-tuples (got %d)" % len(other)
329 elif type(other) == EnharmonicCoordinate:
330
331
332 delta = self.delta and other.delta
333
334
335
336 return self.add_coord(other.harmonic_coord, delta=delta)
337 else:
338 raise TypeError, "cannot add object of type %s to an "\
339 "EnharmonicCoordinate" % type(other).__name__
340
342 """
343 Define subtraction for 2D coordinates and C{EnharmonicCoordinate}s.
344
345 Result is always a delta.
346
347 """
348 if type(other) == tuple:
349 if len(other) == 2:
350 return self.add_coord((-other[0],-other[1]), delta=True)
351 else:
352 raise TypeError, "tuples subtracted from an "\
353 "EnharmonicCoordinate must be 2-tuples (got %d)" % len(other)
354 elif type(other) == EnharmonicCoordinate:
355
356
357 hc = other.harmonic_coord
358 return self.add_coord((-hc[0], -hc[1]), delta=True)
359 else:
360 raise TypeError, "cannot add object of type %s to an "\
361 "EnharmonicCoordinate" % type(other).__name__
362
364 """
365 When a logical form is read in from the lexicon, coordinates
366 must be specified relative to the root of the chord. Only when the
367 sign is used as the interpretation of a chord will this root
368 be known. In the meantime, we store a coordinate
369 (0,0) <= (cx,cy) <= (4,3) that specifies the position of the point
370 relative to the (equal temperament) position (x,y) of the chord
371 root.
372 Once this is known, an L{EnharmonicCoordinate} will be produced
373 at ((x+cx)%4, (y+cy)%3).
374
375 """
376 timed_object = True
377
378 - def __init__(self, coord=(0,0), time=None, duration=None, *args, **kwargs):
382
384 return "X(%d,%d)" % (self.x, self.y)
385
388
391
393 """
394 Same as __str__. Only here for compatibility with EnharmonicCoordinate.
395
396 """
397 return str(self)
398
400 return type(self) == type(other) and \
401 self.x == other.x and self.y == other.y
402
405
407 """
408 Produces the actual L{EnharmonicCoordinate}, given the 2D
409 position of the chord root.
410
411 """
412 return EnharmonicCoordinate(
413 ((self.x+coord[0])%4, (self.y+coord[1])%3))
414
418
420 """
421 Wrapper that stores an L{EnharmonicCoordinate}. The idea of this is
422 that it can be used in getting a path from coordinations to represent
423 the resolution of a cadence, but without the coordinate itself
424 featuring in the path. It just signals that things should be taken relative
425 to this point, but that the point itself should be burnt after reading.
426
427 """
428 - def __init__(self, coordinate, *args, **kwargs):
433
436
438 return "~%s~" % self.coordinate
439
442
444 return type(self) == type(other) and \
445 self.coordinate == other.coordinate
446
449
450 -class List(LogicalForm, Temporal):
451 """
452 The I{Path} or I{List} data structure. Primarily this is a list of
453 enharmonic coordinates or logical forms. In the written semantics, this
454 is shown as a list, but it is subject to certain reductions, such as when
455 a predicate (like I{leftonto}) is applied to it.
456
457 The items are implemented simply as a Python list, which should
458 make list processing much more efficient than if we used a
459 linked list implementation that corresponds better to the
460 theoretical definition of the semantics.
461
462 """
463 - def __init__(self, items=[], *args, **kwargs):
469
471 return "[%s]" % ", ".join(str(child) for child in self)
472
475
477 return type(self) == type(other) and \
478 all(this==that for (this,that) in zip(self,other))
479
481 if len(self) > 0:
482 return self[0].get_start_time()
483 else:
484
485 return None
486
490
492 """
493 Appends the point to the path.
494
495 """
496 self._items.append(point)
497 point.parent = self
498
500 """
501 Prepends the point to the start of the path.
502
503 Same as L{insert}(0, point).
504
505 """
506 self.insert(0, point)
507
509 """
510 Appends all points in the given list to the points of the path.
511
512 """
513 self._items.extend(points)
514 for p in points:
515 p.parent = self
516
518 return len(self._items)
519
521 return self._items[i]
523 self._items[i] = val
524 val.parent = self
525
527 """Removes and returns the ith point from the path"""
528 child = self._items.pop(i)
529
530 child.parent = None
531 return child
532
534 """Inserts a point at the given position on the path"""
535 self._items.insert(i, point)
536 point.parent = self
537
539 """
540 Moves all the points on the path to another block, given relative to
541 their old one C{shift}.
542
543 @type shift: 2-tuple of ints
544 @param shift: block shift to apply to each point on the path.
545
546 """
547 raise NotImplementedError, "I haven't got round to doing this yet"
548
550 items = [p.copy() for p in self]
551 return List(items=items)
552
554 """
555 Implemented for backward-compatibility with older formalisms. Just
556 returns list(path).
557 """
558 return list(self)
559
560
564
569
573
580
583
586
589
591 if type(self) != type(other):
592 return False
593 if len(self) != len(other):
594 return False
595
596
597 for our_child,its_child in izip(self, other):
598 if not our_child.alpha_equivalent(its_child, substitution):
599 return False
600 return True
601
602 -class ListCat(LogicalForm, Temporal):
603 """
604 Represents the concatenation of multiple L{List}s. As soon as all of the
605 elements beta-reduce to L{List}s, this will beta-reduce to a list as
606 well. This allows you to concatenate things that will be lists, but
607 aren't yet.
608
609 """
610 - def __init__(self, lists=[], *args, **kwargs):
617
619 return "+".join([str(l) for l in self.lists])
620
624
626 return type(self) == type(other) and \
627 self.lists == other.lists
628
630 return len(self.lists)
631
633 if len(self.lists) > 0:
634 return self.lists[0].get_start_time()
635 else:
636 return []
637
639 if len(self.lists) > 0:
640 self.lists[0].set_time(time)
641
643 lists = [l.copy() for l in self.lists]
644 return ListCat(lists=lists)
645
646
650
652
653 for child in self.get_children():
654 child.beta_reduce()
655
656 if all(isinstance(l, List) for l in self.lists):
657
658 biglist = self.lists[0]
659 for latter in self.lists[1:]:
660 biglist.extend(latter)
661
662 self.replace_in_parent(biglist)
663 return biglist
664 else:
665 return self
666
670
679
682
685
687 return [p for p in self.lists]
688
690 if type(self) != type(other):
691 return False
692 if len(self.lists) != len(other.lists):
693 return False
694 for our_child,its_child in zip(self.lists, other.lists):
695 if not our_child.alpha_equivalent(its_child, substitution):
696 return False
697 return True
698
701 """
702 Special operator to represent coordination. Beta reduction collapses
703 nested coordinations into one.
704
705 """
706 - def __init__(self, cadences=[], *args, **kwargs):
712
714 return "(%s)" % " & ".join([str(c) for c in self._cadences])
715
718
720 return type(self) == type(other) and \
721 all(this==that for (this,that) in zip(self,other))
722
724 return len(self._cadences)
726 return self._cadences[i]
728 self._cadences[i] = val
729 val.parent = self
730
732 if len(self):
733 return self[0].get_start_time()
734 else:
735 return None
736
738 self._cadences.append(cad)
739 cad.parent = self
740
742 for cad in other:
743 self.append(cad)
744
746 self._cadences.insert(i, item)
747 item.parent = self
748
752
756
757
761
763
764 for child in self.get_children():
765 child.beta_reduce()
766
767 coords = [(i,c) for (i,c) in enumerate(self) if isinstance(c, Coordination)]
768 shift = 0
769 for i,coord in coords:
770
771 self._cadences.pop(i+shift)
772 shift -= 1
773
774 for child in coord:
775 self.insert(i+shift+1, child)
776 shift += 1
777
778 coord.parent = None
779 return self
780
784
793
796
799
802
804 if type(self) != type(other):
805 return False
806 if len(self) != len(other):
807 return False
808 for our_child,its_child in zip(self, other):
809 if not our_child.alpha_equivalent(its_child, substitution):
810 return False
811 return True
812
813
815 """
816 Just as with predicates, reduce when this is applied to a list.
817
818 """
819 if isinstance(argument, List):
820 argument[0] = FunctionApplication(self, argument[0])
821 return argument
822 else:
823
824 return None
825
827 """
828 Standard variable implementation overridden to add time
829 processing.
830
831 """
835
838
841
844
847
849 """
850 Standard abstraction implementation overridden to add time
851 processing.
852
853 """
854 VARIABLE_CLASS = Variable
855
859
862
865
873
876
878 """
879 Standard application implementation overridden to add time
880 processing.
881
882 """
885
887 """
888 Special str to make predicates appear in FOL style, rather than HOL
889 style, which is the default.
890
891 """
892 if isinstance(self.functor, Predicate):
893 return "%s(%s)" % (self.functor.predicate_str(), self.argument)
894 else:
895 return FunctionApplicationBase.__str__(self)
896
903
906
908 """
909 Superclass of literal predicates (such as leftonto).
910
911 """
912 timed_object = True
913
919
923
930
932 """
933 Returns a string that should be used as the str representation of
934 this predicate when it's used in a function application:
935 e.g. leftonto(...)
936
937 """
938 if self.time is not None and OPTIONS.OUTPUT_ALL_TIMES:
939 return "%s@%d" % (self.name, self.time)
940 else:
941 return self.name
942
946
948 """
949 Implements the special behaviours of predicates when applied to
950 lists.
951
952 """
953 if isinstance(argument, List):
954 argument[0] = FunctionApplication(self, argument[0])
955 return argument
956 else:
957
958 return None
959
962
965
968
970 """
971 A leftonto predicate literal. Apply leftonto to a path using
972 a function application:
973 (leftonto ...)
974
975 """
978
980 """
981 A rightonto predicate literal, analagous to L{Leftonto}.
982
983 """
986
987 -class Now(Predicate):
988 """
989 A special predicate that doesn't change its argument at all, except to
990 set its start time to be no later than the start time associated with the
991 predicate.
992
993 """
996
998 """
999 Returns a string that should be used as the str representation of
1000 this predicate when it's used in a function application:
1001 e.g. leftonto(...)
1002
1003 """
1004 if self.time is not None:
1005 return "%s@%d" % (self.name, self.time)
1006
1008 """
1009 Override function application behaviour.
1010
1011 If we're applied to a coordinate or a predicate application, we give
1012 them our time and burn after reading.
1013
1014 """
1015
1016 modified = super(Now, self)._function_apply(argument)
1017 if modified is not None:
1018
1019
1020
1021
1022 return modified
1023
1024 if isinstance(argument, EnharmonicCoordinate):
1025
1026 argument.set_time(self.time)
1027 elif isinstance(argument, FunctionApplication) and \
1028 isinstance(argument.functor, (Leftonto,Rightonto,Coordination,Now)):
1029
1030
1031
1032
1033 argument.functor.set_time(self.time)
1034 elif isinstance(argument, Coordination):
1035
1036 argument[0].set_time(self.time)
1037 else:
1038 return None
1039 return argument
1040
1047 """
1048 Simple subclass of EnharmonicCoordinate to store points on a path directly.
1049 Doesn't provide all of the fancy coordinate manipulation stuff provided
1050 by EC: use EC itself for that.
1051
1052 This is a special
1053 kind of semantics for storing results from backoff models.
1054
1055 """
1059
1063
1068
1072
1077
1078 @staticmethod
1083
1085 """
1086 Just a list of enharmonic coordinates, forming a tonal space path.
1087 Should not be used as part of the regular semantics. This is a special
1088 kind of semantics for storing results from backoff models.
1089
1090 """
1091 - def __init__(self, items=[], *args, **kwargs):
1096
1098 return "[%s]" % ", ".join(str(child) for child in self)
1099
1102
1104 return type(self) == type(other) and \
1105 all(this==that for (this,that) in zip(self,other))
1106
1110
1112 """
1113 Appends the point to the path.
1114
1115 """
1116 if type(point) != PathCoordinate:
1117 raise TypeError, "CoordinateList can only store PathCoordinates"
1118 self._items.append(point)
1119 point.parent = self
1120
1122 """
1123 Appends all points in the given list to the points of the path.
1124
1125 """
1126 if not all(type(p) == PathCoordinate for p in points):
1127 raise TypeError, "CoordinateList can only store PathCoordinates"
1128 self._items.extend(points)
1129 for p in points:
1130 p.parent = self
1131
1133 return len(self._items)
1134
1136 return self._items[i]
1138 if type(val) != PathCoordinate:
1139 raise TypeError, "CoordinateList can only store PathCoordinates"
1140 self._items[i] = val
1141 val.parent = self
1142
1146
1150
1154
1158
1167
1170
1173
1176
1178
1179 return self == other
1180
1181
1182 -def apply(fn, arg, grammar=None):
1183 """
1184 Applies fn to arg by creating a FunctionApplication and returns the
1185 result. This is used for formalism-unspecific access to this feature.
1186
1187 """
1188 new_sem = Semantics(FunctionApplication(fn.lf, arg.lf))
1189 new_sem.beta_reduce()
1190 return new_sem
1191
1217
1220
1223
1225 """
1226 Returns the concatenation of the two lists.
1227 Uses existing instances passed in, so make sure they're copies
1228 if you don't want your original objects modified.
1229
1230 """
1231 cat = Semantics(ListCat([lst1.lf, lst2.lf]))
1232 cat.beta_reduce()
1233 return cat
1234
1250
1252 """
1253 Produces a list of (x,y) coordinates in the tonal space, given
1254 a L{List} or L{CoordinateList}.
1255
1256 @type lst: L{List}
1257 @param lst: list from the semantics representing constraints on a TS path
1258 @type start_block: pair of ints
1259 @param start_block: enharmonic block to start in (if other than
1260 the default (0,0))
1261 @return: 2D coordinates of the path through the tonal space and the
1262 times at which they were reached.
1263
1264 """
1265 if isinstance(lst, CoordinateList):
1266 path = [p.harmonic_coord for p in lst]
1267 times = [p.time for p in lst]
1268 return zip(path, times)
1269
1270 def _remove_nows(el):
1271 if isinstance(el, FunctionApplication) and isinstance(el.functor, Now):
1272
1273 return _remove_nows(el.argument)
1274 else:
1275 for child in el.get_children():
1276 el.replace_immediate_constituent(child, _remove_nows(child))
1277 return el
1278
1279 def _to_coords(el):
1280 if isinstance(el, EnharmonicCoordinate):
1281
1282 return [(EnharmonicCoordinate((el.x,el.y)), el.time)]
1283 elif isinstance(el, GhostCoordinate):
1284
1285 return [(EnharmonicCoordinate((el.coordinate.x,el.coordinate.y)), el.coordinate.time)]
1286 elif isinstance(el, FunctionApplication):
1287
1288 remove_last = isinstance(el.argument, GhostCoordinate)
1289
1290 argpath = _to_coords(el.argument)
1291
1292 if isinstance(el.functor, Leftonto):
1293 fullpath = [((argpath[0][0]+(1,0)), el.functor.time)] + argpath
1294 elif isinstance(el.functor, Rightonto):
1295 fullpath = [((argpath[0][0]+(-1,0)), el.functor.time)] + argpath
1296 elif isinstance(el.functor, Coordination):
1297
1298 resolution = Semantics(GhostCoordinate(argpath[0][0]))
1299 cadpaths = []
1300 for cad in el.functor:
1301
1302
1303 cadsem = apply(Semantics(cad), resolution.copy())
1304 cadpath = _to_coords(cadsem.lf)
1305
1306 cadpaths.extend(cadpath)
1307
1308 fullpath = cadpaths + argpath
1309 else:
1310 raise SemanticsPostProcessError, "unknown functor %s" % \
1311 el.functor
1312
1313
1314 if remove_last:
1315 fullpath = fullpath[:-1]
1316 return fullpath
1317 elif isinstance(el, Coordination):
1318 raise SemanticsPostProcessError, "can't get a path for a "\
1319 "coordination that's not applied to anything: %s" % el
1320 else:
1321 raise SemanticsPostProcessError, "don't know how to get a path "\
1322 "for the lf %s" % el
1323
1324 start_point = None
1325 path = []
1326
1327 for lf in lst:
1328
1329
1330 lf = _remove_nows(lf)
1331
1332 coords,times = zip(*_to_coords(lf))
1333
1334 if len(path) == 0:
1335
1336 new_block = start_block
1337 after_block = coords[0].block_coord
1338 else:
1339
1340 before = path[-1][0].harmonic_coord
1341 before_block = path[-1][0].block_coord
1342 after = coords[0].harmonic_coord
1343 after_block = coords[0].block_coord
1344
1345
1346 diff = (after[0]-before[0], after[1]-before[1])
1347
1348 rel_pitch = coordinate_to_et_2d(diff)
1349 """
1350 Each ET pitch has a closest instance to the start point: choose
1351 the one for this pitch.
1352 They're in a costellation around the base point (before) like this:
1353 VI III VII
1354 bVII IV I V II
1355 bV bII bVI bIII
1356
1357 """
1358 rel_coord = {
1359 0 : (0,0),
1360 1 : (-1,-1),
1361 2 : (2,0),
1362 3 : (1,-1),
1363 4 : (0,1),
1364 5 : (-1,0),
1365 6 : (-2,-1),
1366 7 : (1,0),
1367 8 : (0,-1),
1368 9 : (-1,1),
1369 10 : (-2,0),
1370 11 : (1,1),
1371 }[rel_pitch]
1372
1373 new_start = (before[0]+rel_coord[0], before[1]+rel_coord[1])
1374
1375 new_start_coord = EnharmonicCoordinate.from_harmonic_coord(new_start)
1376 assert coords[0].zero_coord == new_start_coord.zero_coord
1377 new_block = new_start_coord.block_coord
1378 block_shift = (new_block[0]-after_block[0], new_block[1]-after_block[1])
1379
1380
1381 for coord in coords:
1382 coord.X += block_shift[0]
1383 coord.Y += block_shift[1]
1384 path.extend(zip(coords,times))
1385 return [(coord.harmonic_coord, time) for (coord, time) in path]
1386
1388 """
1389 Like L{list_lf_to_coordinates}, but produces a list of (function,time)
1390 pairs instead of (coordinate,time).
1391
1392 @type lst: L{List}
1393 @param lst: list from the semantics representing constraints on a TS path
1394 @return: function strings of points on the path through the tonal space
1395 and the times at which they were reached.
1396
1397 """
1398 if isinstance(lst, CoordinateList):
1399 funs = [p.function for p in lst]
1400 times = [p.time for p in lst]
1401 return zip(funs, times)
1402
1403 def _to_funs(el):
1404 if isinstance(el, EnharmonicCoordinate):
1405
1406 return [("T", EnharmonicCoordinate((el.x,el.y)), el.time)]
1407 elif isinstance(el, GhostCoordinate):
1408
1409
1410 return [("GHOST",
1411 EnharmonicCoordinate((el.coordinate.x,el.coordinate.y)),
1412 el.coordinate.time)]
1413 elif isinstance(el, FunctionApplication):
1414
1415 remove_last = isinstance(el.argument, GhostCoordinate)
1416
1417 argpath = _to_funs(el.argument)
1418
1419 if isinstance(el.functor, Leftonto):
1420 fullpath = [("D", (argpath[0][1]+(1,0)), el.functor.time)] + argpath
1421 elif isinstance(el.functor, Rightonto):
1422 fullpath = [("S", (argpath[0][1]+(-1,0)), el.functor.time)] + argpath
1423 elif isinstance(el.functor, Coordination):
1424
1425 resolution = Semantics(GhostCoordinate(argpath[0][1]))
1426 cadpaths = []
1427 for cad in el.functor:
1428
1429
1430 cadsem = apply(Semantics(cad), resolution.copy())
1431 cadpath = _to_funs(cadsem.lf)
1432
1433 cadpaths.extend(cadpath)
1434
1435 fullpath = cadpaths + argpath
1436 else:
1437 raise SemanticsPostProcessError, "unknown functor %s" % \
1438 el.functor
1439
1440
1441 if remove_last:
1442 fullpath = fullpath[:-1]
1443 return fullpath
1444 elif isinstance(el, Coordination):
1445 raise SemanticsPostProcessError, "can't get a path for a "\
1446 "coordination that's not applied to anything: %s" % el
1447 else:
1448 raise SemanticsPostProcessError, "don't know how to get a path "\
1449 "for the lf %s" % el
1450
1451 start_point = None
1452 path = []
1453
1454
1455
1456 path = sum((_to_funs(lf) for lf in lst), [])
1457
1458 funs,coords,times = zip(*path)
1459 path = zip(funs,times)
1460 return path
1461
1464
1466 """
1467 Generates a list of (coordinates,time) tuples given a parse result.
1468
1469 @raise ValueError: if the result can't produce a list of coordinates.
1470
1471 """
1472 if not isinstance(sems.lf, (List, CoordinateList)):
1473
1474 raise ValueError, "can't generate coordinates from a %s" % \
1475 type(sems.lf).__name__
1476 return list_lf_to_coordinates(sems.lf)
1477
1479 """
1480 Generates a list of (function,time) tuples given a parse result.
1481
1482 @raise ValueError: if the result can't produce a list of coordinates.
1483
1484 """
1485 if not isinstance(sems.lf, (List, CoordinateList)):
1486
1487 raise ValueError, "can't generate a function list from a %s" % \
1488 type(sems.lf).__name__
1489 return list_lf_to_functions(sems.lf)
1490
1493 """
1494 Builds a logical form given a list of states and the
1495 chords they were assigned to. Accepts a list of labels,time
1496 and return a logical form for the formalism (a L{Semantics} in this
1497 case).
1498
1499 This uses the special logical form classes L{CoordinateList} and
1500 L{PathCoordinate}.
1501
1502 """
1503 from jazzparser.data import Chord, Fraction
1504
1505 points = []
1506
1507
1508 ((first_point,first_fun), first_time) = states[0]
1509 first = PathCoordinate.from_enharmonic_coord(
1510 EnharmonicCoordinate((first_point[2],first_point[3])))
1511 first.fun = first_fun
1512 first.time = first_time
1513
1514 points = [first]
1515 for ((point,fun),time) in states[1:]:
1516
1517 dX,dY,x,y = point
1518
1519 nearest = points[-1].nearest((x,y))
1520
1521 nearest.X += dX
1522 nearest.Y += dY
1523
1524 coord = PathCoordinate.from_enharmonic_coord(nearest)
1525 coord.function = fun
1526 coord.time = time
1527 points.append(coord)
1528
1529 if len(points):
1530
1531 previous = points[0]
1532 remove = []
1533 for point in points[1:]:
1534 if point.harmonic_coord == previous.harmonic_coord and \
1535 point.function == previous.function:
1536 remove.append(id(point))
1537 previous = point
1538 points = [p for p in points if id(p) not in remove]
1539
1540
1541 path = CoordinateList(items=points)
1542
1543 return Semantics(path)
1544
1564
1565
1566 keys = []
1567 last_key = None
1568 for lf in sems.lf:
1569
1570 key = _get_key(lf)
1571
1572 if key != last_key:
1573 keys.append((key, lf.get_start_time()))
1574 last_key = key
1575 return keys
1576
1579 """
1580 Builds a L{Semantics} instance from a string representation of the logical
1581 form. This is mainly for testing and debugging and shouldn't be used in
1582 the wild (in the parser, for example). This is not how we construct
1583 logical forms out of the lexicon: they're specified in XML, which is a
1584 safer way to build LFs, though more laborious to write.
1585
1586 The strings may be constructed as follows.
1587
1588 B{Tonic semantics}: "<x,y>". This will build an L{EnharmonicCoordinate}.
1589
1590 B{List}: "[ITEM0, ITEM1, ...]". This will build a L{List}.
1591
1592 B{List concatenation}: "LIST0+LIST1", that is with infix notation.
1593 This will build a L{ListCat}.
1594
1595 B{Variable}: "$x0". Begin every variable with a $, as if you're writing PHP
1596 or Bash, or something. Use any variable name and, optionally, a number.
1597 If no number is given, 0 will be used by default.
1598
1599 B{Predicates} - leftonto, rightonto: "leftonto(...)", etc. Builds a
1600 L{FunctionApplication} with the predicate as its functor.
1601
1602 B{Now}: "now@x(...)". Works like the other predicates, but has the special
1603 "@x" value to give it a time.
1604
1605 B{Lambda abstraction}: "\\$x.EXPR". Use a backslash to represent a lambda.
1606 You can have multiple abstracted variables, which will result in multiple
1607 nested abstractions. EXPR can be any expression.
1608
1609 B{Function application}: "(FUNCTOR ARGUMENT)". Always enclose a function
1610 application in brackets, even if you wouldn't write them.
1611
1612 Note that the output is not beta-reduced.
1613
1614 """
1615 def _find_matching(s, opener="[", closer="]"):
1616 opened = 0
1617 for i,char in enumerate(s):
1618 if char == closer:
1619 if opened == 0:
1620 return i
1621 else:
1622 opened -= 1
1623 elif char == opener:
1624 opened += 1
1625
1626 raise SemanticsStringBuildError, "%s was not matched by a %s in %s" % \
1627 (opener, closer, s)
1628
1629
1630 def _build_lf(text):
1631 text = text.strip()
1632 if text.startswith("<"):
1633
1634 end = text.find(">")
1635 if end == -1:
1636 raise SemanticsStringBuildError, "unclosed enharmonic "\
1637 "coordinate: %s" % text
1638 coord = text[1:end].split(",")
1639 if len(coord) != 2:
1640 raise SemanticsStringBuildError, "enharmonic coordinate must "\
1641 "be a pair of values: %s" % text
1642 coord = int(coord[0]), int(coord[1])
1643 lf = EnharmonicCoordinate.from_harmonic_coord(coord)
1644 leftover = text[end+1:]
1645 elif text.startswith("["):
1646
1647
1648 end = _find_matching(text[1:]) + 1
1649
1650 remainder = text[1:end]
1651 items = []
1652
1653 while len(remainder):
1654 item, remainder = _build_lf(remainder)
1655 remainder = remainder.strip()
1656 if len(remainder):
1657 if not remainder.startswith(","):
1658 raise SemanticsStringBuildError, "could not parse %s in "\
1659 "logical form %s" % (remainder, string)
1660 else:
1661 remainder = remainder[1:]
1662 items.append(item)
1663 lf = List(items=items)
1664 leftover = text[end+1:]
1665 elif text.startswith("$"):
1666
1667
1668 non_alph = re.compile(r'[^a-zA-Z0-9]')
1669 match = non_alph.search(text[1:])
1670 if match is None:
1671
1672 end = len(text)
1673 else:
1674 end = match.start() + 1
1675
1676
1677 var_splitter = re.compile(r'^(?P<name>.+?)(?P<number>\d*)$')
1678 match = var_splitter.match(text[1:end])
1679 if match is None:
1680 raise SemanticsStringBuildError, "invalid variable name '%s'" % \
1681 text[:end]
1682 matchgd = match.groupdict()
1683 var_name,var_num = matchgd['name'], matchgd['number']
1684 if var_num == "":
1685 var_num = 0
1686 else:
1687 var_num = int(var_num)
1688 lf = Variable(var_name, var_num)
1689 leftover = text[end+1:]
1690 elif text.startswith("leftonto(") or text.startswith("rightonto(") or \
1691 text.startswith("now@"):
1692
1693 if text.startswith("leftonto("):
1694 predicate = "leftonto"
1695 pred_obj = Leftonto()
1696 pred_len = len(predicate)
1697 elif text.startswith("rightonto("):
1698 predicate = "rightonto"
1699 pred_obj = Rightonto()
1700 pred_len = len(predicate)
1701 else:
1702 predicate = "now"
1703 pred_obj = Now()
1704
1705 pred_len = text.find("(")
1706 time = int(text[4:pred_len])
1707 pred_obj.time = time
1708
1709
1710 end = _find_matching(text[pred_len+1:],
1711 opener="(", closer=")") + pred_len + 1
1712 arg,rest = _build_lf(text[pred_len+1:end])
1713
1714
1715 if len(rest.strip()):
1716 raise SemanticsStringBuildError, "could not parse '%s' in "\
1717 "argument to %s in '%s'" % (rest,predicate,string)
1718
1719
1720 lf = FunctionApplication(
1721 pred_obj,
1722 arg)
1723 leftover = text[end+1:]
1724 elif text.startswith("\\"):
1725
1726
1727 dot = text.find(".")
1728 if dot == -1:
1729 raise SemanticsStringBuildError, "lambda abstraction needs a "\
1730 "dot: %s (in %s)" % (text,string)
1731
1732 var_text = text[1:dot]
1733 variables = []
1734
1735 while len(var_text.strip()):
1736 var_text.lstrip(",")
1737 variable,var_text = _build_lf(var_text)
1738 if not isinstance(variable, Variable):
1739 raise SemanticsStringBuildError, "bad variable %s in "\
1740 "lambda abstraction (%s)" % (variable,string)
1741 variables.append(variable)
1742
1743
1744 expr, leftover = _build_lf(text[dot+1:])
1745
1746 args = variables + [expr]
1747 lf = multi_abstract(*args)
1748 elif text.startswith("("):
1749
1750
1751 end = _find_matching(text[1:], opener="(", closer=")") + 1
1752
1753 lf,rest = _build_lf(text[1:end])
1754
1755 if len(rest.strip()):
1756
1757 second_lf,rest = _build_lf(rest)
1758
1759 if len(rest.strip()):
1760 raise SemanticsStringBuildError, "don't know what to do "\
1761 "with '%s' in '%s'" % (rest, string)
1762 lf = FunctionApplication(lf, second_lf)
1763
1764 leftover = text[end+1:]
1765 else:
1766 raise SemanticsStringBuildError, "could not parse '%s' in '%s'" % \
1767 (text,string)
1768
1769
1770 leftover = leftover.strip()
1771 if leftover.startswith("+"):
1772
1773
1774 second_lf, leftover = _build_lf(leftover[1:])
1775 lf = ListCat([lf, second_lf])
1776 elif leftover.startswith("&"):
1777
1778 second_lf, leftover = _build_lf(leftover[1:])
1779 lf = Coordination([lf, second_lf])
1780
1781 return lf, leftover
1782
1783
1784 lf,leftover = _build_lf(string.strip())
1785 if len(leftover.strip()):
1786 raise SemanticsStringBuildError, "could not parse the rest of the "\
1787 "string '%s' in '%s'" % (leftover,string)
1788
1789
1790 return Semantics(lf)
1791
1794
1797
1800