]> git.donarmstrong.com Git - lilypond.git/commitdiff
Add woodwind fingering diagrams
authorMike Solomon <mike@apollinemike.com>
Tue, 18 May 2010 18:53:39 +0000 (20:53 +0200)
committerCarl Sorensen <c_sorensen@byu.edu>
Sat, 26 Jun 2010 14:19:13 +0000 (08:19 -0600)
Add new file scm/woodwind-diagrams.scm

Modify drawing routines as necessary

Add new procedures to lily-library

Add new file with bezier manipulation routines

Add new stencils for use in woodwind diagrams

Add regression tests for woodwind diagrams

13 files changed:
input/regression/woodwind-diagrams-empty.ly [new file with mode: 0644]
input/regression/woodwind-diagrams-key-lists.ly [new file with mode: 0644]
ps/music-drawing-routines.ps
scm/bezier-tools.scm [new file with mode: 0644]
scm/define-stencil-commands.scm
scm/define-woodwind-diagrams.scm [new file with mode: 0644]
scm/display-woodwind-diagrams.scm [new file with mode: 0644]
scm/flag-styles.scm
scm/lily-library.scm
scm/lily.scm
scm/output-ps.scm
scm/output-svg.scm
scm/stencil.scm

diff --git a/input/regression/woodwind-diagrams-empty.ly b/input/regression/woodwind-diagrams-empty.ly
new file mode 100644 (file)
index 0000000..b534b63
--- /dev/null
@@ -0,0 +1,70 @@
+\version "2.13.24"
+
+\header {
+  texidoc="Empty woodwind diagrams for all instruments
+in woodwind-diagrams.scm."
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'piccolo
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'flute
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'oboe
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'clarinet
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'bass-clarinet
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'saxophone
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'bassoon
+      #'(1.0 0.1 #t ())
+  }
+}
+
+\relative c' {
+  c1^\markup {
+    \woodwind-diagram
+      #'contrabassoon
+      #'(1.0 0.1 #t ())
+  }
+}
\ No newline at end of file
diff --git a/input/regression/woodwind-diagrams-key-lists.ly b/input/regression/woodwind-diagrams-key-lists.ly
new file mode 100644 (file)
index 0000000..d5bfd54
--- /dev/null
@@ -0,0 +1,18 @@
+\version "2.13.24"
+
+\header {
+  texidoc="Lists all possible keys for all instruments in
+woodwind-diagrams.scm"
+}
+
+#(print-keys-verbose 'piccolo)
+#(print-keys-verbose 'flute)
+#(print-keys-verbose 'flute-b-extension)
+#(print-keys-verbose 'oboe)
+#(print-keys-verbose 'clarinet)
+#(print-keys-verbose 'bass-clarinet)
+#(print-keys-verbose 'low-bass-clarinet)
+#(print-keys-verbose 'saxophone)
+#(print-keys-verbose 'baritone-saxophone)
+#(print-keys-verbose 'bassoon)
+#(print-keys-verbose 'contrabassoon)
\ No newline at end of file
index 8fd31f3587502f0e7dfa4813f9604b730bc0a2af..7ea8c0fb90a5e33fe1eda30984b06a9187f4f461 100644 (file)
@@ -35,7 +35,7 @@
 }
 bind def
 
