]> git.donarmstrong.com Git - lilypond.git/commitdiff
Issue 2445: Add measure counter to LilyPond
authorDavid Nalesnik <david.nalesnik@gmail.com>
Sat, 8 Sep 2012 12:38:28 +0000 (07:38 -0500)
committerDavid Kastrup <dak@gnu.org>
Mon, 5 Nov 2012 13:25:34 +0000 (14:25 +0100)
These changes implement a new grob, `MeasureCounter,' a spanner used to
number groups of successive measures.

This commit:
- defines a new event class, `MeasureCounterEvent.'
- defines a new interface, `measure-counter-interface.' This
interface contains `count-from,' the number which begins the count, and
`columns,' which contains the NonMusicalPaperColumn grobs forming
the bounds of the new spanner.
- defines the stencil with a function in scm/music-functions.scm.
- defines an engraver, `Measure_counter_engraver.'  This engraver
is written in Scheme and placed in a new file, scm/scheme-engravers.scm,
which is added to the load list in scm/lily.scm.
- sets various grob properties to default values.
- provides two commands to frame the measures to be counted:
\startMeasureCount and \stopMeasureCount.
- provides a regression test.

input/regression/measure-counter.ly [new file with mode: 0644]
ly/spanners-init.ly
scm/define-event-classes.scm
scm/define-grob-interfaces.scm
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/define-music-types.scm
scm/lily.scm
scm/music-functions.scm
scm/scheme-engravers.scm [new file with mode: 0644]

diff --git a/input/regression/measure-counter.ly b/input/regression/measure-counter.ly
new file mode 100644 (file)
index 0000000..095d1a5
--- /dev/null
@@ -0,0 +1,41 @@
+\version "2.17.6"
+
+\header {
+  texidoc = "Measures can be numbered sequentially by enclosing them with 
+@code{\\startMeasureCount} and @code{\\stopMeasureCount}."
+}
+
+\layout {
+  indent = 0
+  ragged-right = ##t
+}
+
+\relative c' {
+  \startMeasureCount
+  \repeat unfold 5 {
+    a4 b c d
+  }
+  \stopMeasureCount
+  a'4 b c d
+  \override Staff.MeasureCounter.count-from = #2
+  \startMeasureCount
+  \repeat unfold 4 {
+    a4 b c d
+  }
+  \stopMeasureCount\startMeasureCount
+  \revert Staff.MeasureCounter.count-from
+  \clef bass
+  \key fis \major
+  \time 3/4
+  \repeat unfold 3 {
+    R2.
+  }
+  \stopMeasureCount
+}
+
+\layout {
+  \context {
+    \Staff
+    \consists #Measure_counter_engraver
+  }
+}
index b507d01829a527abe49866bfdf36a77fbd970732..44dcdb663352fb5a5974daf78ed7b0ee7dcb8bb4 100644 (file)
@@ -10,6 +10,10 @@ enddecr = #(make-span-event 'DecrescendoEvent STOP)
 endcr = #(make-span-event 'CrescendoEvent STOP) 
 
 
+startMeasureCount = #(make-span-event 'MeasureCounterEvent START)
+stopMeasureCount = #(make-span-event 'MeasureCounterEvent STOP)
+
+
 startTextSpan = #(make-span-event 'TextSpanEvent START)
 stopTextSpan = #(make-span-event 'TextSpanEvent STOP)
 
index 2beabeeff7e6756e1e1f2874d7b12ed069397dfb..09dfbf79a34b39898a30e7b413166aea0f15d078 100644 (file)
     (break-event . (line-break-event page-break-event page-turn-event))
     (dynamic-event . (absolute-dynamic-event))
     (span-event . (span-dynamic-event beam-event episema-event ligature-event
-                        pedal-event phrasing-slur-event slur-event staff-span-event
-                        text-span-event trill-span-event tremolo-span-event
-                        tuplet-span-event))
+                                      measure-counter-event pedal-event
+                                      phrasing-slur-event slur-event
+                                      staff-span-event text-span-event
+                                      trill-span-event tremolo-span-event
+                                      tuplet-span-event))
     (span-dynamic-event . (decrescendo-event crescendo-event))
     (break-span-event . (break-dynamic-span-event))
     (pedal-event . (sostenuto-event sustain-event una-corda-event))
