summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/changes.tely19
-rw-r--r--Documentation/contributor/programming-work.itexi96
-rw-r--r--Documentation/notation/expressive.itely3
-rw-r--r--Documentation/notation/pitches.itely11
-rw-r--r--input/regression/retrograde.ly30
-rw-r--r--lily/include/slur-engraver.hh4
-rw-r--r--lily/parser.yy74
-rw-r--r--lily/slur-engraver.cc22
-rw-r--r--ly/grace-init.ly4
-rw-r--r--ly/music-functions-init.ly7
-rw-r--r--ly/spanners-init.ly11
-rw-r--r--scm/define-grob-properties.scm2
-rw-r--r--scm/define-grobs.scm2
-rw-r--r--scm/define-music-properties.scm2
-rw-r--r--scm/define-music-types.scm2
-rw-r--r--scm/modal-transforms.scm122
16 files changed, 292 insertions, 119 deletions
diff --git a/Documentation/changes.tely b/Documentation/changes.tely
index fb1a77f633..c9315d59da 100644
--- a/Documentation/changes.tely
+++ b/Documentation/changes.tely
@@ -62,6 +62,25 @@ which scares away people.
@end ignore
@item
+Slurs and phrasing slurs may now be started from individual notes
+in a chord. Several simultanous slurs per @code{Voice} need to be
+distinguished by @code{spanner-id} setting.
+
+@item
+The music and grob property @code{spanner-id} for distinguishing
+simultaneous slurs and phrasing slurs has been changed from a
+string to a @q{key}, a non-negative integer or symbol.
+
+@item
+There is a new command @code{\=} for specifying the
+@code{spanner-id} for simultaneous slurs and phrasing slurs.
+@lilypond[verbatim,quote]
+\fixed c' {
+ <c~ f\=1( g\=2( >2 <c e\=1) a\=2) >
+}
+@end lilypond
+
+@item
Blocks introduced with @code{\header} can be stored in variables
and used as arguments to music and scheme functions and as the
body of @code{#@{@dots{}#@}} constructs. They are represented as
diff --git a/Documentation/contributor/programming-work.itexi b/Documentation/contributor/programming-work.itexi
index 4b0b43dad4..8dc8d2755b 100644
--- a/Documentation/contributor/programming-work.itexi
+++ b/Documentation/contributor/programming-work.itexi
@@ -367,40 +367,35 @@ If you like using font-lock, you can also add this to your
@end example
-@subheading Indenting with vim
-
-Although emacs indentation is the GNU standard, acceptable
-indentation can usually be accomplished with vim. Some hints for
-vim are as follows:
-
-A workable .vimrc:
-
-@example
-set cindent
-set smartindent
-set autoindent
-set expandtab
-set softtabstop=2
-set shiftwidth=2
-filetype plugin indent on
-set incsearch
-set ignorecase smartcase
-set hlsearch
-set confirm
-set statusline=%F%m%r%h%w\ %@{&ff@}\ %Y\ [ASCII=\%03.3b]\ [HEX=\%02.2B]\ %04l,%04v\ %p%%\ [LEN=%L]
-set laststatus=2
-set number
-" Remove trailing whitespace on write
+@subsubheading Indenting with vim
+
+Although emacs indentation is the GNU standard, correct
+indentation for C++ files can be achieved by using the settings
+recommended in the
+@url{https://gcc.gnu.org/wiki/FormattingCodeForGCC, GNU GCC Wiki}.
+Save the following in @file{~/.vim/after/ftplugin/cpp.vim}:
+
+@example
+setlocal cindent
+setlocal cinoptions=>4,n-2,@{2,^-2,:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1
+setlocal shiftwidth=2
+setlocal softtabstop=2
+setlocal textwidth=79
+setlocal fo-=ro fo+=cql
+" use spaces instead of tabs
+setlocal expandtab
+" remove trailing whitespace on write
autocmd BufWritePre * :%s/\s\+$//e
@end example
-With this @file{.vimrc}, files can be reindented automatically by
+With these settings, files can be reindented automatically by
highlighting the lines to be indented in visual mode (use V to
-enter visual mode) and pressing @code{=}.
+enter visual mode) and pressing @code{=}, or a single line
+correctly indented in normal mode by pressing @code{==}.
-A @file{scheme.vim} file will help improve the indentation. This
-one was suggested by Patrick McCarty. It should be saved in
-@file{~/.vim/after/syntax/scheme.vim}.
+A @file{scheme.vim} file will help improve the indentation of
+Scheme code. This one was suggested by Patrick McCarty. It
+should be saved in @file{~/.vim/after/syntax/scheme.vim}.
@example
" Additional Guile-specific 'forms'
@@ -417,24 +412,45 @@ syn keyword schemeSyntax define-safe-public define-music-function
syn keyword schemeSyntax def-grace-function
" All of the above should influence indenting too
-set lw+=define-public,define*-public
-set lw+=define*,lambda*,let-keywords*
-set lw+=defmacro,defmacro*,define-macro
-set lw+=defmacro-public,defmacro*-public
-set lw+=use-modules,define-module
-set lw+=define-method,define-class
-set lw+=define-markup-command,define-markup-list-command
-set lw+=define-safe-public,define-music-function
-set lw+=def-grace-function
+setlocal lw+=define-public,define*-public
+setlocal lw+=define*,lambda*,let-keywords*
+setlocal lw+=defmacro,defmacro*,define-macro
+setlocal lw+=defmacro-public,defmacro*-public
+setlocal lw+=use-modules,define-module
+setlocal lw+=define-method,define-class
+setlocal lw+=define-markup-command,define-markup-list-command
+setlocal lw+=define-safe-public,define-music-function
+setlocal lw+=def-grace-function
" These forms should not influence indenting
-set lw-=if
-set lw-=set!
+setlocal lw-=if
+setlocal lw-=set!
" Try to highlight all ly: procedures
syn match schemeFunc "ly:[^) ]\+"
@end example
+For documentation work on texinfo files, identify the file
+extensions used as texinfo files in your @file{.vim/filetype.vim}:
+
+@example
+if exists("did_load_filetypes")
+ finish
+endif
+augroup filetypedetect
+ au! BufRead,BufNewFile *.itely setfiletype texinfo
+ au! BufRead,BufNewFile *.itexi setfiletype texinfo
+ au! BufRead,BufNewFile *.tely setfiletype texinfo
+augroup END
+@end example
+
+and add these settings in @file{.vim/after/ftplugin/texinfo.vim}:
+
+@example
+setlocal expandtab
+setlocal shiftwidth=2
+setlocal textwidth=66
+@end example
@node Naming conventions
@subsection Naming Conventions
diff --git a/Documentation/notation/expressive.itely b/Documentation/notation/expressive.itely
index d3f0235246..3c249389c2 100644
--- a/Documentation/notation/expressive.itely
+++ b/Documentation/notation/expressive.itely
@@ -685,7 +685,8 @@ occurences of outer slurs actually indicate phrasing, and phrasing
slurs may overlap a regular slur, see @ref{Phrasing slurs}. When
multiple regular slurs are needed in a single @code{Voice},
matching slur starts and ends need to be labelled by preceding
-them with @code{\=} followed by an identifying number or string.
+them with @code{\=} followed by an identifying key (a symbol or
+non-negative integer).
@lilypond[verbatim,quote]
\fixed c' {
diff --git a/Documentation/notation/pitches.itely b/Documentation/notation/pitches.itely
index 0699239db5..bfed32447e 100644
--- a/Documentation/notation/pitches.itely
+++ b/Documentation/notation/pitches.itely
@@ -903,9 +903,14 @@ music = \relative { c'8. ees16( fis8. a16 b8.) gis16 f8. d16 }
@end lilypond
@knownissues
-Manual ties inside @code{\retrograde} will be broken and
-generate warnings. Some ties can be generated automatically
-by enabling @ref{Automatic note splitting}.
+@code{\retrograde} is a rather simple tool. Since many events are
+@q{mirrored} rather than exchanged, tweaks and directional
+modifiers for opening spanners need to be added at the matching
+closing spanners: @code{^(} needs to be ended by @code{^)}, every
+@code{\<} or @code{\cresc} needs to be ended by @code{\!} or
+@code{\endcr}, every @code{\>} or @code{\decr} needs to be ended
+by @code{\enddecr}. Property-changing commands/overrides with a
+lasting effect will likely cause surprises.
@seealso
Notation Reference:
diff --git a/input/regression/retrograde.ly b/input/regression/retrograde.ly
new file mode 100644
index 0000000000..dd7f7eba95
--- /dev/null
+++ b/input/regression/retrograde.ly
@@ -0,0 +1,30 @@
+\version "2.18.0"
+
+\header {
+ texidoc = "@code{\\retrograde} can deal with crescendo and
+ decrescendo as long as they are properly paired with
+ @code{\\endcr}/@code{\\!} and @code{\\enddecr}. Direction modifiers
+ on slurs like @code{^(} need to be repeated as @code{^)} at the end.
+ Ties and glissandi work mostly (in-chord ties are turned into
+ ordinary per-chord/note ties, however)."
+}
+
+\layout { ragged-right = ##t }
+
+motif =
+\relative {
+ \override TextSpanner.bound-details.left.text = "motif"
+ <c' e>2~\startTextSpan c16\< d^( e f~ f4:32^)\!\> |
+ <<
+ \context Voice = "voice" {
+ <g~ b>4 g8\glissando f\stopTextSpan\enddecr }
+ \\
+ { c2 }
+ >>
+}
+
+\new Voice = "voice" {
+ \motif
+ \override TextSpanner.bound-details.left.text = "retrograde motif"
+ \retrograde \motif \bar "|."
+}
diff --git a/lily/include/slur-engraver.hh b/lily/include/slur-engraver.hh
index 572e7ea023..1885625ed9 100644
--- a/lily/include/slur-engraver.hh
+++ b/lily/include/slur-engraver.hh
@@ -60,8 +60,8 @@ protected:
void stop_translation_timestep ();
void process_music ();
- bool can_create_slur (const string&, vsize, vsize *, Stream_event *);
- void create_slur (const string &spanner_id, Event_info evi, Grob *g_cause, Direction dir, bool left_broken);
+ bool can_create_slur (SCM, vsize, vsize *, Stream_event *);
+ void create_slur (SCM spanner_id, Event_info evi, Grob *g_cause, Direction dir, bool left_broken);
bool try_to_end (Event_info evi);
virtual void set_melisma (bool);
diff --git a/lily/parser.yy b/lily/parser.yy
index 159786b93c..83e95491c2 100644
--- a/lily/parser.yy
+++ b/lily/parser.yy
@@ -229,7 +229,7 @@ static Music *make_music_with_input (SCM name, Input where);
SCM check_scheme_arg (Lily_parser *parser, Input loc,
SCM arg, SCM args, SCM pred, SCM disp = SCM_UNDEFINED);
SCM make_music_from_simple (Lily_parser *parser, Input loc, SCM pitch);
-SCM loc_on_music (Lily_parser *parser, Input loc, SCM arg);
+SCM loc_on_copy (Lily_parser *parser, Input loc, SCM arg);
SCM make_chord_elements (Input loc, SCM pitch, SCM dur, SCM modification_list);
SCM make_chord_step (SCM step, Rational alter);
SCM make_simple_markup (SCM a);
@@ -500,8 +500,8 @@ lookup:
LOOKUP_IDENTIFIER
| LOOKUP_IDENTIFIER '.' symbol_list_rev
{
- $$ = loc_on_music (parser, @$,
- nested_property ($1, scm_reverse_x ($3, SCM_EOL)));
+ $$ = loc_on_copy (parser, @$,
+ nested_property ($1, scm_reverse_x ($3, SCM_EOL)));
}
;
@@ -1992,7 +1992,7 @@ function_arglist_backup:
$$ = scm_cons ($$, $3);
else
{
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (SCM_ARG, $4, @4);
}
}
@@ -2003,7 +2003,7 @@ function_arglist_backup:
{
$$ = scm_cons ($4, $3);
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (EVENT_IDENTIFIER, $4, @4);
}
}
@@ -2019,7 +2019,7 @@ function_arglist_backup:
} else if (scm_is_true (scm_call_1 ($2, $4)))
$$ = scm_cons ($4, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (PITCH_IDENTIFIER, $4, @4);
}
}
@@ -2035,7 +2035,7 @@ function_arglist_backup:
} else if (scm_is_true (scm_call_1 ($2, $4)))
$$ = scm_cons ($4, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (TONICNAME_PITCH, $4, @4);
}
}
@@ -2044,7 +2044,7 @@ function_arglist_backup:
if (scm_is_true (scm_call_1 ($2, $4)))
$$ = scm_cons ($4, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (SCM_IDENTIFIER, $4, @4);
}
}
@@ -2066,11 +2066,11 @@ function_arglist_backup:
($2, make_music_from_simple (parser, @4, d))))
MYREPARSE (@4, $2, DURATION_ARG, d);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (UNSIGNED, $4, @4);
}
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (UNSIGNED, $4, @4);
}
}
@@ -2082,7 +2082,7 @@ function_arglist_backup:
$$ = $3;
MYREPARSE (@4, $2, REAL, $4);
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (REAL, $4, @4);
}
}
@@ -2092,7 +2092,7 @@ function_arglist_backup:
{
$$ = scm_cons ($4, $3);
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (NUMBER_IDENTIFIER, $4, @4);
}
}
@@ -2109,7 +2109,7 @@ function_arglist_backup:
if (scm_is_true (scm_call_1 ($2, $$)))
$$ = scm_cons ($$, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (UNSIGNED, $5, @5);
parser->lexer_->push_extra_token (@4, '-');
}
@@ -2122,7 +2122,7 @@ function_arglist_backup:
MYREPARSE (@5, $2, REAL, n);
$$ = $3;
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (REAL, n, @5);
}
}
@@ -2132,7 +2132,7 @@ function_arglist_backup:
if (scm_is_true (scm_call_1 ($2, n))) {
$$ = scm_cons (n, $3);
} else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (NUMBER_IDENTIFIER, n, @5);
}
}
@@ -2146,7 +2146,7 @@ function_arglist_backup:
($2, make_music_from_simple (parser, @4, $4))))
MYREPARSE (@4, $2, DURATION_ARG, $4);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (DURATION_IDENTIFIER, $4, @4);
}
}
@@ -2161,7 +2161,7 @@ function_arglist_backup:
else
$$ = scm_cons (res, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (SCM_IDENTIFIER, $4, @4);
}
}
@@ -2176,7 +2176,7 @@ function_arglist_backup:
else
$$ = scm_cons (res, $3);
else {
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
MYBACKUP (STRING, $4, @4);
}
}
@@ -2215,7 +2215,7 @@ function_arglist:
function_arglist_nonbackup
| EXPECT_OPTIONAL EXPECT_SCM function_arglist_skip_nonbackup DEFAULT
{
- $$ = scm_cons (loc_on_music (parser, @4, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @4, $1), $3);
}
;
@@ -2223,7 +2223,7 @@ function_arglist_skip_nonbackup:
function_arglist_nonbackup
| EXPECT_OPTIONAL EXPECT_SCM function_arglist_skip_nonbackup
{
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
}
;
@@ -2483,7 +2483,7 @@ function_arglist_optional:
function_arglist_backup
| EXPECT_OPTIONAL EXPECT_SCM function_arglist_skip_backup DEFAULT
{
- $$ = scm_cons (loc_on_music (parser, @4, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @4, $1), $3);
}
| function_arglist_skip_backup BACKUP
;
@@ -2492,7 +2492,7 @@ function_arglist_skip_backup:
function_arglist_backup
| EXPECT_OPTIONAL EXPECT_SCM function_arglist_skip_backup
{
- $$ = scm_cons (loc_on_music (parser, @3, $1), $3);
+ $$ = scm_cons (loc_on_copy (parser, @3, $1), $3);
}
;
@@ -4146,7 +4146,7 @@ SCM check_scheme_arg (Lily_parser *parser, Input loc,
return args;
}
-SCM loc_on_music (Lily_parser *parser, Input loc, SCM arg)
+SCM loc_on_copy (Lily_parser *parser, Input loc, SCM arg)
{
if (Music *m = unsmob<Music> (arg))
{
@@ -4154,6 +4154,34 @@ SCM loc_on_music (Lily_parser *parser, Input loc, SCM arg)
m->set_spot (parser->lexer_->override_input (loc));
return m->unprotect ();
}
+ if (Book *b = unsmob<Book> (arg))
+ {
+ b = b->clone ();
+ b->origin ()->set_spot (parser->lexer_->override_input (loc));
+ return b->unprotect ();
+ }
+ if (Context_def *cd = unsmob<Context_def> (arg))
+ {
+ cd = cd->clone ();
+ cd->origin ()->set_spot (parser->lexer_->override_input (loc));
+ return cd->unprotect ();
+ }
+ if (Output_def *od = unsmob<Output_def> (arg))
+ {
+ od = od->clone ();
+ od->input_origin_ = parser->lexer_->override_input (loc);
+ return od->unprotect ();
+ }
+ if (Score *s = unsmob<Score> (arg))
+ {
+ s = s->clone ();
+ s->origin ()->set_spot (parser->lexer_->override_input (loc));
+ return s->unprotect ();
+ }
+ if (Context_mod *cm = unsmob<Context_mod> (arg))
+ {
+ return cm->smobbed_copy ();
+ }
return arg;
}
diff --git a/lily/slur-engraver.cc b/lily/slur-engraver.cc
index 1a06ca6422..80ceb5f431 100644
--- a/lily/slur-engraver.cc
+++ b/lily/slur-engraver.cc
@@ -189,14 +189,14 @@ Slur_engraver::finalize ()
}
void
-Slur_engraver::create_slur (const string &spanner_id, Event_info evi, Grob *g_cause, Direction dir, bool left_broken)
+Slur_engraver::create_slur (SCM spanner_id, Event_info evi, Grob *g_cause, Direction dir, bool left_broken)
{
Grob *ccc = left_broken
? unsmob<Grob> (get_property ("currentCommandColumn"))
: 0; // efficiency
SCM cause = evi.slur_ ? evi.slur_->self_scm () : g_cause->self_scm ();
Spanner *slur = make_spanner (grob_symbol (), cause);
- slur->set_property ("spanner-id", ly_string2scm (spanner_id));
+ slur->set_property ("spanner-id", spanner_id);
if (dir)
set_grob_direction (slur, dir);
if (left_broken)
@@ -209,7 +209,7 @@ Slur_engraver::create_slur (const string &spanner_id, Event_info evi, Grob *g_ca
{
set_grob_direction (slur, DOWN);
slur = make_spanner (grob_symbol (), cause);
- slur->set_property ("spanner-id", ly_string2scm (spanner_id));
+ slur->set_property ("spanner-id", spanner_id);
set_grob_direction (slur, UP);
if (left_broken)
slur->set_bound (LEFT, ccc);
@@ -221,7 +221,7 @@ Slur_engraver::create_slur (const string &spanner_id, Event_info evi, Grob *g_ca
}
bool
-Slur_engraver::can_create_slur (const string &id, vsize old_slurs, vsize *event_idx, Stream_event *ev)
+Slur_engraver::can_create_slur (SCM id, vsize old_slurs, vsize *event_idx, Stream_event *ev)
{
for (vsize j = slurs_.size (); j--;)
{
@@ -229,7 +229,7 @@ Slur_engraver::can_create_slur (const string &id, vsize old_slurs, vsize *event_
Direction updown = to_dir (ev->get_property ("direction"));
// Check if we already have a slur with the same spanner-id.
- if (id == robust_scm2string (slur->get_property ("spanner-id"), ""))
+ if (ly_is_equal (id, slur->get_property ("spanner-id")))
{
if (j < old_slurs)
{
@@ -280,13 +280,13 @@ Slur_engraver::can_create_slur (const string &id, vsize old_slurs, vsize *event_
bool
Slur_engraver::try_to_end (Event_info evi)
{
- string id = robust_scm2string (evi.slur_->get_property ("spanner-id"), "");
+ SCM id = evi.slur_->get_property ("spanner-id");
// Find the slurs that are ended with this event (by checking the spanner-id)
bool ended = false;
for (vsize j = slurs_.size (); j--;)
{
- if (id == robust_scm2string (slurs_[j]->get_property ("spanner-id"), ""))
+ if (ly_is_equal (id, slurs_[j]->get_property ("spanner-id")))
{
ended = true;
end_slurs_.push_back (slurs_[j]);
@@ -305,16 +305,14 @@ Slur_engraver::process_music ()
{
for (vsize i = 0; i < stop_events_.size (); i++)
{
- string id = robust_scm2string
- (stop_events_[i].slur_->get_property ("spanner-id"), "");
+ SCM id = stop_events_[i].slur_->get_property ("spanner-id");
bool ended = try_to_end (stop_events_[i]);
if (ended)
{
// Ignore redundant stop events for this id
for (vsize j = stop_events_.size (); --j > i;)
{
- if (id == robust_scm2string
- (stop_events_[j].slur_->get_property ("spanner-id"), ""))
+ if (ly_is_equal (id, stop_events_[j].slur_->get_property ("spanner-id")))
stop_events_.erase (stop_events_.begin () + j);
}
}
@@ -326,7 +324,7 @@ Slur_engraver::process_music ()
for (vsize i = start_events_.size (); i--;)
{
Stream_event *ev = start_events_[i].slur_;
- string id = robust_scm2string (ev->get_property ("spanner-id"), "");
+ SCM id = ev->get_property ("spanner-id");
Direction updown = to_dir (ev->get_property ("direction"));
if (can_create_slur (id, old_slurs, &i, ev))
diff --git a/ly/grace-init.ly b/ly/grace-init.ly
index 6fe32f49fb..a46111e4d9 100644
--- a/ly/grace-init.ly
+++ b/ly/grace-init.ly
@@ -1,7 +1,7 @@
\version "2.17.6"
-startGraceSlur = #(make-music 'SlurEvent 'span-direction START 'spanner-id "grace")
-stopGraceSlur = #(make-music 'SlurEvent 'span-direction STOP 'spanner-id "grace")
+startGraceSlur = #(make-music 'SlurEvent 'span-direction START 'spanner-id 'grace)
+stopGraceSlur = #(make-music 'SlurEvent 'span-direction STOP 'spanner-id 'grace)
startGraceMusic = {
diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly
index ee9fdb8c20..76c2ec64b8 100644
--- a/ly/music-functions-init.ly
+++ b/ly/music-functions-init.ly
@@ -1551,7 +1551,12 @@ retrograde =
#(define-music-function (music)
(ly:music?)
(_i "Return @var{music} in reverse order.")
- (retrograde-music music))
+ (retrograde-music
+ (expand-repeat-notes!
+ (expand-repeat-chords!
+ (cons 'rhythmic-event
+ (ly:parser-lookup '$chord-repeat-events))
+ music))))
revertTimeSignatureSettings =
#(define-music-function
diff --git a/ly/spanners-init.ly b/ly/spanners-init.ly
index 756a55579e..8d8a69444f 100644
--- a/ly/spanners-init.ly
+++ b/ly/spanners-init.ly
@@ -1,18 +1,15 @@
\version "2.19.29"
"\\=" =
-#(define-event-function (id event) (number-or-string? ly:event?)
+#(define-event-function (id event) (key? ly:event?)
(_i "This sets the @code{spanner-id} property of the following
-@var{event} to the given @var{id} (numbers will be converted to a
-string). This can be used to tell LilyPond how to connect overlapping
+@var{event} to the given @var{id} (non-negative integer or symbol).
+This can be used to tell LilyPond how to connect overlapping
or parallel slurs or phrasing slurs within a single @code{Voice}.
@lilypond[quote,verbatim]
\\fixed c' { c\\=1( d\\=2( e\\=1) f\\=2) }
@end lilypond\n")
- (set! (ly:music-property event 'spanner-id)
- (if (number? id)
- (number->string id)
- id))
+ (set! (ly:music-property event 'spanner-id) id)
event)
startGroup = #(make-span-event 'NoteGroupingEvent START)
diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm
index cb9103a6fc..7ca44b6f3d 100644
--- a/scm/define-grob-properties.scm
+++ b/scm/define-grob-properties.scm
@@ -992,7 +992,7 @@ override:
\\override MultiMeasureRest
#'spacing-pair = #'(staff-bar . staff-bar)
@end example")
- (spanner-id ,string? "An identifier to distinguish concurrent spanners.")
+ (spanner-id ,key? "An identifier to distinguish concurrent spanners.")
(springs-and-rods ,boolean? "Dummy variable for triggering
spacing routines.")
(stacking-dir ,ly:dir? "Stack objects in which direction?")
diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm
index f525edd69a..6ca25b586e 100644
--- a/scm/define-grobs.scm
+++ b/scm/define-grobs.scm
@@ -1787,7 +1787,6 @@
(height-limit . 2.0)
(minimum-length . 1.5)
(ratio . 0.333)
- (spanner-id . "")
(springs-and-rods . ,ly:spanner::set-spacing-rods)
(stencil . ,ly:slur::print)
(thickness . 1.1)
@@ -1957,7 +1956,6 @@
(line-thickness . 0.8)
(minimum-length . 1.5)
(ratio . 0.25)
- (spanner-id . "")
(springs-and-rods . ,ly:spanner::set-spacing-rods)
(stencil . ,ly:slur::print)
(thickness . 1.2)
diff --git a/scm/define-music-properties.scm b/scm/define-music-properties.scm
index 4df3f93340..f8c567d1fa 100644
--- a/scm/define-music-properties.scm
+++ b/scm/define-music-properties.scm
@@ -183,7 +183,7 @@ If zero, signals a beat containing varying durations.")
Options are @code{'text} and @code{'hairpin}.")
(span-text ,markup? "The displayed text for dynamic text
spanners (e.g., cresc.)")
- (spanner-id ,string? "Identifier to distinguish concurrent spanners.")
+ (spanner-id ,key? "Identifier to distinguish concurrent spanners.")
(start-callback ,procedure? "Function to compute the negative length
of starting grace notes. This property can only be defined as initializer
in @file{scm/@/define-music-types.scm}.")
diff --git a/scm/define-music-types.scm b/scm/define-music-types.scm
index e0190eda55..348bcd0e87 100644
--- a/scm/define-music-types.scm
+++ b/scm/define-music-types.scm
@@ -438,7 +438,6 @@ goes down).")
. ((description . "Start or end phrasing slur.
Syntax: @var{note}@code{\\(} and @var{note}@code{\\)}")
- (spanner-id . "")
(types . (post-event span-event event phrasing-slur-event))
))
@@ -570,7 +569,6 @@ Syntax: @code{\\skip} @var{duration}")
. ((description . "Start or end slur.
Syntax: @var{note}@code{(} and @var{note}@code{)}")
- (spanner-id . "")
(types . (post-event span-event event slur-event))
))
diff --git a/scm/modal-transforms.scm b/scm/modal-transforms.scm
index 98e9ac3021..f9e26ed360 100644
--- a/scm/modal-transforms.scm
+++ b/scm/modal-transforms.scm
@@ -185,32 +185,110 @@ Typically used to construct a scale for input to
(define-public (retrograde-music music)
"Returns @var{music} in retrograde (reversed) order."
- ;; Copied from LSR #105 and renamed.
;; Included here to allow this module to provide a complete set of
;; common formal operations on motives, i.e transposition,
;; inversion and retrograding.
- (let* ((elements (ly:music-property music 'elements))
- (arts (ly:music-property music 'articulations))
- (reversed (reverse elements))
- (element (ly:music-property music 'element))
- (span-dir (ly:music-property music 'span-direction)))
-
- (ly:music-set-property! music 'elements reversed)
-
- (for-each retrograde-music arts)
-
- (if (ly:music? element)
- (ly:music-set-property!
- music 'element
- (retrograde-music element)))
-
- (if (ly:dir? span-dir)
- (ly:music-set-property! music 'span-direction (- span-dir)))
-
- (for-each retrograde-music reversed)
-
- music))
+ (define (reverse-span! m)
+ ;; invert direction of two-sided spanners
+ (let ((spd (ly:music-property m 'span-direction)))
+ (if (ly:dir? spd)
+ (begin
+ (set! (ly:music-property m 'span-direction) (- spd))
+ (case (ly:music-property m 'name)
+ ((CrescendoEvent)
+ (make-music 'DecrescendoEvent m))
+ ((DecrescendoEvent)
+ (make-music 'CrescendoEvent m))
+ (else m)))
+ m)))
+
+ ;; carryover is a possible list of tie events, the loop returns any
+ ;; such trailing list from the given expression
+ (define (loop m carryover)
+ (define (filter-ties! m carryover field)
+ (let ((vals (ly:music-property m field)))
+ (if (pair? vals)
+ (call-with-values
+ (lambda ()
+ (partition! (music-type-predicate
+ '(tie-event glissando-event)) vals))
+ (lambda (ties no-ties)
+ (set! (ly:music-property m field)
+ (append! (map! reverse-span! no-ties) carryover))
+ ties))
+ (begin
+ (if (pair? carryover)
+ (set! (ly:music-property m field) carryover))
+ '()))))
+
+ ;; The reversal will let some prefatory material stay in front of
+ ;; the following element. Most prominently single
+ ;; overrides/reverts/sets/unsets and applyContext. This does not
+ ;; change the position of a clef (which will generally be useless
+ ;; after retrograding) but it does not jumble the clef change
+ ;; command internals. Also, stuff like \once\override stays at
+ ;; the affected element.
+
+ (define (prefatory? m)
+ (or ((music-type-predicate
+ '(apply-context apply-output-event layout-instruction-event)) m)
+ (and
+ (music-is-of-type? m 'music-wrapper-music)
+ (prefatory? (ly:music-property m 'element)))))
+
+ (define (musiclistreverse lst)
+ (let loop ((lst lst) (res '()) (zeros '()))
+ (cond ((null? lst) (reverse! zeros res))
+ ((prefatory? (car lst))
+ (loop (cdr lst) res (cons (car lst) zeros)))
+ (else
+ (loop (cdr lst) (reverse! zeros (cons (car lst) res)) '())))))
+
+ (cond ((music-is-of-type? m 'event-chord)
+ (let* ((chord-ties
+ (append!
+ (filter-ties! m carryover 'elements)
+ ;; articulations on an event-chord do not occur
+ ;; "naturally" but are supported when user-generated
+ ;; elsewhere, so we treat them properly
+ (filter-ties! m '() 'articulations)))
+ ;; in-chord ties are converted to per-chord ties.
+ ;; This is less than optimal but pretty much the
+ ;; best we can hope to achieve with this approach.
+ (element-ties
+ (append-map!
+ (lambda (m) (filter-ties! m '() 'articulations))
+ (ly:music-property m 'elements))))
+ (append! chord-ties element-ties)))
+
+ ((music-is-of-type? m 'rhythmic-event)
+ (filter-ties! m carryover 'articulations))
+
+ ;; The following is hardly correct but tieing inside of
+ ;; <<...>> is really beyond our pay grade.
+ ((music-is-of-type? m 'simultaneous-music)
+ (append-map! (lambda (m) (loop m (ly:music-deep-copy carryover)))
+ (ly:music-property m 'elements)))
+ (else
+ (let ((elt (ly:music-property m 'element))
+ (elts (ly:music-property m 'elements)))
+ (let ((res
+ (fold loop
+ (if (ly:music? elt) (loop elt carryover) carryover)
+ elts)))
+ (if (ly:music? elt)
+ (set! (ly:music-property m 'element)
+ (reverse-span! elt)))
+ (if (pair? elts)
+ (set! (ly:music-property m 'elements)
+ (map! reverse-span! (musiclistreverse elts))))
+ (append! res (filter-ties! m '() 'articulations)))))))
+ (let ((dangling (loop music '())))
+ (for-each
+ (lambda (t) (ly:music-warning t (_ "Dangling tie in \\retrograde")))
+ dangling))
+ music)
(define-public (pitch-invert around to music)
"If @var{music} is a single pitch, inverts it about @var{around}