-% from adobe tech note 5002. 
+% from adobe tech note 5002.
 /BeginEPSF { %def
     /b4_Inc_state save def % Save state for cleanup
     /dict_count countdictstack def % Count objects on dict stack
@@ -57,7 +57,7 @@ bind def
   count op_count sub {pop} repeat % Clean up stacks
   countdictstack dict_count sub {end} repeat
   b4_Inc_state restore
-} bind def 
+} bind def
 
 /stroke_and_fill {
        gsave
@@ -103,7 +103,7 @@ bind def
        def
        rmoveto % x(0) y(0)
        { polygon_x polygon_y vector_add lineto } repeat % n times
-       closepath 
+       closepath
        { %fill?
                stroke_and_fill
        }{
@@ -126,7 +126,7 @@ bind def
 % this is for drawing slurs and barre-indicators.
 /draw_bezier_sandwich  % x5 y5 x6 y6 x7 y7
                        % x4 y4
-                       % x1 y1 x2 y2 x3 y3 
+                       % x1 y1 x2 y2 x3 y3
                        % x0 y0
                        % linewidth draw_bezier_sandwich
 {
@@ -159,7 +159,7 @@ bind def
        3 2 roll        % f? x0 y0 r
        dup 0 rmoveto
        0 360 arc closepath
-               { stroke_and_fill } 
+               { stroke_and_fill }
                { stroke }
        ifelse
 } bind def
@@ -192,6 +192,64 @@ bind def
   ifelse
 } bind def
 
+/draw_partial_ellipse % filled connect x-radius y-radius startangle endangle thickness draw_partial_ellipse
+% Note that filled is not boolean to permit for different graylevels (ie for trill keys)
+{
+  gsave
+  currentpoint translate
+  /thickness exch def
+  /endangle exch def
+  /startangle exch def
+  /y_radius exch def
+  /x_radius exch def
+  /endrad x_radius y_radius mul
+    x_radius x_radius mul
+    endangle cos endangle cos mul mul
+    y_radius y_radius mul
+    endangle sin endangle sin mul mul add sqrt div def
+  /endangle endangle sin endrad mul y_radius div
+    endangle cos endrad mul x_radius div atan def
+  /startrad x_radius y_radius mul
+    x_radius x_radius mul
+      startangle cos startangle cos mul mul
+    y_radius y_radius mul
+      startangle sin startangle sin mul mul add sqrt div def
+  /startangle startangle sin startrad mul y_radius div
+    startangle cos startrad mul x_radius div atan def
+  /connect exch def
+  /filled exch def
+  /savematrix matrix currentmatrix def
+  thickness setlinewidth
+  x_radius y_radius scale
+  startangle cos startangle sin moveto
+  0 0 1 startangle
+    startangle endangle eq { endangle 360 add } { endangle } ifelse
+    arc
+  connect {
+    startangle cos startangle sin moveto endangle cos endangle sin lineto }
+    if
+  savematrix setmatrix filled { stroke_and_fill } { stroke } ifelse
+  grestore
+} bind def
+
+/draw_connected_shape
+{
+  gsave
+  currentpoint translate
+  /filled exch def
+  /connect exch def
+  /savematrix matrix currentmatrix def
+  setlinewidth
+  scale
+  /arlen exch def
+  arlen {
+    /shlen exch def
+    shlen { } repeat shlen 2 eq { lineto } { curveto } ifelse
+  } repeat connect { closepath } if
+  savematrix setmatrix filled { stroke_and_fill } { stroke } ifelse
+  grestore
+} bind def
+
 /draw_line % dx dy x1 y1 thickness draw_line
 {
        setlinewidth % dx dy x1 y1
diff --git a/scm/bezier-tools.scm b/scm/bezier-tools.scm
new file mode 100644 (file)
index 0000000..0d7955a
--- /dev/null
@@ -0,0 +1,105 @@
+;;;; This file is part of LilyPond, the GNU music typesetter.
+;;;;
+;;;; Copyright (C) 2010 Carl D. Sorensen <c_sorensen@byu.edu>
+;;;;
+;;;; LilyPond is free software: you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation, either version 3 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; LilyPond is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+
+(define (make-coord x-value y-value)
+ "Make a coordinate pair from @var{x-valye} and @var{y-value}."
+ (cons x-value y-value))
+
+(define (coord+ coord1 coord2)
+ "Add @var{coord1} to @var{coord2}, returning a coordinate."
+ (cons (+ (car coord1) (car coord2))
+       (+ (cdr coord1) (cdr coord2))))
+
+(define (coord- coord1 coord2)
+ "Subtract @var{coord2} from @var{coord1}."
+ (cons (- (car coord1) (car coord2))
+       (- (cdr coord1) (cdr coord2))))
+
+(define (coord* scalar coord)
+ "Multiply each component of @var{coord} by @var{scalar}."
+ (cons (* (car coord) scalar)
+       (* (cdr coord) scalar)))
+
+(define (make-bezier point-0 point-1 point-2 point-3)
+ "Create a cubic bezier from the four control points."
+ (list point-0 point-1 point-2 point-3))
+
+(define (interpolated-control-points control-points split-value)
+ "Interpolate @var{control-points} at @var{split-value}.  Return a
+set of control points that is one degree less than @var{control-points}."
+  (if (null? (cdr control-points))
+      '()
+      (let ((first (car control-points))
+            (second (cadr control-points)))
+       (cons* (coord+ first (coord* split-value (coord- second first)))
+             (interpolated-control-points
+              (cdr control-points)
+              split-value)))))
+
+(define (split-bezier bezier split-value)
+ "Split a cubic bezier defined by @var{bezier} at the value
+@var{split-value}.  @var{bezier} is a list of pairs; each pair is
+is the coordinates of a control point.  Returns a list of beziers.
+The first element is the LHS spline; the second
+element is the RHS spline."
+   (let* ((quad-points (interpolated-control-points
+                       bezier
+                       split-value))
+          (lin-points (interpolated-control-points
+                       quad-points
+                       split-value))
+          (const-point (interpolated-control-points
+                        lin-points
+                        split-value))
+          (left-side (list (car bezier)
+                           (car quad-points)
+                           (car lin-points)
+                           (car const-point)))
+          (right-side (list (car const-point)
+                            (list-ref lin-points 1)
+                            (list-ref quad-points 2)
+                            (list-ref bezier 3))))
+   (cons left-side right-side)))
+
+(define (multi-split-bezier bezier start-t split-list)
+ "Split @var{bezier} at all the points listed in @var{split-list}.
+@var{bezier} has a parameter value that goes from @var{start-t} to 1.
+Returns a list of @var{(1+ (length split-list))} beziers."
+  (let* ((bezier-split (split-bezier bezier
+                                     (/ (- (car split-list) start-t)
+                                        (- 1 start-t))))
+         (left-bezier (car bezier-split))
+         (right-bezier (cdr bezier-split)))
+    (if (null? (cdr split-list))
+        bezier-split
+        (cons* left-bezier
+               (multi-split-bezier right-bezier
+                                   (car split-list)
+                                   (cdr split-list))))))
+
+
+(define (bezier-sandwich-list top-bezier bottom-bezier)
+ "create the list of control points for a bezier sandwich consisting
+of @var{top-bezier} and @var{bottom-bezier}."
+ (list (list-ref bottom-bezier 1)
+       (list-ref bottom-bezier 2)
+       (list-ref bottom-bezier 3)
+       (list-ref bottom-bezier 0)
+       (list-ref top-bezier 2)
+       (list-ref top-bezier 1)
+       (list-ref top-bezier 0)
+       (list-ref top-bezier 3)))
index 6bbd313dd4c1111ed30020b13a992443d69bb70c..c1452128af49a92e27067064d42ef54c03589a25 100644 (file)
@@ -27,6 +27,7 @@ defined in the output modules (output-*.scm)"
     bracket
     char
     circle
+    connected-shape
     dashed-line
     dashed-slur
     dot
@@ -40,6 +41,7 @@ defined in the output modules (output-*.scm)"
     no-origin
     oval
     path
+    partial-ellipse
     placebox
     polygon
     repeat-slash
diff --git a/scm/define-woodwind-diagrams.scm b/scm/define-woodwind-diagrams.scm
new file mode 100644 (file)
index 0000000..604395e
--- /dev/null
@@ -0,0 +1,1230 @@
+;;;; This file is part of LilyPond, the GNU music typesetter.
+;;;;
+;;;; Copyright (C) 2010 Mike Solomon <mikesol@stanfordalumni.org>
+;;;;    Clarinet drawings copied from diagrams created by
+;;;;    Gilles Thibault <gilles.thibault@free.fr>
+;;;;
+;;;; LilyPond is free software: you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation, either version 3 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; LilyPond is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+
+(define HOLE-FILL-LIST '((R . 3) (1q . 5) (1h . 7) (3q . 11) (F . 13)))
+
+;; Utility functions
+
+(define-public (symbol-concatenate . names)
+  "Like string-concatenate, but for symbols"
+  (string->symbol (apply string-append (map symbol->string names))))
+
+(define-public (function-chain arg function-list)
+  "Applies a list of functions in function list to arg.
+   Each element of function list is structured (cons function '(arg2 arg3 ...))
+   If function takes arguments besides arg, they are provided in function list.
+   For example:
+   @code{guile> (function-chain 1 `((,+ 1) (,- 2) (,+ 3) (,/)))}
+   @code{1/3}"
+  (if (null? function-list)
+    arg
+    (function-chain
+      (apply (caar function-list) (append `(,arg) (cdar function-list)))
+      (cdr function-list))))
+
+(define (rotunda-map function inlist rotunda)
+  "Like map, but with a rotating last argument to function.
+   For example:
+   @code{guile> (rotunda-map + '(1 2 3 4) '(1 -10))}
+   @code{(2 -8 4 -6)}"
+  (define (rotunda-map-chain function inlist outlist rotunda)
+    (if (null? inlist)
+      outlist
+     (rotunda-map-chain
+       function
+       (cdr inlist)
+       (append outlist (list (function (car inlist) (car rotunda))))
+       (append (cdr rotunda) (list (car rotunda))))))
+  (rotunda-map-chain function inlist '() rotunda))
+
+(define (assoc-keys alist)
+  "Gets the keys of an alist."
+  (map (lambda (x) (car x)) alist))
+
+(define (assoc-values alist)
+  "Gets the values of an alist."
+  (map (lambda (x) (cdr x)) alist))
+
+(define (get-slope-offset p1 p2)
+  "Gets the slope and offset for p1 and p2.
+   For example:
+   @code{(get-slope-offset '(1 . 2) '(3 . -5.1))}
+   @code{(-3.55 . 5.55)}"
+  (let*
+    ((slope (/ (- (cdr p1) (cdr p2)) (- (car p1) (car p2))))
+    (offset (- (cdr p1) (* slope (car p1)))))
+   `(,slope . ,offset)))
+
+(define (is-square? x input-list)
+  "Returns true if x is the square of a value in input-list."
+  (pair? (memv (inexact->exact (sqrt x)) input-list)))
+
+(define (satisfies-function? function input-list)
+  "Returns true if an element in @code{input-list} is true
+   when @code{function} is applied to it.
+   For example:
+   @code{guile> (satisfies-function? null? '((1 2) ()))}
+   @code{#t}
+   @code{guile> (satisfies-function? null? '((1 2) (3)))}
+   @code{#f}"
+  (if (null?  input-list)
+    #f
+    (or (function (car input-list))
+      (satisfies-function? function (cdr input-list)))))
+
+(define (true-entry? input-list)
+  "Is there a true entry in @code{input-list}?"
+  (satisfies-function? identity input-list))
+
+(define (entry-greater-than-x? input-list x)
+  "Is there an entry greater than @code{x} in @code{input-list}?"
+  (satisfies-function? (lambda (y) (> y x)) input-list))
+
+(define (n-true-entries input-list)
+  "Returns number of true entries in @code{input-list}."
+  (reduce + 0 (map (lambda (x) (if x 1 0)) input-list)))
+
+(define (bezier-head-for-stencil bezier cut-point)
+  "Prepares a split-bezier to be used in a connected shape stencil."
+  (list-tail (flatten-list (car (split-bezier bezier cut-point))) 2))
+
+;; Translators for keys
+
+; Translates a "normal" key (open, closed, trill)
+(define (key-fill-translate fill)
+  (cond
+    ((= fill 1) #f)
+    ((= fill 2) #f)
+    ((= fill (expt (assoc-get 'F HOLE-FILL-LIST) 2)) 0.5)
+    ((= fill (assoc-get 'F HOLE-FILL-LIST)) #t)))
+
+; Similar to above, but trans vs opaque doesn't matter
+(define (text-fill-translate fill)
+  (cond
+    ((< fill 3) 1.0)
+    ((= fill (expt (assoc-get 'F HOLE-FILL-LIST) 2)) 0.5)
+    ((= fill (assoc-get 'F HOLE-FILL-LIST)) 0.0)))
+
+; Emits a list for the central-column-hole maker
+; (not-full?, 1-quarter-full?, 1-half-full?, 3-quarters-full?, full?)
+; Multiple values, such as (#t #f #f #t #f), mean a trill between
+; not-full and 3-quarters-full
+(define (process-fill-value fill)
+  (let* ((avals (list-tail (assoc-values HOLE-FILL-LIST) 1)))
+  (append `(,(or (< fill 3) (is-square? fill avals)))
+    (map (lambda (x) (= 0 (remainder fill x))) avals))))
+
+; Color a stencil gray
+(define (gray-colorize stencil)
+  (apply ly:stencil-in-color (cons stencil (x11-color 'grey))))
+
+; A connected shape stencil that is surrounded by proc
+(define (rich-mcs-stencil ls x-stretch y-stretch proc)
+  (lambda (radius thick fill layout props)
+    (let*
+      ((fill-translate (key-fill-translate fill))
+       (gray? (eqv? fill-translate 0.5)))
+     (ly:stencil-add
+      ((if gray? gray-colorize identity)
+      (proc
+        (make-connected-shape-stencil
+        ls
+        thick
+        (* x-stretch radius)
+        (* y-stretch radius)
+        #f
+        (if gray? #t fill-translate))))
+      (if (not gray?)
+          empty-stencil
+          ((rich-mcs-stencil ls x-stretch y-stretch proc)
+           radius
+           thick
+           1
+           layout
+           props))))))
+
+; A connected shape stencil without a surrounding proc
+(define (standard-mcs-stencil ls x-stretch y-stretch)
+  (rich-mcs-stencil ls x-stretch y-stretch identity))
+
+; An ellipse stencil that is surrounded by a proc
+(define (rich-pe-stencil x-stretch y-stretch start end proc)
+  (lambda (radius thick fill layout props)
+    (let*
+      ((fill-translate (key-fill-translate fill))
+       (gray? (eqv? fill-translate 0.5)))
+     (ly:stencil-add
+      ((if gray? gray-colorize identity)
+      (proc
+        (make-partial-ellipse-stencil
+        (* x-stretch radius)
+        (* y-stretch radius)
+        start
+        end
+        thick
+        #t
+        (if gray? #t fill-translate))))
+      (if (not gray?)
+          empty-stencil
+          ((rich-pe-stencil x-stretch y-stretch start end proc)
+           radius
+           thick
+           1
+           layout
+           props))))))
+
+(define (rich-e-stencil x-stretch y-stretch proc)
+  (lambda (radius thick fill layout props)
+    (let*
+      ((fill-translate (key-fill-translate fill))
+       (gray? (eqv? fill-translate 0.5)))
+     (ly:stencil-add
+      ((if gray? gray-colorize identity)
+      (proc
+        (make-ellipse-stencil
+          (* x-stretch radius)
+          (* y-stretch radius)
+          thick
+          (if gray? #t fill-translate))))
+      (if (not gray?)
+        empty-stencil
+        ((rich-e-stencil x-stretch y-stretch proc)
+          radius
+          thick
+          1
+          layout
+          props))))))
+
+; An ellipse stencil without a surrounding proc
+(define (standard-e-stencil x-stretch y-stretch)
+  (rich-e-stencil x-stretch y-stretch identity))
+
+; Translates all possible representations of symbol.
+; If simple? then the only representations are open, closed, and trill.
+; Otherwise, there can be various levels of "closure" on the holes
+; ring? allows for a ring around the holes as well
+(define (make-symbol-alist symbol simple? ring?)
+  (filter (lambda (x)
+            (not
+              (equal?
+                x
+                `(,(symbol-concatenate symbol 'T 'F) .
+                 ,(expt (assoc-get 'F HOLE-FILL-LIST) 2)))))
+          (append
+            `((,symbol . ,(assoc-get 'F HOLE-FILL-LIST))
+              (,(symbol-concatenate symbol 'T) .
+               ,(expt (assoc-get 'F HOLE-FILL-LIST) 2)))
+            (if simple?
+                '()
+                (apply append
+                  (map (lambda (x)
+                         (append
+                           `((,(symbol-concatenate symbol (car x) 'T)
+                              . ,(expt (cdr x) 2))
+                             (,(symbol-concatenate symbol 'T (car x))
+                              . ,(* (cdr x) (assoc-get 'F HOLE-FILL-LIST)))
+                             (,(symbol-concatenate symbol (car x))
+                              . ,(cdr x)))
+                             (apply append
+                               (map (lambda (y)
+                                      (map (lambda (a b)
+                                             `(,(symbol-concatenate symbol
+                                                                    (car a)
+                                                                    'T
+                                                                    (car b))
+                                               . ,(* (cdr a) (cdr b))))
+                                           `(,x ,y) `(,y ,x)))
+                                    (cdr (member x HOLE-FILL-LIST))))))
+                       (if ring? HOLE-FILL-LIST (cdr HOLE-FILL-LIST))))))))
+
+;;; Commands for text layout
+
+; Draws a circle around markup if (= trigger 0.5)
+(define-markup-command
+  (conditional-circle-markup layout props trigger in-markup)
+  (number? markup?)
+  (interpret-markup layout props
+    (if (eqv? trigger 0.5)
+      (markup #:circle (markup in-markup))
+      (markup in-markup))))
+
+; Makes a list of named-keys
+(define (make-name-keylist input-list key-list font-size)
+  (map (lambda (x y)
+         (if (< x 1)
+           (markup #:conditional-circle-markup
+             x
+             (make-concat-markup
+               (list
+                 (markup #:abs-fontsize font-size (car y))
+                 (if (and (< x 1) (cdr y))
+                   (if (eqv? (cdr y) 1)
+                     (markup
+                       #:abs-fontsize
+                       font-size
+                       #:raise
+                       1
+                       #:fontsize
+                       -2
+                       #:sharp)
+                     (markup
+                       #:abs-fontsize
+                       font-size
+                       #:raise
+                       1
+                       #:fontsize
+                       -2
+                       #:flat))
+                 (markup #:null)))))
+           (markup #:null)))
+         input-list key-list))
+
+; Makes a list of number-keys
+(define (make-number-keylist input-list key-list font-size)
+  (map (lambda (x y)
+         (if (< x 1)
+           (markup
+             #:conditional-circle-markup
+             x
+             (markup #:abs-fontsize font-size #:number y))
+           (markup #:null)))
+       input-list
+       key-list))
+
+; Creates a named-key list with a certain alignment
+(define (aligned-text-stencil-function dir hv)
+  (lambda (key-name-list radius fill-list layout props)
+    (interpret-markup
+      layout
+      props
+      (make-general-align-markup
+        X
+        dir
+        ((if hv make-concat-markup make-center-column-markup)
+          (make-name-keylist
+            (map text-fill-translate fill-list)
+            key-name-list
+            (* 12 radius)))))))
+
+(define number-column-stencil
+  (lambda (key-name-list radius fill-list layout props)
+    (interpret-markup
+      layout
+      props
+      (make-general-align-markup
+        Y
+        CENTER
+        (make-general-align-markup
+          X
+          RIGHT
+          (make-override-markup
+            '(baseline-skip . 0)
+            (make-column-markup
+              (make-number-keylist
+                (map text-fill-translate fill-list)
+                key-name-list
+                (* radius 8)))))))))
+
+; Utility function for the left-hand keys
+(define lh-woodwind-text-stencil
+  (aligned-text-stencil-function LEFT #t))
+
+; Utility function for the right-hand keys
+(define rh-woodwind-text-stencil
+  (aligned-text-stencil-function RIGHT #t))
+
+(define octave-woodwind-text-stencil
+  (aligned-text-stencil-function CENTER #f))
+
+;;; Draw rules
+
+(define (rich-group-draw-rule alist target-part change-part)
+  (if
+    (entry-greater-than-x?
+      (map (lambda (key) (assoc-get key alist)) target-part) 3)
+    (map-selected-alist-keys (lambda (x) (if (= x 0) 1 x)) change-part alist)
+    alist))
+
+(define (bassoon-midline-rule alist target-part)
+  (if
+    (entry-greater-than-x?
+      (map (lambda (key) (assoc-get key alist)) target-part) 0)
+    (map-selected-alist-keys (lambda (x) 1) '((hidden . long-midline)) alist)
+    (map-selected-alist-keys (lambda (x) 1) '((hidden . midline)) alist)))
+
+(define (group-draw-rule alist target-part)
+  (rich-group-draw-rule alist target-part target-part))
+
+(define (group-automate-rule alist change-part)
+  (map-selected-alist-keys (lambda (x) (if (= x 0) 1 x)) change-part alist))
+
+(define (apply-group-draw-rule-series alist target-part-list)
+  (if (null? target-part-list)
+    alist
+    (apply-group-draw-rule-series
+      (group-draw-rule alist (car target-part-list))
+      (cdr target-part-list))))
+
+;; Extra-offset rules
+
+(define (rich-group-extra-offset-rule alist target-part change-part eos)
+  (if
+    (entry-greater-than-x?
+      (map (lambda (key) (assoc-get key alist)) target-part) 0)
+    (map-selected-alist-keys (lambda (x) eos) change-part alist)
+    alist))
+
+(define (group-extra-offset-rule alist target-part eos)
+  (rich-group-extra-offset-rule alist target-part target-part eos))
+
+(define (uniform-extra-offset-rule alist eos)
+  (map-selected-alist-keys
+    (lambda (x) (if (pair? x) x eos))
+    (assoc-keys alist)
+    alist))
+
+;;; General drawing commands
+
+; Used all the time for a dividing line
+(define (midline-stencil radius thick fill layout props)
+  (make-line-stencil (* thick 2) (* -0.80 radius) 0 (* 0.80 radius) 0))
+
+(define (long-midline-stencil radius thick fill layout props)
+  (make-line-stencil (* thick 2) (* -5.75 radius) 0 (* 0.75 radius) 0))
+
+; Used all the time for a small, between-hole key
+(define little-elliptical-key-stencil (standard-e-stencil 0.75 0.2))
+
+; Used for several upper keys in the clarinet and sax
+(define (upper-key-stencil tailw tailh bodyw bodyh)
+  (let*
+   ((xmove (lambda (x) (+ tailw (+ 0.2 (* bodyw (- x 0.2))))))
+    (ymove (lambda (x) (+ (- tailh) (+ -0.05 (* bodyh (+ x 0.05)))))))
+  (standard-mcs-stencil
+    `((,(xmove 0.7)
+       ,(ymove -0.2)
+       ,(xmove 1.0)
+       ,(ymove -1.0)
+       ,(xmove 0.5)
+       ,(ymove -1.0))
+      (,(xmove 0.2)
+       ,(ymove -1.0)
+       ,(xmove 0.2)
+       ,(ymove -0.2)
+       ,(xmove 0.3)
+       ,(ymove -0.1))
+      (,(+ 0.2 tailw)
+       ,(- -0.05 tailh)
+       ,(+ 0.1 (/ tailw 2))
+       ,(- -0.025 (/ tailh 2))
+       0.0
+       0.0))
+    1.0
+    1.0)))
+
+; Utility function for the column-hole maker.
+; Returns the left and right degrees for the drawing of a given
+; fill level (1-quarter, 1-half, etc...)
+(define (degree-first-true fill-list left? reverse?)
+  (define (dfl-crawler fill-list os-list left?)
+    (if (car fill-list)
+      ((if left? car cdr) (car os-list))
+      (dfl-crawler (cdr fill-list) (cdr os-list) left?)))
+  (dfl-crawler
+    ((if reverse? reverse identity) fill-list)
+    ((if reverse? reverse identity)
+      '((0 . 0) (215 . 325) (180 . 0) (145 . 35) (90 . 90)))
+    left?))
+
+; Gets the position of the first (or last if reverse?) element of a list.
+(define (position-true-endpoint in-list reverse?)
+  (define (pte-crawler in-list n)
+    (if (car in-list)
+      n
+      (pte-crawler (cdr in-list) (+ n 1))))
+  ((if reverse? - +)
+    (if reverse? (length in-list) 0)
+    (pte-crawler ((if reverse? reverse identity) in-list) 0)))
+
+; Huge, kind-of-ugly maker of a circle in a column.
+; I think this is the clearest way to write it, though...
+
+(define (column-circle-stencil radius thick fill layout props)
+  (let* ((fill-list (process-fill-value fill)))
+    (cond
+      ((and
+        (list-ref fill-list 0)
+        (not (true-entry? (list-tail fill-list 1)))) ; is it empty?
+       ((standard-e-stencil 1.0 1.0) radius thick fill layout props))
+      ((and
+        (list-ref fill-list 4)
+        (not (true-entry? (list-head fill-list 4)))) ; is it full?
+       ((standard-e-stencil 1.0 1.0) radius thick fill layout props))
+      ((and
+        (list-ref fill-list 0)
+        (list-ref fill-list 4)) ; is it a trill between empty and full?
+       ((standard-e-stencil 1.0 1.0) radius thick fill layout props))
+      (else  ;If none of these, it is partially full.
+        (ly:stencil-add
+          ((rich-pe-stencil 1.0 1.0 0 360 identity)
+            radius
+            thick
+            (if (list-ref fill-list 4)
+              (expt (assoc-get 'F HOLE-FILL-LIST) 2)
+              1)
+            layout
+            props)
+          ((rich-pe-stencil
+            1.0
+            1.0
+            (degree-first-true fill-list #t #t)
+            (degree-first-true fill-list #f #t)
+            identity)
+            radius
+            thick
+            (if
+              (true-entry?
+                (list-head fill-list (position-true-endpoint fill-list #t)))
+              (expt (assoc-get 'F HOLE-FILL-LIST) 2)
+              (assoc-get 'F HOLE-FILL-LIST))
+            layout
+            props)
+          (if
+            (= 2 (n-true-entries (list-tail fill-list 1))) ; trill?
+            ((rich-pe-stencil
+              1.0
+              1.0
+              (degree-first-true fill-list #t #f)
+              (degree-first-true fill-list #f #f)
+              identity)
+              radius
+              thick
+              (assoc-get 'F HOLE-FILL-LIST)
+              layout
+              props)
+            empty-stencil))))))
+
+(define (variable-column-circle-stencil scaler)
+  (lambda (radius thick fill layout props)
+    (column-circle-stencil (* radius scaler) thick fill layout props)))
+
+; A stencil for ring-column circles that combines two of the above
+(define (ring-column-circle-stencil radius thick fill layout props)
+  (if (= 0 (remainder fill (assoc-get 'R HOLE-FILL-LIST)))
+    (ly:stencil-add
+      ((if
+        (= fill (expt (assoc-get 'R HOLE-FILL-LIST) 2))
+        gray-colorize
+        identity)
+        ((standard-e-stencil
+            (* (+ (- 1.0 (* 2 thick)) (/ thick 2)))
+            (* (+ (- 1.0 (* 2 thick)) (/ thick 2))))
+          radius
+          (* (* 4 radius) thick)
+          1
+          layout
+          props))
+      ((standard-e-stencil 1.0 1.0) radius thick 1 layout props)
+      (column-circle-stencil
+        (+ (* (- 1.0 (* 4 thick)) radius) (/ thick 2))
+        thick
+        (*
+          (if (= 0 (remainder fill (assoc-get 'F HOLE-FILL-LIST)))
+            (assoc-get 'F HOLE-FILL-LIST)
+            1)
+          (if (= fill (expt (assoc-get 'R HOLE-FILL-LIST) 2))
+            (/ fill (expt (assoc-get 'R HOLE-FILL-LIST) 2))
+            (/ fill (assoc-get 'R HOLE-FILL-LIST))))
+        layout
+        props))
+    (column-circle-stencil radius thick fill layout props)))
+
+;;; Flute family stencils
+
+(define flute-lh-b-key-stencil
+  (standard-mcs-stencil
+    '((0 1.3)
+      (0 1.625 -0.125 1.75 -0.25 1.75)
+      (-0.55 1.75 -0.55 0.95 -0.25 0.7)
+      (0 0.4 0 0.125 0 0))
+    2
+    1.55))
+
+(define flute-lh-bes-key-stencil
+  (standard-mcs-stencil
+    '((0 1.3)
+      (0 1.625 -0.125 1.75 -0.25 1.75)
+      (-0.55 1.75 -0.55 0.95 -0.25 0.7)
+      (0 0.4 0 0.125 0 0))
+    2.0
+    1.3))
+
+(define (flute-lh-gis-rh-bes-key-stencil deg)
+  (rich-mcs-stencil
+    '((0.1 0.1 0.2 0.4 0.3 0.6)
+      (0.3 1.0 0.8 1.0 0.8 0.7)
+      (0.8 0.3 0.5 0.3 0 0))
+    1.0
+    1.0
+    (lambda (stencil) (ly:stencil-rotate stencil deg 0 0))))
+
+(define flute-lh-gis-key-stencil (flute-lh-gis-rh-bes-key-stencil 0))
+
+(define flute-rh-bes-key-stencil (flute-lh-gis-rh-bes-key-stencil 200))
+
+(define flute-rh-d-key-stencil little-elliptical-key-stencil)
+
+(define flute-rh-dis-key-stencil little-elliptical-key-stencil)
+
+(define flute-rh-ees-key-stencil
+  (standard-mcs-stencil
+    '((0.8 0) (1.1 0 1.1 0.75 0.7 0.75) (0.5 0.75) (0.15 0.75 0.1 0.2 0 0))
+    -2.38
+    1.4))
+
+(define (piccolo-rh-x-key-stencil radius thick fill layout props)
+  (interpret-markup
+    layout
+    props
+    (make-general-align-markup
+      Y
+      DOWN
+      (make-concat-markup
+        (make-name-keylist
+          `(,(text-fill-translate fill))
+          '(("X" . #f))
+          (* 9 radius))))))
+
+(define flute-lower-row-stretch 1.4)
+
+(define flute-rh-cis-key-stencil
+  (standard-mcs-stencil
+    '((0 0.75) (-0.8 0.75 -0.8 0 0 0))
+    flute-lower-row-stretch
+    flute-lower-row-stretch))
+
+(define flute-rh-c-key-stencil
+  (standard-mcs-stencil
+    '((0 0.75) (0.4 0.75) (0.4 0) (0 0))
+    flute-lower-row-stretch
+    flute-lower-row-stretch))
+
+(define flute-rh-b-key-stencil
+  (standard-mcs-stencil
+    '((0 0.75) (0.25 0.75) (0.25 0) (0 0))
+    flute-lower-row-stretch
+    flute-lower-row-stretch))
+
+(define flute-rh-gz-key-stencil
+  (rich-mcs-stencil
+      '((0.1 0.1 0.4 0.2 0.6 0.3)
+        (1.0 0.3 1.0 0.8 0.7 0.8)
+        (0.3 0.8 0.3 0.5 0 0))
+      flute-lower-row-stretch
+      flute-lower-row-stretch
+      (lambda (stencil) (ly:stencil-rotate stencil 160 0 0))))
+
+;;; Shared oboe/clarinet stencils
+
+(define (oboe-lh-gis-lh-low-b-key-stencil gis?)
+  (let*
+    ((x 1.2)
+     (y 0.4)
+     (scaling-factor 1.7)
+     (up-part
+       (car
+         (split-bezier
+           `((0.0 . 0.0) (0.0 . ,y) (,x . ,y) (,x . 0.0))
+           0.8)))
+     (down-part
+       (cdr
+         (split-bezier
+           `((,x . 0.0) (,x . ,(- y)) (0.0 . ,(- y)) (0.0 . 0.0))
+           0.2))))
+    (if gis?
+      (standard-mcs-stencil
+        (append
+          (append
+            `((0.25 ,(/ y -2) 0.75 ,(/ y -2) 1.0 0.0))
+            (map (lambda (l)
+                   (flatten-list
+                     (map (lambda (x)
+                            (coord-translate
+                              (coord-rotate x (atan (/ y (* 2 0.25))))
+                              '(1.0 . 0)))
+                          l)))
+                 `(((0 . ,y) (,x . ,y) (,x . 0))
+                   ((,x . ,(- y)) (0 . ,(- y)) (0 . 0)))))
+          `((0.75 ,(/ y -2) 0.25 ,(/ y -2) 0.0 0.0)))
+        scaling-factor
+        scaling-factor)
+      (standard-mcs-stencil
+        (map (lambda (l)
+               (flatten-list
+                 (map (lambda (x)
+                        (coord-rotate x (atan (/ y (* 2 0.25)))))
+                      l)))
+             `(,(list-tail up-part 1)
+               ,(list-head down-part 1)
+               ,(list-tail down-part 1)))
+        (- scaling-factor)
+        (- scaling-factor)))))
+
+(define oboe-lh-gis-key-stencil (oboe-lh-gis-lh-low-b-key-stencil #t))
+
+(define oboe-lh-low-b-key-stencil (oboe-lh-gis-lh-low-b-key-stencil #f))
+
+(define (oboe-lh-ees-lh-bes-key-stencil ees?)
+  (standard-mcs-stencil
+    `((0 1.5)
+      (0 1.625 -0.125 1.75 -0.25 1.75)
+      (-0.5 1.75 -0.5 0.816 -0.25 0.5)
+      (0 0.25 0 0.125 0 0)
+      (0 ,(if ees? -0.6 -0.3)))
+    (* (if ees? -1.0 1.0) -1.8)
+    1.8))
+
+(define oboe-lh-ees-key-stencil (oboe-lh-ees-lh-bes-key-stencil #t))
+
+(define oboe-lh-bes-key-stencil (oboe-lh-ees-lh-bes-key-stencil #f))
+
+;;; Oboe family stencils
+
+(define (oboe-lh-octave-key-stencil long?)
+  (let* ((h (if long? 1.4 1.2)))
+    (standard-mcs-stencil
+    `((-0.4 0 -0.4 1.0 -0.1 1.0)
+      (-0.1 ,h)
+      (0.1 ,h)
+      (0.1 1.0)
+      (0.4 1.0 0.4 0 0 0))
+    2.0
+    2.0)))
+
+(define oboe-lh-I-key-stencil (oboe-lh-octave-key-stencil #f))
+
+(define oboe-lh-II-key-stencil (oboe-lh-octave-key-stencil #f))
+
+(define oboe-lh-III-key-stencil (oboe-lh-octave-key-stencil #t))
+
+(define oboe-lh-b-key-stencil (standard-e-stencil 0.6 0.8))
+
+(define oboe-lh-d-key-stencil little-elliptical-key-stencil)
+
+(define oboe-lh-cis-key-stencil little-elliptical-key-stencil)
+
+(define oboe-lh-f-key-stencil (standard-e-stencil 0.5 1.0))
+
+(define oboe-rh-a-key-stencil (standard-e-stencil 1.0 0.45))
+
+(define oboe-rh-gis-key-stencil (standard-e-stencil 0.45 1.2))
+
+(define oboe-rh-d-key-stencil little-elliptical-key-stencil)
+
+(define oboe-rh-f-key-stencil little-elliptical-key-stencil)
+
+(define (oboe-rh-c-rh-ees-key-stencil c?)
+  (rich-mcs-stencil
+    '((1.0 0.0 1.0 0.70 1.5 0.70)
+      (2.25 0.70 2.25 -0.4 1.5 -0.4)
+      (1.0 -0.4 1.0 0 0 0)
+      (-0.15 0))
+    2.0
+    1.4
+    (lambda (stencil) (ly:stencil-rotate stencil (if c? 170 180) 0 0))))
+
+(define oboe-rh-banana-key-stencil oboe-rh-gis-key-stencil)
+
+(define oboe-rh-c-key-stencil (oboe-rh-c-rh-ees-key-stencil #t))
+
+(define oboe-rh-cis-key-stencil
+  (rich-mcs-stencil
+    '((0.6 0.0 0.6 0.50 1.25 0.50)
+      (2.25 0.50 2.25 -0.4 1.25 -0.4)
+      (0.6 -0.4 0.6 0 0 0))
+    -0.9
+    1.0
+    (lambda (stencil) (ly:stencil-rotate stencil 0 0 0))))
+
+(define oboe-rh-ees-key-stencil (oboe-rh-c-rh-ees-key-stencil #f))
+
+;;; Clarinet family stencils
+
+(define clarinet-lh-thumb-key-stencil
+  (variable-column-circle-stencil 0.9))
+
+(define clarinet-lh-R-key-stencil
+  (let* ((halfbase (cos (/ PI 10)))
+    (height (*
+      halfbase
+      (/ (sin (/ (* 4 PI) 10)) (cos (/ (* 4 PI) 10))))))
+   (standard-mcs-stencil
+      `(
+        (0 ,(/ -4.0 3.0) -2.0 ,(/ -4.0 3.0) -2.0 0.0)
+        (-1.5 ,(* 0.5 height) -1.25 ,(* 0.75 height) -1.0 ,height)
+        (-0.75 ,(* 0.75 height) -0.5 ,(* 0.5 height) 0.0 0.0))
+      0.9
+      0.9)))
+
+(define (clarinet-lh-a-key-stencil radius thick fill layout props)
+  (let* ((width 0.4) (height 0.75) (linelen 0.45))
+  (ly:stencil-add
+    ((standard-e-stencil width height) radius thick fill layout props)
+    (ly:stencil-translate
+      (make-line-stencil thick 0 0 0 (* linelen radius))
+      (cons 0 (* height radius))))))
+
+(define clarinet-lh-gis-key-stencil (upper-key-stencil 0.0 0.0 1.3 2.0))
+
+(define clarinet-lh-ees-key-stencil little-elliptical-key-stencil)
+
+(define clarinet-lh-cis-key-stencil oboe-lh-gis-key-stencil)
+
+(define clarinet-lh-f-key-stencil oboe-lh-low-b-key-stencil)
+
+(define clarinet-lh-e-key-stencil oboe-lh-ees-key-stencil)
+
+(define clarinet-lh-fis-key-stencil oboe-lh-bes-key-stencil)
+
+(define clarinet-lh-d-key-stencil (standard-e-stencil 1.0 0.4))
+
+(define clarinet-rh-low-c-key-stencil
+  (standard-mcs-stencil
+    '((0.0 1.5)
+      (0.0 2.5 -1.0 2.5 -1.0 0.75)
+      (-1.0 0.1 0.0 0.25 0.0 0.3)
+      (0.0 0.0))
+    0.8
+    0.8))
+
+(define clarinet-rh-low-cis-key-stencil
+  (standard-mcs-stencil
+    '((0.0 1.17)
+      (0.0 1.67 -1.0 1.67 -1.0 0.92)
+      (-1.0 0.47 0.0 0.52 0.0 0.62)
+      (0.0 0.0))
+    0.8
+    0.8))
+
+(define clarinet-rh-low-d-key-stencil
+  (standard-mcs-stencil
+    '((0.0 1.05)
+      (0.0 1.55 -1.0 1.55 -1.0 0.8)
+      (-1.0 0.35 0.0 0.4 0.0 0.5)
+      (0.0 0.0))
+    0.8
+    0.8))
+
+(define clarinet-rh-one-key-stencil (standard-e-stencil 0.5 0.25))
+
+(define clarinet-rh-two-key-stencil clarinet-rh-one-key-stencil)
+
+(define clarinet-rh-three-key-stencil clarinet-rh-one-key-stencil)
+
+(define clarinet-rh-four-key-stencil clarinet-rh-one-key-stencil)
+
+(define clarinet-rh-b-key-stencil little-elliptical-key-stencil)
+
+; cl low-rh values
+(define CL-RH-HAIR 0.09)
+(define CL-RH-H-STRETCH 2.7)
+(define CL-RH-V-STRETCH 0.9)
+
+; TODO
+; there is some unnecessary information duplication here.
+; need a way to control all of the below stencils so that if one
+; changes, all change...
+
+(define clarinet-rh-fis-key-stencil
+  (standard-mcs-stencil
+    `(,(bezier-head-for-stencil
+        '((0.0 . 0.0) (0.0 . -1.0) (1.0 . -1.0) (1.0 . 0.0))
+        0.5)
+      ,(bezier-head-for-stencil
+        '((0.5 . -0.75) (0.5 . 0.25) (1.5 . 0.25) (1.5 . -0.75))
+        0.5)
+      (1.0 1.0 0.0 1.0 0.0 0.0))
+      CL-RH-H-STRETCH
+      CL-RH-V-STRETCH))
+
+(define clarinet-rh-e-key-stencil
+  (standard-mcs-stencil
+    '((0.0 1.0 1.0 1.0 1.0 0.0) (1.0 -1.0 0.0 -1.0 0.0 0.0))
+    CL-RH-H-STRETCH
+    CL-RH-V-STRETCH))
+
+(define clarinet-rh-ees-key-stencil
+  (standard-mcs-stencil
+    `(,(bezier-head-for-stencil
+        '((0.0 .  0.0) (0.0 . -1.0) (1.0 . -1.0) (1.0 . 0.0))
+        0.5)
+      ,(bezier-head-for-stencil
+        '((0.5 . -0.75) (0.5 . 0.25) (1.5 . 0.25) (1.5 . -0.75))
+        0.5)
+      ,(bezier-head-for-stencil
+        `((1.0 . 0.0) (,(/ 1 3) . 0.0) (,(/ 1 3) . 1.5) (1.0 .  1.5))
+        0.5)
+       ,(bezier-head-for-stencil
+        `((0.5 . 0.75) (,(/ -1 6) . 0.75) (,(/ -1 6) . -0.75) (0.5 . -0.75))
+        0.5))
+    CL-RH-H-STRETCH
+    CL-RH-V-STRETCH))
+
+(define clarinet-rh-gis-key-stencil clarinet-rh-e-key-stencil)
+
+(define bass-clarinet-rh-f-key-stencil
+  (standard-mcs-stencil
+    `(,(bezier-head-for-stencil
+        '((0.0 . 0.0) (0.0 . -1.0) (1.0 . -1.0) (1.0 . 0.0))
+        0.5)
+      ,(bezier-head-for-stencil
+        '((0.5 . -0.75) (0.5 . 0.25) (1.5 . 0.25) (1.5 . -0.75))
+        0.5)
+      (1.0 1.0 0.0 1.0 0.0 0.0))
+    CL-RH-H-STRETCH
+    (- CL-RH-V-STRETCH)))
+
+(define low-bass-clarinet-rh-f-key-stencil clarinet-rh-ees-key-stencil)
+
+(define clarinet-rh-d-key-stencil clarinet-rh-e-key-stencil)
+
+;;; Saxophone family stencils
+
+(define saxophone-lh-ees-key-stencil (upper-key-stencil 0.0 0.0 1.3 2.0))
+
+(define saxophone-lh-f-key-stencil (upper-key-stencil 0.0 0.0 1.3 2.0))
+
+(define saxophone-lh-d-key-stencil (upper-key-stencil 0.0 0.0 1.3 2.0))
+
+(define saxophone-lh-front-f-key-stencil (standard-e-stencil 0.7 0.7))
+
+(define saxophone-lh-bes-key-stencil (standard-e-stencil 0.5 0.5))
+
+(define saxophone-lh-T-key-stencil (standard-e-stencil 0.75 0.75))
+
+(define saxophone-lh-gis-key-stencil
+  (standard-mcs-stencil
+    '((0.0 0.4)
+      (0.0 0.8 3.0 0.8 3.0 0.4)
+      (3.0 0.0)
+      (3.0 -0.4 0.0 -0.4 0.0 0.0))
+    0.8
+    0.8))
+
+(define (saxophone-lh-b-cis-key-stencil flip?)
+  (standard-mcs-stencil
+    '((0.0 1.0)
+      (0.4 1.0 0.8 0.9 1.35 0.8)
+      (1.35 0.0)
+      (0.0 0.0))
+    (* (if flip? -1 1) 0.8)
+    0.8))
+
+(define saxophone-lh-cis-key-stencil (saxophone-lh-b-cis-key-stencil #t))
+
+(define saxophone-lh-b-key-stencil (saxophone-lh-b-cis-key-stencil #f))
+
+(define saxophone-lh-low-bes-key-stencil
+  (standard-mcs-stencil
+    '((3.0 0.0) (3.0 -1.5 0.0 -1.5 0.0 0.0))
+    0.8
+    0.8))
+
+(define (saxophone-rh-side-key-stencil width height)
+  (standard-mcs-stencil
+    `((0.0 ,height)
+    (0.05 ,(+ height 0.05) 0.1 ,(+ height 0.1) 0.15 ,(+ height 0.15))
+    (,(- width 0.15) ,(+ height 0.15))
+    (,(- width 0.1)
+     ,(+ height 0.1)
+     ,(- width 0.05)
+     ,(+ height 0.05)
+     ,width
+     ,height)
+    (,width 0.0)
+    (,(- width 0.05) -0.05 ,(- width 0.1) -0.1 ,(- width 0.15) -0.15)
+    (0.15 -0.15)
+    (0.1 -0.1 0.05 -0.05 0.0 0.0))
+    1.0
+    1.0))
+
+(define saxophone-rh-e-key-stencil (saxophone-rh-side-key-stencil 0.9 1.2))
+
+(define saxophone-rh-c-key-stencil (saxophone-rh-side-key-stencil 0.9 0.6))
+
+(define saxophone-rh-bes-key-stencil (saxophone-rh-side-key-stencil 0.9 0.45))
+
+(define saxophone-rh-high-fis-key-stencil
+  (standard-mcs-stencil
+    (append
+      '((0.0 1.0) (0.0 1.4 0.6 1.4 0.6 1.0) (0.6 0.0))
+      (map (lambda (l)
+             (flatten-list
+               (map (lambda (x)
+                      (coord-rotate x (atan (* -1 (/ PI 6)))))
+                    l)))
+           '(((0.6 . -1.0))
+             ((0.6 . -1.4) (0.0 . -1.4) (0.0 . -1.0))
+             ((0.0 . 0.0)))))
+       0.75
+       0.75))
+
+(define saxophone-rh-fis-key-stencil (standard-e-stencil 1.0 0.5))
+
+(define saxophone-rh-ees-key-stencil (standard-e-stencil 1.2 0.5))
+
+(define saxophone-rh-low-c-key-stencil
+  (standard-mcs-stencil
+    '((3.0 0.0) (3.0 -1.5 0.0 -1.5 0.0 0.0))
+    0.8
+    0.8))
+
+(define (saxophone-lh-low-a-key-stencil radius thick fill layout props)
+  (interpret-markup
+    layout
+    props
+    (make-general-align-markup
+      Y
+      DOWN
+      (make-concat-markup
+        (make-name-keylist
+          `(,(text-fill-translate fill))
+          '(("lowA" . #f))
+          (* 9 radius))))))
+
+;;; Bassoon family stencils
+
+(define (bassoon-bend-info-maker height gap cut)
+  (let* (
+    (first-bezier
+      (flatten-list
+        (car
+          (split-bezier
+            `((0.0 . ,(+ height gap))
+             (0.0 . ,(+ height (+ gap 1.0)))
+             (1.0 . ,(+ height (+ gap 2.0)))
+             (2.0 . ,(+ height (+ gap 2.0))))
+             cut))))
+    (second-bezier
+      (flatten-list
+        (reverse
+          (car
+            (split-bezier
+              `((1.0 . ,height)
+               (1.0 . ,(+ 0.5 height))
+               (1.5 . ,(+ 1.0 height))
+               (2.0 . ,(+ 1.0 height)))
+              cut)))))
+    (slope-offset1
+      (get-slope-offset
+        `(,(list-ref first-bezier 4) . ,(list-ref first-bezier 5))
+        `(,(list-ref first-bezier 6) . ,(list-ref first-bezier 7))))
+    (slope-offset2
+      (get-slope-offset
+        `(,(list-ref second-bezier 0) . ,(list-ref second-bezier 1))
+        `(,(list-ref second-bezier 2) . ,(list-ref second-bezier 3)))))
+   (list first-bezier second-bezier slope-offset1 slope-offset2)))
+
+(define
+  (make-tilted-portion
+    first-bezier
+    second-bezier
+    slope-offset1
+    slope-offset2
+    keylen
+    bezier?)
+  (append
+    `((,(+ keylen (list-ref first-bezier 6))
+     ,(+
+        (*
+          (car slope-offset1)
+          (+ keylen (list-ref first-bezier 6))) (cdr slope-offset1))))
+    ((if bezier? (lambda (x) `(,(apply append x))) identity)
+     `((,(+ (+ keylen 1.75) (list-ref first-bezier 6))
+       ,(+
+          (*
+            (car slope-offset1)
+            (+ (+ keylen 1.75) (list-ref first-bezier 6)))
+          (cdr slope-offset1)))
+       (,(+ (+ keylen 1.75) (list-ref second-bezier 0))
+       ,(+
+          (*
+            (car slope-offset2)
+            (+ (+ keylen 1.75) (list-ref second-bezier 0)))
+          (cdr slope-offset2)))
+       (,(+ keylen (list-ref second-bezier 0))
+        ,(+
+          (* (car slope-offset2)  (+ keylen (list-ref second-bezier 0)))
+          (cdr slope-offset2)))))
+    `(,(list-head second-bezier 2))))
+
+(define (rich-bassoon-uber-key-stencil height gap cut keylen d1 d2 proc bezier?)
+  (let* ((info-list (bassoon-bend-info-maker height gap cut))
+   (first-bezier (car info-list))
+   (second-bezier (cadr info-list))
+   (slope-offset1 (caddr info-list))
+   (slope-offset2 (cadddr info-list)))
+  (rich-mcs-stencil
+    (append
+      `((0.0 ,(+ height gap))
+      ,(list-tail first-bezier 2))
+      (make-tilted-portion
+        first-bezier
+        second-bezier
+        slope-offset1
+        slope-offset2
+        keylen
+        bezier?)
+      `(,(list-tail second-bezier 2)
+      (1.0 0.0)
+      (0.0 0.0)))
+    d1
+    d2
+    proc)))
+
+(define (bassoon-uber-key-stencil height gap cut keylen d1 d2)
+  (rich-bassoon-uber-key-stencil height gap cut keylen d1 d2 identity #t))
+
+(define bassoon-cc-one-key-stencil (standard-e-stencil 1.5 0.8))
+
+(define bassoon-lh-he-key-stencil little-elliptical-key-stencil)
+
+(define bassoon-lh-hees-key-stencil little-elliptical-key-stencil)
+
+(define bassoon-lh-ees-key-stencil
+  (rich-e-stencil
+    1.2
+    0.6
+   (lambda (stencil) (ly:stencil-rotate stencil 30 0 0))))
+
+(define bassoon-lh-cis-key-stencil
+  (rich-e-stencil
+    1.0
+    0.5
+    (lambda (stencil) (ly:stencil-rotate stencil 30 0 0))))
+
+(define bassoon-lh-lbes-key-stencil
+  (bassoon-uber-key-stencil 1.0 0.5 0.7 0.5 0.6 -0.6))
+
+(define bassoon-lh-lb-key-stencil
+  (bassoon-uber-key-stencil 2.0 0.5 0.9 1.2 0.6 -0.6))
+
+(define bassoon-lh-lc-key-stencil
+  (rich-pe-stencil 1.0 1.0 135 315 identity))
+
+(define bassoon-lh-ld-key-stencil
+  (standard-mcs-stencil
+    '((-0.8 4.0 1.4 4.0 0.6 0.0)
+      (0.5 -0.5 0.5 -0.8 0.6 -1.0)
+      (0.7 -1.2 0.8 -1.3 0.8 -1.8)
+      (0.5 -1.8)
+      (0.5 -1.4 0.4 -1.2 0.3 -1.1)
+      (0.2 -1.0 0.1 -0.5 0.0 0.0))
+    1.0
+    1.0))
+
+(define bassoon-lh-d-flick-key-stencil
+  (let ((height 3.0))
+    (standard-mcs-stencil
+      `((0.0 ,height)
+       (0.2 ,(+ height 1.6) 0.8 ,(+ height 1.8) 1.0 ,(+ height 1.8))
+       (1.4 ,(+ height 1.8) 1.9 ,(+ height 1.3) 1.9 ,(+ height 1.0))
+       (1.9 ,(+ height 0.7) 1.0 ,(+ height 0.4) 0.8 ,(+ height 0.3))
+       (0.6 ,(+ height 0.2) 0.4 ,(+ height 0.1) 0.4 ,(- height 0.1))
+       (0.4 0.0)
+       (0.0 0.0))
+      -1.0
+      -1.0)))
+
+(define bassoon-lh-c-flick-key-stencil
+  (let ((height 3.0))
+    (standard-mcs-stencil
+      `((0.0 ,height)
+         (0.0 ,(+ height 1.6) 0.4 ,(+ height 1.8) 0.5 ,(+ height 1.8))
+         (0.7 ,(+ height 1.8) 0.9 ,(+ height 1.3) 0.9 ,(+ height 1.0))
+         (0.9 ,(+ height 0.5) 0.7 ,(+ height 0.4) 0.6 ,(+ height 0.3))
+         (0.5 ,(+ height 0.2) 0.4 ,(+ height 0.1) 0.4 ,(- height 0.1))
+         (0.4 0.0)
+         (0.0 0.0))
+      -1.0
+      -1.0)))
+
+(define bassoon-lh-a-flick-key-stencil
+  (bassoon-uber-key-stencil 5.0 1.0 0.3 0.6 -0.5 -0.5))
+
+(define bassoon-lh-thumb-cis-key-stencil
+  (bassoon-uber-key-stencil 1.5 1.5 0.6 0.6 -0.6 0.6))
+
+(define bassoon-lh-whisper-key-stencil (variable-column-circle-stencil 0.7))
+
+(define bassoon-rh-cis-key-stencil
+  (rich-bassoon-uber-key-stencil
+    1.1
+    1.5
+    0.9
+    0.3
+    0.5
+    0.5
+    (lambda (stencil) (ly:stencil-rotate stencil -76 0 0))
+    #t))
+
+(define bassoon-rh-bes-key-stencil little-elliptical-key-stencil)
+
+(define bassoon-rh-fis-key-stencil
+  (rich-bassoon-uber-key-stencil 0.5 1.0 0.8 1.5 -0.7 0.7 identity #f))
+
+(define bassoon-rh-f-key-stencil
+  (let* ((height 0.5) (gap 1.0) (cut 0.8) (keylen 1.5)
+    (info-list (bassoon-bend-info-maker height gap cut))
+    (first-bezier (car info-list))
+    (second-bezier (cadr info-list))
+    (slope-offset1 (caddr info-list))
+    (slope-offset2 (cadddr info-list)))
+  (standard-mcs-stencil
+    (append
+      (map
+        (lambda (l)
+          (rotunda-map
+            -
+            l
+            (list-tail first-bezier 6)))
+        (make-tilted-portion
+          first-bezier
+          second-bezier
+          slope-offset1
+          slope-offset2
+          keylen
+          #t))
+      '((0.0 0.0)))
+    -0.7
+    0.7)))
+
+(define bassoon-rh-gis-key-stencil
+  (bassoon-uber-key-stencil 0.3 1.0 0.8 1.0 -0.7 0.7))
+
+(define bassoon-rh-thumb-bes-key-stencil
+  (bassoon-uber-key-stencil 1.0 1.0 0.9 1.0 0.7 0.7))
+
+(define bassoon-rh-thumb-e-key-stencil (variable-column-circle-stencil 0.7))
+
+(define bassoon-rh-thumb-fis-key-stencil
+  (bassoon-uber-key-stencil 1.0 1.2 0.9 1.0 0.7 0.7))
+
+(define bassoon-rh-thumb-gis-key-stencil
+  (bassoon-uber-key-stencil 1.2 0.8 0.9 0.4 0.7 0.7))
\ No newline at end of file
diff --git a/scm/display-woodwind-diagrams.scm b/scm/display-woodwind-diagrams.scm
new file mode 100644 (file)
index 0000000..51e2392
--- /dev/null
@@ -0,0 +1,1987 @@
+;;;; This file is part of LilyPond, the GNU music typesetter.
+;;;;
+;;;; Copyright (C) 2010 Mike Solomon <mikesol@stanfordalumni.org>
+;;;;
+;;;; LilyPond is free software: you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation, either version 3 of the License, or
+;;;; (at your option) any later version.
+;;;;
+;;;; LilyPond is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
+
+;; Constants
+
+(define CENTRAL-COLUMN-HOLE-PLACEMENTS '((one . (0.0 . 6.5))
+                                         (two . (0.0 . 5.5))
+                                         (three . (0.0 . 4.5))
+                                         (four . (0.0 . 3.0))
+                                         (five . (0.0 . 2.0))
+                                         (six . (0.0 . 1.0))))
+
+(define CENTRAL-COLUMN-HOLE-LIST (map car CENTRAL-COLUMN-HOLE-PLACEMENTS))
+(define CENTRAL-COLUMN-HOLE-H-LIST (cons 'h CENTRAL-COLUMN-HOLE-LIST))
+
+;; Utility functions
+
+(define (return-1 x) 1.0)
+
+(define (make-spreadsheet parameter-list)
+  "Makes a spreadsheet function with columns of parameter-list.
+   This function can then be filled with rows.
+   For example:
+   @code{guile> ((make-spreadsheet '(foo bar)) '((1 2) (3 4) (5 6)))}
+   @code{(((foo . 1) (bar . 2)) ((foo . 3) (bar . 4)) ((foo . 5) (bar . 6)))}"
+  (lambda (ls)
+    (map (lambda (list-to-translate)
+           (map (lambda (name element)
+                  `(,name . ,element))
+                parameter-list
+                list-to-translate))
+         ls)))
+
+(define (get-spreadsheet-column column spreadsheet)
+  "Gets all the values in @code{column} form @code{spreadsheet}
+   made by @{make-spreadsheet}.
+   For example:
+   @code{guile> (get-spreadsheet-column 'bar ((make-spreadsheet '(foo bar)) '((1 2) (3 4) (5 6))))}
+   @code{(2 4 6)}"
+  (map (lambda (row) (assoc-get column row)) spreadsheet))
+
+(define (make-named-spreadsheet parameter-list)
+  "Makes a named spreadsheet function with columns of parameter-list.
+   This function can then be filled with named rows
+   For example:
+   @code{guile> ((make-named-spreadsheet '(foo bar)) '((x . (1 2)) (y . (3 4)) (z . (5 6))))}
+   @code{((x (foo . 1) (bar . 2)) (y (foo . 3) (bar . 4)) (z (foo . 5) (bar . 6)))}"
+  (lambda (ls)
+    (map (lambda (list-to-translate)
+           `(,(list-ref list-to-translate 0)
+            . ,(map (lambda (name element)
+                      `(,name . ,element))
+                    parameter-list
+                    (list-tail list-to-translate 1))))
+         ls)))
+
+(define (get-named-spreadsheet-column column spreadsheet)
+  "Gets all the values in @code{column} form @code{spreadsheet}
+   made by @{make-named-spreadsheet}.
+   For example:
+   @code{guile> (get-spreadsheet-column 'bar ((make-named-spreadsheet '(foo bar)) '((x . (1 2)) (y . (3 4)) (z . (5 6)))))}
+   @code{((x . 2) (y . 4) (z . 6))}"
+  (map
+    (lambda (row) (cons (car row) (assoc-get column (cdr row))))
+    spreadsheet))
+
+(define make-key-alist
+  (make-named-spreadsheet '(name offset graphical textual)))
+
+(define (simple-stencil-alist stencil offset)
+  "A stencil alist that contains one and only one stencil.
+   Shorthand used repeatedly in various instruments."
+  `((stencils . (,stencil))
+    (offset . ,offset)
+    (textual?  . #f)
+    (xy-scale-function . (,return-1 . ,return-1))))
+
+(define (make-central-column-hole-addresses keys)
+  "Takes @code{keys} and ascribes them to the central column."
+  (map
+    (lambda (key) `(central-column . ,key))
+    keys))
+
+(define (make-key-symbols hand)
+  "Takes @code{hand} and ascribes @code{key} to it."
+  (lambda (keys)
+    (map (lambda (key) `(,hand . ,key))
+         keys)))
+
+(define make-left-hand-key-addresses (make-key-symbols 'left-hand))
+
+(define make-right-hand-key-addresses (make-key-symbols 'right-hand))
+
+;; Flute assembly instructions
+
+(define flute-change-points
+  ((make-named-spreadsheet '(piccolo flute flute-b-extension))
+    `((bottom-group-key-names
+       . (((x
+            . ((offset . (-0.45 . -1.05))
+               (stencil . ,piccolo-rh-x-key-stencil)
+               (text? . ("X" . #f))
+               (complexity . trill))))
+        ((cis
+          . ((offset . (0.0 . 0.0))
+            (stencil . ,flute-rh-cis-key-stencil)
+            (text? . ("C" . 1))
+            (complexity . trill)))
+         (c
+          . ((offset . (0.3 . 0.0))
+             (stencil . ,flute-rh-c-key-stencil)
+             (text? . ("C" . #f))
+             (complexity . trill)))
+         (gz
+          . ((offset . (0.0 . -1.2))
+             (stencil . ,flute-rh-gz-key-stencil)
+             (text? . ("gz" . #f))
+             (complexity . trill))))
+        ((cis
+          . ((offset . (0.0 . 0.0))
+            (stencil . ,flute-rh-cis-key-stencil)
+            (text? . ("C" . 1))
+            (complexity . trill)))
+         (c
+          . ((offset . (0.3 . 0.0))
+             (stencil . ,flute-rh-c-key-stencil)
+             (text? . ("C" . #f))
+             (complexity . trill)))
+         (b
+          . ((offset . (1.0 . 0.0))
+             (stencil . ,flute-rh-b-key-stencil)
+             (text? . ("B" . #f))
+             (complexity . trill)))
+         (gz
+          . ((offset . (0.0 . -1.2))
+             (stencil . ,flute-rh-gz-key-stencil)
+             (text? . ("gz" . #f))
+             (complexity . trill))))))
+      (bottom-group-graphical-stencil
+       . (((right-hand . ees) (right-hand . x))
+          ,(make-right-hand-key-addresses '(ees cis c gz))
+          ,(make-right-hand-key-addresses '(ees cis c b gz))))
+     (bottom-group-graphical-draw-instruction
+       . (((right-hand . ees))
+          ,(make-right-hand-key-addresses '(ees cis c))
+          ,(make-right-hand-key-addresses '(ees cis c b))))
+     (bottom-group-special-key-instruction
+      . ((,rich-group-draw-rule ((right-hand . x)) ((right-hand . ees)))
+         (,rich-group-draw-rule ((right-hand . gz))
+                                ,(make-right-hand-key-addresses
+                                    '(ees cis c)))
+         (,rich-group-draw-rule ((right-hand . gz))
+                                ,(make-right-hand-key-addresses
+                                    '(ees cis c b)))))
+     (bottom-group-text-stencil
+      . (,(make-right-hand-key-addresses '(bes d dis ees x))
+         ,(make-right-hand-key-addresses '(bes d dis ees cis c gz))
+         ,(make-right-hand-key-addresses '(bes d dis ees cis c b gz)))))))
+
+(define (generate-flute-family-entry flute-name)
+  (let*
+      ((change-points
+        (get-named-spreadsheet-column
+          flute-name
+          flute-change-points)))
+  `(,flute-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))))
+           (left-hand
+            . ((bes
+                . ((offset . (0.5 . 1.8))
+                   (stencil . ,flute-lh-bes-key-stencil)
+                   (text? . ("B" . 0))
+                   (complexity . trill)))
+               (b
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,flute-lh-b-key-stencil)
+                   (text? . ("B" . #f))
+                   (complexity . trill)))
+               (gis
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,flute-lh-gis-key-stencil)
+                   (text? . ("G" . 1))
+                   (complexity . trill)))))
+           (right-hand
+            . ,(append `((bes
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,flute-rh-bes-key-stencil)
+                             (text? . ("B" . 0))
+                             (complexity . trill)))
+                         (d
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,flute-rh-d-key-stencil)
+                             (text? . ("D" . #f))
+                             (complexity . trill)))
+                         (dis
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,flute-rh-dis-key-stencil)
+                             (text? . ("D" . 1))
+                             (complexity . trill)))
+                         (ees
+                          . ((offset . (1.5 . 1.3))
+                             (stencil . ,flute-rh-ees-key-stencil)
+                             (text? . ("E" . 0))
+                             (complexity . trill))))
+                       (assoc-get 'bottom-group-key-names change-points)))))
+       (graphical-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                           CENTRAL-COLUMN-HOLE-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))
+                   ((stencils . ((left-hand . bes) (left-hand . b)))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (-1.5 . 6.5)))
+                   ,(simple-stencil-alist '(left-hand . gis) '(1.0 . 4.0))
+                   ,(simple-stencil-alist '(right-hand . bes)  '(-1.75 . 3.05))
+                   ,(simple-stencil-alist '(right-hand . d)  '(-1.0 . 2.5))
+                   ,(simple-stencil-alist '(right-hand . dis)  '(-1.0 . 1.5))
+                   ((stencils
+                     . ,(assoc-get 'bottom-group-graphical-stencil
+                                   change-points))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (0.0 . -0.6)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                (((left-hand . bes) (left-hand . b))
+                 ,(assoc-get 'bottom-group-graphical-draw-instruction
+                             change-points)))
+               ,(assoc-get 'bottom-group-special-key-instruction
+                           change-points)
+               (,group-automate-rule
+                ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,uniform-extra-offset-rule (0.0 . 0.0))))))
+       (text-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                           CENTRAL-COLUMN-HOLE-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))
+                   ((stencils . ,(make-left-hand-key-addresses '(bes b gis)))
+                    (textual? . ,lh-woodwind-text-stencil)
+                    (offset . (1.5 . 3.75)))
+                   ((stencils . ,(assoc-get 'bottom-group-text-stencil
+                                            change-points))
+                    (textual? . ,rh-woodwind-text-stencil)
+                    (offset . (-1.25 . 0.0)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                 (,(make-left-hand-key-addresses '(bes b gis))
+                  ,(assoc-get 'bottom-group-text-stencil change-points)))
+               (,group-automate-rule
+                ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;;; Tin whistle assembly instructions
+
+(define tin-whistle-change-points
+  ((make-named-spreadsheet '(tin-whistle)) '()))
+
+(define (generate-tin-whistle-family-entry tin-whistle-name)
+  (let*
+    ((change-points
+     (get-named-spreadsheet-column tin-whistle-name tin-whistle-change-points)))
+  `(,tin-whistle-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))))
+           (left-hand . ())
+           (right-hand . ())))
+       (graphical-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                          CENTRAL-COLUMN-HOLE-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,group-automate-rule
+                 ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,uniform-extra-offset-rule (0.0 . 0.0))))))
+    (text-commands
+     . ((stencil-alist
+         . ((stencils .
+             (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+              ((stencils
+                . ,(make-central-column-hole-addresses
+                      CENTRAL-COLUMN-HOLE-H-LIST))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0)))))
+            (xy-scale-function . (,identity . ,identity))
+            (textual? . #f)
+            (offset . (0.0 . 0.0))))
+        (draw-instructions
+         . ((,group-automate-rule
+              ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+            (,group-automate-rule ((hidden . midline)))))
+        (extra-offset-instructions
+         . ((,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;;; Oboe assembly instructions
+
+(define oboe-change-points
+  ((make-named-spreadsheet '(oboe)) '()))
+
+(define (generate-oboe-family-entry oboe-name)
+  (let*
+    ((change-points
+     (get-named-spreadsheet-column oboe-name oboe-change-points)))
+  `(,oboe-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (h
+                . ((offset . (0.0 . 6.25))
+                   (stencil . ,(variable-column-circle-stencil 0.4))
+                   (text? . #f)
+                   (complexity . trill)))))
+           (left-hand
+            . ((I
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-lh-I-key-stencil)
+                   (text? . ("I" . #f))
+                   (complexity . trill)))
+               (III
+                . ((offset . (0.0 . 2.6))
+                   (stencil . ,oboe-lh-III-key-stencil)
+                   (text? . ("III" . #f))
+                   (complexity . trill)))
+               (II
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-lh-II-key-stencil)
+                   (text? . ("II" . #f))
+                   (complexity . trill)))
+               (b
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-lh-b-key-stencil)
+                   (text? . ("B" . #f))
+                   (complexity . trill)))
+               (d
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-lh-d-key-stencil)
+                   (text? . ("D" . #f))
+                   (complexity . trill)))
+               (cis
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-lh-cis-key-stencil)
+                   (text? . ("C" . 1))
+                   (complexity . trill)))
+               (gis
+                . ((offset . (-0.85 . 0.2))
+                   (stencil . ,oboe-lh-gis-key-stencil)
+                   (text? . ("G" . 1))
+                   (complexity . trill)))
+               (ees
+                . ((offset . (2.05 . -3.65))
+                   (stencil . ,oboe-lh-ees-key-stencil)
+                   (text? . ("E" . 0))
+                   (complexity . trill)))
+               (low-b
+                . ((offset . (3.6 . 0.5))
+                   (stencil . ,oboe-lh-low-b-key-stencil)
+                   (text? . ("b" . #f))
+                   (complexity . trill)))
+               (bes
+                . ((offset . (2.25 . -4.15))
+                   (stencil . ,oboe-lh-bes-key-stencil)
+                   (text? . ("B" . 0))
+                   (complexity . trill)))
+               (f
+                . ((offset . (2.15 . -3.85))
+                   (stencil . ,oboe-lh-f-key-stencil)
+                   (text? . ("F" . #f))
+                   (complexity . trill)))))
+           (right-hand
+            . ((a
+                . ((offset . (1.5 . 1.2))
+                   (stencil . ,oboe-rh-a-key-stencil)
+                   (text? . ("A" . #f))
+                   (complexity . trill)))
+               (gis
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-rh-gis-key-stencil)
+                   (text? . ("G" . 1))
+                   (complexity . trill)))
+               (d
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-rh-d-key-stencil)
+                   (text? . ("D" . #f))
+                   (complexity . trill)))
+               (f
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-rh-f-key-stencil)
+                   (text? . ("F" . #f))
+                   (complexity . trill)))
+               (banana
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-rh-banana-key-stencil)
+                   (text? . ("ban" . #f))
+                   (complexity . trill)))
+               (c
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,oboe-rh-c-key-stencil)
+                   (text? . ("C" . #f))
+                   (complexity . trill)))
+               (cis
+                . ((offset . (3.8 . -0.6))
+                   (stencil . ,oboe-rh-cis-key-stencil)
+                   (text? . ("C" . 1))
+                   (complexity . trill)))
+               (ees
+                . ((offset . (0.0 . -1.8))
+                   (stencil . ,oboe-rh-ees-key-stencil)
+                   (text? . ("E" . 0))
+                   (complexity . trill)))))))
+       (graphical-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                          CENTRAL-COLUMN-HOLE-H-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))
+                   ((stencils . ((left-hand . I) (left-hand . III)))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (-2.5 . 6.5)))
+                   ,(simple-stencil-alist '(left-hand . II) '(2.5 . 6.0))
+                   ,(simple-stencil-alist '(left-hand . b) '(-1.35 . 6.0))
+                   ,(simple-stencil-alist '(left-hand . d) '(1.0 . 6.0))
+                   ,(simple-stencil-alist '(left-hand . cis) '(1.0 . 5.0))
+                   ((stencils
+                     . ,(make-left-hand-key-addresses '(gis bes low-b ees f)))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (0.0 . 3.9)))
+                   ((stencils .
+                    ,(make-right-hand-key-addresses '(a gis)))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (-3.5 . 3.5)))
+                   ,(simple-stencil-alist '(right-hand . d) '(1.0 . 2.5))
+                   ,(simple-stencil-alist '(right-hand . f)  '(-1.0 . 1.5))
+                   ,(simple-stencil-alist '(right-hand . banana)  '(1.7 . 1.0))
+                   ((stencils . ,(make-right-hand-key-addresses '(c cis ees)))
+                    (xy-scale-function . (,return-1 . ,return-1))
+                    (textual? . #f)
+                    (offset . (-3.4 . 0.3)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                 (((right-hand . a) (right-hand . gis))
+                  ,(make-left-hand-key-addresses '(gis bes low-b ees))
+                  ,(make-right-hand-key-addresses '(cis c ees))))
+               (,rich-group-draw-rule
+                 ((left-hand . III))
+                 ((left-hand . I)))
+               (,rich-group-draw-rule
+                 ((left-hand . f))
+                 ,(make-left-hand-key-addresses '(gis bes low-b ees)))
+               (,group-automate-rule
+                 ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,rich-group-extra-offset-rule
+                 ((central-column . h)) ((central-column . one)) (0.0 . 0.8))
+               (,uniform-extra-offset-rule (0.0 . 0.0))))))
+    (text-commands
+     . ((stencil-alist
+         . ((stencils .
+             (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+              ((stencils
+                . ,(make-central-column-hole-addresses
+                      CENTRAL-COLUMN-HOLE-H-LIST))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0)))
+              ((stencils . ,(make-left-hand-key-addresses '(III I)))
+               (textual? . ,lh-woodwind-text-stencil)
+               (offset . (-2.8 . 7.0)))
+              ((stencils . ,(make-left-hand-key-addresses '(II)))
+               (textual? . ,lh-woodwind-text-stencil)
+               (offset . (2.2 . 7.0)))
+              ((stencils
+                .  ,(make-left-hand-key-addresses
+                      '(b d cis gis ees low-b bes f)))
+               (textual? . ,lh-woodwind-text-stencil)
+               (offset . (1.5 . 3.75)))
+              ((stencils
+                . ,(make-right-hand-key-addresses
+                      '(a gis d f banana c cis ees)))
+               (textual? . ,rh-woodwind-text-stencil)
+               (offset . (-1.25 . 0.0)))))
+            (xy-scale-function . (,identity . ,identity))
+            (textual? . #f)
+            (offset . (0.0 . 0.0))))
+        (draw-instructions
+         . ((,apply-group-draw-rule-series
+              (,(make-left-hand-key-addresses '(b d cis gis ees low-b bes f))
+             ,(make-left-hand-key-addresses '(III I))
+             ,(make-right-hand-key-addresses '(a gis d f banana c cis ees))))
+            (,group-automate-rule
+              ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+            (,group-automate-rule ((hidden . midline)))))
+        (extra-offset-instructions
+         . ((,rich-group-extra-offset-rule
+              ((central-column . h))
+              ((central-column . one))
+              (0.0 . 0.8))
+            (,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;; Clarinet assembly instructions
+
+(define clarinet-change-points
+  ((make-named-spreadsheet '(clarinet bass-clarinet low-bass-clarinet))
+    `((bottom-group-key-names
+       . (()
+          ((f
+            . ((offset . (0.0 . ,(* 0 (+ 0.75 CL-RH-HAIR))))
+               (stencil . ,bass-clarinet-rh-f-key-stencil)
+               (text? . ("F" . #f))
+               (complexity . trill))))
+          ((f
+            . ((offset . (0.0 . ,(* 0 (+ 0.75 CL-RH-HAIR))))
+               (stencil . ,low-bass-clarinet-rh-f-key-stencil)
+               (text? . ("F" . #f))
+               (complexity . trill)))
+           (d
+            . ((offset . (,(+ 1.5 CL-RH-HAIR) . ,(* -1 (+ 0.75 CL-RH-HAIR))))
+               (stencil . ,clarinet-rh-d-key-stencil)
+               (text? . ("d" . #f))
+               (complexity . trill)))
+           (low-cis
+            . ((offset . (0.0 . 1.4))
+               (stencil . ,clarinet-rh-low-cis-key-stencil)
+               (text? . ("c" . 1))
+               (complexity . trill)))
+           (low-d
+            . ((offset . (0.0 . 2.4))
+               (stencil . ,clarinet-rh-low-d-key-stencil)
+               (text? . ("d" . #f))
+               (complexity . trill)))
+           (low-c
+            . ((offset . (0.0 . 0.0))
+               (stencil . ,clarinet-rh-low-c-key-stencil)
+               (text? . ("c" . #f))
+               (complexity . trill))))))
+      (left-extra-key-names
+       . (()
+          ()
+          ((d
+            . ((offset . (4.0 . -0.8))
+               (stencil . ,clarinet-lh-d-key-stencil)
+               (text? . ("D" . #f))
+               (complexity . trill))))))
+      (right-thumb-group
+       . (()
+          ()
+          (((stencils
+            . ,(make-right-hand-key-addresses '(low-c low-cis)))
+           (xy-scale-function . (,return-1 . ,return-1))
+           (textual? . #f)
+           (offset . (-1.3 . 4.0))))))
+      (low-left-hand-key-addresses
+       . (,(make-left-hand-key-addresses '(cis f e fis))
+          ,(make-left-hand-key-addresses '(cis f e fis))
+          ,(make-left-hand-key-addresses '(cis f e fis d))))
+      (all-left-hand-key-addresses
+       . (,(make-left-hand-key-addresses '(a gis ees cis f e fis))
+          ,(make-left-hand-key-addresses '(a gis ees cis f e fis))
+          ,(make-left-hand-key-addresses '(a gis ees cis f e fis d))))
+      (low-key-group
+       . (()
+          ()
+          (,(make-right-hand-key-addresses '(low-c low-cis)))))
+      (low-rich-draw-rules
+       . (()
+          ()
+          ((,rich-group-draw-rule
+                  ((left-hand . d))
+                  ,(make-left-hand-key-addresses '(cis f e fis)))
+           (,rich-group-draw-rule
+                  ((right-hand . low-d))
+                  ((right-hand . low-cis) (right-hand . low-c))))))
+      (low-extra-offset-rule
+       . (()
+          ()
+          ((,rich-group-extra-offset-rule
+                 ,(make-right-hand-key-addresses '(low-c low-d low-cis))
+                 ,(make-right-hand-key-addresses '(one two three four))
+                 (-0.5 . -0.7)))))
+      (bottom-right-group-key-addresses
+       . (,(make-right-hand-key-addresses '(fis e ees gis))
+          ,(make-right-hand-key-addresses '(fis e ees gis f))
+          ,(make-right-hand-key-addresses '(fis e ees gis f d))))
+      (right-hand-key-addresses
+       . (,(make-right-hand-key-addresses '(fis e ees gis))
+          ,(make-right-hand-key-addresses '(fis e ees gis f))
+          ,(make-right-hand-key-addresses
+              '(low-d low-cis low-c fis e ees gis f d)))))))
+
+(define (generate-clarinet-family-entry clarinet-name)
+  (let*
+    ((change-points
+      (get-named-spreadsheet-column clarinet-name clarinet-change-points)))
+  `(,clarinet-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . covered)))
+               (h
+                . ((offset . (0.0 . 6.25))
+                   (stencil . ,(variable-column-circle-stencil 0.4))
+                   (text? . #f)
+                   (complexity . covered)))))
+           (left-hand
+            . ,(append `((thumb
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,clarinet-lh-thumb-key-stencil)
+                             (text? . #f)
+                             (complexity . trill)))
+                         (R
+                          . ((offset . (1.0 . 1.0))
+                             (stencil . ,clarinet-lh-R-key-stencil)
+                             (text? . #f)
+                             (complexity . trill)))
+                         (a
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,clarinet-lh-a-key-stencil)
+                             (text? . ("A" . #f))
+                             (complexity . trill)))
+                         (gis
+                          . ((offset . (0.8 . 1.0))
+                             (stencil . ,clarinet-lh-gis-key-stencil)
+                             (text? . ("G" . 1))
+                             (complexity . trill)))
+                         (ees
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,clarinet-lh-ees-key-stencil)
+                             (text? . ("E" . 0))
+                             (complexity . trill)))
+                         (cis
+                          . ((offset . (-0.85 . 0.2))
+                             (stencil . ,clarinet-lh-cis-key-stencil)
+                             (text? . ("C" . 1))
+                             (complexity . trill)))
+                         (f
+                          . ((offset . (3.6 . 0.5))
+                             (stencil . ,clarinet-lh-f-key-stencil)
+                             (text? . ("F" . #f))
+                             (complexity . trill)))
+                         (e
+                          . ((offset . (2.05 . -3.65))
+                             (stencil . ,clarinet-lh-e-key-stencil)
+                             (text? . ("E" . #f))
+                             (complexity . trill)))
+                         (fis
+                          . ((offset . (2.25 . -4.15))
+                             (stencil . ,clarinet-lh-fis-key-stencil)
+                             (text? . ("F" . 1))
+                             (complexity . trill))))
+                        (assoc-get 'left-extra-key-names change-points)))
+           (right-hand
+            . ,(append `((one
+                          . ((offset . (0.0 . 0.75))
+                             (stencil . ,clarinet-rh-one-key-stencil)
+                             (text? . "1")
+                             (complexity . trill)))
+                         (two
+                          . ((offset . (0.0 . 0.25))
+                             (stencil . ,clarinet-rh-two-key-stencil)
+                             (text? . "2")
+                             (complexity . trill)))
+                         (three
+                          . ((offset . (0.0 . -0.25))
+                             (stencil . ,clarinet-rh-three-key-stencil)
+                             (text? . "3")
+                             (complexity . trill)))
+                         (four
+                          . ((offset . (0.0 . -0.75))
+                             (stencil . ,clarinet-rh-four-key-stencil)
+                             (text? . "4")
+                             (complexity . trill)))
+                         (b
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,clarinet-rh-b-key-stencil)
+                             (text? . ("B" . #f))
+                             (complexity . trill)))
+                         (fis
+                          . ((offset . (0.0 . ,(* 4 (+ 0.75 CL-RH-HAIR))))
+                             (stencil . ,clarinet-rh-fis-key-stencil)
+                             (text? . ("F" . 1))
+                             (complexity . trill)))
+                         (e
+                          . ((offset . (,(+ 1.5 CL-RH-HAIR)
+                                        . ,(* 3 (+ 0.75 CL-RH-HAIR))))
+                             (stencil . ,clarinet-rh-e-key-stencil)
+                             (text? . ("E" . #f))
+                             (complexity . trill)))
+                         (ees
+                          . ((offset . (0.0 . ,(* 2 (+ 0.75 CL-RH-HAIR))))
+                             (stencil . ,clarinet-rh-ees-key-stencil)
+                             (text? . ("E" . 0))
+                             (complexity . trill)))
+                         (gis
+                          . ((offset . (,(+ 1.5 CL-RH-HAIR)
+                                        . ,(* 1 (+ 0.75 CL-RH-HAIR))))
+                             (stencil . ,clarinet-rh-gis-key-stencil)
+                             (text? . ("G" . 1))
+                             (complexity . trill))))
+                       (assoc-get 'bottom-group-key-names change-points)))))
+       (graphical-commands
+        . ((stencil-alist
+            . ((stencils
+                . ,(append (assoc-get 'right-thumb-group change-points)
+                           `(,(simple-stencil-alist '(hidden . midline)
+                                                    '(0.0 . 3.75))
+                            ((stencils
+                              . ,(make-central-column-hole-addresses
+                                   CENTRAL-COLUMN-HOLE-H-LIST))
+                             (xy-scale-function . (,identity . ,identity))
+                             (textual? . #f)
+                             (offset . (0.0 . 0.0)))
+                            ((stencils
+                              . ,(make-left-hand-key-addresses '(thumb R)))
+                             (xy-scale-function . (,identity . ,identity))
+                             (textual? . #f)
+                             (offset . (-2.5 . 6.5)))
+                            ((stencils
+                              . ((left-hand . a) (left-hand . gis)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (0.0 . 7.5)))
+                            ,(simple-stencil-alist '(left-hand . ees)
+                                                   '(1.0 . 5.0))
+                            ((stencils
+                              . ,(make-left-hand-key-addresses '(cis f e fis)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (0.0 . 3.9)))
+                            ((stencils
+                              . ,(make-right-hand-key-addresses
+                                    '(one two three four)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (-1.25 . 3.75)))
+                            ,(simple-stencil-alist '(right-hand . b)
+                                                   '(-1.0 . 1.5))
+                            ((stencils
+                              . ,(assoc-get 'bottom-right-group-key-addresses
+                                            change-points))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (-4.0 . -0.75))))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ,(append (assoc-get 'low-rich-draw-rules change-points)
+                       `((,apply-group-draw-rule-series
+                          ,(append (assoc-get 'low-key-group change-points)
+                                   `(((left-hand . a) (left-hand . gis))
+                                     ,(make-right-hand-key-addresses
+                                         '(one two three four))
+                                     ,(assoc-get 'low-left-hand-key-addresses
+                                                 change-points)
+                                     ,(assoc-get 'right-hand-key-addresses
+                                                 change-points))))
+                        (,rich-group-draw-rule
+                           ((left-hand . R))
+                           ((left-hand . thumb)))
+                        (,group-automate-rule
+                           ,(make-central-column-hole-addresses
+                               CENTRAL-COLUMN-HOLE-LIST))
+                        (,group-automate-rule ((hidden . midline))))))
+           (extra-offset-instructions
+            . ,(append (assoc-get 'low-extra-offset-rule change-points)
+                       `((,rich-group-extra-offset-rule
+                          ((central-column . h))
+                          ((central-column . one)
+                           (left-hand . a)
+                           (left-hand . gis))
+                          (0.0 . 0.8))
+                         (,uniform-extra-offset-rule (0.0 . 0.0)))))))
+       (text-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                           CENTRAL-COLUMN-HOLE-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))
+                   ((stencils . ((left-hand . thumb) (left-hand . R)))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (-2.5 . 6.5)))
+                   ((stencils
+                     . ,(assoc-get 'all-left-hand-key-addresses change-points))
+                    (textual? . ,lh-woodwind-text-stencil)
+                    (offset . (1.5 . 3.75)))
+                   ((stencils
+                     . ,(make-right-hand-key-addresses '(one two three four)))
+                    (textual? . ,number-column-stencil)
+                    (offset . (-1.25 . 3.75)))
+                   ((stencils . ,(assoc-get 'right-hand-key-addresses
+                                            change-points))
+                    (textual? . ,rh-woodwind-text-stencil)
+                    (offset . (-1.25 . 0.0)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                 (,(assoc-get 'all-left-hand-key-addresses change-points)
+                  ,(make-right-hand-key-addresses '(one two three four))
+                  ,(assoc-get 'right-hand-key-addresses change-points)))
+               (,group-automate-rule
+                 ,(make-central-column-hole-addresses
+                     CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,rich-group-extra-offset-rule
+                  ((central-column . h))
+                  ((central-column . one) (left-hand . a) (left-hand . gis))
+                  (0.0 . 0.8))
+               (,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;; Saxophone assembly instructions
+
+(define (saxophone-name-passerelle name)
+  (cond ((eqv? name 'saxophone) 'saxophone)
+        ((eqv? name 'soprano-saxophone) 'saxophone)
+        ((eqv? name 'alto-saxophone) 'saxophone)
+        ((eqv? name 'tenor-saxophone) 'saxophone)
+        ((eqv? name 'baritone-saxophone) 'baritone-saxophone)))
+
+(define saxophone-change-points
+  ((make-named-spreadsheet '(saxophone baritone-saxophone))
+    `((low-a-key-definition
+       . (()
+          ((low-a
+            . ((offset . (0.0 . 0.0))
+               (stencil . ,saxophone-lh-low-a-key-stencil)
+               (text? . #f)
+               (complexity . trill))))))
+     (low-a-key-group
+       . (()
+          (,(simple-stencil-alist '(left-hand . low-a) '(-5.0 . 7.0)))))
+     (low-a-presence
+       . (()
+          ((left-hand . low-a))))
+     (left-hand-key-names
+       . (,(make-right-hand-key-addresses
+              '(ees d f front-f bes gis cis b low-bes))
+          ,(make-right-hand-key-addresses
+              '(ees d f front-f bes gis cis b low-bes low-a)))))))
+
+(define (generate-saxophone-family-entry saxophone-name)
+  (let*
+    ((change-points
+     (get-named-spreadsheet-column
+       (saxophone-name-passerelle saxophone-name) saxophone-change-points)))
+  `(,saxophone-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,column-circle-stencil)
+                   (text? . #f)
+                   (complexity . trill)))))
+           (left-hand
+            . ,(append (assoc-get 'low-a-key-definition change-points)
+                       `((T
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,saxophone-lh-T-key-stencil)
+                             (text? . ("T" . #f))
+                             (complexity . trill)))
+                         (ees
+                          . ((offset . (0.4 . 1.6))
+                             (stencil . ,saxophone-lh-ees-key-stencil)
+                             (text? . ("E" . 0))
+                             (complexity . trill)))
+                         (d
+                          . ((offset . (1.5 . 0.5))
+                             (stencil . ,saxophone-lh-d-key-stencil)
+                             (text? . ("D" . #f))
+                             (complexity . trill)))
+                         (f
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,saxophone-lh-f-key-stencil)
+                             (text? . ("F" . #f))
+                             (complexity . trill)))
+                         (front-f
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,saxophone-lh-front-f-key-stencil)
+                             (text? . ("f" . #f))
+                             (complexity . trill)))
+                         (bes
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,saxophone-lh-bes-key-stencil)
+                             (text? . ("B" . 0))
+                             (complexity . trill)))
+                         (gis
+                          . ((offset . (0.0 . 1.1))
+                             (stencil . ,saxophone-lh-gis-key-stencil)
+                             (text? . ("G" . 1))
+                             (complexity . trill)))
+                         (cis
+                          . ((offset . (2.4 . 0.0))
+                             (stencil . ,saxophone-lh-cis-key-stencil)
+                             (text? . ("C" . 1))
+                             (complexity . trill)))
+                         (b
+                          . ((offset . (0.0 . 0.0))
+                            (stencil . ,saxophone-lh-b-key-stencil)
+                            (text? . ("B" . #f))
+                            (complexity . trill)))
+                         (low-bes
+                          . ((offset . (0.0 . -0.2))
+                             (stencil . ,saxophone-lh-low-bes-key-stencil)
+                             (text? . ("b" . 0))
+                             (complexity . trill))))))
+           (right-hand
+            . ((e
+                . ((offset . (0.0 . 2.0))
+                   (stencil . ,saxophone-rh-e-key-stencil)
+                   (text? . ("E" . #f))
+                   (complexity . trill)))
+               (c
+                . ((offset . (0.0 . 0.9))
+                   (stencil . ,saxophone-rh-c-key-stencil)
+                   (text? . ("C" . #f))
+                   (complexity . trill)))
+               (bes
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,saxophone-rh-bes-key-stencil)
+                   (text? . ("B" . 0))
+                   (complexity . trill)))
+               (high-fis
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,saxophone-rh-high-fis-key-stencil)
+                   (text? . ("hF" . 1))
+                   (complexity . trill)))
+               (fis
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,saxophone-rh-fis-key-stencil)
+                   (text? . ("F" . 1))
+                   (complexity . trill)))
+               (ees
+                . ((offset . (0.0 . 0.7))
+                   (stencil . ,saxophone-rh-ees-key-stencil)
+                   (text? . ("E" . 0))
+                   (complexity . trill)))
+               (low-c
+                . ((offset . (-1.2 . -0.1))
+                   (stencil . ,saxophone-rh-low-c-key-stencil)
+                   (text? . ("c" . #f))
+                   (complexity . trill)))))))
+      (graphical-commands
+       . ((stencil-alist
+           . ((stencils
+               . ,(append (assoc-get 'low-a-key-group change-points)
+                          `(,(simple-stencil-alist '(hidden . midline)
+                                                   '(0.0 . 3.75))
+                            ((stencils
+                              . ,(make-central-column-hole-addresses
+                                    CENTRAL-COLUMN-HOLE-LIST))
+                             (xy-scale-function . (,identity . ,identity))
+                             (textual? . #f)
+                             (offset . (0.0 . 0.0)))
+                            ((stencils
+                              . ,(make-left-hand-key-addresses '(ees d f)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (1.5 . 6.8)))
+                            ,(simple-stencil-alist '(left-hand . front-f)
+                                                   '(0.0 . 7.35))
+                            ,(simple-stencil-alist '(left-hand . T)
+                                                   '(-2.2 . 6.5))
+                            ,(simple-stencil-alist '(left-hand . bes)
+                                                   '(0.0 . 6.2))
+                            ((stencils
+                              . ,(make-left-hand-key-addresses
+                                    '(gis cis b low-bes)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (1.2 . 3.5)))
+                            ((stencils
+                              . ,(make-right-hand-key-addresses '(e c bes)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (-2.3 . 3.4)))
+                            ,(simple-stencil-alist '(right-hand . high-fis)
+                                                   '(-1.8 . 2.5))
+                            ,(simple-stencil-alist '(right-hand . fis)
+                                                   '(-1.5 . 1.5))
+                            ((stencils
+                              . ,(make-right-hand-key-addresses '(ees low-c)))
+                             (xy-scale-function . (,return-1 . ,return-1))
+                             (textual? . #f)
+                             (offset . (-2.0 . 0.3))))))
+              (xy-scale-function . (,identity . ,identity))
+              (textual? . #f)
+              (offset . (0.0 . 0.0))))
+          (draw-instructions
+           . ((,apply-group-draw-rule-series
+                (,(make-left-hand-key-addresses '(ees d f))
+                 ,(make-left-hand-key-addresses '(gis cis b low-bes))
+                 ,(make-right-hand-key-addresses '(e c bes))
+                 ,(make-right-hand-key-addresses '(ees low-c))))
+              (,group-automate-rule
+                ,(make-central-column-hole-addresses CENTRAL-COLUMN-HOLE-LIST))
+              (,group-automate-rule ((hidden . midline)))))
+          (extra-offset-instructions
+           . ((,rich-group-extra-offset-rule
+                ((left-hand . bes))
+                ,(append (assoc-get 'low-a-presence change-points)
+                         '((central-column . one)
+                           (left-hand . front-f)
+                           (left-hand . T)
+                           (left-hand . ees)
+                           (left-hand . d)
+                           (left-hand . f)))
+                (0.0 . 1.0))
+              (,uniform-extra-offset-rule (0.0 . 0.0))))))
+      (text-commands
+       . ((stencil-alist
+           . ((stencils
+               . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                  ((stencils
+                    . ,(make-central-column-hole-addresses
+                          CENTRAL-COLUMN-HOLE-LIST))
+                   (xy-scale-function . (,identity . ,identity))
+                   (textual? . #f)
+                   (offset . (0.0 . 0.0)))
+                  ,(simple-stencil-alist '(left-hand . T) '(-1.0 . 7.0))
+                  ((stencils
+                    . ,(assoc-get 'left-hand-key-names change-points))
+                   (textual? . ,lh-woodwind-text-stencil)
+                   (offset . (1.5 . 3.75)))
+                  ((stencils
+                    . ,(make-right-hand-key-addresses
+                          '(e c bes high-fis fis ees low-c)))
+                   (textual? . ,rh-woodwind-text-stencil)
+                   (offset . (-1.25 . 0.0)))))
+              (xy-scale-function . (,identity . ,identity))
+              (textual? . #f)
+              (offset . (0.0 . 0.0))))
+          (draw-instructions
+           . ((,apply-group-draw-rule-series
+                (,(make-left-hand-key-addresses
+                    '(ees d f front-f bes gis cis b low-bes))
+                 ,(make-right-hand-key-addresses
+                    '(e c bes high-fis fis ees low-c))))
+              (,group-automate-rule
+                 ,(make-central-column-hole-addresses
+                    CENTRAL-COLUMN-HOLE-LIST))
+              (,group-automate-rule ((hidden . midline)))))
+          (extra-offset-instructions
+           . ((,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;; Bassoon assembly instructions
+
+(define bassoon-change-points
+  ((make-named-spreadsheet '(bassoon contrabassoon))
+    `((left-hand-additional-keys .
+      (((a .
+         ((offset . (0.0 . -0.3))
+         (stencil . ,bassoon-lh-a-flick-key-stencil)
+         (text? . ("A" . #f))
+         (complexity . trill)))
+        (w .
+         ((offset . (0.0 . 0.0))
+         (stencil . ,bassoon-lh-whisper-key-stencil)
+         (text? . ("w" . #f))
+         (complexity . trill))))
+        ()))
+      (right-hand-additional-keys .
+      (((cis .
+          ((offset . (0.0 . 0.0))
+          (stencil . ,bassoon-rh-cis-key-stencil)
+          (text? . ("C" . 1))
+          (complexity . trill)))
+        (thumb-gis .
+          ((offset . (0.0 . 0.0))
+          (stencil . ,bassoon-rh-thumb-gis-key-stencil)
+          (text? . ("G" . 1))
+          (complexity . trill))))
+        ()))
+     (left-hand-flick-group .
+       (((left-hand . d) (left-hand . c) (left-hand . a))
+         ((left-hand . d) (left-hand . c))))
+     (left-hand-thumb-group .
+       (((left-hand . w) (left-hand . thumb-cis))
+         ((left-hand . thumb-cis))))
+     (cis-offset-instruction .
+       (((,rich-group-extra-offset-rule
+         ((right-hand . cis))
+         ,(append
+           '((hidden . midline) (hidden . long-midline))
+           (make-central-column-hole-addresses '(three two one))
+           (make-left-hand-key-addresses
+            '(low-b low-bes low-c low-d d a c w thumb-cis
+              high-ees high-e cis ees)))
+         (0.0 . 0.9)))
+        ()))
+     (right-hand-lower-thumb-group .
+       (((right-hand . thumb-gis) (right-hand . thumb-fis))
+         ((right-hand . thumb-fis))))
+     (right-hand-cis-key .
+       ((,(simple-stencil-alist '(right-hand . cis) '(-2.3 . 3.22)))
+         ()))
+     (back-left-hand-key-addresses .
+      ((low-b low-bes low-c low-d d a c w thumb-cis)
+       (low-b low-bes low-c low-d d c thumb-cis)))
+     (front-right-hand-key-addresses .
+      ((cis bes fis f gis) (bes fis f gis)))
+     (back-right-hand-key-addresses .
+      ((thumb-bes thumb-gis thumb-e thumb-fis)
+       (thumb-bes thumb-e thumb-fis))))))
+
+(define (generate-bassoon-family-entry bassoon-name)
+  (let*
+    ((change-points
+     (get-named-spreadsheet-column bassoon-name bassoon-change-points)))
+  `(,bassoon-name
+    . ((keys
+        . ((hidden
+            . ((midline
+                .  ((offset . (0.0 . 0.0))
+                    (stencil . ,midline-stencil)
+                    (text? . #f)
+                    (complexity . basic)))
+               (long-midline
+                . ((offset . (0.0 . 0.0))
+                   (stencil . ,long-midline-stencil)
+                   (text? . #f)
+                   (complexity . basic)))))
+           (central-column
+            . ((one
+                . ((offset . ,(assoc-get 'one CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,bassoon-cc-one-key-stencil)
+                   (text? . #f)
+                   (complexity . trill)))
+               (two
+                . ((offset . ,(assoc-get 'two CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (three
+                . ((offset . ,(assoc-get 'three CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (four
+                . ((offset . ,(assoc-get 'four CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (five
+                . ((offset . ,(assoc-get 'five CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))
+               (six
+                . ((offset . ,(assoc-get 'six CENTRAL-COLUMN-HOLE-PLACEMENTS))
+                   (stencil . ,ring-column-circle-stencil)
+                   (text? . #f)
+                   (complexity . ring)))))
+           (left-hand
+            . ,(append (assoc-get 'left-hand-additional-keys
+                                  change-points)
+                       `((high-e
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-he-key-stencil)
+                             (text? . ("hE" . #f))
+                             (complexity . trill)))
+                         (high-ees
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-hees-key-stencil)
+                             (text? . ("hE" . 0))
+                             (complexity . trill)))
+                         (ees
+                          . ((offset . (-1.0 . 1.0))
+                             (stencil . ,bassoon-lh-ees-key-stencil)
+                             (text? . ("E" . 0))
+                             (complexity . trill)))
+                         (cis
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-cis-key-stencil)
+                             (text? . ("C" . 1))
+                             (complexity . trill)))
+                         (low-bes
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-lbes-key-stencil)
+                             (text? . ("b" . 0))
+                             (complexity . trill)))
+                         (low-b
+                          . ((offset . (-1.0 . -0.7))
+                             (stencil . ,bassoon-lh-lb-key-stencil)
+                             (text? . ("b" . #f))
+                             (complexity . trill)))
+                         (low-c
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-lc-key-stencil)
+                             (text? . ("c" . #f))
+                             (complexity . trill)))
+                         (low-d
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-lh-ld-key-stencil)
+                             (text? . ("d" . #f))
+                             (complexity . trill)))
+                         (d
+                          . ((offset . (-1.5 . 2.0))
+                             (stencil . ,bassoon-lh-d-flick-key-stencil)
+                             (text? . ("D" . #f))
+                             (complexity . trill)))
+                         (c
+                          . ((offset . (-0.8 . 1.1))
+                             (stencil . ,bassoon-lh-c-flick-key-stencil)
+                             (text? . ("C" . #f))
+                             (complexity . trill)))
+                         (thumb-cis
+                          . ((offset . (2.0 . -1.0))
+                             (stencil . ,bassoon-lh-thumb-cis-key-stencil)
+                             (text? . ("C" . 1))
+                             (complexity . trill))))))
+           (right-hand
+            . ,(append (assoc-get 'right-hand-additional-keys
+                                  change-points)
+                       `((bes
+                          . ((offset . (0.0 . 0.8))
+                             (stencil . ,bassoon-rh-bes-key-stencil)
+                             (text? . ("B" . 0))
+                             (complexity . trill)))
+                         (f
+                          . ((offset . (-2.2 . 4.35))
+                             (stencil . ,bassoon-rh-f-key-stencil)
+                             (text? . ("F" . #f))
+                             (complexity . trill)))
+                         (fis
+                          . ((offset . (1.5 . 1.0))
+                             (stencil . ,bassoon-rh-fis-key-stencil)
+                             (text? . ("F" . 1))
+                             (complexity . trill)))
+                         (gis
+                          . ((offset . (0.0 . -0.15))
+                             (stencil . ,bassoon-rh-gis-key-stencil)
+                             (text? . ("G" . 1))
+                             (complexity . trill)))
+                         (thumb-bes
+                          . ((offset . (0.0 . 0.0))
+                             (stencil . ,bassoon-rh-thumb-bes-key-stencil)
+                             (text? . ("B" . 0))
+                             (complexity . trill)))
+                         (thumb-e
+                          . ((offset . (1.75 . 0.4))
+                             (stencil . ,bassoon-rh-thumb-e-key-stencil)
+                             (text? . ("E" . #f))
+                             (complexity . trill)))
+                         (thumb-fis
+                          . ((offset . (-1.0 . 1.6))
+                             (stencil . ,bassoon-rh-thumb-fis-key-stencil)
+                             (text? . ("F" . 1))
+                             (complexity . trill))))))))
+       (graphical-commands
+        . ((stencil-alist
+            . ((stencils
+                . ,(append (assoc-get 'right-hand-cis-key change-points)
+                           `(,(simple-stencil-alist '(hidden . midline)
+                                                    '(0.0 . 3.75))
+                             ,(simple-stencil-alist '(hidden . long-midline)
+                                                    '(0.0 . 3.80))
+                             ((stencils
+                               . ,(make-central-column-hole-addresses
+                                     CENTRAL-COLUMN-HOLE-LIST))
+                              (xy-scale-function . (,identity . ,identity))
+                              (textual? . #f)
+                              (offset . (0.0 . 0.0)))
+                             ,(simple-stencil-alist '(left-hand . high-e)
+                                                    '(-1.0 . 7.0))
+                             ,(simple-stencil-alist '(left-hand . high-ees)
+                                                    '(-1.0 . 6.0))
+                             ((stencils
+                               . ((left-hand . ees) (left-hand . cis)))
+                              (xy-scale-function . (,return-1 . ,return-1))
+                              (textual? . #f)
+                              (offset . (3.0 . 3.75)))
+                             ((stencils
+                               . (((stencils
+                                    . ((left-hand . low-b)
+                                       (left-hand . low-bes)))
+                                   (xy-scale-function
+                                    . (,return-1 . ,return-1))
+                                   (textual? . #f)
+                                   (offset . (-2.0 . 9.0)))
+                                  ((stencils
+                                    . ,(assoc-get 'left-hand-flick-group
+                                                  change-points))
+                                   (xy-scale-function
+                                    . (,return-1 . ,return-1))
+                                   (textual? . #f)
+                                   (offset . (3.0 . 7.0)))
+                                  ,(simple-stencil-alist '(left-hand . low-c)
+                                                         '(-1.0 . 4.5))
+                                  ,(simple-stencil-alist '(left-hand . low-d)
+                                                         '(-1.0 . 0.1))
+                                  ((stencils
+                                    . ,(assoc-get 'left-hand-thumb-group
+                                                  change-points))
+                                   (xy-scale-function
+                                    . (,return-1 . ,return-1))
+                                   (textual? . #f)
+                                   (offset . (1.5 . -0.6)))))
+                              (xy-scale-function . (,return-1 . ,return-1))
+                              (textual? . #f)
+                              (offset . (-5.5 . 4.7)))
+                             ,(simple-stencil-alist '(right-hand . bes)
+                                                    '(1.0 . 1.2))
+                             ((stencils
+                               . ,(make-right-hand-key-addresses '(gis f fis)))
+                              (xy-scale-function . (,return-1 . ,return-1))
+                              (textual? . #f)
+                              (offset . (2.0 . -1.25)))
+                             ((stencils
+                               . (((stencils
+                                    . ((right-hand . thumb-bes)
+                                       (right-hand . thumb-e)))
+                                   (xy-scale-function
+                                    . (,return-1 . ,return-1))
+                                   (textual? . #f)
+                                   (offset . (-1.22 . 5.25)))
+                                  ((stencils
+                                    . ,(assoc-get 'right-hand-lower-thumb-group
+                                                  change-points))
+                                   (xy-scale-function
+                                    . (,return-1 . ,return-1))
+                                   (textual? . #f)
+                                   (offset . (0.0 . 0.0)))))
+                              (xy-scale-function
+                               . (,return-1 . ,return-1))
+                              (textual? . #f)
+                              (offset . (-5.0 . 0.0))))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                (,(make-left-hand-key-addresses '(ees cis))
+                 ,(make-left-hand-key-addresses
+                 (assoc-get 'back-left-hand-key-addresses change-points))
+                 ,(make-right-hand-key-addresses '(f fis gis))
+                 ,(make-right-hand-key-addresses
+                 (assoc-get 'back-right-hand-key-addresses change-points))))
+               (,group-automate-rule
+                ,(make-central-column-hole-addresses
+                  CENTRAL-COLUMN-HOLE-LIST))
+               (,bassoon-midline-rule
+                  ,(append
+                     (make-left-hand-key-addresses
+                       (assoc-get 'back-left-hand-key-addresses change-points))
+                     (make-right-hand-key-addresses
+                        (assoc-get 'back-right-hand-key-addresses
+                                   change-points))))))
+           (extra-offset-instructions
+            . ,(append
+                 (assoc-get 'cis-offset-instruction change-points)
+                 `((,uniform-extra-offset-rule (0.0 . 0.0)))))))
+       (text-commands
+        . ((stencil-alist
+            . ((stencils
+                . (,(simple-stencil-alist '(hidden . midline) '(0.0 . 3.75))
+                   ((stencils
+                     . ,(make-central-column-hole-addresses
+                         CENTRAL-COLUMN-HOLE-LIST))
+                    (xy-scale-function . (,identity . ,identity))
+                    (textual? . #f)
+                    (offset . (0.0 . 0.0)))
+                   ((stencils
+                     . ,(make-left-hand-key-addresses
+                           '(high-e high-ees ees cis)))
+                    (textual? . ,lh-woodwind-text-stencil)
+                    (offset . (1.5 . 3.75)))
+                   ((stencils
+                     . ,(make-left-hand-key-addresses
+                          (assoc-get 'back-left-hand-key-addresses
+                                     change-points)))
+                    (textual? . ,rh-woodwind-text-stencil)
+                    (offset . (-1.25 . 3.75)))
+                   ((stencils
+                     . ,(make-right-hand-key-addresses
+                          (assoc-get 'front-right-hand-key-addresses
+                                     change-points)))
+                    (textual? . ,lh-woodwind-text-stencil)
+                    (offset . (1.5 . 0.0)))
+                   ((stencils .
+                     ,(make-right-hand-key-addresses
+                       (assoc-get 'back-right-hand-key-addresses
+                                  change-points)))
+                    (textual? . ,rh-woodwind-text-stencil)
+                    (offset . (-1.25 . 0.0)))))
+               (xy-scale-function . (,identity . ,identity))
+               (textual? . #f)
+               (offset . (0.0 . 0.0))))
+           (draw-instructions
+            . ((,apply-group-draw-rule-series
+                 (,(make-left-hand-key-addresses
+                     (assoc-get 'back-left-hand-key-addresses change-points))
+                  ,(make-right-hand-key-addresses
+                     (assoc-get 'front-right-hand-key-addresses change-points))
+                  ,(make-right-hand-key-addresses
+                      (assoc-get 'back-right-hand-key-addresses change-points))
+                  ,(make-left-hand-key-addresses '(high-e high-ees ees cis))))
+               (,group-automate-rule
+                 ,(make-central-column-hole-addresses
+                     CENTRAL-COLUMN-HOLE-LIST))
+               (,group-automate-rule ((hidden . midline)))))
+           (extra-offset-instructions
+            . ((,uniform-extra-offset-rule (0.0 . 0.0))))))))))
+
+;; Assembly functions
+
+; Scans a bank for name.
+; for example, '(left-hand . bes) will return bes in the left-hand
+; of a given bank
+(define (get-key name bank)
+  (assoc-get (cdr name) (assoc-get (car name) bank)))
+
+(define (translate-key-instruction key-instruction)
+  (let*
+    ((key-name (car key-instruction))
+    (key-complexity (assoc-get 'complexity (cdr key-instruction))))
+   (cond
+    ((eqv? key-complexity 'basic)
+      `((,key-name . ,(assoc-get 'F HOLE-FILL-LIST))))
+    ((eqv? key-complexity 'trill)
+       (make-symbol-alist key-name #t #f))
+    ((eqv? key-complexity 'covered)
+       (make-symbol-alist key-name #f #f))
+    ((eqv? key-complexity 'ring)
+       (make-symbol-alist key-name #f #t)))))
+
+(define (update-possb-list input-key possibility-list canonic-list)
+  (if (null? possibility-list)
+    (ly:error "woodwind markup error - invalid key or hole requested")
+    (if
+      (assoc-get input-key (cdar possibility-list))
+      (append
+        `(((,(caaar possibility-list) .
+            ,(assoc-get input-key (cdar possibility-list))) .
+           ,(assoc-get (caar possibility-list) canonic-list)))
+          (assoc-remove (caar possibility-list) canonic-list))
+      (update-possb-list input-key (cdr possibility-list) canonic-list))))
+
+(define (key-crawler input-list possibility-list)
+  (if (null? input-list)
+    (map car possibility-list)
+    (key-crawler
+      (cdr input-list)
+      (update-possb-list
+        (car input-list)
+        possibility-list
+        possibility-list))))
+
+(define (translate-draw-instructions input-alist key-name-alist)
+  (apply append
+    (map (lambda (short long)
+           (let*
+             ((key-instructions
+               (map (lambda (instr)
+                      `(((,long . ,(car instr)) . 0)
+                        . ,(translate-key-instruction instr)))
+                    (assoc-get long key-name-alist))))
+            (key-crawler (assoc-get short input-alist) key-instructions)))
+         '(hd cc lh rh)
+         '(hidden central-column left-hand right-hand))))
+
+(define (uniform-draw-instructions key-name-alist)
+    (apply append
+      (map (lambda (long)
+             (map (lambda (key-instructions)
+                    `((,long . ,(car key-instructions)) . 1))
+                  (assoc-get long key-name-alist)))
+           '(hidden central-column left-hand right-hand))))
+
+(define (list-all-possible-keys key-name-alist)
+  (map (lambda (short long)
+         `(,short
+           . ,(map (lambda (key-instructions)
+                     (car key-instructions))
+                   (assoc-get long key-name-alist))))
+       '(cc lh rh)
+       '(central-column left-hand right-hand)))
+
+(define (list-all-possible-keys-verbose key-name-alist)
+  (map (lambda (short long)
+         `(,short
+           . ,(map (lambda (key-instructions)
+                     `(,(car key-instructions)
+                       . ,(map (lambda (x)
+                                 (car x))
+                               (translate-key-instruction key-instructions))))
+                   (assoc-get long key-name-alist))))
+       '(cc lh rh)
+       '(central-column left-hand right-hand)))
+
+(define woodwind-data-assembly-instructions
+  `((,generate-flute-family-entry . piccolo)
+    (,generate-flute-family-entry . flute)
+    (,generate-flute-family-entry . flute-b-extension)
+    (,generate-tin-whistle-family-entry . tin-whistle)
+    (,generate-oboe-family-entry . oboe)
+    (,generate-clarinet-family-entry . clarinet)
+    (,generate-clarinet-family-entry . bass-clarinet)
+    (,generate-clarinet-family-entry . low-bass-clarinet)
+    (,generate-saxophone-family-entry . saxophone)
+    (,generate-saxophone-family-entry . soprano-saxophone)
+    (,generate-saxophone-family-entry . alto-saxophone)
+    (,generate-saxophone-family-entry . tenor-saxophone)
+    (,generate-saxophone-family-entry . baritone-saxophone)
+    (,generate-bassoon-family-entry . bassoon)
+    (,generate-bassoon-family-entry . contrabassoon)))
+
+(define-public woodwind-instrument-list
+  (map cdr woodwind-data-assembly-instructions))
+
+(define woodwind-data-alist
+  (map (lambda (instruction)
+         ((car instruction) (cdr instruction)))
+       woodwind-data-assembly-instructions))
+
+;;; The brains of the markup function: takes drawing and offset information
+;;; about a key region and calls the appropriate stencils to draw the region.
+
+(define
+  (assemble-stencils
+    stencil-alist
+    key-bank
+    draw-instructions
+    extra-offset-instructions
+    radius
+    thick
+    xy-stretch
+    layout
+    props)
+  (apply
+    ly:stencil-add
+    (map (lambda (node)
+           (ly:stencil-translate
+             (if (pair? (cdr node))
+                 (if (assoc-get 'textual? node)
+                     ((assoc-get 'textual? node) (map (lambda (key)
+                                                        (assoc-get 'text? key))
+                                                      (map (lambda (instr)
+                                                             (get-key
+                                                               instr
+                                                               key-bank))
+                                                 (assoc-get 'stencils node)))
+                                                 radius
+                                                 (map (lambda (key)
+                                                        (assoc-get
+                                                          key
+                                                          draw-instructions))
+                                                      (assoc-get 'stencils
+                                                                 node))
+                                                 layout
+                                                 props)
+                     (assemble-stencils
+                       node
+                       key-bank
+                       draw-instructions
+                       extra-offset-instructions
+                       radius
+                       thick
+                       (coord-apply (assoc-get 'xy-scale-function stencil-alist)
+                                    xy-stretch)
+                       layout
+                       props))
+               (if (= 0 (assoc-get node draw-instructions))
+                   empty-stencil
+                   ((assoc-get 'stencil (get-key node key-bank))
+                     radius
+                     thick
+                     (assoc-get node draw-instructions)
+                     layout
+                     props)))
+             (coord-scale
+               (coord-translate
+                 (coord-scale
+                   (assoc-get
+                     'offset
+                     (if (pair? (cdr node))
+                       node
+                       (get-key node key-bank)))
+                   (coord-apply
+                     (assoc-get 'xy-scale-function stencil-alist)
+                     xy-stretch))
+                 (if
+                   (assoc-get node extra-offset-instructions)
+                   (assoc-get node extra-offset-instructions)
+                   '(0.0 . 0.0)))
+               radius)))
+         (assoc-get 'stencils stencil-alist))))
+
+(define-public (print-keys instrument)
+  (let*
+    ((chosen-instrument
+      (begin
+        (format #t "\nPrinting keys for: ~a\n" instrument)
+        (assoc-get instrument woodwind-data-alist)))
+   (key-list (list-all-possible-keys (assoc-get 'keys chosen-instrument))))
+  (define (key-list-loop key-list)
+    (if (null? key-list)
+      0
+      (begin
+        (format #t "~a\n   ~a\n" (caar key-list) (cdar key-list))
+        (key-list-loop (cdr key-list)))))
+  (key-list-loop key-list)))
+
+(define-public (get-woodwind-key-list instrument)
+  (list-all-possible-keys-verbose
+    (assoc-get
+      'keys
+      (assoc-get instrument woodwind-data-alist))))
+
+(define-public (print-keys-verbose instrument)
+  (let*
+    ((chosen-instrument
+      (begin
+        (format #t "\nPrinting keys in verbose mode for: ~a\n" instrument)
+        (assoc-get instrument woodwind-data-alist)))
+   (key-list
+     (list-all-possible-keys-verbose (assoc-get 'keys chosen-instrument))))
+  (define (key-list-loop key-list)
+    (if (null? key-list)
+      0
+      (begin
+        (format #t "~a\n" (caar key-list))
+        (map (lambda (x)
+               (format #t "   possibilities for ~a:\n      ~a\n" (car x) (cdr x)))
+             (cdar key-list))
+        (key-list-loop (cdr key-list)))))
+  (key-list-loop key-list)))
+
+(define-markup-command
+  (woodwind-diagram layout props instrument input-list)
+  (symbol? list?)
+  #:category instrument-specific-markup ; markup category
+  "Make a woodwind-instrument diagram.  For example, say
+
+@example
+\\markup \\woodwind-diagram #'oboe #'(1.4 0.1 #t ((lh . (d ees)) (cc . (five3qT1q)) (rh . (gis))))
+@end example
+
+@noindent
+for an oboe with the left-hand d key, left-hand ees key,
+and right-hand gis key depressed while the five-hole of
+the central column effectuates a trill between 1/4 and 3/4 closed.
+
+The following instruments are supported:
+@itemize @minus
+
+@item
+piccolo
+
+@item
+flute
+
+@item
+oboe
+
+@item
+clarinet
+
+@item
+bass-clarinet
+
+@item
+saxophone
+
+@item
+bassoon
+
+@item
+contrabassoon
+
+@end itemize
+
+To see all of the callable keys for a given instrument,
+include the function @code{(print-keys 'instrument)}
+in your .ly file, where instrument is the instrument
+whose keys you want to print.
+
+Certain keys allow for special configurations.  The entire gamut of
+configurations possible is as follows:
+
+@itemize @minus
+
+@item
+1q (1/4 covered)
+
+@item
+1h (1/2 covered)
+
+@item
+3q (3/4 covered)
+
+@item
+R (ring depressed)
+
+@item
+F (fully covered; the default if no state put)
+
+@end itemize
+
+Additionally, these configurations can be used in trills.  So, for example,
+@code{three3qTR} effectuates a trill between 3/4 full and ring depressed
+on the three hole.  As another example, @code{threeRT} effectuates a trill
+between R and open, whereas @code{threeTR} effectuates a trill between open
+and shut.  To see all of the possibilities for all of the keys of a given
+instrument, invoke @code{(print-keys-verbose 'instrument)}.
+
+Lastly, substituting an empty list for the pressed-key alist will result in
+a diagram with all of the keys drawn but none filled. ie...
+
+@example
+\\markup \\woodwind-diagram #'oboe #'(1.4 0.1 #t ())
+@end example"
+  (let*  ((radius (car input-list))
+    (thick (cadr input-list))
+    (display-graphic (caddr input-list))
+    (xy-stretch `(1.0 . 2.5))
+    (chosen-instrument (assoc-get instrument woodwind-data-alist))
+    (chosen-instrument
+      (if (not chosen-instrument)
+          (ly:error "~a is not a valid woodwind instrument."
+                    instrument)
+          chosen-instrument))
+    (stencil-info
+      (assoc-get
+        (if display-graphic 'graphical-commands 'text-commands)
+        chosen-instrument))
+    (user-draw-commands (cadddr input-list))
+    (pressed-info
+      (if (null? user-draw-commands)
+        (uniform-draw-instructions (assoc-get 'keys chosen-instrument))
+        (translate-draw-instructions
+          (append '((hd . ())) user-draw-commands)
+          (assoc-get 'keys chosen-instrument))))
+    (draw-info
+      (function-chain
+        pressed-info
+        (assoc-get 'draw-instructions stencil-info)))
+    (extra-offset-info
+      (function-chain
+        pressed-info
+        (assoc-get 'extra-offset-instructions stencil-info))))
+   (assemble-stencils
+     (assoc-get 'stencil-alist stencil-info)
+     (assoc-get 'keys chosen-instrument)
+     draw-info
+     extra-offset-info
+     radius
+     thick
+     xy-stretch
+     layout
+     props)))
\ No newline at end of file
index f858ac20746b5767d6608aab2fb3c89fb0693351..55191e0f07642fa580658fe413036ac97e73148b 100644 (file)
 
 (define-public (add-stroke-straight stencil stem-grob dir log stroke-style offset length thickness stroke-thickness)
   "Add the stroke for acciaccatura to the given flag stencil.
-  The stroke starts for up-flags at upper-end-of-flag+(0,length/2) and 
+  The stroke starts for up-flags at upper-end-of-flag+(0,length/2) and
   ends at (0, vertical-center-of-flag-end) - (flag-x-width/2, flag-x-width + flag-thickness).
-  Here length is the whole length, while flag-x-width is just the 
-  x-extent and thus depends on the angle! Other combinations don't look as 
+  Here length is the whole length, while flag-x-width is just the
+  x-extent and thus depends on the angle! Other combinations don't look as
   good... For down-stems the y-coordinates are simply mirrored."
   (let* ((start (offset-add offset (cons 0  (* (/ length 2) dir))))
-         (end (offset-add (cons 0 (cdr offset)) 
+         (end (offset-add (cons 0 (cdr offset))
                           (cons (- (/ (car offset) 2)) (* (- (+ thickness (car offset))) dir))))
          (stroke (make-line-stencil stroke-thickness (car start) (cdr start) (car end) (cdr end))))
   (ly:stencil-add stencil stroke)))
 
-(define PI-OVER-180  (/ (atan 1 1) 45))
-(define (degrees->radians angle-degrees)
-  "Convert the given angle from degrees to radians"
-  (* angle-degrees PI-OVER-180))
-
-(define (polar->rectangular radius angle-in-degrees)
-  "Convert polar coordinate @code{radius} and @code{angle-in-degrees}
-   to (x-length . y-length)"
-  (let* ((complex (make-polar
-                    radius
-                    (degrees->radians angle-in-degrees))))
-     (cons
-       (real-part complex)
-       (imag-part complex))))
-
 (define (buildflag flag-stencil remain curr-stencil spacing)
   "Internal function to recursively create a stencil with @code{remain} flags
    from the single-flag stencil curr-stencil, which is already translated to
@@ -72,7 +57,7 @@
                        downflag-angle downflag-length)
     "Create a stencil for a straight flag.
      flag-thickness, -spacing are given in staff spaces,
-     *flag-angle is given in degree, *flag-length is given in staff spaces. 
+     *flag-angle is given in degree, *flag-length is given in staff spaces.
      All lengths will be scaled according to the font size of the note."
   (lambda (stem-grob)
     (let* ((log (ly:grob-property stem-grob 'duration-log))
 (define-public (mensural-flag stem-grob)
   "Mensural flags: Create the flag stencil by loading the glyph from the font.
    Flags are always aligned with staff lines, so we need to check the end point
-   of the stem: For stems ending on staff lines, use different flags than for 
-   notes between staff lines.  The idea is that flags are always vertically 
-   aligned with the staff lines, regardless of whether the note head is on a 
-   staff line or between two staff lines.  In other words, the inner end of 
+   of the stem: For stems ending on staff lines, use different flags than for
+   notes between staff lines.  The idea is that flags are always vertically
+   aligned with the staff lines, regardless of whether the note head is on a
+   staff line or between two staff lines.  In other words, the inner end of
    a flag always touches a staff line."
 
   (let* ((adjust #t)
 
 
 (define-public (default-flag stem-grob)
-  "Create a flag stencil for the stem. Its style will be derived from the 
+  "Create a flag stencil for the stem. Its style will be derived from the
    @code{'flag-style} Stem property. By default, @code{lilypond} uses a
-   C++ Function (which is slightly faster) to do exactly the same as this 
-   function. However, if one wants to modify the default flags, this function 
+   C++ Function (which is slightly faster) to do exactly the same as this
+   function. However, if one wants to modify the default flags, this function
    can be used to obtain the default flag stencil, which can then be modified
    at will. The correct way to do this is:
 @example
index 0864e57deca4271ce57dddb3f33b65e8ef8dedd2..8958c81cc1897d6e88cc9bc632434ccef93200e7 100644 (file)
@@ -280,6 +280,36 @@ bookoutput function"
            (cons (cdar alist)
                  (flatten-alist (cdr alist))))))
 
+(define (assoc-remove key alist)
+  "Remove key (and its corresponding value) from an alist.
+   Different than assoc-remove! because it is non-destructive."
+  (define (assoc-crawler key l r)
+    (if (null? r)
+        l
+        (if (equal? (caar r) key)
+            (append l (cdr r))
+            (assoc-crawler key (append l `(,(car r))) (cdr r)))))
+  (assoc-crawler key '() alist))
+
+(define-public (map-selected-alist-keys function keys alist)
+  "Returns alist with function applied to all of the values in list keys.
+   For example:
+   @code{guile> (map-selected-alist-keys - '(a b) '((a . 1) (b . -2) (c . 3) (d . 4)))}
+   @code{((a . -1) (b . 2) (c . 3) (d . 4))}"
+   (define (map-selected-alist-keys-helper function key alist)
+     (map
+     (lambda (pair)
+       (if (equal? key (car pair))
+           (cons key (function (cdr pair)))
+           pair))
+     alist))
+   (if (null? keys)
+       alist
+       (map-selected-alist-keys
+         function
+         (cdr keys)
+         (map-selected-alist-keys-helper function (car keys) alist))))
+
 ;;;;;;;;;;;;;;;;
 ;; vector
 
@@ -369,7 +399,7 @@ bookoutput function"
    (lambda (x) x)
    (map proc lst)))
 
-(define (flatten-list x)
+(define-public (flatten-list x)
   "Unnest list."
   (cond ((null? x) '())
         ((not (pair? x)) (list x))
@@ -471,27 +501,25 @@ bookoutput function"
 
 (define-public interval-end cdr)
 
-(define-public (interval-translate iv amount)
-  (cons (+ amount (car iv))
-       (+ amount (cdr iv))))
-
 (define (other-axis a)
   (remainder (+ a 1) 2))
 
 (define-public (interval-widen iv amount)
-   (cons (- (car iv) amount)
-         (+ (cdr iv) amount)))
+  (cons (- (car iv) amount)
+    (+ (cdr iv) amount)))
 
 (define-public (interval-empty? iv)
    (> (car iv) (cdr iv)))
 
 (define-public (interval-union i1 i2)
-   (cons (min (car i1) (car i2))
-        (max (cdr i1) (cdr i2))))
+  (cons
+    (min (car i1) (car i2))
+    (max (cdr i1) (cdr i2))))
 
 (define-public (interval-intersection i1 i2)
-   (cons (max (car i1) (car i2))
-        (min (cdr i1) (cdr i2))))
+   (cons
+     (max (car i1) (car i2))
+     (min (cdr i1) (cdr i2))))
 
 (define-public (interval-sane? i)
   (not (or  (nan? (car i))
@@ -504,6 +532,104 @@ bookoutput function"
   (cons (min (interval-start interval) p)
         (max (interval-end interval) p)))
 
+(define-public (reverse-interval iv)
+  (cons (cdr iv) (car iv)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; coordinates
+
+(define coord-x car)
+(define coord-y cdr)
+
+(define (coord-operation operator operand coordinate)
+  (if (pair? operand)
+    (cons (operator (coord-x operand) (coord-x coordinate))
+          (operator (coord-y operand) (coord-y coordinate)))
+    (cons (operator operand (coord-x coordinate))
+          (operator operand (coord-y coordinate)))))
+
+(define (coord-apply function coordinate)
+  (if (pair? function)
+    (cons
+      ((coord-x function) (coord-x coordinate))
+      ((coord-y function) (coord-y coordinate)))
+    (cons
+      (function (coord-x coordinate))
+      (function (coord-y coordinate)))))
+
+(define-public (coord-translate coordinate amount)
+  (coord-operation + amount coordinate))
+
+(define-public (coord-scale coordinate amount)
+  (coord-operation * amount coordinate))
+
+(define-public (coord-rotate coordinate degrees-in-radians)
+  (let*
+    ((coordinate
+      (cons
+        (exact->inexact (coord-x coordinate))
+        (exact->inexact (coord-y coordinate))))
+     (radius
+      (sqrt
+        (+ (* (coord-x coordinate) (coord-x coordinate))
+           (* (coord-y coordinate) (coord-y coordinate)))))
+    (angle (angle-0-2pi (atan (coord-y coordinate) (coord-x coordinate)))))
+   (cons
+     (* radius (cos (+ angle degrees-in-radians)))
+     (* radius (sin (+ angle degrees-in-radians))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; trig
+
+(define-public PI (* 4 (atan 1)))
+
+(define-public TWO-PI (* 2 PI))
+
+(define-public PI-OVER-TWO (/ PI 2))
+
+(define-public THREE-PI-OVER-TWO (* 3 PI-OVER-TWO))
+
+(define-public (cyclic-base-value value cycle)
+  "Takes a value and modulo-maps it between 0 and base."
+  (if (< value 0)
+      (cyclic-base-value (+ value cycle) cycle)
+      (if (>= value cycle)
+          (cyclic-base-value (- value cycle) cycle)
+          value)))
+
+(define-public (angle-0-2pi angle)
+  "Takes an angle in radians and maps it between 0 and 2pi."
+  (cyclic-base-value angle TWO-PI))
+
+(define-public (angle-0-360 angle)
+  "Takes an angle in radians and maps it between 0 and 2pi."
+  (cyclic-base-value angle 360.0))
+
+(define-public PI-OVER-180  (/ PI 180))
+
+(define-public (degrees->radians angle-degrees)
+  "Convert the given angle from degrees to radians"
+  (* angle-degrees PI-OVER-180))
+
+(define-public (ellipse-radius x-radius y-radius angle)
+  (/
+    (* x-radius y-radius)
+    (sqrt
+      (+ (* (expt y-radius 2)
+            (* (cos angle) (cos angle)))
+        (* (expt x-radius 2)
+           (* (sin angle) (sin angle)))))))
+
+(define-public (polar->rectangular radius angle-in-degrees)
+  "Convert polar coordinate @code{radius} and @code{angle-in-degrees}
+   to (x-length . y-length)"
+  (let ((complex (make-polar
+                    radius
+                    (degrees->radians angle-in-degrees))))
+     (cons
+       (real-part complex)
+       (imag-part complex))))
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; string
 
@@ -626,6 +752,9 @@ applied to function @var{getter}.")
 (define-public (car< a b)
   (< (car a) (car b)))
 
+(define-public (car<= a b)
+  (<= (car a) (car b)))
+
 (define-public (symbol<? lst r)
   (string<? (symbol->string lst) (symbol->string r)))
 
index 68ab46a6e7e7aaa491b75a39b68ae7ae00c2372e..67958290d837f8bc76f5d328f4d60992bd1c5151 100644 (file)
@@ -378,7 +378,7 @@ LilyPond safe mode.  The syntax is the same as `define*-public'."
     "beam-settings.scm"
     "auto-beam.scm"
     "chord-name.scm"
-
+    "bezier-tools.scm"
     "parser-ly-from-scheme.scm"
     "ly-syntax-constructors.scm"
 
@@ -395,6 +395,8 @@ LilyPond safe mode.  The syntax is the same as `define*-public'."
     "flag-styles.scm"
     "fret-diagrams.scm"
     "harp-pedals.scm"
+    "define-woodwind-diagrams.scm"
+    "display-woodwind-diagrams.scm"
     "predefined-fretboards.scm"
     "define-markup-commands.scm"
     "define-grob-properties.scm"
index 06b324b799c7abd07f10c1518ead850bd897724e..128b1d410424ea26db70b68255b4f2d285638deb 100644 (file)
@@ -18,7 +18,7 @@
 
 ;;;; Note: currently misused as testbed for titles with markup, see
 ;;;;       input/test/title-markup.ly
-;;;; 
+;;;;
 ;;;; TODO:
 ;;;;   * %% Papersize in (header ...)
 ;;;;   * text setting, kerning.
@@ -51,7 +51,7 @@
       (ly:number->string num)))
 
 (define (number-pair->string4 numpair)
-  (ly:format "~4l" numpair)) 
+  (ly:format "~4l" numpair))
 
 ;;;
 ;;; Lily output interface, PostScript implementation --- cleanup and docme
@@ -59,7 +59,7 @@
 
 ;; two beziers
 (define (bezier-sandwich lst thick)
-  (ly:format "~l ~4f draw_bezier_sandwich" 
+  (ly:format "~l ~4f draw_bezier_sandwich"
             (map number-pair->string4 lst)
          thick))
 
          (- x2 x1) (- y2 y1)
          x1 y1 thick))
 
+(define (connected-shape pointlist thick x-scale y-scale connect fill)
+  (ly:format "~a~4f ~4f ~4f ~4f ~a ~a draw_connected_shape"
+    (string-concatenate
+      (map (lambda (x)
+             (apply (if (eq? (length x) 6)
+                        (lambda (x1 x2 x3 x4 x5 x6)
+                          (ly:format "~4f ~4f ~4f ~4f ~4f ~4f 6 "
+                                     x1
+                                     x2
+                                     x3
+                                     x4
+                                     x5
+                                     x6))
+                        (lambda (x1 x2)
+                           (ly:format "~4f ~4f 2 " x1 x2)))
+                    x))
+           (reverse pointlist)))
+      (length pointlist)
+      x-scale
+      y-scale
+      thick
+      (if connect "true" "false")
+      (if fill "true" "false")))
+
+(define (partial-ellipse x-radius y-radius start-angle end-angle thick connect fill)
+  (ly:format "~a ~a ~4f ~4f ~4f ~4f ~4f draw_partial_ellipse"
+        (if fill "true" "false")
+       (if connect "true" "false")
+       x-radius
+       y-radius
+       start-angle
+       end-angle
+       thick))
+
 (define (ellipse x-radius y-radius thick fill)
   (ly:format
    "~a ~4f ~4f ~4f draw_ellipse"
       (ly:format "~4f ~4f ~4f ~a~a"
                 w x y
                 prefix g)))
-  
-  (ly:format 
+
+  (ly:format
    (if cid?
 "/~a /CIDFont findresource ~a output-scale div scalefont setfont
 ~a
      "false")
    x-radius y-radius thick))
 
-(define (placebox x y s) 
+(define (placebox x y s)
   (if (not (string-null? s))
       (ly:format "~4f ~4f moveto ~a\n" x y s)
       ""))
 (define (resetrotation ang x y)
   "grestore  ")
 
-(define (unknown) 
+(define (unknown)
   "\n unknown\n")
 
 (define (url-link url x y)
        (let*
            ((head (car exps))
             (rest (cdr exps))
-            (arity 
+            (arity
              (cond
               ((memq head '(rmoveto rlineto lineto moveto)) 2)
               ((memq head '(rcurveto curveto)) 6)
          ;; WARNING: this is a vulnerability: a user can output arbitrary PS code here.
          (cons (ly:format
                        "~l ~a "
-                       args 
+                       args
                        head)
                (convert-path-exps (drop rest arity))))
        '()))
-    
-    
+
+
   (ly:format
    "gsave currentpoint translate 1 setlinecap ~a setlinewidth\n~l stroke grestore"
    thickness
    (convert-path-exps exps)))
-  
index 4c421006986e1433e641d239a0ee406e01d2e6e7..9004694a058d7e3b71c2dc12bfe9b2fe81c5d72e 100644 (file)
     `(rx . ,x-radius)
     `(ry . ,y-radius)))
 
+(define (partial-ellipse x-radius y-radius start-angle end-angle thick connect fill)
+  (define (make-ellipse-radius x-radius y-radius angle)
+    (/ (* x-radius y-radius)
+       (sqrt (+ (* (* y-radius y-radius)
+                   (* (cos angle) (cos angle)))
+                (* (* x-radius x-radius)
+                   (* (sin angle) (sin angle)))))))
+  (let*
+    ((dummy (format #t "INFO XR ~a YR ~a SA ~a EA ~a\n" x-radius y-radius start-angle end-angle))
+     (new-start-angle (* PI-OVER-180 (angle-0-360 start-angle)))
+     (start-radius (make-ellipse-radius x-radius y-radius new-start-angle))
+     (new-end-angle (* PI-OVER-180 (angle-0-360 end-angle)))
+     (end-radius (make-ellipse-radius x-radius y-radius new-end-angle))
+     (epsilon 1.5e-3)
+     (x-end (- (* end-radius (cos new-end-angle))
+               (* start-radius (cos new-start-angle))))
+     (y-end (- (* end-radius (sin new-end-angle))
+               (* start-radius (sin new-start-angle))))
+     (dummy (format #t "INFO NSA ~a SR ~a NEA ~a ER ~a\n" new-start-angle start-radius new-end-angle end-radius)))
+   (if (and (< (abs x-end) epsilon) (< (abs y-end) epsilon))
+    (entity
+      'ellipse ""
+      `(fill . ,(if fill "currentColor" "none"))
+      `(stroke . "currentColor")
+      `(stroke-width . ,thick)
+      '(stroke-linejoin . "round")
+      '(stroke-linecap . "round")
+      '(cx . 0)
+      '(cy . 0)
+      `(rx . ,x-radius)
+      `(ry . ,y-radius))
+    (entity
+      'path ""
+      `(fill . ,(if fill "currentColor" "none"))
+      `(stroke . "currentColor")
+      `(stroke-width . ,thick)
+      '(stroke-linejoin . "round")
+      '(stroke-linecap . "round")
+      (cons
+        'd
+        (string-append
+          (ly:format
+            "M~4f ~4fA~4f ~4f 0 ~4f 0 ~4f ~4f"
+            (* start-radius (cos new-start-angle))
+            (- (* start-radius (sin new-start-angle)))
+            x-radius
+            y-radius
+            (if (> 0 (- new-start-angle new-end-angle)) 0 1)
+            (* end-radius (cos new-end-angle))
+            (- (* end-radius (sin new-end-angle))))
+            (if connect
+                (ly:format "L~4f,~4f"
+                           (* start-radius (cos new-start-angle))
+                           (- (* start-radius (sin new-start-angle))))
+                "")))))))
+
+(define (connected-shape pointlist thick x-scale y-scale connect fill)
+  (entity
+    'path ""
+    `(fill . ,(if fill "currentColor" "none"))
+    `(stroke . "currentColor")
+    `(stroke-width . ,thick)
+    '(stroke-linejoin . "round")
+    '(stroke-linecap . "round")
+    (cons
+      'd
+      (ly:format
+        "M0 0~a ~a"
+        (string-concatenate
+          (map (lambda (x)
+                 (apply
+                   (if (eq? (length x) 6)
+                       (lambda (x1 x2 x3 x4 x5 x6)
+                         (ly:format "C~4f ~4f ~4f ~4f ~4f ~4f"
+                                    (* x1 x-scale)
+                                    (- (* x2 y-scale))
+                                    (* x3 x-scale)
+                                    (- (* x4 y-scale))
+                                    (* x5 x-scale)
+                                    (- (* x6 y-scale))))
+                       (lambda (x1 x2)
+                         (ly:format "L~4f ~4f"
+                                    (* x-scale x1)
+                                    (- (* y-scale x2)))))
+                   x))
+               pointlist))
+        (if connect "z " "")))))
+
 (define (embedded-svg string)
   string)
 
index 5e32f1efbb6896f918631269661f7482297ed683..7ac43c10290b304d0eaf62e5aed6e607ce62e255 100644 (file)
@@ -202,6 +202,188 @@ the more angular the shape of the parenthesis."
    (cons (- x-out-radius) x-out-radius)
    (cons (- y-out-radius) y-out-radius))))
 
+(define-public
+  (make-partial-ellipse-stencil
+    x-radius y-radius start-angle end-angle thick connect fill)
+
+  (define (make-radius-list x-radius y-radius)
+    (apply append
+           (map (lambda (adder)
+                  (map (lambda (quadrant)
+                         (cons (+ adder (car quadrant))
+                               (cdr quadrant)))
+                       `((0.0 . (,x-radius . 0.0))
+                         (,PI-OVER-TWO . (0.0 . ,y-radius))
+                         (,PI . (,(- x-radius) . 0.0))
+                         (,THREE-PI-OVER-TWO . (0.0 . ,(- y-radius))))))
+                `(0.0 ,TWO-PI))))
+
+  (define
+    (insert-in-ordered-list ordering-function value inlist cutl? cutr?)
+    (define
+      (helper ordering-function value left-list right-list cutl? cutr?)
+      (if (null? right-list)
+          (append
+            (if cutl? '() left-list)
+            (list value)
+            (if cutr? '() right-list))
+          (if (ordering-function value (car right-list))
+              (append
+                (if cutl? '() left-list)
+                (list value)
+                (if cutr? '() right-list))
+              (helper
+                ordering-function
+                value
+                (append left-list (list (car right-list)))
+                (cdr right-list)
+                cutl?
+                cutr?))))
+    (helper ordering-function value '() inlist cutl? cutr?))
+
+  (define (ordering-function-1 a b) (car< a b))
+
+  (define (ordering-function-2 a b) (car<= a b))
+
+  (define (min-max-crawler min-max side l)
+    (reduce min-max
+            (if (eq? min-max min) 100000 -100000)
+            (map (lambda (x) (side x)) l)))
+
+  (let*
+      ((x-out-radius (+ x-radius (/ thick 2.0)))
+       (y-out-radius (+ y-radius (/ thick 2.0)))
+       (new-end-angle (angle-0-2pi (degrees->radians end-angle)))
+       (end-radius (ellipse-radius x-out-radius y-out-radius new-end-angle))
+       (new-start-angle (angle-0-2pi (degrees->radians start-angle)))
+       (start-radius (ellipse-radius x-out-radius y-out-radius new-start-angle))
+       (radius-list (make-radius-list x-out-radius y-out-radius))
+       (rectangular-end-radius (polar->rectangular end-radius end-angle))
+       (rectangular-start-radius (polar->rectangular start-radius start-angle))
+       (new-end-angle
+         (if (<= new-end-angle new-start-angle)
+             (+ TWO-PI new-end-angle)
+             new-end-angle))
+       (possible-extrema
+         (insert-in-ordered-list
+           ordering-function-2
+           (cons new-end-angle rectangular-end-radius)
+           (insert-in-ordered-list
+             ordering-function-1
+             (cons new-start-angle rectangular-start-radius)
+             radius-list
+             #t
+             #f)
+           #f
+           #t)))
+    (ly:make-stencil
+      (list
+        'partial-ellipse
+        x-radius
+        y-radius
+        start-angle
+        end-angle
+        thick
+        connect
+        fill)
+      (cons (min-max-crawler min cadr possible-extrema)
+            (min-max-crawler max cadr possible-extrema))
+      (cons (min-max-crawler min cddr possible-extrema)
+            (min-max-crawler max cddr possible-extrema)))))
+
+(define-public
+  (make-connected-shape-stencil pointlist
+                                thickness
+                                x-scale
+                                y-scale
+                                connect
+                                fill)
+
+  (define (connected-shape-min-max pointlist)
+
+    (define (line-part-min-max x1 x2)
+      (list (min x1 x2) (max x1 x2)))
+
+    (define (bezier-part-min-max x1 x2 x3 x4)
+      ((lambda (x) (list (reduce min 10000 x) (reduce max -10000 x)))
+        (map
+          (lambda (x)
+            (+ (* x1 (expt (- 1 x) 3))
+               (+ (* 3 (* x2 (* (expt (- 1 x) 2) x)))
+                  (+ (* 3 (* x3 (* (- 1 x) (expt x 2))))
+                     (* x4 (expt x 3))))))
+          (if (< (+ (expt x2 2) (+ (expt x3 2) (* x1 x4)))
+                 (+ (* x1 x3) (+ (* x2 x4) (* x2 x3))))
+              (list 0.0 1.0)
+              (filter
+                (lambda (x) (and (>= x 0) (<= x 1)))
+                (append
+                  (list 0.0 1.0)
+                  (map (lambda (op)
+                         (if (not (eqv? 0.0
+                                        (- (+ x1 (* 3 x3)) (+ x4 (* 3 x2)))))
+                             ;; Zeros of the bezier curve
+                             (/ (+ (- x1 (* 2 x2))
+                                   (op x3
+                                       (sqrt (- (+ (expt x2 2)
+                                                   (+ (expt x3 2) (* x1 x4)))
+                                                (+ (* x1 x3)
+                                                   (+ (* x2 x4) (* x2 x3)))))))
+                                (- (+ x1 (* 3 x3)) (+ x4 (* 3 x2))))
+                             ;; Apply L'hopital's rule to get the zeros if 0/0
+                             (* (op 0 1)
+                                (/ (/ (- x4 x3) 2)
+                                   (sqrt (- (+ (* x2 x2)
+                                               (+ (* x3 x3) (* x1 x4)))
+                                            (+ (* x1 x3)
+                                               (+ (* x2 x4) (* x2 x3)))))))))
+                       (list + -))))))))
+
+  (define (bezier-min-max x1 y1 x2 y2 x3 y3 x4 y4)
+    (map (lambda (x)
+           (apply bezier-part-min-max x))
+         `((,x1 ,x2 ,x3 ,x4) (,y1 ,y2 ,y3 ,y4))))
+
+  (define (line-min-max x1 y1 x2 y2)
+    (map (lambda (x)
+           (apply line-part-min-max x))
+         `((,x1 ,x2) (,y1 ,y2))))
+
+  ((lambda (x)
+     (list
+       (reduce min +inf.0 (map caar x))
+       (reduce max -inf.0 (map cadar x))
+       (reduce min +inf.0 (map caadr x))
+       (reduce max -inf.0 (map cadadr x))))
+    (map (lambda (x)
+           (if (eq? (length x) 8)
+               (apply bezier-min-max x)
+               (apply line-min-max x)))
+         (map (lambda (x y)
+                (append (list (cadr (reverse x)) (car (reverse x))) y))
+              (append (list (list 0 0))
+                      (reverse (cdr (reverse pointlist)))) pointlist))))
+
+  (let* ((boundlist (connected-shape-min-max pointlist)))
+  (ly:make-stencil
+    `(connected-shape
+      ',pointlist
+      ',thickness
+      ',x-scale
+      ',y-scale
+      ',connect
+      ',fill)
+    (coord-translate
+      ((if (< x-scale 0) reverse-interval identity)
+        (cons (* x-scale (list-ref boundlist 0))
+              (* x-scale (list-ref boundlist 1))))
+        `(,(/ thickness -2) . ,(/ thickness 2)))
+    (coord-translate
+      ((if (< y-scale 0) reverse-interval identity)
+        (cons (* y-scale (list-ref boundlist 2))
+              (* y-scale (list-ref boundlist 3))))
+        `(,(/ thickness -2) . ,(/ thickness 2))))))
+
 (define-public (make-ellipse-stencil x-radius y-radius thickness fill)
   "Make an ellipse of x radius @var{x-radius}, y radius @code{y-radius},
     and thickness @var{thickness} with fill defined by @code{fill}."
@@ -335,9 +517,11 @@ encloses the contents.
      stencil)
     ))
 
-(define-public (dimension-arrows destination max-size)
-  "Draw twosided arrow from here to @var{destination}"
-
+(define-public (arrow-stencil-maker start? end?)
+  "Returns a function drawing a line from current point to @var{destination},
+   with optional arrows of @var{max-size} on start and end controlled by
+   @var{start?} and @var{end?}."
+  (lambda (destination max-size)
   (let*
       ((e_x 1+0i)
        (e_y 0+1i)
@@ -388,10 +572,15 @@ encloses the contents.
              (cons (max 0 (car destination))
                    (max 0 (cdr destination)))))
 
-       (result (ly:stencil-add arrow-2 arrow-1 line)))
+       (result
+         (ly:stencil-add
+           (if start? arrow-2 empty-stencil)
+           (if end? arrow-1 empty-stencil)
+           line)))
 
+    result)))
 
-    result))
+(define-public dimension-arrows (arrow-stencil-maker #t #t))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; ANNOTATIONS