start IKEBANA_0_0
authorhanwen <hanwen>
Wed, 13 Jul 2005 19:10:53 +0000 (19:10 +0000)
committerhanwen <hanwen>
Wed, 13 Jul 2005 19:10:53 +0000 (19:10 +0000)
GNUmakefile [new file with mode: 0644]
README [new file with mode: 0644]
ikebana.py [new file with mode: 0644]
music.py [new file with mode: 0644]
notation.py [new file with mode: 0644]
notationcanvas.py [new file with mode: 0644]
server.ly [new file with mode: 0644]

diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644 (file)
index 0000000..cc0040e
--- /dev/null
@@ -0,0 +1,19 @@
+
+
+NAME=ikebana
+VERSION=0.0
+PY_FILES=$(wildcard *.py)
+ALL_FILES=$(PY_FILES) GNUmakefile server.ly README
+
+DISTNAME=$(NAME)-$(VERSION)
+
+dist:
+       mkdir $(DISTNAME)
+       ln $(ALL_FILES) $(DISTNAME)/
+       tar cfz $(DISTNAME).tar.gz $(DISTNAME)/
+       rm -rf $(DISTNAME)
+
+
+
+
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..43d1379
--- /dev/null
+++ b/README
@@ -0,0 +1,25 @@
+
+This is Ikebana, a simple GUI for entering LilyPond music.
+
+USAGE:
+
+       lilypond -b socket server.ly &
+       python ikebana.py
+
+KEYS
+
+a..g           pitch
+, '            octave
+r              rest
+space          enter note
+left,right     move
+.              dot
+* /            duration longer/shorter
++ -            alteration up/down
+backspace      delete note
+
+
+
+CAVEAT
+
+This is a proof-of-concept demo. Don't expect features, support, maintenance.
diff --git a/ikebana.py b/ikebana.py
new file mode 100644 (file)
index 0000000..166dbe6
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+import os
+import gtk
+import gnomecanvas
+import random
+import string
+import notation
+import notationcanvas
+import music
+
+def mainquit(*args):
+    gtk.main_quit()
+
+
+
+class NotationApplication:
+    def __init__ (self):
+        self.music = music.Music_document()
+        
+        nc = notation.Notation_controller (self.music)
+        self.notation_controller = nc
+
+        ncc = notationcanvas.Notation_canvas_controller(nc.notation)
+        self.notation_canvas_controller = ncc
+        
+        self.window = self.create_window()
+        
+    def create_window (self):
+        win = gtk.Window()
+        win.connect('destroy', mainquit)
+        win.set_title('Ikebana - visual music notation')
+
+        canvas = self.notation_canvas_controller.canvas
+        canvas.show()
+        win.add (canvas)
+        win.show()
+        
+        return win
+        
+    def main (self):
+        self.notation_controller.update_notation ()
+        self.notation_canvas_controller.update_canvas ()
+
+if __name__ == '__main__':
+    c = NotationApplication()
+    c.main ()
+    gtk.main()
diff --git a/music.py b/music.py
new file mode 100644 (file)
index 0000000..bfbcc81
--- /dev/null
+++ b/music.py
@@ -0,0 +1,241 @@
+import string
+
+               
+class Duration:
+       def __init__ (self):
+               self.duration_log = 2
+               self.dots = 0
+               self.factor = (1,1)
+
+       def lisp_expression (self):
+               return '(ly:make-duration %d %d %d %d)' % (self.duration_log,
+                                                          self.dots,
+                                                          self.factor[0],
+                                                          self.factor[1])
+
+       def ly_expression (self):
+               str = '%d%s' % (1 << self.duration_log, '.'*self.dots)
+
+               if self.factor <> (1,1):
+                       str += '*%d/%d' % self.factor
+               return str
+
+       def copy (self):
+               d = Duration ()
+               d.dots = self.dots
+               d.duration_log = self.duration_log
+               d.factor = self.factor
+               return d
+       
+class Pitch:
+       def __init__ (self):
+               self.alteration = 0
+               self.step = 0
+               self.octave = 0
+
+       def lisp_expression (self):
+               return '(ly:make-pitch %d %d %d)' % (self.octave,
+                                                    self.step,
+                                                    self.alteration)
+
+       def copy (self):
+               p = Pitch ()
+               p.alteration = self.alteration
+               p.step = self.step
+               p.octave = self.octave
+               return p
+
+       def steps (self):
+               return self.step + self.octave * 7
+       
+       def ly_expression (self):
+
+               str = 'cdefgab'[self.step]
+               if self.alteration > 0:
+                       str += 'is'* self.alteration
+               elif self.alteration < 0:
+                       str += 'is'* (-self.alteration)
+
+               if self.octave >= 0:
+                       str += "'" * (self.octave + 1) 
+               elif self.octave < -1:
+                       str += "," * (-self.octave - 1) 
+                       
+               return str
+
+class Music:
+       def __init__ (self):
+               self.tag = None
+               self.parent = None
+               pass
+
+       def set_tag (self, counter, tag_dict):
+               self.tag = counter
+               tag_dict [counter] = self
+               return counter + 1
+       
+       def get_properties (self):
+               return ''
+       
+       def lisp_expression (self):
+               name = self.name()
+               tag = ''
+               if self.tag:
+                       tag = "'input-tag %d" % self.tag
+
+               props = self.get_properties ()
+               
+               return "(make-music '%s %s %s)" % (name, tag,  props)
+
+       def find_first (self, predicate):
+               if predicate (self):
+                       return self
+               return None
+        
+class Music_document:
+       def __init__ (self):
+               self.music = test_expr ()
+               self.tag_dict = {}
+               
+       def reset_tags (self):
+               self.tag_dict = {}
+               self.music.set_tag (0, self.tag_dict)
+
+class NestedMusic(Music):
+       def __init__ (self):
+               Music.__init__ (self)
+               self.elements = [] 
+
+       def set_tag (self, counter, dict):
+               counter = Music.set_tag (self, counter, dict)
+               for e in self.elements :
+                       counter = e.set_tag (counter, dict)
+               return counter
+       
+       def insert_around (self, succ, elt, dir):
+               assert elt.parent == None
+               assert succ == None or succ in self.elements
+
+               
+               idx = 0
+               if succ:
+                       idx = self.elements.index (succ)
+                       if dir > 0:
+                               idx += 1
+               else:
+                       if dir < 0:
+                               idx = 0
+                       elif dir > 0:
+                               idx = len (self.elements)
+
+               self.elements.insert (idx, elt)
+               elt.parent = self
+               
+       def get_properties (self):
+               return ("'elements (list %s)"
+                       % string.join (map (lambda x: x.lisp_expression(),
+                                           self.elements)))
+       def get_neighbor (self, music, dir):
+               assert music.parent == self
+               idx = self.elements.index (music)
+               idx += dir
+               idx = min (idx, len (self.elements) -1)
+               idx = max (idx, 0)
+
+               return self.elements[idx]
+
+       def delete_element (self, element):
+               assert element in self.elements
+               
+               self.elements.remove (element)
+               element.parent = None
+               
+       def find_first (self, predicate):
+               r = Music.find_first (self, predicate)
+               if r:
+                       return r
+               
+               for e in self.elements:
+                       r = e.find_first (predicate)
+                       if r:
+                               return r
+               return None
+               
+class SequentialMusic (NestedMusic):
+       def name(self):
+               return 'SequentialMusic'
+       
+       def ly_expression (self):
+               return '{ %s }' % string.join (map (lambda x:x.ly_expression(),
+                                                   self.elements)) 
+
+
+class EventChord(NestedMusic):
+       def name(self):
+               return "EventChord"
+       def ly_expression (self):
+               str = string.join (map (lambda x: x.ly_expression(),
+                                       self.elements))
+               if len (self.elements) > 1:
+                       str = '<<%s>>' % str
+               
+               return str
+                       
+class Event(Music):
+       def __init__ (self):
+               Music.__init__ (self)
+
+       def name (self):
+               return "Event"
+
+class RhythmicEvent(Event):
+       def __init__ (self):
+               Event.__init__ (self)
+               self.duration = Duration()
+               
+       def get_properties (self):
+               return ("'duration %s"
+                       % self.duration.lisp_expression ())
+       
+       def name (self):
+               return 'RhythmicEvent'
+
+class RestEvent (RhythmicEvent):
+       def name (self):
+               return 'RestEvent'
+       def ly_expression (self):
+               return '%s' % self.duration.ly_expression ()
+
+class NoteEvent(RhythmicEvent):
+       def  __init__ (self):
+               RhythmicEvent.__init__ (self)
+               self.pitch = Pitch()
+
+       def name (self):
+               return 'NoteEvent'
+       
+       def get_properties (self):
+               return ("'pitch %s\n 'duration %s"
+                       % (self.pitch.lisp_expression (),
+                          self.duration.lisp_expression ()))
+
+       def ly_expression (self):
+               return '%s%s' % (self.pitch.ly_expression (),
+                                self.duration.ly_expression ())
+
+def test_expr ():
+       m = SequentialMusic()
+
+       evc = EventChord()
+       n = NoteEvent()
+       evc.insert_around (None, n, 0)
+       m.insert_around (None, evc, 0)
+
+       return m
+
+
+if __name__ == '__main__':
+       expr = test_expr()
+       print expr.lisp_expression()
+       print expr.ly_expression()
+
diff --git a/notation.py b/notation.py
new file mode 100644 (file)
index 0000000..7b59fbb
--- /dev/null
@@ -0,0 +1,341 @@
+import string
+import re
+import gnomecanvas
+import gtk
+import os
+import socket
+import music
+import pango
+
+def talk_to_lilypond (expression_str):
+    """Send a LISP expression to LilyPond, wait for return value."""
+    sock = socket.socket (socket.AF_INET)
+    address = ("localhost", 2904)
+    sock.connect (address)
+    sock.send (expression_str, socket.MSG_WAITALL)
+
+    cont = 1
+    retval = ''
+    while  cont:
+       try:
+           (data, from_addr) = sock.recvfrom (1024)
+       except socket.error:
+           break
+       cont = len (data) > 0
+       retval += data
+
+    return retval
+
+
+class Lilypond_socket_parser:
+    """Maintain state of reading multi-line lilypond output for socket transport."""
+    def __init__ (self, interpret_function):
+        self.clear ()
+        self.interpret_socket_line = interpret_function
+
+    def clear (self):
+        self.cause_tag = None
+       self.bbox = None
+        
+    def parse_line (self, line):
+        fields = string.split (line)
+        if not fields:
+            return
+        offset = (0,0)
+       if 0:
+           pass
+       elif fields[0] == 'hello':
+           print 'found server: ', fields[1:]
+           return
+        elif fields[0] == 'at':
+                offset = (string.atof (fields[1]),
+                          string.atof (fields[2]))
+                fields = fields[3:]
+        elif fields[0] == 'nocause':
+            self.cause_tag = None
+           self.bbox = None
+            return
+        elif fields[0] == 'cause':
+            self.cause_tag = string.atoi (fields[1])
+            self.name = fields[2]
+           self.bbox = tuple (map (string.atof, fields[3:]))
+            return
+
+        return self.interpret_socket_line (offset, self.cause_tag,
+                                          self.bbox, fields)
+
+class Notation_controller:
+    """Couple Notation and the music model. Stub for now. """
+    def __init__ (self, music_document):
+       self.document = music_document
+        
+        self.notation = Notation (self)
+        self.parser = Lilypond_socket_parser (self.interpret_line)
+
+    def interpret_line (self, offset, cause, bbox, fields):
+       notation_item = self.notation.add_item (offset, cause, bbox, fields)
+    
+    def update_notation(self):
+       doc = self.document
+       doc.reset_tags()
+       
+       expr = doc.music
+
+       str = expr.lisp_expression()
+       str = talk_to_lilypond (str)
+        self.parse_socket_file (str)
+        
+    def parse_socket_file (self, str):
+        self.notation.clear ()
+        lines = string.split (str, '\n')
+        self.parse_lines (lines)
+        
+    def parse_lines (self, lines):
+        for l in lines:
+            self.parser.parse_line (l)
+
+class Notation_item:
+    """A single notation element (corresponds to a Grob in LilyPond)"""
+    def __init__ (self):
+        self.origin_tag = None
+        self.bbox = None
+        self.offset = (0,0)
+        self.tag = None
+        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',
+                                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?
+
+       (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,
+                               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]
+           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
+
+           
+class Notation:
+
+    """A complete line/system/page of LilyPond output. Consists of a
+    number of Notation_items"""
+    
+    def __init__ (self, controller):
+        self.items = []
+       self.notation_controller = controller
+
+
+        toplevel = controller.document.music
+        self.music_cursor = toplevel.find_first (lambda x: x.name()== "NoteEvent") 
+        
+    def get_document (self):
+       return self.notation_controller.document
+       
+    def add_item (self, offset, cause, bbox, fields):
+           item = Notation_item()
+           item.tag = fields[0]
+           item.args = map (eval, fields[1:])
+           item.offset = offset
+           item.origin_tag = cause
+           
+           if cause and cause >= 0:
+               item.music_expression = self.get_document ().tag_dict[cause]
+               
+           item.bbox = bbox
+           
+           self.items.append (item)
+
+    
+    def clear(self):
+        self.items = [] 
+       
+    def paint_on_canvas (self,  canvas):
+        for w in  canvas.root().item_list:
+            if w.notation_item:
+                w.destroy()
+                
+       for i in self.items:
+           c_item = i.create_canvas_item (canvas)
+
+        canvas.set_cursor_to_music (self.music_cursor)
+
+    def cursor_move (self, dir):
+        mus = self.music_cursor
+        if mus.parent.name() == 'EventChord':
+            mus = mus.parent
+        
+        mus = mus.parent.get_neighbor (mus, dir)
+        mus = mus.find_first (lambda x: x.name() in ('NoteEvent', 'RestEvent'))
+        self.music_cursor = mus
+
+    def insert_at_cursor (self, music, dir):
+        mus = self.music_cursor
+        if mus.parent.name() == 'EventChord':
+            mus = mus.parent
+
+       mus.parent.insert_around (mus, music, dir)
+
+    def backspace (self):
+        mus = self.music_cursor
+        if mus.parent.name() == 'EventChord':
+            mus = mus.parent
+
+        neighbor = mus.parent.get_neighbor (mus, -1)
+        mus.parent.delete_element (neighbor)
+
+    def change_octave (self, dir):
+        if self.music_cursor.name() == 'NoteEvent':
+            p = self.music_cursor.pitch
+            p.octave += dir 
+
+    def change_step (self, step):
+        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
+                    
+            self.music_cursor.pitch = p1
+
+        else:
+            print 'not a NoteEvent'
+            
+    def change_duration_log (self, dir):
+        if ( self.music_cursor.name() == 'NoteEvent'
+             or self.music_cursor.name() == 'RestEvent'):
+
+            dur = self.music_cursor.duration
+            dl = dur.duration_log
+            dl += dir
+            if dl <= 6 and dl >= -3:
+                dur.duration_log = dl
+            
+
+    def ensure_note (self):
+        if self.music_cursor.name() == 'RestEvent':
+            m = self.music_cursor
+            note = music.NoteEvent()
+            m.parent.insert_around (None, note, 1)
+            m.parent.delete_element (m)
+            self.music_cursor = note
+            
+    def ensure_rest (self):
+        if self.music_cursor.name() == 'NoteEvent':
+            m = self.music_cursor
+            rest = music.RestEvent()
+            m.parent.insert_around (None, rest, 1)
+            m.parent.delete_element (m)
+            self.music_cursor = rest  
+            
+    def change_dots (self):
+        if self.music_cursor.name() == 'NoteEvent':
+            p = self.music_cursor.duration
+            if p.dots == 1:
+                p.dots = 0
+            elif p.dots == 0:
+                p.dots = 1
+            
+        
+    def change_alteration (self, dir):
+        if self.music_cursor.name() == 'NoteEvent':
+            p = self.music_cursor.pitch
+
+            new_alt = p.alteration + dir
+            if abs (new_alt) <= 4: 
+                p.alteration = new_alt
+
+                
diff --git a/notationcanvas.py b/notationcanvas.py
new file mode 100644 (file)
index 0000000..468e2dd
--- /dev/null
@@ -0,0 +1,145 @@
+import gtk
+import gnomecanvas
+import music
+
+class Notation_canvas (gnomecanvas.Canvas):
+       """The canvas for drawing notation symbols."""
+       
+       def __init__ (self):
+               gnomecanvas.Canvas.__init__ (self,
+                                            #aa=True
+                                            )
+               w,h = 800,400
+               self.set_size_request (w, h) 
+               self.set_scroll_region(0, 0, w, h)
+               root = self.root()
+               root.affine_relative ((1,0,0,-1,0,0))
+               self.pixel_scale = 10
+               self.set_pixels_per_unit (self.pixel_scale)
+               self.create_cursor ()
+               
+       def create_cursor (self):
+               type = gnomecanvas.CanvasRect
+               w = self.root().add (type,
+                                    fill_color = 'None',
+                                    outline_color = 'blue')
+               w.notation_item = None
+               
+               self.cursor_widget = w
+               
+       def set_cursor (self, notation_item):
+               if not notation_item.bbox:
+                       print 'no bbox'
+                       return
+
+               (x1, y1, x2, y2) = notation_item.bbox
+               self.cursor_widget.set (x1 = x1,
+                                       x2 = x2,
+                                       y1 = y1,
+                                       y2 = y2)
+               
+               
+       def item_set_active_state (self, item, active):
+               color = 'black'
+               if active:
+                       color = 'red'
+                       
+               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
+               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)
+
+       def set_cursor_to_music (self, music_expression):
+               c_items = [it for it in self.root().item_list if
+                          (it.notation_item
+                           and it.notation_item.music_expression
+                               == music_expression)]
+
+               if c_items:
+                       self.set_cursor (c_items[0].notation_item)
+
+class Notation_canvas_controller:
+       """The connection between canvas and the abstract notation graphics."""
+
+       def __init__ (self, notation):
+               self.canvas = Notation_canvas ()
+               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)
+                       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 == '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.notation_controller.update_notation ()
+               self.notation.paint_on_canvas (self.canvas)
+               return True
+
diff --git a/server.ly b/server.ly
new file mode 100644 (file)
index 0000000..2b586f1
--- /dev/null
+++ b/server.ly
@@ -0,0 +1,78 @@
+
+\paper
+{
+  raggedright = ##t
+  indent = 0.0 
+}
+
+\layout {
+  \context {
+    \Score
+    \override BarNumber  #'break-visibility = #all-visible
+  }
+}
+
+#(define (render-socket-music music socket)
+  (let*
+   ((score (ly:make-score music)) 
+    )
+
+   (ly:score-process score #f $defaultpaper $defaultlayout socket)
+  ))
+
+
+
+#(if #t
+  (let ((s (socket PF_INET SOCK_STREAM 0)))
+       (setsockopt s SOL_SOCKET SO_REUSEADDR 1)
+       ;; Specific address?
+       ;; (bind s AF_INET (inet-aton "127.0.0.1") 2904)
+       (bind s AF_INET INADDR_ANY 2904)
+       (listen s 5)
+
+       (simple-format #t "Listening for clients in pid: ~S" (getpid))
+       (newline)
+
+       (while #t
+              (let* ((client-connection (accept s))
+                    (start-time (get-internal-real-time))
+                     (client-details (cdr client-connection))
+                     (client (car client-connection)))
+                (simple-format #t "Got new client connection: ~S"
+                               client-details)
+                (newline)
+                (simple-format #t "Client address: ~S"
+                               (gethostbyaddr
+                                (sockaddr:addr client-details)))
+                (newline)
+                ;; Send back the greeting to the client port
+                (display "hello LilyPond 2.7.1\n" client)
+
+              (let* ((question (read client))
+                     (music (eval question (current-module))))
+
+               (render-socket-music music client)
+                (close client)
+               (display (format "Finished. Time elapsed: ~a\n"
+                         (/ (- (get-internal-real-time) start-time) (* 1.0 internal-time-units-per-second))
+                       ))
+             )))))
+
+
+#(define test-exp (make-music
+  'SequentialMusic
+  'elements
+  (list (make-music
+          'EventChord
+          'elements
+          (list (make-music
+                  'NoteEvent
+                 'input-tag 42
+                  'duration
+                  (ly:make-duration 2 0 1 1)
+                  'pitch
+                  (ly:make-pitch -1 0 0))))))
+)
+
+#(render-socket-music test-exp "test")
+