10 from rational import Rational
14 def get_display_dpi():
15 str = os.popen ('xdpyinfo | grep "dots per inch"').read ()
16 m = re.match ('([0-9]+)x([0-9]+) dots')
18 display_dpi = (string.atoi (m.group (2)) + string.atoi (m.group (2)))/2
21 scale_alterations = [0, 0, -2, 0, 0,-2,-2]
23 copy_lilypond_input = 1
25 measure_length = Rational (time_sig[0], time_sig[1])
30 server = os.environ['IKEBANASERVER']
34 lilypond_input_log_file = open ("input.log", 'w')
36 def talk_to_lilypond (expression_str):
37 """Send a LISP expression to LilyPond, wait for return value."""
38 if copy_lilypond_input:
39 lilypond_input_log_file.write (expression_str)
40 lilypond_input_log_file.flush ()
42 sock = socket.socket (socket.AF_INET)
43 address = (server, 2904)
44 sock.connect (address)
45 sock.send (expression_str, socket.MSG_WAITALL)
51 (data, from_addr) = sock.recvfrom (1024)
59 def set_measure_number (str, num):
60 return """(make-music 'SequentialMusic 'elements (list
62 (make-property-set 'timeSignatureFraction (cons %d %d)) 'Score)
64 (make-property-set 'measureLength (ly:make-moment %d %d)) 'Score)
66 (make-property-set 'beatLength (ly:make-moment 1 %d)) 'Score)
68 (make-property-set 'currentBarNumber %d) 'Score)
70 %s))""" % (time_sig[0], time_sig[1], time_sig[0],
71 time_sig[1], time_sig[1], num, str)
73 def render_score (filename, ly):
79 open (filename, 'w').write (str)
80 base = os.path.splitext (filename)[0] + '.ps'
81 os.system ('(lilypond %s && gv %s)& ' % (filename, base))
83 def add_start_skip (str, start_skip):
84 return """(make-music 'SequentialMusic 'elements
86 (make-music 'SkipMusic
87 'duration (ly:make-duration 0 0 %d %d))
89 """ % (start_skip.num, start_skip.den, str)
93 class Lilypond_socket_parser:
94 """Maintain state of reading multi-line lilypond output for socket transport."""
95 def __init__ (self, interpret_function):
97 self.interpret_socket_line = interpret_function
100 self.cause_tag = None
103 def parse_line (self, line):
104 fields = string.split (line)
110 elif fields[0] == 'hello':
111 print 'found server: ', fields[1:]
113 elif fields[0] == 'at':
114 offset = (string.atof (fields[1]),
115 string.atof (fields[2]))
117 elif fields[0] == 'nocause':
118 self.cause_tag = None
121 elif fields[0] == 'cause':
122 self.cause_tag = string.atoi (fields[1])
123 self.name = fields[2][1:-1]
124 self.bbox = tuple (map (string.atof, fields[3:]))
127 return self.interpret_socket_line (offset, self.cause_tag,
128 self.bbox, self.name,
131 class Notation_controller:
132 """Couple Notation and the music model. Stub for now. """
133 def __init__ (self, music_document):
134 self.document = music_document
136 self.notation = Notation (self)
137 self.parser = Lilypond_socket_parser (self.interpret_line)
138 self.start_moment = Rational (0)
139 self.stop_moment = Rational (3)
141 def interpret_line (self, offset, cause, bbox, name, fields):
142 notation_item = self.notation.add_item (offset, cause, bbox, fields)
143 notation_item.name = name
145 def touch_document (self):
146 self.document.touched = True
148 def update_notation(self):
155 ok = (x.start >= self.start_moment and
156 x.start + x.length() <= self.stop_moment)
160 return x.name() in ('RestEvent','NoteEvent') and sub(x)
162 start_note = expr.find_first (sub2)
164 start_skip = start_note.start - start_note.start.floor()
166 str = expr.lisp_sub_expression (sub)
167 str = add_start_skip (str, start_skip)
169 bar_count = (self.start_moment / measure_length).floor()
170 str = set_measure_number (str, bar_count.num)
171 str = talk_to_lilypond (str)
172 self.parse_socket_file (str)
174 def ensure_visible (self, when):
175 new_start = max ((when - measure_length).floor(), Rational(0))
176 new_stop = new_start + Rational (measure_count) * measure_length
178 if new_start <> self.start_moment or new_stop <> self.stop_moment:
179 # print "render interval", new_start, new_stop
180 self.touch_document()
182 self.start_moment = new_start
183 self.stop_moment = new_stop
185 def parse_socket_file (self, str):
186 self.notation.clear ()
187 lines = string.split (str, '\n')
188 self.parse_lines (lines)
189 self.notation.touched = True
190 self.document.touched = False
192 def parse_lines (self, lines):
194 self.parser.parse_line (l)
197 """A single notation element (corresponds to a Grob in LilyPond)"""
199 self.origin_tag = None
205 self.canvas_item = None
206 self.music_expression = None
209 """A complete line/system/page of LilyPond output. Consists of a
210 number of Notation_items"""
212 def __init__ (self, controller):
214 self.notation_controller = controller
216 self.cursor_touched = True
217 self.cursor_callback = None
219 toplevel = controller.document.music
220 self.music_cursor = toplevel.find_first (lambda x: x.name()== "NoteEvent")
222 def get_document (self):
223 return self.notation_controller.document
225 def add_item (self, offset, cause, bbox, fields):
226 item = Notation_item()
228 item.args = map (eval, fields[1:])
230 item.origin_tag = cause
232 if cause and cause >= 0:
233 item.music_expression = self.get_document ().tag_dict[cause]
237 self.items.append (item)
243 def paint_on_canvas (self, canvas):
244 for w in canvas.root().item_list:
249 c_item = canvas.create_canvas_item (i)
251 canvas.set_cursor_to_music (self.music_cursor)
254 def set_cursor (self, music_expr):
255 if music_expr <> self.music_cursor:
256 self.music_cursor = music_expr
257 self.cursor_touched = True
258 self.ensure_cursor_visible ()
259 proc = self.set_cursor_callback
263 def cursor_move (self, dir):
264 mus = self.music_cursor
265 if mus.parent.name() == 'EventChord':
268 mus = mus.parent.get_neighbor (mus, dir)
269 mus = mus.find_first (lambda x: x.name() in ('NoteEvent', 'RestEvent'))
270 self.set_cursor (mus)
272 def cursor_move_chord (self, dir):
273 mus = self.music_cursor
274 if mus.name ()=='NoteEvent':
275 current_steps = mus.pitch.steps()
276 other_steps = [(note, note.pitch.steps()) for
277 note in mus.parent.elements if note.name()=='NoteEvent']
286 bound_set = [(note, dir * step) for (note, step) in other_steps
287 if dir * (step - current_steps) > 0]
290 self.set_cursor (bound_set[0][0])
292 def insert_at_cursor (self, music, dir):
293 mus = self.music_cursor
294 if mus.parent.name() == 'EventChord':
297 mus.parent.insert_around (mus, music, dir)
298 self.touch_document()
300 def touch_document (self):
301 self.get_document ().touched = True
303 def check_update (self):
304 if self.get_document().touched:
305 self.notation_controller.update_notation ()
307 def backspace (self):
308 mus = self.music_cursor
309 if (mus.parent.name() == 'EventChord'
310 and len (mus.parent.elements) <= 1):
314 neighbor = mus.parent.get_neighbor (mus, -1)
315 mus.parent.delete_element (neighbor)
316 self.touch_document ()
318 def change_octave (self, dir):
319 if self.music_cursor.name() == 'NoteEvent':
320 p = self.music_cursor.pitch
322 self.touch_document ()
324 def set_step (self, step):
326 if self.music_cursor.name() == 'NoteEvent':
329 p = self.music_cursor.pitch
332 p1.alteration = scale_alterations [step]
334 orig_steps = p.steps ()
335 new_steps = p1.steps ()
336 diff = new_steps - orig_steps
342 self.music_cursor.pitch = p1
343 self.touch_document ()
346 print 'not a NoteEvent'
348 def add_step (self, step):
350 if self.music_cursor.name() == 'NoteEvent':
353 p = self.music_cursor.pitch
357 orig_steps = p.steps ()
358 new_steps = p1.steps ()
359 diff = new_steps - orig_steps
365 new_ev = music.NoteEvent()
367 new_ev.duration = self.music_cursor.duration.copy()
369 self.music_cursor.parent.insert_around (self.music_cursor,
371 self.music_cursor = new_ev
372 self.touch_document ()
374 print 'not a NoteEvent'
376 def change_step (self, dstep):
378 if self.music_cursor.name() == 'NoteEvent':
381 p = self.music_cursor.pitch
392 p1.alteration = scale_alterations [p1.step]
394 self.music_cursor.pitch = p1
395 self.touch_document ()
398 print 'not a NoteEvent'
400 def change_duration_log (self, dir):
401 if ( self.music_cursor.name() == 'NoteEvent'
402 or self.music_cursor.name() == 'RestEvent'):
404 m = self.music_cursor
405 dur = self.music_cursor.duration
406 dl = dur.duration_log
408 if dl > 6 and dl < -2:
411 evs = [x for x in m.parent.elements if x.name() in ('NoteEvent', 'RestEvent')]
413 e.duration.duration_log = dl
415 self.touch_document ()
417 def ensure_note (self):
418 if self.music_cursor.name() == 'RestEvent':
419 m = self.music_cursor
420 note = music.NoteEvent()
421 m.parent.insert_around (None, note, 1)
422 m.parent.delete_element (m)
423 self.music_cursor = note
424 self.touch_document ()
426 def ensure_rest (self):
427 if self.music_cursor.name() == 'NoteEvent':
428 m = self.music_cursor
429 rest = music.RestEvent()
430 m.parent.insert_around (None, rest, 1)
431 m.parent.delete_element (m)
432 self.music_cursor = rest
433 self.touch_document ()
435 def change_dots (self):
436 if self.music_cursor.name() == 'NoteEvent':
437 p = self.music_cursor.duration
442 self.touch_document ()
444 def ensure_cursor_visible(self):
445 self.notation_controller.document.recompute()
446 self.notation_controller.ensure_visible (self.music_cursor.start)
448 def change_alteration (self, dir):
449 if self.music_cursor.name() == 'NoteEvent':
450 p = self.music_cursor.pitch
452 new_alt = p.alteration + dir
453 if abs (new_alt) <= 4:
454 p.alteration = new_alt
455 self.touch_document ()
457 def set_alteration (self, alter):
458 if self.music_cursor.name() == 'NoteEvent':
459 p = self.music_cursor.pitch
461 self.touch_document ()
463 def toggle_arpeggio (self):
464 par = self.music_cursor.parent
465 if par.name()== "EventChord":
466 arps = [e for e in par.elements if e.name()=='ArpeggioEvent']
468 par.delete_element (arps[0])
470 arp = music.ArpeggioEvent()
471 par.insert_around (self.music_cursor, arp, -1)
472 self.touch_document()
474 def print_score(self):
475 doc = self.notation_controller.document
476 ly = doc.music.ly_expression()
477 render_score('score.ly', ly)
480 if self.music_cursor.name () == 'NoteEvent':
481 note = music.NoteEvent ()
482 note.pitch = self.music_cursor.pitch.copy()
483 note.duration = self.music_cursor.duration.copy()
485 ch = music.EventChord ()
486 ch.insert_around (None, note, 0)
488 self.insert_at_cursor (ch, 1)
490 self.touch_document ()
492 elif self.music_cursor.name () == 'RestEvent':
493 rest = music.RestEvent ()
494 rest.duration = self.music_cursor.duration.copy()
496 ch = music.EventChord ()
497 ch.insert_around (None, rest, 0)
499 self.insert_at_cursor (ch, 1)
501 self.touch_document ()