> 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
}

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to