index bb6197463b492192cffce1dea25a14702bd62c50..6068579995115078b81526a2793326f97c285de3 100644 (file)
@@ -169,6 +169,11 @@ accidentals)."
  "A rehearsal mark."
  '())
 
+(ly:add-interface
+ 'measure-counter-interface
+ "A counter for numbering measures."
+ '(columns count-from))
+
 (ly:add-interface
  'metronome-mark-interface
  "A metronome mark."
index f8c5b29ba815d4efe20c481f011d2eb2c2b426ab..0b5585a71aea6d3dcf51178fa80e21ccda500d72 100644 (file)
@@ -201,6 +201,9 @@ this grob looks as a continued break.")
      (control-points ,list? "List of offsets (number pairs) that form
 control points for the tie, slur, or bracket shape.  For B@'eziers,
 this should list the control points of a third-order B@'ezier curve.")
+     (count-from ,integer? "The first measure in a measure count
+receives this number.  The following measures are numbered in
+increments from this initial value.")
 
 ;;
 ;; d
index 1ebd9c34e88263d05d7b92dccb541b848e62d332..68d1fcdafde7fe77174da8950419465ea4c9df65 100644 (file)
                                self-alignment-interface
                                text-interface))))))
 
+    (MeasureCounter
+     . (
+        (count-from . 1)
+        (direction . ,UP)
+        (font-encoding . fetaText)
+        (font-size . -2)
+        (outside-staff-horizontal-padding . 0.5)
+        (outside-staff-padding . 0.5)
+        (outside-staff-priority . 750)
+        (self-alignment-X . ,CENTER)
+        (staff-padding . 0.5)
+        (stencil . ,measure-counter-stencil)
+        (meta . ((class . Spanner)
+                 (interfaces . (font-interface
+                                measure-counter-interface
+                                self-alignment-interface
+                                text-interface))))))
+
     (MeasureGrouping
      . (
        (direction . ,UP)
index 0b828c1516c161f78e1a98d67b37954d629b64a5..dc93024041d961a8918d8c4ed0a79377fee3eed2 100644 (file)
@@ -309,6 +309,11 @@ Example: @code{\\mark \"A\"}")
        (types . (general-music mark-event event))
        ))
 
