> Hi all, Hi Marc,
> I’d like to define helper markup commands so I can write compact examples
> and (ideally) avoid repeating the same fragment twice.
The issue here is that you want to preserve the actual Lilypond input code,
which means you
need to get it BEFORE it is parsed (which should also preserve all kinds of
commends,
whitespace, linebreaks, ...).
Once the object is parsed there is no real way to get back to a lilypond input
expression. There is
the internal function `music->lily-string` used in `display-lily-music` you can
use on a single
music expression, but this does not get you what you want:
E.g. if you do
%%%
notes = \relative c' {
c d e f8 e | d4 g c,2
}
mus = \new Staff \with {
instrumentName = "Violin"
} \notes
%%%
and then `#(display-lily-music mus)` you’d get
%%%
\new Staff \with {
\assign instrumentName
} \absolute {
c'4 d'4 e'4 f'8 e'8 |
d'4 g'4 c'2
}
%%%
which is definitely not what you want. So instead of parsing Lilypond code as
code you’ll need to
parse it as string and have it parsed later.
For entering code as string either do `"..."` or (maybe nicer to enter) use `#{
... #}`, but quote it:
`#(quote #{ ... #})` or `#'#{ ... #}`. This results in a call where the second
argument is the string in
beween #{ ... #}.
Now you can have that string parsed using `#(ly:parser-include-string string)`.
But unless your
code ends with some assignment of a variable you will not be able to capture
the result this
easily. Any music or score sitting at toplevel in a document (so with no
assignment or anything) is
passed to the function `toplevel-music-handler` or `toplevel-score-handler`.
So to actually get access to such toplevel scores you’ll need to modify these
handlers.
See attached a file that does that: It includes
* \rawString string: Wraps a string at line breaks
* \collectScores string: Temporarily changes score and music handlers to
collect scores in a list,
returns that list.
* \printScoreList: Turns a list of scores into a markup list of markup scores
* \stack axis direction padding mlist: Use stack-stencils on a markup list
(baseline-to-baseline
spacing is not nice for spacing multiple scores).
with this you can do `\column\rawString\code` to print the code,
`\stack\printScoreList\collectScores\code` to print the scores.
Note though that with this point-and-click does not work on the score. Also
note that if you have
multiple such scores changes to variables and values in the code will persist
outside of the code
example.
For a more robust implementation it would be better to use an outside
typesetting solution
which prints the text, runs lilypond only on the snippet and includes the pdf
(as would be the
case with lilypond-book and lyluatex).
Best regards,
Tina
% Wordwrap a string as indicated by newlines.
rawString =
#(define-scheme-function (str) (string?)
(let
((lines (string-split str #\newline)))
lines))
% Parse Lilypond code and collect scores (rather than printing them)
collectScores =
#(define-scheme-function (expr) (string?)
(let
((toplevel-music-handler-orig
(ly:parser-lookup 'toplevel-music-handler))
(toplevel-score-handler-orig
(ly:parser-lookup 'toplevel-score-handler))
(scores '()))
(ly:parser-define!
'toplevel-music-handler
(lambda (x)
(set! scores (cons (scorify-music x) scores))))
(ly:parser-define!
'toplevel-score-handler
(lambda (x) (set! scores (cons x scores))))
(ly:parser-parse-string (ly:parser-clone) expr)
(ly:parser-define!
'toplevel-music-handler
toplevel-music-handler-orig)
(ly:parser-define!
'toplevel-score-handler
toplevel-score-handler-orig)
; markup scores requires a layout. Thus if a score has no
; output-def add an empty layout
(for-each
(lambda (score)
(if (null? (ly:score-output-defs score))
(ly:score-add-output-def! score #{ \layout { } #})))
scores)
(reverse scores)))
printScoreList =
#(define-scheme-function (scores) (list?)
(let
((markup-scores (map make-score-markup scores)))
markup-scores))
% Like \column, but instead of aligning by baseline (which does not make
% sense for non-line and multiline objects)
#(define-markup-command (stack layout props axis dir padding list)
(integer? ly:dir? number? markup-list?)
(let ((stcs (interpret-markup-list layout props list)))
(stack-stencils axis dir padding stcs)))
% Define some code. Could also simply be defined as string "...", but
% using a quoted #{ ... #} expression and extracting the string probably
% reads nicer.
code = #(second '#{
notes = \relative c' {
c d e f8 e | d4 g c,2
}
\new Staff \with {
instrumentName = "Violin"
} \notes
\new StaffGroup \with {
instrumentName = "Strings"
} <<
\new Staff \with {
instrumentName = "Violin"
} \notes
\new Staff \with {
instrumentName = "Cello"
\clef bass
} \transpose c c, \notes
>>
#})
\markup \justify-line {
\vcenter
\box\pad-around #2 \typewriter\column\rawString\code
\vcenter"⇒"
\vcenter
\stack#Y #DOWN #4
\printScoreList\collectScores\code
}
signature.asc
Description: This is a digitally signed message part.
