From 0c6b9c0a459a2116e028f533ef978bf7d918ef00 Mon Sep 17 00:00:00 2001 From: David Nalesnik Date: Sat, 8 Sep 2012 07:38:28 -0500 Subject: [PATCH] Issue 2445: Add measure counter to LilyPond 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 | 41 +++++++++++ ly/spanners-init.ly | 4 ++ scm/define-event-classes.scm | 8 ++- scm/define-grob-interfaces.scm | 5 ++ scm/define-grob-properties.scm | 3 + scm/define-grobs.scm | 18 +++++ scm/define-music-types.scm | 5 ++ scm/lily.scm | 1 + scm/music-functions.scm | 36 ++++++++++ scm/scheme-engravers.scm | 103 ++++++++++++++++++++++++++++ 10 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 input/regression/measure-counter.ly create mode 100644 scm/scheme-engravers.scm diff --git a/input/regression/measure-counter.ly b/input/regression/measure-counter.ly new file mode 100644 index 0000000000..095d1a5f96 --- /dev/null +++ b/input/regression/measure-counter.ly @@ -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 + } +} diff --git a/ly/spanners-init.ly b/ly/spanners-init.ly index b507d01829..44dcdb6633 100644 --- a/ly/spanners-init.ly +++ b/ly/spanners-init.ly @@ -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) diff --git a/scm/define-event-classes.scm b/scm/define-event-classes.scm index 2beabeeff7..09dfbf79a3 100644 --- a/scm/define-event-classes.scm +++ b/scm/define-event-classes.scm @@ -44,9 +44,11 @@ (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)) diff --git a/scm/define-grob-interfaces.scm b/scm/define-grob-interfaces.scm index bb6197463b..6068579995 100644 --- a/scm/define-grob-interfaces.scm +++ b/scm/define-grob-interfaces.scm @@ -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." diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index f8c5b29ba8..0b5585a71a 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -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 diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index 1ebd9c34e8..68d1fcdafd 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -1321,6 +1321,24 @@ 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) diff --git a/scm/define-music-types.scm b/scm/define-music-types.scm index 0b828c1516..dc93024041 100644 --- a/scm/define-music-types.scm +++ b/scm/define-music-types.scm @@ -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.") diff --git a/scm/lily.scm b/scm/lily.scm index 1a5674df57..4905e0687b 100644 --- a/scm/lily.scm +++ b/scm/lily.scm @@ -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" diff --git a/scm/music-functions.scm b/scm/music-functions.scm index 23a797779d..22d731fe4e 100644 --- a/scm/music-functions.scm +++ b/scm/music-functions.scm @@ -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 index 0000000000..1709db1f99 --- /dev/null +++ b/scm/scheme-engravers.scm @@ -0,0 +1,103 @@ +;;;; This file is part of LilyPond, the GNU music typesetter. +;;;; +;;;; Copyright (C) 2012 David Nalesnik +;;;; +;;;; 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 . + + +(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"))))))) -- 2.39.2