+2004-02-02 Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+ * VERSION: release 2.1.17
+
+ * Documentation/user/refman.itely (Automatic part combining):
+ revise documentation of the part-combiner.
+
+ * input/regression/pc-switch-slur.ly (texidoc): remove file.
+
+ * input/test/part-combine-{moment,staff}.ly: remove file.
+
+ * input/regression/pc-mmrest.ly (two): remove file.
+
+ * scm/define-music-types.scm (music-descriptions): add
+ Solo{One,Two}Event and UnisonoEvent
+
+ * lily/new-part-combine-iterator.cc (unisono): add state_
+ variable, signal changes with part-combine-event.
+
+ * input/regression/new-part-combine-text.ly: new file.
+
+ * lily/part-combine-engraver.cc: new file.
+
+2004-02-02 Jan Nieuwenhuizen <janneke@gnu.org>
+
+ * scripts/filter-lilypond-book.py: Handle snippet options.
+
+2004-02-02 Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+ * scm/music-functions.scm (determine-split-list): detect solo1/2
+ and unisono.
+
+ * lily/new-lyric-combine-music-iterator.cc (construct_children):
+ revert fix, document why.
+
+ * lily/tie-performer.cc (process_music): new function. Set melisma
+ property.
+
+ * lily/tuplet-bracket.cc (make_bracket): make sure that gap is
+ always smaller than bracket size.
+ (make_bracket): oops. Size of the gap was off by factor 2
+
+ * input/test/text-rotate.ly: new file.
+
+ * scm/new-markup.scm (markup): a macro that provides a
+ LilyPond-like syntax in scheme for building markups, in order to
+ help markup command definition. (Nicolas Sceaux)
+
+ * input/test/lyrics-skip-notes.ly: new example.
+
+ * lily/parser.yy (post_event): make HYPHEN into postfix event.
+
+ * lily/new-lyric-combine-music-iterator.cc (find_context_below):
+ use is_alias() iso. == . This fixes lyrics on
+ GregorianTranscriptions.
+
+2004-02-02 Mats Bengtsson <mabe@drongo.s3.kth.se>
+
+ * scripts/lilypond.py (ly_paper_to_latexpaper): Add newline before
+ \thispagestyle{lastpage} to avoid that it's printed on the second
+ last page when there's only a single score line on the last page.
+ (The page breaking mechanism in TeX played some ugly tricks to us.)
+
2004-02-01 Jan Nieuwenhuizen <janneke@gnu.org>
* scripts/filter-lilypond-book.py: Further development.
2004-01-28 Han-Wen Nienhuys <hanwen@xs4all.nl>
+ * VERSION: release 2.1.16
+
* scm/music-functions.scm (determine-split-list): bugfix: only use
new-active for both voices if both indexes advance. This fixes the
biggest outstanding problem with new-pc.
2004-01-26 Han-Wen Nienhuys <hanwen@xs4all.nl>
+ * VERSION: release 2.1.15
+
* lily/lyric-phrasing-engraver.cc (stop_translation_timestep):
align all stanza numbers.
@end ignore
@itemize @bullet
+@item The ergonomic syntax of @code{\markup} now has an equivalent in
+Scheme. The @code{markup*} macro creates such objects; the following
+two markup commands are equivalent:
+@example
+ f4^#(markup* #:raise 0.2 #:bold "hi")
+ f4^\markup @{ \raise #0.2 \bold hi @}
+@end example
+
+@item Voice names, for vocal lines, have been added. They are similar
+to instrument names. They can be set by defining @code{vocalName}
+and @code{vocNam}.
@item Safe mode has been reinstated for lilypond.
When lilypond is invoked with @code{--safe-mode}, @TeX{} and
@item footer
A text to print in the footer of all but the last page.
@item tagline
- Line to print at the bottom of last page. The default text is ``Engraved by LilyPond @var{version-number}''.
+ Line to print at the bottom of last page. The default text is ``Engraved
+by LilyPond @var{version-number}''.
@end table
@seealso
-Internals: @internalsref{LyricCombineMusic}, @internalsref{Lyrics},
-@internalsref{Melisma_engraver}.
+Internals: Music expressions: @internalsref{LyricCombineMusic},
+Contexts: @internalsref{LyricsVoice}, @internalsref{Melisma_engraver}.
Examples: @inputfileref{input/template,satb.ly},
@inputfileref{input/regression,lyric-combine-new.ly}.
@refbugs
-Melismata are not detected automatically, and must be inserted by hand.
+Melismata are not detected automatically, and extender lines must be
+inserted by hand.
@node More stanzas
string.
Names of the singers should be added using @code{LyricsVoice
-. instrument} and @code{LyricsVoice . instr}, analogous to instrument
+. vocalName} and @code{LyricsVoice . vocNam}, analogous to instrument
annotations for staves.
To make empty spaces in lyrics, use @code{\skip}.
+@seealso
+
+Internals: Layout objects @internalsref{LyricText}
+@internalsref{VocalName}. Music expressions:
+@internalsref{LyricEvent}.
@refbugs
@end example
+
+
@node Ambitus
@subsection Ambitus
@cindex ambitus
\transpose @var{from} @var{to} @var{musicexpr}
@end example
-This means that @var{musicexpr} is transposed by the interval
-between the pitches @var{from} and @var{to}.
-Since @var{from} and @var{to} are pitches, @code{\transpose} must be
-inside a @code{\notes} section.
+This means that @var{musicexpr} is transposed by the interval between
+the pitches @var{from} and @var{to}: any note with pitch @code{from}
+is changed to @code{to}.
+
+
+For example, consider a piece written in the key of D major. If
+this piece is a little too low for its performer, it can be
+transposed up to E major with
+@example
+ \tranpose d e @dots{}
+@end example
-@code{\transpose} distinguishes between enharmonic pitches: both
-@code{\transpose c cis} or @code{\transpose c des} will transpose up
-half a tone. The first version will print sharps and the second
-version will print flats:
+Consider a part written for violin (a C instrument). If
+this part is to be played on the A clarinet, the following
+transposition will produce the appropriate part
+
+@example
+ \transpose a c @dots{}
+@end example
+
+Since @var{from} and @var{to} are pitches, @code{\transpose} must be
+inside a @code{\notes} section. @code{\transpose} distinguishes
+between enharmonic pitches: both @code{\transpose c cis} or
+@code{\transpose c des} will transpose up half a tone. The first
+version will print sharps and the second version will print flats:
@lilypond[singleline, verbatim]
mus =\notes { \key d \major cis d fis g }
The syntax for part combining is
@example
- \partcombine @var{context} @var{musicexpr1} @var{musicexpr2}
+ \newpartcombine @var{musicexpr1} @var{musicexpr2}
@end example
-where the pieces of music @var{musicexpr1} and @var{musicexpr2} will be
-combined into one context of type @var{context}. The music expressions
-must be interpreted by contexts whose names should start with @code{one}
-and @code{two}.
+
+The music expressions will be interpreted as @internalsref{Voice}
+contexts.
The following example demonstrates the basic functionality of the part
combiner: putting parts on one staff, and setting stem directions and
polyphony:
-@lilypond[verbatim,singleline,fragment]
- \context Staff <<
- \context Voice=one \partcombine Voice
- \context Thread=one \relative c'' {
+@lilypond[verbatim,singleline,fragment,relative 1]
+ \new Staff \newpartcombine
+ {
g a( b) r
}
- \context Thread=two \relative c'' {
+ {
g r4 r f
}
- >>
@end lilypond
The first @code{g} appears only once, although it was
may set the property @var{soloADue} to false:
@lilypond[verbatim,singleline,fragment]
- \context Staff <<
+ \new Staff <<
\property Staff.soloADue = ##f
- \context Voice=one \partcombine Voice
- \context Thread=one \relative c'' {
- b4 a c g
- }
- \context Thread=two \relative c'' {
- d,2 a4 g'
+ \newpartcombine
+ {
+ g a( b) r
}
- >>
+ {
+ g r4 r f
+ } >>
@end lilypond
@seealso
Internals: @internalsref{PartCombineMusic},
-@internalsref{Thread_devnull_engraver}, and
-@internalsref{Voice_devnull_engraver} and @internalsref{A2_engraver}.
+@internalsref{SoloOneEvent}, and
+@internalsref{SoloTwoEvent}, and
+@internalsref{UnisonoEvent}.
@refbugs
-The syntax for naming contexts in inconsistent with the syntax for
-combining stanzas.
-
In @code{soloADue} mode, when the two voices play the same notes on and
off, the part combiner may typeset @code{a2} more than once in a
measure:
-@lilypond[fragment,singleline]
- \context Staff <<
- \context Voice=one \partcombine Voice
- \context Thread=one \relative c'' {
- c b c b c a c a
- }
- \context Thread=two \relative c'' {
- b b b b f a f a
- }
- >>
-@end lilypond
-
-The part combiner is rather buggy, and it will be replaced by a better
-mechanism in the near future.
-@cindex @code{Thread_devnull_engraver}
-@cindex @code{Voice_engraver}
-@cindex @code{A2_engraver}
@node Hiding staves
@subsection Hiding staves
Many musically interesting effects, such as swing, articulation,
slurring, etc., are not translated to MIDI.
+Since slurs are not interpreted, @code{\lyricsto} and
+@code{\addlyrics} sections will be interpreted wrongly.
+
@menu
* MIDI block::
PACKAGE_NAME=LilyPond
MAJOR_VERSION=2
MINOR_VERSION=1
-PATCH_LEVEL=16
+PATCH_LEVEL=17
MY_PATCH_LEVEL=
+++ /dev/null
-% from José Luis Cruz <joseluis AT jazzartistas.com>
-\version "2.1.0"
-
-\header {
- texidoc="@cindex A tie in RhythmicStaff fails to compile."
-}
-
-\score {
- \context RhythmicStaff {
- \notes {
- c1 ~ c1
- }
- }
-}
\header {
texidoc = "Partcombiner and grace notes can go together."
}
- \paper { raggedright= ##t }
+
+\paper { raggedright= ##t }
\score {
- \new StaffGroup <<
- \new Staff <<
- \context Voice=one \partcombine Voice
- \context Thread=one \notes \relative c'' {
+ \new Staff
+ \newpartcombine
+ \notes \relative c'' {
c4 d e f \grace f16 g1
}
- \context Thread=two \notes \relative c' {
+ \notes \relative c' {
c4 d e2 g1
}
- >>
- >>
+
+
}
\version "2.1.10"
\header {
- texidoc = "With the newaddlyrics mechanism, individual lyric lines
- can be associated with one melody line. For each lyric line, can
- be tuned whether to follow melismata or not."
+ texidoc = "With the @code{\\lyricsto} mechanism, individual lyric
+ lines can be associated with one melody line. For each lyric line,
+ can be tuned whether to follow melismata or not."
}
--- /dev/null
+
+\header {
+
+ texidoc = "There is a Scheme macro @code{markup} to produce markup
+ texts using a similar syntax as @code{\\markup}."
+
+ }
+\version "2.1.16"
+\score {
+ \notes {
+ \fatText
+ f'1-\markup {
+ foo
+ \raise #0.2 \hbracket \bold bar
+ \override #'(baseline-skip . 4)
+
+ \bracket \column < baz bazr bla >
+ \hspace #2.0
+ \override #'(font-family . music) {
+ \lookup #"noteheads-0"
+ \char #53
+ }
+ \musicglyph #"accidentals--1"
+ \combine "X" "+"
+ \combine "o" "/"
+ \box \column < { "string 1" } { "string 2" } >
+ "$\\emptyset$"
+ \italic Norsk
+ \super "2"
+ \dynamic sfzp
+ \huge { "A" \smaller "A" \smaller \smaller "A"
+ \smaller \smaller \smaller "A" }
+ \sub "alike"
+ }
+ \break
+ f'1-#(markup*
+ "foo"
+ #:raise 0.2 #:hbracket #:bold "bar"
+ #:override '(baseline-skip . 4)
+ #:bracket #:column ( "baz" "bazr" "bla" )
+ #:hspace 2.0
+ #:override '(font-family . music) #:line (#:lookup "noteheads-0"
+ #:char 53)
+ #:musicglyph "accidentals--1"
+ #:combine "X" "+"
+ #:combine "o" "/"
+ #:box #:column ("string 1" "string 2")
+ "$\\emptyset$"
+ #:italic "Norsk"
+ #:super "2"
+ #:dynamic "sfzp"
+ #:huge #:line ("A" #:smaller "A" #:smaller #:smaller "A"
+ #:smaller #:smaller #:smaller "A")
+ #:sub "alike")
+ }
+ \paper {
+ raggedright = ##t
+ indent = #0
+ \translator {
+ \StaffContext
+ \remove Time_signature_engraver
+ }
+ }
+}
--- /dev/null
+
+\header {
+ texidoc ="The new part combiner:
+
+Detect a2, solo1, solo2 and print texts accordingly.
+"
+
+
+ }
+
+
+vone = \notes \relative a' { R1 a4 r4 r r a a a a }
+vtwo = \notes \relative a' { R1 f4 f4 f4 f f f a a }
+
+\score {
+ \newpartcombine \vone \vtwo
+}
+
"
}
-theMusic = \context Staff \notes { c4 d8-. }
-
-
-
vone = \notes \relative a' { g2 g g g4 g f' c c( c) c c c ~ c
c2. c4 c
}
+++ /dev/null
-
-\version "2.1.7"
-\header {
-texidoc="Multi measure rests of second voice should not disappear."
-}
-
-
-%{
-
-TODO: what does this test? The contexts are tweaked too much.
-
-%}
-
-#(ly:set-point-and-click 'line-column)
-one = \notes\relative c'' {
-
- f4 e\!-.\f r2|
- R1|
- f2\p(es )|
- d1\<|
- d1|
- d2 d2|
- d2 d2|
- f1\!\ff |
- f1
-}
-
-two = \notes \relative c'' {
- r4 c r2|
- R1*6|
- c1 ~|
- c1
-}
-
-\score {
- \notes <<
- \context Staff = Viole <<
- \context Voice=one \partcombine Voice
- \context Thread=one \one
- \context Thread=two \two
- >>
- >>
- \paper {
- \translator {
- \ThreadContext
- \consists "Rest_engraver"
- }
- \translator {
- \VoiceContext
- \remove "Rest_engraver"
- \consists Multi_measure_rest_engraver
- \consists Bar_engraver
- }
- \translator {
- \RemoveEmptyStaffContext
- \remove Multi_measure_rest_engraver
- \remove Bar_engraver
- }
- \translator {
- \OrchestralScoreContext
- RestCollision \override #'maximum-rest-count = #1
- }
- }
-}
+++ /dev/null
-
-\version "2.1.7"
-\header{
-texidoc="The partcombiner should not combine two small slurs into a big one."
-}
-
-\score{
- \context Staff <<
- \context Voice=one\skip 1
- \context Voice=two\skip 1
- \context Voice=one \partcombine Voice
- \context Thread=one \notes\relative c'' {
- c4( c b a)
- a( b c d)
- }
- \context Thread=two \notes\relative c'' {
- a4( c b a)
- a( b c a)
-
- }
- >>
- \paper{
- linewidth=140.\mm
- }
-}
-
-
-
--- /dev/null
+
+\header
+{
+texidoc ="
+
+By inserting @code{\\skip} statements into lyric lines, one can put less lyric syllables to a melody.
+
+"
+}
+
+
+% shorthand for Skip Lyric
+sl = \notes { \skip 4 }
+
+\version "2.1.16"
+\score {
+ <<
+ \context Voice = "A" \notes {c4 c c c}
+ \lyricsto "A" \context LyricsVoice=A \lyrics { foo __ \sl \sl bar }
+ \lyricsto "A" \context LyricsVoice=B \lyrics { foo -- \sl baz bar }
+ \lyricsto "A" \context LyricsVoice=C \lyrics { foo -- baz -- baaz bar }
+ >>
+}
+++ /dev/null
-
-\version "2.1.7"
-% TODO: this example has a ton of text for very little output.
-% Is it all needed? -gp
-% should this go to regression or be delete'd? -gp
-\header{ texidoc="@cindex Part Combine Moments
-When you combine two voices with the same notes, you should
-only have one stem. "
-}
-
-\score{
- \context PianoStaff <<
- \context StaffCombineStaff=one \skip 1*2
- \context StaffCombineStaff=two \skip 1*2
- \context StaffCombineStaff=one \partcombine StaffCombineStaff
- \context StaffCombineVoice=one \notes\relative c''
- {
- c4 d e f\break
- c2 e4 f\break
- }
- \context StaffCombineVoice=two \notes\relative c''
- {
- c4 d e f
- c2 e2
- }
- >>
- \paper {
- raggedright= ##t
- textheight = 295.0\mm
- linewidth = 180.0\mm
-
- \translator{ \RemoveEmptyStaffContext }
- %
- % The Voice combine hierarchy
- %
- \translator{
- \ThreadContext
- \name "VoiceCombineThread"
- \consists "Rest_engraver"
- }
- \translator{
- \VoiceContext
- \name "VoiceCombineVoice"
- soloText = #"I."
- soloIIText = #"II."
- \remove "Rest_engraver"
- \accepts "VoiceCombineThread"
- }
- \translator{
- \RemoveEmptyStaffContext
- \consists "Mark_engraver"
- \name "VoiceCombineStaff"
- \accepts "VoiceCombineVoice"
- }
-
- %
- % The Staff combine hierarchy
- %
- \translator{
- \ThreadContext
- \name "StaffCombineThread"
- }
- \translator{
- \VoiceContext
- \name "StaffCombineVoice"
- \accepts "StaffCombineThread"
- \consists "Thread_devnull_engraver"
- }
- \translator {
- \RemoveEmptyStaffContext
- \name "StaffCombineStaff"
- \accepts "StaffCombineVoice"
-
- soloADue = ##t
- soloText = #""
- soloIIText = #""
- aDueText = #""
- splitInterval = #'(1 . 0)
- changeMoment = #`(,(ly:make-moment 1 1) . ,(ly:make-moment 1 1))
-
- }
- \translator {
- \StaffGroupContext
- \accepts "VoiceCombineStaff"
- \accepts "StaffCombineStaff"
- }
- \translator{ \RemoveEmptyStaffContext }
-
- \translator {
- \ScoreContext
- \accepts "VoiceCombineStaff"
- \accepts "StaffCombineStaff"
- skipBars = ##t
-
- RehearsalMark \override #'padding = #4
- BarNumber \override #'padding = #3
- TimeSignature \override #'style = #'C
- RestCollision \set #'maximum-rest-count = #1
- }
- }
-}
-
+++ /dev/null
-
-\version "2.1.7"
-\header { texidoc = "@cindex Part Combine Staff
-You can combine parts on two staffs, as well as two voices. "
-}
-
-\score{
- \context PianoStaff <<
- \context StaffCombineStaff=one \skip 1*7
- \context StaffCombineStaff=two \skip 1*7
- \context StaffCombineStaff=one \partcombine StaffCombineStaff
- \context StaffCombineVoice=one \notes\relative c''
- {
- c4 d e f\break
- c d e f\break
- c d e f
- c4 d e f\break
- a8 a a a b b b b
- a,4 a a a\break
- c2 e4 f\break
- }
- \context StaffCombineVoice=two \notes\relative c''
- {
- c2 e2
- c4 d e f
- c2 e2
- c,4 d e f
- a8 a a a b b b b
- a4 a a a
- c2 e2
- }
- >>
- \paper {
- textheight = 295.0\mm
- linewidth = 180.0\mm
-
- \translator{ \RemoveEmptyStaffContext }
- %
- % The Voice combine hierarchy
- %
- \translator{
- \ThreadContext
- \name "VoiceCombineThread"
- \consists "Rest_engraver"
- }
- \translator{
- \VoiceContext
- \name "VoiceCombineVoice"
- soloText = #"I."
- soloIIText = #"II."
- \remove "Rest_engraver"
- \accepts "VoiceCombineThread"
- }
- \translator{
- \RemoveEmptyStaffContext
- \consists "Mark_engraver"
- \name "VoiceCombineStaff"
- \accepts "VoiceCombineVoice"
- }
-
- %
- % The Staff combine hierarchy
- %
- \translator{
- \ThreadContext
- \name "StaffCombineThread"
- }
- \translator{
- \VoiceContext
- \name "StaffCombineVoice"
- \accepts "StaffCombineThread"
- \consists "Thread_devnull_engraver"
- }
- \translator {
- \RemoveEmptyStaffContext
- \name "StaffCombineStaff"
- \accepts "StaffCombineVoice"
-
- soloADue = ##t
- soloText = #""
- soloIIText = #""
- aDueText = #""
- splitInterval = #'(1 . 0)
- changeMoment = #`(,(ly:make-moment 1 1) . ,(ly:make-moment 1 1))
-
- }
- \translator {
- \StaffGroupContext
- \accepts "VoiceCombineStaff"
- \accepts "StaffCombineStaff"
- }
- \translator{ \RemoveEmptyStaffContext }
-
- \translator {
- \ScoreContext
- \accepts "VoiceCombineStaff"
- \accepts "StaffCombineStaff"
- skipBars = ##t
-
- %%% FIXME barScriptPadding = #2.0 % dimension \pt
- RehearsalMark \override #'padding = #4
- BarNumber \override #'padding = #3
-
- %% URG: this changes dynamics too
- %%textStyle = #"italic"
- TimeSignature \override #'style = #'C
-
- RestCollision \set #'maximum-rest-count = #1
-
- }
- }
-}
-
--- /dev/null
+\header {
+ latexpackages = "graphicx"
+
+ texidoc = "
+@cindex rotated text
+@cindex choir, rotated text
+
+Rotated text may be faked using inline TeX (or inline
+postscript). To see the result, this files must be processed with
+the lilypond.py script, and a line must be uncommented.
+
+"
+
+ %% fixme.
+
+
+ }
+\score {
+\new Staff \notes \relative c'' {
+ \property Staff.instrument = #"rotatebox{90}{Chor}"
+
+%% uncomment this for rotation
+% \property Staff.instrument = #"\\rotatebox{90}{Chor}"
+
+ c4 c4 }
+
+\paper { raggedright = ##t }
+}
+
+
}
-%{
-Hi all,
-
-I have extend Mats' patches to allow accidental transposition:
- \keysignature bes es fis
- = \transpose c' d =>>
- \keysignature gis
-
-As you can see in output = the example file "test.ly" there are a few problems left:
-- key undo (already in the Todo)
-- "wrong" transposition: e.g. \transpose c' d of fis-major = gis-major (better as-major?).
- The solution gis=as etc. would concern both note and key transposistion
-
-Eric
-
-- Note that transpose eses of fis-major = as-major
-- Note also that key signatures specified with \property keySignature
- are not transposed!
-
- /Mats B, March 21, 2001
-%}
-
-
-
vOne = \notes \relative c''{
\clef"violin"
vThree = \notes \relative c''{
\clef"violin"
-% \keysignature fis cis
+% keysignature fis cis
\property Staff.keySignature = #'((0 . 2)(3 . 2))
\time 2/4
d4 d |
-% \keysignature bes es fis
+% keysignature bes es fis
\property Staff.keySignature = #'((3 . 2)(2 . -2)(6 . -2))
fis4 fis |
-% \keysignature fis cis gis dis ais eis
+% keysignature fis cis gis dis ais eis
\property Staff.keySignature = #'((2 . -2)(5 . -2)(1 . 2)(4 . 2)(0 . 2)(3 . 2))
cis4 ais4 |
}
+
vFour = \notes \transpose c d \vThree
\score {
hyphen-engraver.cc -- implement Hyphen_engraver
source file of the GNU LilyPond music typesetter
-
- (c) 1999--2003 Glen Prideaux <glenprideaux@iname.com>
+
+ (c) 1999--2003 Glen Prideaux <glenprideaux@iname.com>,
+ Han-Wen Nienhuys <hanwen@cs.uu.nl>,
+ Jan Nieuwenhuizen <janneke@gnu.org>
*/
-#include "flower-proto.hh"
-#include "event.hh"
+#include "warn.hh"
#include "hyphen-spanner.hh"
-#include "paper-column.hh"
#include "item.hh"
#include "engraver.hh"
-/**
- Generate an centred hyphen. Should make a Hyphen_spanner that
- typesets a nice centred hyphen of varying length depending on the
- gap between syllables.
-
- We remember the last Item that come across. When we get a
- event, we create the spanner, and attach the left point to the
- last lyrics, and the right point to any lyrics we receive by
- then. */
class Hyphen_engraver : public Engraver
{
- Grob *last_lyric_;
- Grob *current_lyric_;
- Music* req_;
+ Music* ev_;
Spanner* hyphen_;
+ Spanner * finished_hyphen_;
public:
TRANSLATOR_DECLARATIONS(Hyphen_engraver);
virtual void finalize ();
virtual bool try_music (Music*);
virtual void stop_translation_timestep ();
- virtual void process_acknowledged_grobs ();
+ virtual void process_music ();
private:
};
+
Hyphen_engraver::Hyphen_engraver ()
{
- current_lyric_ = 0;
- last_lyric_ = 0;
hyphen_ = 0;
- req_ = 0;
+ finished_hyphen_ = 0;
+ ev_ = 0;
}
void
Hyphen_engraver::acknowledge_grob (Grob_info i)
{
- // -> text-item
- if (i.grob_->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
+ Item * item = dynamic_cast<Item*> (i.grob_);
+ // -> text_item
+ if (item && item->internal_has_interface (ly_symbol2scm ("lyric-syllable-interface")))
{
- current_lyric_ = i.grob_;
- if (hyphen_
- && !hyphen_->get_bound (RIGHT))
- {
- hyphen_->set_bound (RIGHT, i.grob_);
- }
+ if (hyphen_)
+ hyphen_->set_bound (LEFT, item);
+
+ if (finished_hyphen_)
+ finished_hyphen_->set_bound (RIGHT, item);
}
}
bool
Hyphen_engraver::try_music (Music* r)
{
- if (req_)
- return false;
+ if (ev_)
+ return false;
- req_ = r;
- return true;
+ ev_ = r;
+ return true;
+}
+
+void
+completize_hyphen (Spanner* sp)
+{
+ if (!sp->get_bound (RIGHT))
+ {
+ SCM heads = sp->get_grob_property ("heads");
+ if (gh_pair_p (heads))
+ {
+ Item* it = dynamic_cast<Item*> (unsmob_grob (gh_car (heads)));
+ if (it)
+ sp->set_bound (RIGHT, it);
+ }
+ }
}
+
+
void
Hyphen_engraver::finalize ()
{
if (hyphen_)
{
- req_->origin ()->warning (_ ("unterminated hyphen"));
- hyphen_->set_bound (RIGHT, unsmob_grob (get_property ("currentCommandColumn")));
+ completize_hyphen (hyphen_);
+
+ if (!hyphen_->get_bound (RIGHT))
+ hyphen_->warning (_ ("unterminated hyphen"));
+ typeset_grob (hyphen_);
+ hyphen_ = 0;
+ }
+
+ if (finished_hyphen_)
+ {
+ completize_hyphen (finished_hyphen_);
+
+ if (!finished_hyphen_->get_bound (RIGHT))
+ finished_hyphen_->warning (_("unterminated hyphen"));
+ typeset_grob (finished_hyphen_);
+ finished_hyphen_ =0;
}
}
void
-Hyphen_engraver::process_acknowledged_grobs ()
+Hyphen_engraver::process_music ()
{
- if (req_ &&! hyphen_)
+ if (ev_)
{
- if (!last_lyric_)
- {
- req_->origin ()->warning (_ ("Nothing to connect hyphen to on the left. Ignoring hyphen event."));
- return;
- }
-
hyphen_ = make_spanner ("LyricHyphen");
-
- hyphen_->set_bound (LEFT, last_lyric_);
- announce_grob(hyphen_, req_->self_scm());
+ announce_grob (hyphen_, ev_->self_scm());
}
}
void
Hyphen_engraver::stop_translation_timestep ()
{
- if (hyphen_)
+ if (finished_hyphen_ && finished_hyphen_->get_bound (RIGHT))
{
- typeset_grob (hyphen_);
- hyphen_ = 0;
+ typeset_grob (finished_hyphen_);
+ finished_hyphen_ = 0;
}
- if (current_lyric_)
+ if (finished_hyphen_ && hyphen_)
{
- last_lyric_ = current_lyric_;
- current_lyric_ =0;
+ programming_error ("Haven't finished hyphen yet.");
+ typeset_grob (finished_hyphen_);
+ finished_hyphen_ =0;
}
- req_ = 0;
+
+ if (hyphen_)
+ finished_hyphen_ = hyphen_;
+ hyphen_ = 0;
+
+ ev_ = 0;
}
+
+
ENTER_DESCRIPTION(Hyphen_engraver,
/* descr */ "Create lyric hyphens",
/* creats*/ "LyricHyphen",
return false;
if (!lyrics_context_)
- construct_children ();
+ return false;
if (!to_boolean (lyrics_context_->get_property ("ignoreMelismata")))
{
find_context_below (Translator_group * where,
String type, String id)
{
- if (where->context_name () == type)
+ if (where->is_alias (ly_symbol2scm (type.to_str0 ())))
{
if (id == "" || where->id_string_ == id)
return where;
{
Translator_group * tr = dynamic_cast<Translator_group*> (unsmob_translator (gh_car (s)));
-
found = find_context_below (tr, type, id);
}
if (lyric_iter_)
lyrics_context_ = find_context_below (lyric_iter_->report_to (),
"LyricsVoice", "");
-
- if (music_context_ && !lyrics_context_)
- lyrics_context_ = music_context_
- ->find_create_translator (ly_symbol2scm ("LyricsVoice"), "", SCM_EOL);
+
+ /*
+ We do not create a LyricsVoice context, because the user might
+ create one with a different name, and then we will not find that
+ one.
+ */
}
void
bool is_shared_ ;
SCM split_list_;
+ enum {
+ APART, TOGETHER,
+ SOLO1, SOLO2,
+ UNISONO,
+ } state_;
+
Interpretation_context_handle one_;
Interpretation_context_handle two_;
Interpretation_context_handle null_;
first_iter_ = 0;
second_iter_ = 0;
split_list_ = SCM_EOL;
+ state_ = APART;
}
void
void
New_pc_iterator::chords_together ()
{
- first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
- first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
- second_iter_->substitute_outlet (two_.report_to (), shared_.report_to ());
- second_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ if (state_ == TOGETHER)
+ return;
+ else
+ {
+ state_ = TOGETHER;
+ first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
+ first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ second_iter_->substitute_outlet (two_.report_to (), shared_.report_to ());
+ second_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ }
}
void
New_pc_iterator::solo1 ()
{
- first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
- first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
+ if (state_ == SOLO1)
+ return;
+ else
+ {
+ state_ = SOLO1;
+ first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
- second_iter_->substitute_outlet (two_.report_to (), null_.report_to ());
- second_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
-}
+ second_iter_->substitute_outlet (two_.report_to (), null_.report_to ());
+ second_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
+
+ static Music* event;
+ if (!event)
+ event = make_music_by_name (ly_symbol2scm ("SoloOneEvent"));
+ first_iter_-> try_music_in_children (event);
+ }
+}
void
New_pc_iterator::unisono ()
{
- /*
- like solo1, but should set a2 string.
- */
- first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
- first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
-
- second_iter_->substitute_outlet (two_.report_to (), null_.report_to ());
- second_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
-}
+ if (state_ == UNISONO)
+ return;
+ else
+ {
+ state_ = UNISONO;
+
+ first_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ first_iter_->substitute_outlet (one_.report_to (), shared_.report_to ());
+
+ second_iter_->substitute_outlet (two_.report_to (), null_.report_to ());
+ second_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
+ static Music* event;
+ if (!event)
+ event = make_music_by_name (ly_symbol2scm ("UnisonoEvent"));
+
+ first_iter_-> try_music_in_children (event);
+ }
+}
+
void
New_pc_iterator::solo2 ()
{
- second_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
- second_iter_->substitute_outlet (two_.report_to (), shared_.report_to ());
+ if (state_ == SOLO2)
+ return;
+ else
+ {
+ state_ = SOLO2;
+ second_iter_->substitute_outlet (null_.report_to (), shared_.report_to ());
+ second_iter_->substitute_outlet (two_.report_to (), shared_.report_to ());
- first_iter_->substitute_outlet (one_.report_to (), null_.report_to ());
- first_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
-}
+ first_iter_->substitute_outlet (one_.report_to (), null_.report_to ());
+ first_iter_->substitute_outlet (shared_.report_to (), null_.report_to ());
+ static Music* event;
+ if (!event)
+ event = make_music_by_name (ly_symbol2scm ("SoloTwoEvent"));
+
+ second_iter_-> try_music_in_children (event);
+ }
+}
void
New_pc_iterator::apart ()
{
- first_iter_->substitute_outlet (null_.report_to (), one_.report_to ());
- first_iter_->substitute_outlet (shared_.report_to (), one_.report_to ());
+ if (state_ == APART)
+ return;
+ else
+ {
+ state_ = APART;
+
+ first_iter_->substitute_outlet (null_.report_to (), one_.report_to ());
+ first_iter_->substitute_outlet (shared_.report_to (), one_.report_to ());
- second_iter_->substitute_outlet (null_.report_to (), two_.report_to ());
- second_iter_->substitute_outlet (shared_.report_to (), two_.report_to ());
+ second_iter_->substitute_outlet (null_.report_to (), two_.report_to ());
+ second_iter_->substitute_outlet (shared_.report_to (), two_.report_to ()); }
}
-
void
New_pc_iterator::construct_children ()
{
%type <music> shorthand_command_req
%type <music> post_event tagged_post_event
%type <music> command_req verbose_command_req
-%type <music> hyphen_req
%type <music> string_number_event
%type <scm> string bare_number number_expression number_term number_factor
%type <score> score_block score_body
;
shorthand_command_req:
- hyphen_req {
- $$ = $1;
- }
- | BREATHE {
+ BREATHE {
$$ = MY_MAKE_MUSIC("BreathingSignEvent");
}
| E_TILDE {
direction_less_event {
$$ = $1;
}
+ | HYPHEN {
+ if (!THIS->lexer_->lyric_state_b ())
+ THIS->parser_error (_ ("Have to be in Lyric mode for lyrics"));
+ $$ = MY_MAKE_MUSIC("HyphenEvent");
+ }
| EXTENDER {
if (!THIS->lexer_->lyric_state_b ())
THIS->parser_error (_ ("Have to be in Lyric mode for lyrics"));
| steno_tonic_pitch
;
-hyphen_req:
- HYPHEN {
- if (!THIS->lexer_->lyric_state_b ())
- THIS->parser_error (_ ("Have to be in Lyric mode for lyrics"));
- $$ = MY_MAKE_MUSIC("HyphenEvent");
- }
- ;
-
close_event:
'(' {
Music * s= MY_MAKE_MUSIC("SlurEvent");
--- /dev/null
+/*
+ part-combine-engraver.cc -- implement PC-engraver
+
+ source file of the GNU LilyPond music typesetter
+
+ (c) 2000--2003 Jan Nieuwenhuizen <janneke@gnu.org>
+
+ Han-Wen Nienhuys <hanwen@xs4all.nl>
+
+*/
+
+#include "engraver.hh"
+#include "item.hh"
+#include "text-item.hh"
+#include "note-head.hh"
+#include "stem.hh"
+#include "side-position-interface.hh"
+#include "multi-measure-rest.hh"
+
+class Part_combine_engraver : public Engraver
+{
+ TRANSLATOR_DECLARATIONS(Part_combine_engraver);
+
+protected:
+ virtual void acknowledge_grob (Grob_info);
+ virtual void process_music ();
+ virtual void stop_translation_timestep ();
+ virtual bool try_music (Music *);
+private:
+ Item *text_;
+ Music *event_;
+};
+
+bool
+Part_combine_engraver::try_music (Music*m)
+{
+ event_ = m;
+ return true;
+}
+
+Part_combine_engraver::Part_combine_engraver ()
+{
+ text_ = 0;
+ event_ =0;
+}
+
+void
+Part_combine_engraver::process_music ()
+{
+ if (event_
+ && to_boolean (get_property ("soloADue")))
+ {
+ SCM what = event_->get_mus_property ("part-combine-status");
+ SCM text = SCM_EOL;
+ if (what == ly_symbol2scm ("solo1"))
+ text = get_property ("soloText");
+ else if (what== ly_symbol2scm ("solo2"))
+ text = get_property ("soloIIText");
+ else if (what == ly_symbol2scm ("unisono"))
+ text = get_property ("aDueText");
+
+ if (Text_item::markup_p (text))
+ {
+ text_ = make_item ("CombineTextScript");
+ text_->set_grob_property ("text", text);
+ announce_grob (text_, event_->self_scm ());
+ }
+ }
+}
+
+void
+Part_combine_engraver::acknowledge_grob (Grob_info i)
+{
+ if (text_)
+ {
+ if (Note_head::has_interface (i.grob_))
+ {
+ Grob*t = text_;
+ Side_position_interface::add_support (t, i.grob_);
+ if (Side_position_interface::get_axis (t) == X_AXIS
+ && !t->get_parent (Y_AXIS))
+ t->set_parent (i.grob_, Y_AXIS);
+ }
+ if (Stem::has_interface (i.grob_))
+ {
+ Side_position_interface::add_support (text_, i.grob_);
+ }
+ }
+}
+
+void
+Part_combine_engraver::stop_translation_timestep ()
+{
+ if (text_)
+ {
+ typeset_grob (text_);
+ text_ = 0;
+ }
+ event_ = 0;
+}
+
+ENTER_DESCRIPTION(Part_combine_engraver,
+/* descr */ "Part combine engraver for orchestral scores: "
+ "Print markings a2, Solo, Solo II, and unisono ",
+/* creats*/ "CombineTextScript",
+/* accepts */ "part-combine-event",
+/* acks */ "multi-measure-rest-interface "
+"slur-interface stem-interface note-head-interface"
+,/* reads */ "soloADue",
+/* write */ "");
*/
-
+#include "translator-group.hh"
#include "audio-item.hh"
#include "event.hh"
#include "pqueue.hh"
Link_array<Audio_tie> ties_;
protected:
+ virtual void process_music ();
virtual void start_translation_timestep ();
virtual void stop_translation_timestep ();
virtual void acknowledge_audio_element (Audio_element_info);
virtual void create_audio_elements ();
};
+void
+Tie_performer::process_music ()
+{
+ if (event_)
+ daddy_trans_->set_property ("tieMelismaBusy", SCM_BOOL_T);
+}
+
Tie_performer::Tie_performer ()
{
Direction d = LEFT;
do {
straight_corners[d] += - d * shorten[d] /length * dz;
- gap_corners[d] = (dz * 0.5) + d * gap / length * dz;
+ } while (flip (&d) != LEFT);
+
+ /*
+ UGH: the shortening factor is magic.
+ */
+ gap = gap <?
+ (0.66 * (straight_corners[RIGHT] - straight_corners[LEFT]).length ());
+
+ do {
+ gap_corners[d] = (dz * 0.5) + d * 0.5 * gap / length * dz;
} while (flip (&d) != LEFT);
Drul_array<Offset> flare_corners = straight_corners;
\consists "Percent_repeat_engraver"
\consists "Slash_repeat_engraver"
\consists "Melisma_engraver"
+ \consists "Part_combine_engraver"
%{
Must come before text_engraver, but after note_column engraver.
(cautionary-style . parentheses)
(after-line-breaking-callback . ,Accidental_interface::after_line_breaking) (meta . ((interfaces . (item-interface accidental-interface font-interface))))
))
+
(AccidentalPlacement
. (
(X-extent-callback . ,Axis_group_interface::group_extent_callback)
;; this is quite small, but it is very ugly to have
;; accs closer to the previous note than to the next one.
- (right-padding . 0.2)
+ (right-padding . 0.25)
(meta . ((interfaces . (item-interface accidental-placement-interface))))
))
(font-family . roman)
(meta . ((interfaces . (text-script-interface text-interface side-position-interface font-interface item-interface ))))
))
-
+ (CombineTextScript
+ . (
+ (molecule-callback . ,Text_item::brew_molecule)
+ (no-spacing-rods . #t)
+ (Y-offset-callbacks . (,Side_position_interface::aligned_side))
+ (X-offset-callbacks . (,Self_alignment_interface::aligned_on_self))
+ (direction . 1)
+ (padding . 0.5)
+ (staff-padding . 0.5)
+ (script-priority . 200)
+ ;; todo: add X self alignment?
+ (baseline-skip . 2)
+ (font-family . roman)
+ (font-series . bold)
+ (meta . ((interfaces . (text-script-interface text-interface side-position-interface font-interface item-interface ))))
+ ))
(TextSpanner
. (
(molecule-callback . ,Text_spanner::brew_molecule)
(TupletBracket
. (
- (gap . 2.0)
+ (gap . 1.0)
(padding . 1.1)
(thickness . 1.6)
(edge-height . (0.7 . 0.7))
"Do these operations for instantiating the context.")
(music-property-description 'predicate procedure? "the predicate of a \\outputproperty")
(music-property-description 'type symbol? "The type of this music object. Determines iteration in some cases.")
-(music-property-description 'types list? "The types of this music object. Determines iteration in some cases.")
+(music-property-description 'types list? "The types of this music
+object; determines by what engraver this music expression is
+processed.")
+
(music-property-description 'repeat-count integer? "do a @code{\repeat} how ofen?")
(music-property-description 'span-direction ly:dir? "Does this start or stop a spanner?")
(music-property-description 'split-list list? "splitting moments for part combiner.")
(music-property-description 'value scheme? "Assignment value for a
translation property")
(music-property-description 'what symbol? "What to change for auto-change. FIXME, naming")
+(music-property-description 'part-combine-status symbol?
+ "Change to what kind of state? Options are
+solo1, solo2 and unisono")
(music-property-description 'figure string? "a `figure' (which may be
a string) for figured bass")
(CrescendoEvent
. (
- (description . "Begins or ends a crescendo. Syntax: @var{note}\\cr
+ (description . "Begins or ends a crescendo. Syntax: @var{note}\\cr
... @var{note}\\rc (you can also use \\<, \\!, \\cresc, and
\\endcresc. See the user manual for details.).")
(iterator-ctor . ,Sequential_music_iterator::constructor)
(types . (general-music sequential-music))
))
+
+ (SoloOneEvent
+ . (
+ (description . "Print Solo.1")
+ (internal-class-name . "Event")
+ (part-combine-status . solo1)
+ (types . (general-music event part-combine-event))
+ ))
+ (SoloTwoEvent
+ . (
+ (description . "Print Solo.2")
+ (internal-class-name . "Event")
+ (part-combine-status . solo2)
+ (types . (general-music event part-combine-event))
+ ))
+ (UnisonoEvent
+ . ((description . "Print a2")
+ (internal-class-name . "Event")
+ (part-combine-status . unisono)
+ (types . (general-music event part-combine-event))))
(SimultaneousMusic
. (
(cdr (vector-ref v i)))
(define chord-threshold 8)
+ (define (get-note-evs v i)
+ (define (f? x)
+ (equal? (ly:get-mus-property x 'name) 'NoteEvent))
+ (filter f? (map car (what v i))))
(define result
(list->vector
(helper analyse-tie-end active evs) evs) evs)
active<?))
- (define (get-note-evs v i)
- (define (f? x)
- (equal? (ly:get-mus-property x 'name) 'NoteEvent))
- (filter f? (map car (what v i))))
-
+
(define (put x . index)
"Put the result to X, starting from INDEX backwards."
(let
(cond
((> (length notes1) 1) (put 'apart))
((> (length notes2) 1) (put 'apart))
+ ((not (= (length notes1) (length notes2)))
+ (put 'apart))
((and
(= (length durs1) 1)
(= (length durs2) 1)
(put 'apart))
(else
- (if
- (and (= (length pitches1) 1) (= (length pitches2) 1)
- (< chord-threshold (ly:pitch-steps
- (ly:pitch-diff (car pitches1) (car pitches2)))))
- (put 'apart)
-
-
- ;; copy previous split state from spanner state
- (begin
- (map (lambda (key-idx)
- (let*
- ((idx (cdr key-idx))
- (prev (what result idx))
- )
- (if (symbol? prev)
- (put prev))
- )) (append active1 active2))
- (if (and (null? new-active1) (null? new-active2))
- (put 'chords ri)))
-
- ))) )
+ (if (and (= (length pitches1) (length pitches2)))
+ (if
+ (and (pair? pitches1) (pair? pitches2)
+ (< chord-threshold (ly:pitch-steps
+ (ly:pitch-diff (car pitches1) (car pitches2)))))
+ (put 'apart)
+
+
+ ;; copy previous split state from spanner state
+ (begin
+ (map (lambda (key-idx)
+ (let*
+ ((idx (cdr key-idx))
+ (prev (what result idx))
+ )
+ (if (symbol? prev)
+ (put prev))
+ )) (append active1 active2))
+ (if (and (null? new-active1) (null? new-active2))
+ (put 'chords ri))))
+
+ ))))
+
;; active states different:
- (put 'apart) )
- (analyse-time-step (1+ i1) (1+ i2) (1+ ri) new-active1 new-active2))
- )))))
+ (put 'apart))
+ (analyse-time-step (1+ i1) (1+ i2) (1+ ri) new-active1 new-active2)))
+ ))))
;;
-
-
+ (define (analyse-solo12 i1 i2 ri)
+ (cond
+ ((= ri (vector-length result)) '())
+ ((= i1 (vector-length ev1)) '())
+ ((= i2 (vector-length ev2)) '())
+ (else
+ (let*
+ (
+ (m1 (when ev1 i1))
+ (m2 (when ev2 i2))
+ (notes1 (get-note-evs ev1 i1))
+ (durs1 (sort (map (lambda (x) (ly:get-mus-property x 'duration)) notes1) ly:duration<?))
+ (pitches1 (sort
+ (map (lambda (x) (ly:get-mus-property x 'pitch)) notes1) ly:pitch<?))
+ (notes2 (get-note-evs ev2 i2))
+ (durs2 (sort (map (lambda (x) (ly:get-mus-property x 'duration)) notes2) ly:duration<?))
+ (pitches2 (sort
+ (map (lambda (x) (ly:get-mus-property x 'pitch)) notes2) ly:pitch<?))
+ )
+
+ (if (equal? (what result ri) 'apart)
+ (cond
+ ((and (= 0 (length notes1))
+ (< 0 (length notes2)))
+ (set-cdr! (vector-ref result ri) 'solo2))
+ ((and (< 0 (length notes1))
+ (= 0 (length notes2)))
+ (set-cdr! (vector-ref result ri) 'solo1))
+ ))
+
+ (if (and
+ (equal? (what result ri) 'chords)
+ (pair? pitches1)
+ (equal? pitches1 pitches2))
+ (set-cdr! (vector-ref result ri) 'unisono) )
+
+ (cond
+ ((ly:moment<? m1 m2)
+ (analyse-solo12 (1+ i1) i2 (1+ ri) ))
+ ((ly:moment<? m2 m1)
+ (analyse-solo12 i1 (1+ i2) (1+ ri) ))
+ (else
+ (analyse-solo12 (1+ i1) (1+ i2) (1+ ri)))
+ )))))
(analyse-time-step 0 0 0 '() '())
+ (analyse-solo12 0 0 0)
; (display result)
(vector->list result))
error-msg #f)
(cons markup-function args))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; markup constructors
+;;; lilypond-like syntax for markup construction in scheme.
+
+(use-modules (ice-9 optargs)
+ (ice-9 receive))
+
+(defmacro*-public markup (#:rest body)
+ "The `markup' macro provides a lilypond-like syntax for building markups.
+
+ - #:COMMAND is used instead of \\COMMAND
+ - #:lines ( ... ) is used instead of { ... }
+ - #:center ( ... ) is used instead of \\center < ... >
+ - etc.
+
+Example:
+ \\markup { foo
+ \\raise #0.2 \\hbracket \\bold bar
+ \\override #'(baseline-skip . 4)
+ \\bracket \\column < baz bazr bla >
+ }
+ <==>
+ (markup \"foo\"
+ #:raise 0.2 #:hbracket #:bold \"bar\"
+ #:override '(baseline-skip . 4)
+ #:bracket #:column (\"baz\" \"bazr\" \"bla\"))
+Use `markup*' in a \\notes block."
+
+ (car (compile-all-markup-expressions `(#:line ,body))))
+
+(defmacro*-public markup* (#:rest body)
+ "Same as `markup', for use in a \\notes block."
+ `(ly:export (markup ,@body)))
+
+
+(define (compile-all-markup-expressions expr)
+ "Return a list of canonical markups expressions, eg:
+ (#:COMMAND1 arg11 arg12 #:COMMAND2 arg21 arg22 arg23)
+ ===>
+ ((make-COMMAND1-markup arg11 arg12)
+ (make-COMMAND2-markup arg21 arg22 arg23) ...)"
+ (do ((rest expr rest)
+ (markps '() markps))
+ ((null? rest) (reverse markps))
+ (receive (m r) (compile-markup-expression rest)
+ (set! markps (cons m markps))
+ (set! rest r))))
+
+(define (keyword->make-markup key)
+ "Transform a keyword, eg. #:COMMAND, in a make-COMMAND-markup symbol."
+ (string->symbol (string-append "make-" (symbol->string (keyword->symbol key)) "-markup")))
+
+(define (compile-markup-expression expr)
+ "Return two values: the first complete canonical markup expression found in `expr',
+eg (make-COMMAND-markup arg1 arg2 ...), and the rest expression."
+ (cond ((and (pair? expr)
+ (keyword? (car expr)))
+ ;; expr === (#:COMMAND arg1 ...)
+ (let* ((command (symbol->string (keyword->symbol (car expr))))
+ (sig (markup-command-signature (car (lookup-markup-command command))))
+ (sig-len (length sig)))
+ (do ((i 0 (1+ i))
+ (args '() args)
+ (rest (cdr expr) rest))
+ ((>= i sig-len)
+ (values (cons (keyword->make-markup (car expr)) (reverse args)) rest))
+ (cond ((eqv? (list-ref sig i) markup-list?)
+ ;; (car rest) is a markup list
+ (set! args (cons `(list ,@(compile-all-markup-expressions (car rest))) args))
+ (set! rest (cdr rest)))
+ (else
+ ;; pick up one arg in `rest'
+ (receive (a r) (compile-markup-arg rest)
+ (set! args (cons a args))
+ (set! rest r)))))))
+ ((and (pair? expr)
+ (pair? (car expr))
+ (keyword? (caar expr)))
+ ;; expr === ((#:COMMAND arg1 ...) ...)
+ (receive (m r) (compile-markup-expression (car expr))
+ (values m (cdr expr))))
+ (else
+ ;; expr === (symbol ...) or ("string" ...) or ((funcall ...) ...)
+ (values (car expr)
+ (cdr expr)))))
+
+(define (compile-all-markup-args expr)
+ "Transform `expr' into markup arguments"
+ (do ((rest expr rest)
+ (args '() args))
+ ((null? rest) (reverse args))
+ (receive (a r) (compile-markup-arg rest)
+ (set! args (cons a args))
+ (set! rest r))))
+
+(define (compile-markup-arg expr)
+ "Return two values: the desired markup argument, and the rest arguments"
+ (cond ((null? expr)
+ ;; no more args
+ (values '() '()))
+ ((keyword? (car expr))
+ ;; expr === (#:COMMAND ...)
+ ;; ==> build and return the whole markup expression
+ (compile-markup-expression expr))
+ ((and (pair? (car expr))
+ (keyword? (caar expr)))
+ ;; expr === ((#:COMMAND ...) ...)
+ ;; ==> build and return the whole markup expression(s)
+ ;; found in (car expr)
+ (receive (markup-expr rest-expr) (compile-markup-expression (car expr))
+ (if (null? rest-expr)
+ (values markup-expr (cdr expr))
+ (values `(list ,markup-expr ,@(compile-all-markup-args rest-expr))
+ (cdr expr)))))
+ ((and (pair? (car expr))
+ (pair? (caar expr)))
+ ;; expr === (((foo ...) ...) ...)
+ (values (cons 'list (compile-all-markup-args (car expr))) (cdr expr)))
+ (else (values (car expr) (cdr expr)))))
+
;;;;;;;;;;;;;;;
;;; Utilities for storing and accessing markup commands signature
;;; and keyword.