* music.py (Music.set_start): use Rationals for lengths.
* rational.py (Rational.ceil): new class.
* notation.py (add_start_skip): new function
+2005-07-28 Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+ * server.ly: use Completion_heads_engraver
+
+ * music.py (Music.set_start): use Rationals for lengths.
+
+ * rational.py (Rational.ceil): new class.
+
+ * notation.py (add_start_skip): new function
+
2005-07-22 Han-Wen Nienhuys <hanwen@xs4all.nl>
* ikebana.py (NotationApplication.create_window): add zoom.
def mainquit (*args):
gtk.main_quit ()
-
class NotationApplication:
def __init__ (self):
self.music = music.Music_document ()
win.add (vbox)
toolbar.show ()
- toolbar.add_button ('zoom out', 'minus', lambda: canvas.zoom (-1), 0)
- toolbar.add_button ('zoom in', 'Shift+plus', lambda: canvas.zoom (1), 0)
-
win.show()
return win
import string
-
+from rational import Rational
class Duration:
def __init__ (self):
return d
def length (self):
- dot_fact = ((1 << (1 + self.dots))-1.0)/ (1 << self.dots)
+ dot_fact = Rational( (1 << (1 + self.dots))-1,
+ 1 << self.dots)
log = abs (self.duration_log)
dur = 1 << log
if self.duration_log < 0:
- base = 1.0 * dur
+ base = Rational (dur)
else:
- base = 1.0 / dur
+ base = Rational (1, dur)
- return base * dot_fact * self.factor[0]/self.factor[1]
+ return base * dot_fact * Rational (self.factor[0], self.factor[1])
class Pitch:
def __init__ (self):
def __init__ (self):
self.tag = None
self.parent = None
- self.start = 0.0
+ self.start = Rational (0)
pass
def length(self):
- return 0.0
+ return Rational (0)
def set_tag (self, counter, tag_dict):
self.tag = counter
return "(make-music '%s %s %s)" % (name, tag, props)
def set_start (self, start):
- start = round (start * 384) / 384.0
self.start = start
def find_first (self, predicate):
def recompute (self):
self.tag_dict = {}
self.music.set_tag (0, self.tag_dict)
- self.music.set_start (0.0)
+ self.music.set_start (Rational (0))
class NestedMusic(Music):
def __init__ (self):
def ly_expression (self):
return '{ %s }' % string.join (map (lambda x:x.ly_expression(),
- self.elements))
+ self.elements))
+
def lisp_sub_expression (self, pred):
name = self.name()
tag = ''
return "EventChord"
def length (self):
- l = 0.0
+ l = Rational (0)
for e in self.elements:
l = max(l, e.length())
return l
def name (self):
return "Event"
+class ArpeggioEvent(Music):
+ def name (self):
+ return 'ArpeggioEvent'
+
class RhythmicEvent(Event):
def __init__ (self):
Event.__init__ (self)
evc.insert_around (None, n, 0)
m.insert_around (None, evc, 0)
- evc = EventChord()
+ evc = EventChord()
n = NoteEvent()
- n.duration.duration_log = l
- n.pitch.step = 2
+ n.duration.duration_log = 0
+ n.pitch.step = 3
evc.insert_around (None, n, 0)
m.insert_around (None, evc, 0)
- evc = EventChord()
+
+ evc = EventChord()
n = NoteEvent()
n.duration.duration_log = l
- n.pitch.step = 3
+ n.pitch.step = 2
evc.insert_around (None, n, 0)
m.insert_around (None, evc, 0)
if __name__ == '__main__':
expr = test_expr()
- expr.set_start (0.0)
+ expr.set_start (Rational (0))
start = 0.25
stop = 0.5
import music
import pango
import math
+from rational import Rational
+
+display_dpi = 75
+
+def get_display_dpi():
+ str = os.popen ('xdpyinfo | grep "dots per inch"').read ()
+ m = re.match ('([0-9]+)x([0-9]+) dots')
+ if m:
+ display_dpi = (string.atoi (m.group (2)) + string.atoi (m.group (2)))/2
copy_lilypond_input = 1
time_sig = (4, 4)
-measure_length = (1.0 * time_sig[0]) / time_sig[1]
+measure_length = Rational (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 'middleCPosition 0) 'Staff)
"""
-server = 'maagd'
-# server = 'localhost'
+try:
+ server = os.environ['IKEBANASERVER']
+except KeyError:
+ server = 'localhost'
lilypond_input_log_file = open ("input.log", 'w')
break
cont = len (data) > 0
retval += data
-
+
return retval
def set_measure_number (str, num):
%s)
))
'Staff)
-
%s))""" % (time_sig[0], time_sig[1], time_sig[0],
time_sig[1], time_sig[1], num, scale, str)
open (filename, 'w').write (str)
base = os.path.splitext (filename)[0] + '.ps'
os.system ('(lilypond %s && gv %s)& ' % (filename, base))
+
+def add_start_skip (str, start_skip):
+ return """(make-music 'SequentialMusic 'elements
+ (list
+ (make-music 'SkipMusic
+ 'duration (ly:make-duration 0 0 %d %d))
+ %s))
+""" % (start_skip.num, start_skip.den, str)
+
class Lilypond_socket_parser:
self.notation = Notation (self)
self.parser = Lilypond_socket_parser (self.interpret_line)
- self.start_moment = 0.0
- self.stop_moment = 3.0
+ self.start_moment = Rational (0)
+ self.stop_moment = Rational (3)
def interpret_line (self, offset, cause, bbox, name, fields):
notation_item = self.notation.add_item (offset, cause, bbox, fields)
notation_item.name = name
-
+ def touch_document (self):
+ self.document.touched = True
+
def update_notation(self):
doc = self.document
doc.recompute()
def sub(x):
ok = (x.start >= self.start_moment and
- x.start +x.length() <= self.stop_moment)
+ x.start + x.length() <= self.stop_moment)
return ok
+
+ def sub2 (x):
+ return x.name() in ('RestEvent','NoteEvent') and sub(x)
+
+ start_note = expr.find_first (sub2)
+
+ start_skip = start_note.start - start_note.start.floor()
+
+ print 'start_skip = ' , start_skip
+
str = expr.lisp_sub_expression (sub)
- str = set_measure_number (str, int (self.start_moment) + 1)
+ str = add_start_skip (str, start_skip)
+
+ bar_count = (self.start_moment / measure_length).floor()
+ str = set_measure_number (str, bar_count.num)
str = talk_to_lilypond (str)
self.parse_socket_file (str)
def ensure_visible (self, when):
- new_start = max (math.floor (when - measure_length), 0.0)
- new_stop = new_start + 3 * measure_length
+ new_start = max ((when - measure_length).floor(), Rational(0))
+ new_stop = new_start + Rational (3) * measure_length
if new_start <> self.start_moment or new_stop <> self.stop_moment:
- self.document.touched = True
+ print "render interval", new_start, new_stop
+ self.touch_document()
self.start_moment = new_start
self.stop_moment = new_stop
self.args = []
self.canvas_item = None
self.music_expression = None
-
- def create_round_box_canvas_item (self, canvas):
- root = canvas.root()
- type = gnomecanvas.CanvasRect
- (b, w, d, h, blot) = tuple (self.args)
- w = root.add (type,
- fill_color = 'black',
- x1 = - b,
- y1 = - d,
- x2 = w,
- y2 = h)
-
- return w
-
- def create_line_canvas_item (self, canvas):
- type = gnomecanvas.CanvasLine
- (thick, x1, y1, x2, y2) = tuple (self.args)
- w = canvas.root().add (type,
- fill_color = 'black',
- width_units = thick,
- points = [x1, y1, x2, y2]
- )
- return w
-
- def create_glyph_item (self, canvas):
- type = gnomecanvas.CanvasText
- (index, font_name, magnification, name) = tuple (self.args)
- (family, style) = string.split (font_name, '-')
- w = canvas.root().add (type,
- fill_color = 'black',
- family = family,
- family_set = True,
- anchor = gtk.ANCHOR_WEST,
- y_offset = 0.15,
-
- size_points = canvas.pixel_scale * 0.75 * magnification,
- x = 0, y = 0,
- text = unichr (index))
- return w
-
-
- def create_polygon_item (self, canvas):
- type = gnomecanvas.CanvasPolygon
-
- (blot, fill) = tuple (self.args[:2])
- coords = self.args[2:]
- w = canvas.root ().add (type,
- fill_color = 'black',
- outline_color = 'black',
- width_units = blot,
- points = coords)
-
- return w
-
- def create_text_item (self, canvas):
- type = gnomecanvas.CanvasText
- (descr, str) = tuple (self.args)
-
- magnification = 0.5
-
-#ugh: how to get pango_descr_from_string() in pygtk?
-
- 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_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):
- citem = None
- try:
- method = Notation_item.dispatch_table[self.tag]
- citem = method (self, canvas)
- citem.move (*self.offset)
- citem.notation_item = self
-
- canvas.register_notation_canvas_item (citem)
- except KeyError:
- 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
number of Notation_items"""
def add_item (self, offset, cause, bbox, fields):
item = Notation_item()
- item.tag = fields[0]
+ item.tag = fields[0]
item.args = map (eval, fields[1:])
item.offset = offset
item.origin_tag = cause
w.destroy()
for i in self.items:
- c_item = i.create_canvas_item (canvas)
+ c_item = canvas.create_canvas_item (i)
canvas.set_cursor_to_music (self.music_cursor)
-
+ self.touched = False
+
def set_cursor (self, music_expr):
self.music_cursor = music_expr
self.cursor_touched = True
mus = mus.parent.get_neighbor (mus, dir)
mus = mus.find_first (lambda x: x.name() in ('NoteEvent', 'RestEvent'))
self.set_cursor (mus)
-
+
+ def cursor_move_chord (self, dir):
+ mus = self.music_cursor
+ if mus.name ()=='NoteEvent':
+ current_steps = mus.pitch.steps()
+ other_steps = [(note, note.pitch.steps()) for
+ note in mus.parent.elements if note.name()=='NoteEvent']
+
+ def cmp(a,b):
+ if a[1] > b[1]:
+ return 1
+ if a[1] < b[1]:
+ return -1
+ return 0
+
+ bound_set = [(note, dir * step) for (note, step) in other_steps
+ if dir * (step - current_steps) > 0]
+ bound_set.sort (cmp)
+ if bound_set:
+ self.set_cursor (bound_set[0][0])
+
def insert_at_cursor (self, music, dir):
mus = self.music_cursor
if mus.parent.name() == 'EventChord':
p.alteration = new_alt
self.touch_document ()
+ def set_alteration (self, alter):
+ if self.music_cursor.name() == 'NoteEvent':
+ p = self.music_cursor.pitch
+ p.alteration = alter
+ self.touch_document ()
+
+ def toggle_arpeggio (self):
+ par = self.music_cursor.parent
+ if par.name()== "EventChord":
+ arps = [e for e in par.elements if e.name()=='ArpeggioEvent']
+ if arps:
+ par.delete_element (arps[0])
+ else:
+ arp = music.ArpeggioEvent()
+ par.insert_around (self.music_cursor, arp, -1)
+ self.touch_document()
def print_score(self):
doc = self.notation_controller.document
ly = doc.music.ly_expression()
import gnomecanvas
import music
import math
+import string
class Notation_toolbar (gtk.VBox):
def __init__ (self, notation, check_refresh_callback):
lambda: self.notation.print_score(), 0),
('q', 'quit',
lambda: gtk.main_quit(), 0),
- ('Left', '<-',
+ ('Left', 'left',
lambda: self.notation.cursor_move (-1), 0),
- ('Right', '->',
+ ('Right', 'right',
lambda: self.notation.cursor_move (1), 0),
+ ('Up', 'up',
+ lambda: self.notation.cursor_move_chord (1), 0),
+ ('Down', 'down',
+ lambda: self.notation.cursor_move_chord (-1), 0),
('space', 'new',
lambda: self.notation.add_note (), 0),
('BackSpace', 'backspace',
lambda: self.notation.backspace (), 0),
- ('Shift+Up', '#',
- lambda: self.notation.change_alteration (2), 1),
- ('Shift+Down', 'b',
- lambda: self.notation.change_alteration (-2), 1),
- ('Up', 'up',
+ ('Ctrl+Shift+at', 'bb',
+ lambda: self.notation.set_alteration (-4), 1),
+ ('Shift+at', 'b',
+ lambda: self.notation.set_alteration (-2), 1),
+ ('Shift+exclam', 'natural',
+ lambda: self.notation.set_alteration (0), 1),
+ ('Shift+numbersign', '#',
+ lambda: self.notation.set_alteration (2), 1),
+ ('x', 'x',
+ lambda: self.notation.set_alteration (4), 1),
+ ('Shift+Up', 'higher',
lambda: self.notation.change_step (1), 1),
- ('Down', 'down',
+ ('Shift+Down', 'lower',
lambda: self.notation.change_step (-1), 1),
('apostrophe', 'oct up',
lambda: self.notation.change_octave (1), 1),
lambda: self.notation.change_dots (), 1),
('slash', 'shorter',
lambda: self.notation.change_duration_log (1), 1),
+ ('Shift+asciitilde', "arpeggio",
+ lambda: self.notation.toggle_arpeggio (), 1),
('Shift+asterisk', 'longer',
lambda: self.notation.change_duration_log (-1), 1),
('r', 'rest',
self.add_button (text, key_name, func, row)
-
class Notation_canvas (gnomecanvas.Canvas):
"""The canvas for drawing notation symbols."""
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'):
-
+ 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()
fact = pow (1.25, delta)
self.pixel_scale *= fact
self.set_pixels_per_unit (self.pixel_scale)
+
+
+ def create_canvas_item (canvas, item):
+ citem = None
+ try:
+ method = canvas.dispatch_table[item.tag]
+ citem = method (canvas, item)
+ citem.move (*item.offset)
+ citem.notation_item = item
+
+ canvas.register_notation_canvas_item (citem)
+ except KeyError:
+ print 'no such key', item.tag
+
+ return citem
+
+ def create_round_box_canvas_item (self, item):
+ canvas = self
+ root = canvas.root()
+ type = gnomecanvas.CanvasRect
+ (b, w, d, h, blot) = tuple (item.args)
+ w = root.add (type,
+ fill_color = 'black',
+ x1 = - b,
+ y1 = - d,
+ x2 = w,
+ y2 = h)
+
+ return w
+
+ def create_line_canvas_item (self, item):
+ canvas = self
+ type = gnomecanvas.CanvasLine
+ (thick, x1, y1, x2, y2) = tuple (item.args)
+ w = canvas.root().add (type,
+ fill_color = 'black',
+ width_units = thick,
+ points = [x1, y1, x2, y2]
+ )
+ return w
+
+ def create_glyph_item (self, item):
+ canvas = self
+ type = gnomecanvas.CanvasText
+ (index, font_name, magnification, name) = tuple (item.args)
+ (family, style) = string.split (font_name, '-')
+
+ sz = canvas.pixel_scale * 0.75 * magnification
+ sz *= 1.2
+
+ w = canvas.root().add (type,
+ fill_color = 'black',
+ family = family,
+ family_set = True,
+ anchor = gtk.ANCHOR_WEST,
+ y_offset = 0.15,
+
+ size_points = sz,
+ x = 0, y = 0,
+
+ text = unichr (index))
+ return w
+
+
+ def create_polygon_item (self, item):
+ type = gnomecanvas.CanvasPolygon
+ canvas = self
+
+ (blot, fill) = tuple (item.args[:2])
+ coords = item.args[2:]
+ w = canvas.root ().add (type,
+ fill_color = 'black',
+ outline_color = 'black',
+ width_units = blot,
+ points = coords)
+
+ return w
+
+ def create_bezier_sandwich (self, item):
+ type = gnomecanvas.CanvasBpath
+ canvas = self
+
+ (thick,points) = tuple (item.args)
+
+ (tcl, tcr, tr, tl,
+ bcr, bcl, bl, br) = tuple (points)
+
+
+ path = [(gnomecanvas.MOVETO_OPEN, tl[0], tl[1]),
+ (gnomecanvas.CURVETO,
+ tcl[0], tcl[1],
+ tcr[0], tcr[1],
+ tr[0], tr[1]),
+ (gnomecanvas.LINETO,
+ br[0], br[1]),
+ (gnomecanvas.CURVETO,
+ bcr[0], bcr[1],
+ bcl[0], bcl[1],
+ bl[0], bl[1]),
+ (gnomecanvas.LINETO,
+ tl[0], tl[1])
+ ]
+
+ pathdef = gnomecanvas.path_def_new(path)
+ # pathdef.closepath()
+ w = canvas.root().add (type,
+ fill_color = 'black',
+ outline_color = 'black',
+ width_units = thick)
+ w.set_bpath (pathdef)
+
+ return w
+
+ def create_text_item (self, item):
+ canvas = self
+ type = gnomecanvas.CanvasText
+ (descr, str) = tuple (item.args)
+
+ magnification = 0.5
+
+ #ugh: how to get pango_descr_from_string() in pygtk?
+
+ 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_SOUTH_WEST,
+ y_offset = 0.75,
+ size_points = size * canvas.pixel_scale * 0.87 * magnification,
+ text = str)
+ return w
+
+ 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,
+ 'bezier_sandwich': create_bezier_sandwich,
+ }
+
+
class Notation_canvas_controller:
"""The connection between canvas and the abstract notation graphics."""
--- /dev/null
+import math
+
+def gcd (a,b):
+ if b == 0:
+ return a
+ c = a
+ while c:
+ c = a % b
+ a = b
+ b = c
+ return a
+
+
+class Rational:
+ def __init__ (self, n, d = 1):
+ self.num = n
+ self.den = d
+ self.simplify ()
+
+ def simplify (self):
+ (n,d) = (self.num, self.den)
+ if d < 0:
+ d = -d
+ n = -n
+ if n == 0:
+ return (0,1)
+ else:
+ g = gcd (n, d)
+ (n,d) = (n/g, d/g)
+
+ (self.num, self.den) = (n,d)
+
+ def __mul__ (a,b):
+ (x,y) = (a.num, a.den)
+ (p,q) = (b.num, b.den)
+ return Rational (x*p, y*q)
+
+ def __div__ (a,b):
+ (x,y) = (a.num, a.den)
+ (q,p) = (b.num, b.den)
+ return Rational (x*p, y*q)
+
+ def __add__ (a, b):
+ (x,y) = (a.num, a.den)
+ (p,q) = (b.num, b.den)
+
+ return Rational (x*q + p*y, y*q)
+
+ def __neg__ (a):
+ (p,q) = (a.num, a.den)
+ return Rational (-p,q)
+
+ def __str__ (a):
+ return '%d/%d' % (a.num, a.den)
+
+ def __sub__ (a, b):
+ return a + (- b)
+
+ def __cmp__ (a, b):
+ num = (a - b).num
+ return num.__cmp__ (0)
+
+ def float (self):
+ r = 1.0 * self.num
+ r /= self.den
+ return r
+
+ def floor (self):
+ return Rational(int (math.floor (self.float())), 1)
+
+ def ceil (self):
+
+ return Rational(int (math.ceil (self.float())), 1)
+
+if __name__ == '__main__':
+ print '1/2', Rational (1,2)
+ print '1/2', Rational (2,4)
+ r1 = Rational (1,2)
+ r2 = Rational (1,3)
+
+ print '5/6', r1 + r2
+ print '1/6', r1 - r2
+ print '1/6', r1 * r2
+
+ print '1/2 < 2/3 ', Rational (1,2) < Rational (2, 3)
+ print '1/2 == 1/2 ', Rational (1,2) == Rational (1, 2)
+ print 'floor (5/6) ', Rational (5,6).floor()
+ print 'floor (3/3) ', Rational (3,3).floor()
+ print '(1/2) / (2/4)', Rational (1,2) / Rational (2,4)
\Score
\override BarNumber #'break-visibility = #all-visible
}
+ \context {
+ \Voice
+ \remove "Note_heads_engraver"
+ \consists "Completion_heads_engraver"
+ }
}
#(define (render-socket-music music socket)
(close client)
(display (format "Finished. Time elapsed: ~a\n"
(/ (- (get-internal-real-time) start-time) (* 1.0 internal-time-units-per-second))
+
))
+ (gc) ; do GC while app is refreshing the screen.
)))))
#(render-socket-music test-exp "test")
+