+    (MeasureCounterEvent
+     . ((description . "Used to signal the start and end of a measure count.")
+        (types . (general-music measure-counter-event span-event event))
+        ))
+
     (MultiMeasureRestEvent
      . ((description . "Used internally by @code{MultiMeasureRestMusic}
 to signal rests.")
index 1a5674df570336ae6ff30516bd4bbb979d30ca59..4905e0687b6977e1284728d4fa0eae561e16a510 100644 (file)
@@ -496,6 +496,7 @@ messages into errors.")
     "define-grobs.scm"
     "define-grob-interfaces.scm"
     "define-stencil-commands.scm"
+    "scheme-engravers.scm"
     "titling.scm"
     "text.scm"
 
index 23a797779d3bf0dc2632c18c7841fdae57c7ca54..22d731fe4e89003702339537f939f157d1891a33 100644 (file)
@@ -1957,3 +1957,39 @@ of list @var{arg}."
    (if (>= (length siblings) 2)
        (helper siblings arg)
        (car arg))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; measure counter
+
+(define (measure-counter-stencil grob)
+  "Create a number for a measure count.  The number is centered using
+the extents of the @code{BreakAlignment} grobs associated with the
+@code{NonMusicalPaperColumn} grobs which form the left and right bounds
+of the spanner."
+  (let* ((cols (ly:grob-object grob 'columns))
+         (refp (ly:grob-common-refpoint-of-array grob cols X))
+         (col-list (ly:grob-array->list cols))
+         (elts-L (ly:grob-array->list (ly:grob-object (car col-list) 'elements)))
+         (elts-R (ly:grob-array->list (ly:grob-object (cadr col-list) 'elements)))
+         (break-alignment-L
+           (filter
+             (lambda (elt) (grob::has-interface elt 'break-alignment-interface))
+             elts-L))
+         (break-alignment-R
+           (filter
+             (lambda (elt) (grob::has-interface elt 'break-alignment-interface))
+             elts-R))
+         (break-alignment-L-ext (ly:grob-extent (car break-alignment-L) refp X))
+         (break-alignment-R-ext (ly:grob-extent (car break-alignment-R) refp X))
+         (counter (ly:grob-property grob 'count-from))
+         (num (grob-interpret-markup grob (markup (number->string counter))))
+         (num (ly:stencil-aligned-to num X (ly:grob-property grob 'self-alignment-X)))
+         (num
+           (ly:stencil-translate-axis
+             num
+             (+ (interval-length break-alignment-L-ext)
+                (* 0.5
+                   (- (car break-alignment-R-ext)
+                      (cdr break-alignment-L-ext))))
+             X)))
+    num))
diff --git a/scm/scheme-engravers.scm b/scm/scheme-engravers.scm
new file mode 100644 (file)
index 0000000..1709db1
--- /dev/null
@@ -0,0 +1,103 @@
+;;;; This file is part of LilyPond, the GNU music typesetter.
+;;;;
+;;;; Copyright (C) 2012 David Nalesnik <david.nalesnik@gmail.com>
+;;;;
+;;;; 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-public (Measure_counter_engraver context)
+  "This engraver numbers ranges of measures, which is useful in parts as an
+aid for counting repeated measures.  There is no requirement that the
+affected measures be repeated, however.  The user delimits the area to
+receive a count with @code{\\startMeasureCount} and
+@code{\\stopMeasureCount}.
+
+Each element of a count is a spanner, and a count is thus a series of
+spanners.  Each spanner is bounded by the first @code{CommandColumn} of
+successive measures, and boundaries are shared by adjoining spanners."
+  (let ((count-spanner '()) ; a single element of the count
+        (go? #f) ; is the count in progress?
+        (stop? #f) ; do we end the count?
+        (last-measure-seen 0)
+        (new-measure? #f)
+        (elapsed 0))
+
+    (make-engraver
+      (listeners ((measure-counter-event engraver event)
+        (set! last-measure-seen (ly:context-property context 'currentBarNumber))
+        (set! new-measure? #t)
+        (cond
+          ((and (= START (ly:event-property event 'span-direction))
+                go?)
+           (begin
+             (set! stop? #t)
+             (ly:input-warning
+               (ly:event-property event 'origin)
+               "count not ended before another begun")))
+          ((= START (ly:event-property event 'span-direction))
+           (set! go? #t))
+          ((= STOP (ly:event-property event 'span-direction))
+           (begin
+             (set! stop? #t)
+             (set! go? #f))))))
+
+      ((process-music trans)
+        (let ((col (ly:context-property context 'currentCommandColumn))
+              (now (ly:context-property context 'measurePosition))
+              (current-bar (ly:context-property context 'currentBarNumber)))
+          ; If the counter has been started, make sure we're in a new bar
+          ; before finishing a count-spanner and starting a new one.
+          ; Since we consider all CommandColumns encountered, we need this
+          ; check so that a count-spanner is not created for each pair.
+          (if (and (ly:grob? count-spanner)
+                   (> current-bar last-measure-seen))
+              (set! new-measure? #t))
+          (if new-measure?
+              (begin
+                ; Check if we have the first column of the measure.
+                ; The possibility of initial grace notes is considered.
+                (if (moment<=? now ZERO-MOMENT)
+                    (begin
+                      ; If we have the first column, finish the previous
+                      ; counter-spanner (if there is one).
+                      (if (ly:grob? count-spanner)
+                          (begin
+                            (ly:spanner-set-bound! count-spanner RIGHT col)
+                            (ly:pointer-group-interface::add-grob count-spanner 'columns col)
+                            (ly:engraver-announce-end-grob trans count-spanner col)
+                            (set! count-spanner '())))
+                      ; if count is over, reset variables
+                      (if stop?
+                          (begin
+                            (set! elapsed 0)
+                            (set! stop? #f)))
+                      ; if count is in progress, begin a counter object
+                      (if go?
+                          (let* ((c (ly:engraver-make-grob trans 'MeasureCounter col))
+                                 (counter (ly:grob-property c 'count-from)))
+                            (ly:spanner-set-bound! c LEFT col)
+                            (ly:pointer-group-interface::add-grob c 'columns col)
+                            (set! (ly:grob-property c 'count-from) (+ counter elapsed))
+                            (set! count-spanner c)
+                            (set! elapsed (1+ elapsed))))
+              (set! new-measure? #f)))))
+          (set! last-measure-seen current-bar)))
+
+      ((finalize trans)
+       (if go?
+           (begin
+             (set! go? #f)
+             (ly:grob-suicide! count-spanner)
+             (set! count-spanner '())
+             (ly:warning "measure count left unfinished")))))))