From a9d103fa6408a22451becea29a8b11f237ef9646 Mon Sep 17 00:00:00 2001 From: hanwen Date: Thu, 21 Jul 2005 13:50:36 +0000 Subject: [PATCH] *** empty log message *** --- ChangeLog | 9 ++ ikebana.py | 16 +++- music.py | 12 ++- notation.py | 225 +++++++++++++++++++++++++++++++++++++++------- notationcanvas.py | 214 +++++++++++++++++++++++++++---------------- 5 files changed, 363 insertions(+), 113 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0cdb5ae215..e6a21fd68f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2005-07-20 Han-Wen Nienhuys + + * notationcanvas.py (Notation_toolbar.__init__): new class + Notation_toolbar: add a row of buttons, flash button when pressed. + +2005-07-15 Han-Wen Nienhuys + + * notation.py (talk_to_lilypond): send off lilypond input + 2005-07-13 Jan Nieuwenhuizen * ikebana/notationcanvas.py (Notation_canvas.__init__): White diff --git a/ikebana.py b/ikebana.py index 84cffaf54c..bc16b712f6 100644 --- a/ikebana.py +++ b/ikebana.py @@ -32,7 +32,21 @@ class NotationApplication: canvas = self.notation_canvas_controller.canvas canvas.show () - win.add (canvas) + + tb_type = notationcanvas.Notation_toolbar + toolbar = tb_type (self.notation_canvas_controller.notation, + self.notation_canvas_controller.check_update + ) + + canvas.connect ("key-press-event", toolbar.keypress_callback) + + vbox = gtk.VBox () + vbox.pack_start (canvas, expand=True) + vbox.pack_start (toolbar, expand=False) + vbox.show () + + win.add (vbox) + toolbar.show () win.show() return win diff --git a/music.py b/music.py index e0c7477148..5fd96f38c3 100644 --- a/music.py +++ b/music.py @@ -29,8 +29,15 @@ class Duration: def length (self): dot_fact = ((1 << (1 + self.dots))-1.0)/ (1 << self.dots) - - return 1.0/(1 << self.duration_log) * dot_fact * self.factor[0]/self.factor[1] + + log = abs (self.duration_log) + dur = 1 << log + if self.duration_log < 0: + base = 1.0 * dur + else: + base = 1.0 / dur + + return base * dot_fact * self.factor[0]/self.factor[1] class Pitch: def __init__ (self): @@ -110,6 +117,7 @@ class Music_document: def __init__ (self): self.music = test_expr () self.tag_dict = {} + self.touched = True def recompute (self): self.tag_dict = {} diff --git a/notation.py b/notation.py index 84b306247e..a010b57d42 100644 --- a/notation.py +++ b/notation.py @@ -8,8 +8,28 @@ import music import pango import math + +copy_lilypond_input = 1 +time_sig = (4, 4) +measure_length = (1.0 * time_sig[0]) / time_sig[1] +scale = "'((0 . 0) (1 . 0) (2 . -2) (3 . 0) (4 . 0) (5 . -2) (6 . -2))" +clefsetting = """ + (context-spec-music + (make-property-set 'clefGlyph "clefs.C") 'Staff) + (context-spec-music + (make-property-set 'clefPosition 0) 'Staff) + (context-spec-music + (make-property-set 'middleCPosition 0) 'Staff) +""" + +lilypond_input_log_file = open ("input.log", 'w') + def talk_to_lilypond (expression_str): """Send a LISP expression to LilyPond, wait for return value.""" + if copy_lilypond_input: + lilypond_input_log_file.write (expression_str) + lilypond_input_log_file.flush () + sock = socket.socket (socket.AF_INET) address = ("localhost", 2904) sock.connect (address) @@ -29,9 +49,38 @@ def talk_to_lilypond (expression_str): def set_measure_number (str, num): return """(make-music 'SequentialMusic 'elements (list + (context-spec-music + (make-property-set 'timeSignatureFraction (cons %d %d)) 'Score) + (context-spec-music + (make-property-set 'measureLength (ly:make-moment %d %d)) 'Score) + (context-spec-music + (make-property-set 'beatLength (ly:make-moment 1 %d)) 'Score) (context-spec-music (make-property-set 'currentBarNumber %d) 'Score) - %s))""" % (num,str) + (context-spec-music + (make-music 'EventChord + 'elements + (list + (make-music 'KeyChangeEvent + 'pitch-alist + %s) + )) + 'Staff) + + + %s))""" % (time_sig[0], time_sig[1], time_sig[0], + time_sig[1], time_sig[1], num, scale, str) + +def render_score (filename, ly): + print ly + str = ''' +myNotes = %s +\\score { \myNotes } +''' % ly + open (filename, 'w').write (str) + base = os.path.splitext (filename)[0] + '.ps' + os.system ('(lilypond %s && gv %s)& ' % (filename, base)) + class Lilypond_socket_parser: """Maintain state of reading multi-line lilypond output for socket transport.""" @@ -63,12 +112,13 @@ class Lilypond_socket_parser: return elif fields[0] == 'cause': self.cause_tag = string.atoi (fields[1]) - self.name = fields[2] + self.name = fields[2][1:-1] self.bbox = tuple (map (string.atof, fields[3:])) return return self.interpret_socket_line (offset, self.cause_tag, - self.bbox, fields) + self.bbox, self.name, + fields) class Notation_controller: """Couple Notation and the music model. Stub for now. """ @@ -80,8 +130,9 @@ class Notation_controller: self.start_moment = 0.0 self.stop_moment = 3.0 - def interpret_line (self, offset, cause, bbox, fields): + def interpret_line (self, offset, cause, bbox, name, fields): notation_item = self.notation.add_item (offset, cause, bbox, fields) + notation_item.name = name def update_notation(self): doc = self.document @@ -93,7 +144,6 @@ class Notation_controller: ok = (x.start >= self.start_moment and x.start +x.length() <= self.stop_moment) return ok - str = expr.lisp_sub_expression (sub) str = set_measure_number (str, int (self.start_moment) + 1) @@ -101,13 +151,21 @@ class Notation_controller: self.parse_socket_file (str) def ensure_visible (self, when): - self.start_moment = max (math.floor (when - 1.0), 0.0) - self.stop_moment = self.start_moment + 3.0 - + new_start = max (math.floor (when - measure_length), 0.0) + new_stop = new_start + 3 * measure_length + + if new_start <> self.start_moment or new_stop <> self.stop_moment: + self.document.touched = True + + self.start_moment = new_start + self.stop_moment = new_stop + def parse_socket_file (self, str): self.notation.clear () lines = string.split (str, '\n') self.parse_lines (lines) + self.notation.touched = True + self.document.touched = False def parse_lines (self, lines): for l in lines: @@ -120,6 +178,7 @@ class Notation_item: self.bbox = None self.offset = (0,0) self.tag = None + self.name = '' self.args = [] self.canvas_item = None self.music_expression = None @@ -172,6 +231,7 @@ class Notation_item: coords = self.args[2:] w = canvas.root ().add (type, fill_color = 'black', + outline_color = 'black', width_units = blot, points = coords) @@ -184,30 +244,26 @@ class Notation_item: magnification = 0.5 #ugh: how to get pango_descr_from_string() in pygtk? - - (fam,rest) = tuple (string.split (descr, ',')) + + if descr.find (',') == -1: + (fam,rest) = tuple (string.split (descr, ' ')) + else: + (fam,rest) = tuple (string.split (descr, ',')) size = string.atof (rest) w = canvas.root().add (type, fill_color = 'black', family_set = True, family = fam, - anchor = gtk.ANCHOR_WEST, - y_offset = 0.15, - size_points = size * canvas.pixel_scale * 0.75 * magnification, + anchor = gtk.ANCHOR_SOUTH_WEST, + y_offset = 0.75, + size_points = size * canvas.pixel_scale * 0.87 * magnification, text = str) return w def create_canvas_item (self, canvas): - dispatch_table = {'draw_round_box' : Notation_item.create_round_box_canvas_item, - 'drawline': Notation_item.create_line_canvas_item, - 'glyphshow': Notation_item.create_glyph_item, - 'polygon': Notation_item.create_polygon_item, - 'utf-8' : Notation_item.create_text_item, - } - citem = None try: - method = dispatch_table[self.tag] + method = Notation_item.dispatch_table[self.tag] citem = method (self, canvas) citem.move (*self.offset) citem.notation_item = self @@ -217,6 +273,13 @@ class Notation_item: print 'no such key', self.tag return citem + + dispatch_table = {'draw_round_box' : create_round_box_canvas_item, + 'drawline': create_line_canvas_item, + 'glyphshow':create_glyph_item, + 'polygon': create_polygon_item, + 'utf-8' : create_text_item, + } class Notation: """A complete line/system/page of LilyPond output. Consists of a @@ -225,7 +288,9 @@ class Notation: def __init__ (self, controller): self.items = [] self.notation_controller = controller - + self.touched = True + self.cursor_touched = True + toplevel = controller.document.music self.music_cursor = toplevel.find_first (lambda x: x.name()== "NoteEvent") @@ -245,11 +310,11 @@ class Notation: item.bbox = bbox self.items.append (item) - - + return item + def clear(self): self.items = [] - + def paint_on_canvas (self, canvas): for w in canvas.root().item_list: if w.notation_item: @@ -260,6 +325,11 @@ class Notation: canvas.set_cursor_to_music (self.music_cursor) + def set_cursor (self, music_expr): + self.music_cursor = music_expr + self.cursor_touched = True + self.ensure_cursor_visible () + def cursor_move (self, dir): mus = self.music_cursor if mus.parent.name() == 'EventChord': @@ -267,7 +337,7 @@ class Notation: mus = mus.parent.get_neighbor (mus, dir) mus = mus.find_first (lambda x: x.name() in ('NoteEvent', 'RestEvent')) - self.music_cursor = mus + self.set_cursor (mus) def insert_at_cursor (self, music, dir): mus = self.music_cursor @@ -275,21 +345,31 @@ class Notation: mus = mus.parent mus.parent.insert_around (mus, music, dir) + self.touch_document() + + def touch_document (self): + self.get_document ().touched = True + def check_update (self): + if self.get_document().touched: + self.notation_controller.update_notation () + def backspace (self): mus = self.music_cursor - if mus.parent.name() == 'EventChord': + if mus.parent.name() == 'EventChord' and len (mus.parent.elements) <= 1: mus = mus.parent neighbor = mus.parent.get_neighbor (mus, -1) mus.parent.delete_element (neighbor) - + self.touch_document () def change_octave (self, dir): if self.music_cursor.name() == 'NoteEvent': p = self.music_cursor.pitch p.octave += dir + self.touch_document () - def change_step (self, step): + def set_step (self, step): + self.ensure_note () if self.music_cursor.name() == 'NoteEvent': # relative mode. @@ -306,6 +386,57 @@ class Notation: p1.octave += 1 self.music_cursor.pitch = p1 + self.touch_document () + + else: + print 'not a NoteEvent' + + def add_step (self, step): + self.ensure_note () + if self.music_cursor.name() == 'NoteEvent': + + # relative mode. + p = self.music_cursor.pitch + p1 = p.copy() + p1.step = step + + orig_steps = p.steps () + new_steps = p1.steps () + diff = new_steps - orig_steps + if diff >= 4: + p1.octave -= 1 + elif diff <= -4: + p1.octave += 1 + + new_ev = music.NoteEvent() + new_ev.pitch = p1 + new_ev.duration = self.music_cursor.duration.copy() + + self.music_cursor.parent.insert_around (self.music_cursor, + new_ev, 1) + self.music_cursor = new_ev + self.touch_document () + else: + print 'not a NoteEvent' + + def change_step (self, dstep): + self.ensure_note () + if self.music_cursor.name() == 'NoteEvent': + + # relative mode. + p = self.music_cursor.pitch + p1 = p.copy() + p1.step += dstep + + if p1.step > 6: + p1.step -= 7 + p1.octave += 1 + elif p1.step < 0: + p1.step += 7 + p1.octave -= 1 + + self.music_cursor.pitch = p1 + self.touch_document () else: print 'not a NoteEvent' @@ -319,7 +450,9 @@ class Notation: dl += dir if dl <= 6 and dl >= -3: dur.duration_log = dl - + + self.touch_document () + def ensure_note (self): if self.music_cursor.name() == 'RestEvent': @@ -328,6 +461,7 @@ class Notation: m.parent.insert_around (None, note, 1) m.parent.delete_element (m) self.music_cursor = note + self.touch_document () def ensure_rest (self): if self.music_cursor.name() == 'NoteEvent': @@ -336,6 +470,7 @@ class Notation: m.parent.insert_around (None, rest, 1) m.parent.delete_element (m) self.music_cursor = rest + self.touch_document () def change_dots (self): if self.music_cursor.name() == 'NoteEvent': @@ -344,6 +479,7 @@ class Notation: p.dots = 0 elif p.dots == 0: p.dots = 1 + self.touch_document () def ensure_cursor_visible(self): self.notation_controller.document.recompute() @@ -356,8 +492,35 @@ class Notation: new_alt = p.alteration + dir if abs (new_alt) <= 4: p.alteration = new_alt + self.touch_document () def print_score(self): doc = self.notation_controller.document ly = doc.music.ly_expression() - print ly + render_score('score.ly', ly) + + def add_note (self): + if self.music_cursor.name () == 'NoteEvent': + note = music.NoteEvent () + note.pitch = self.music_cursor.pitch.copy() + note.duration = self.music_cursor.duration.copy() + + ch = music.EventChord () + ch.insert_around (None, note, 0) + + self.insert_at_cursor (ch, 1) + self.cursor_move (1) + self.touch_document () + + elif self.music_cursor.name () == 'RestEvent': + rest = music.RestEvent () + rest.duration = self.music_cursor.duration.copy() + + ch = music.EventChord () + ch.insert_around (None, rest, 0) + + self.insert_at_cursor (ch, 1) + self.cursor_move (1) + self.touch_document () + + diff --git a/notationcanvas.py b/notationcanvas.py index 0df4df2c76..77c8242bab 100644 --- a/notationcanvas.py +++ b/notationcanvas.py @@ -2,14 +2,125 @@ import gtk import gnomecanvas import music + +class Notation_toolbar (gtk.HBox): + def __init__ (self, notation, check_refresh_callback): + gtk.HBox.__init__ (self) + self.button_dict = {} + self.key_dict = {} + self.notation = notation + self.add_buttons () + self.check_refresh_callback = check_refresh_callback + + def click_callback (self, widget): + if not self.button_dict.has_key (widget): + print 'no such widget?' + return False + + cb = self.button_dict[widget] + cb() + self.check_refresh_callback() + return True + + def keypress_callback (self, widget, event): + key = event.keyval + name = gtk.gdk.keyval_name (key) + + if event.get_state () & gtk.gdk.SHIFT_MASK: + name = 'Shift+' + name + if event.get_state () & gtk.gdk.CONTROL_MASK: + name = 'Ctrl+' + name + if not self.key_dict.has_key (name): + print 'no such key?', name + return False + + button = self.key_dict[name] + button.do_activate (button) + return True + + def add_button (self, text, key, callback): + b = gtk.Button (text) + self.pack_start (b, expand=True) + b.connect ('clicked', self.click_callback) + b.set_focus_on_click (False) + self.key_dict[key] = b + self.button_dict[b] = callback + b.show () + + def add_buttons (self): + for (key_name, text, func) in \ + [('Left', '<-', + lambda: self.notation.cursor_move (-1)), + ('Right', '->', + lambda: self.notation.cursor_move (1)), + ('space', 'next', + lambda: self.notation.add_note ()), + ('BackSpace', 'backspace', + lambda: self.notation.backspace ()), + ('Shift+Up', '#', + lambda: self.notation.change_alteration (2)), + ('Shift+Down', 'b', + lambda: self.notation.change_alteration (-2)), + ('Up', 'up', + lambda: self.notation.change_step (1)), + ('Down', 'down', + lambda: self.notation.change_step (-1)), + ('apostrophe', 'oct up', + lambda: self.notation.change_octave (1)), + ('comma', 'oct down', + lambda: self.notation.change_octave (-1)), + ('period', '.', + lambda: self.notation.change_dots ()), + ('slash', 'shorter', + lambda: self.notation.change_duration_log (1)), + ('Shift+asterisk', 'longer', + lambda: self.notation.change_duration_log (-1)), + ('p', 'LilyPond', + lambda: self.notation.print_score()), + ('q', 'quit', + lambda: gtk.main_quit()), + ('r', 'rest', + lambda: self.notation.ensure_rest ()), + ('Shift+C', '+C', + lambda: self.notation.add_step (0)), + ('Shift+D', '+D', + lambda: self.notation.add_step (1)), + ('Shift+E', '+E', + lambda: self.notation.add_step (2)), + ('Shift+F', '+F', + lambda: self.notation.add_step (3)), + ('Shift+G', '+G', + lambda: self.notation.add_step (4)), + ('Shift+A', '+A', + lambda: self.notation.add_step (5)), + ('Shift+B', '+B', + lambda: self.notation.add_step (6)), + ('c', 'C', + lambda: self.notation.set_step (0)), + ('d', 'D', + lambda: self.notation.set_step (1)), + ('e', 'E', + lambda: self.notation.set_step (2)), + ('f', 'F', + lambda: self.notation.set_step (3)), + ('g', 'G', + lambda: self.notation.set_step (4)), + ('a', 'A', + lambda: self.notation.set_step (5)), + ('b', 'B', + lambda: self.notation.set_step (6))]: + + self.add_button (text, key_name, func) + + class Notation_canvas (gnomecanvas.Canvas): """The canvas for drawing notation symbols.""" - def __init__ (self): + def __init__ (self, canvas_controller): gnomecanvas.Canvas.__init__ (self, #aa=True ) - w,h = 800,400 + (w,h) = (800,400) self.set_size_request (w, h) self.set_scroll_region (0, 0, w, h) root = self.root () @@ -19,13 +130,14 @@ class Notation_canvas (gnomecanvas.Canvas): i = root.add (gnomecanvas.CanvasRect, x2 = w, y2 = -h, fill_color = 'white', outline_color = 'white') i.notation_item = None + self.notation_canvas_controller = canvas_controller self.create_cursor () def create_cursor (self): type = gnomecanvas.CanvasRect w = self.root ().add (type, - fill_color = 'None', - outline_color = 'blue') + fill_color = 'lightblue', + outline_color = 'lightblue') w.notation_item = None self.cursor_widget = w @@ -49,20 +161,20 @@ class Notation_canvas (gnomecanvas.Canvas): item.set (fill_color = color) - def item_event (self, widget, event=None): - if 0: - pass - elif event.type == gtk.gdk.ENTER_NOTIFY: - self.item_set_active_state(widget, True) - return True - elif event.type == gtk.gdk.LEAVE_NOTIFY: - self.item_set_active_state(widget, False) - return True + def click_event (self, widget, event = None): + if event <> None and event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 1 and widget.notation_item.name in ('Rest', 'NoteHead'): + + notat = self.notation_canvas_controller.notation + notat.set_cursor (widget.notation_item.music_expression) + self.notation_canvas_controller.check_update() + + return True return False def register_notation_canvas_item (self, citem): if citem.notation_item and citem.notation_item.music_expression: - citem.connect ("event", self.item_event) + citem.connect ("event", self.click_event) def set_cursor_to_music (self, music_expression): c_items = [it for it in self.root().item_list if @@ -70,6 +182,8 @@ class Notation_canvas (gnomecanvas.Canvas): and it.notation_item.music_expression == music_expression)] + c_items = filter (lambda it: it.notation_item.name in ('NoteHead', 'Rest'), + c_items) if c_items: self.set_cursor (c_items[0].notation_item) @@ -77,76 +191,18 @@ class Notation_canvas_controller: """The connection between canvas and the abstract notation graphics.""" def __init__ (self, notation): - self.canvas = Notation_canvas () + self.canvas = Notation_canvas (self) self.notation = notation - self.canvas.connect ("key-press-event", self.keypress) - + def update_cursor (self): self.canvas.set_cursor_to_music (self.notation.music_cursor) def update_canvas (self): self.notation.paint_on_canvas (self.canvas) - - def add_note (self): - note = music.NoteEvent () - if self.notation.music_cursor.name () == 'NoteEvent': - note.pitch = self.notation.music_cursor.pitch.copy() - note.pitch.alteration = 0 - note.duration = self.notation.music_cursor.duration.copy() - - ch = music.EventChord () - ch.insert_around (None, note, 0) - - self.notation.insert_at_cursor (ch, 1) - self.notation.cursor_move (1) - - def keypress (self, widget, event): - key = event.keyval - name = gtk.gdk.keyval_name (key) - if 0: - pass - elif name == 'Left': - self.notation.cursor_move (-1) - self.update_cursor () - - elif name == 'Right': - self.notation.cursor_move (1) + def check_update (self): + self.notation.check_update () + if self.notation.touched: + self.update_canvas () + elif self.notation.cursor_touched: self.update_cursor () - elif name == 'space': - self.add_note () - elif name == 'apostrophe': - self.notation.change_octave (1) - elif name == 'comma': - self.notation.change_octave (-1) - elif name == 'BackSpace': - self.notation.backspace () - elif name == 'plus': - self.notation.change_alteration (2) - elif name == 'minus': - self.notation.change_alteration (-2) - elif name == 'period': - self.notation.change_dots () - elif name == 'slash': - self.notation.change_duration_log (1) - elif name == 'asterisk': - self.notation.change_duration_log (-1) - elif name == 'p': - self.notation.print_score() - elif name == 'q': - gtk.main_quit() - elif name == 'r': - self.notation.ensure_rest () - elif len (name) == 1 and name in 'abcdefg': - step = (ord (name) - ord ('a') + 5) % 7 - self.notation.ensure_note () - self.notation.change_step (step) - else: - print 'Unknown key %s' % name - return False - - self.notation.ensure_cursor_visible () - self.notation.notation_controller.update_notation () - self.notation.paint_on_canvas (self.canvas) - return True - -- 2.39.5