]> git.donarmstrong.com Git - lilypond.git/blobdiff - ly/music-functions-init.ly
Let \autochange accept optional arguments for the turning-point and clefs
[lilypond.git] / ly / music-functions-init.ly
index 0dfb92a1c03bf3a867517ea4548b13e7fe9b614b..045262c0498c3cb1b1a42d84a970dda79f181fe0 100644 (file)
@@ -18,7 +18,7 @@
 %%%% You should have received a copy of the GNU General Public License
 %%%% along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
 
-\version "2.19.22"
+\version "2.19.25"
 
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -55,7 +55,7 @@ addQuote =
 #(define-void-function (name music) (string? ly:music?)
    (_i "Define @var{music} as a quotable music expression named
 @var{name}")
-   (add-quotable (*parser*) name music))
+   (add-quotable name music))
 
 %% keep these two together
 afterGraceFraction = #(cons 6 8)
@@ -63,7 +63,7 @@ afterGrace =
 #(define-music-function (main grace) (ly:music? ly:music?)
    (_i "Create @var{grace} note(s) after a @var{main} music expression.")
    (let ((main-length (ly:music-length main))
-         (fraction  (ly:parser-lookup (*parser*) 'afterGraceFraction)))
+         (fraction  (ly:parser-lookup 'afterGraceFraction)))
      (make-simultaneous-music
       (list
        main
@@ -104,11 +104,11 @@ form of a spanner event, @var{property} may also have the form
 @samp{Grob.property} for specifying a directed tweak.")
   (if (ly:music? item)
       (if (eq? (ly:music-property item 'span-direction) START)
-          #{ \tweak #property #(value-for-spanner-piece arg) #item #}
+          (tweak property (value-for-spanner-piece arg) item)
           (begin
             (ly:music-warning item (_ "not a spanner"))
             item))
-      (let* ((p (check-grob-path item (*parser*) (*location*)
+      (let* ((p (check-grob-path item (*location*)
                                  #:default 'Bottom
                                  #:min 2
                                  #:max 2))
@@ -119,10 +119,10 @@ form of a spanner event, @var{property} may also have the form
                  (member 'spanner-interface
                          (assoc-get 'interfaces
                                     (assoc-get 'meta description))))
-            #{
-              \override #item . #property =
-              #(value-for-spanner-piece arg)
-            #}
+            (propertyOverride (append item (if (symbol? property)
+                                       (list property)
+                                       property))
+                      (value-for-spanner-piece arg))
             (begin
               (ly:input-warning (*location*) (_ "not a spanner name, `~a'") name)
               (make-music 'Music))))))
@@ -152,11 +152,18 @@ applyMusic =
    (func music))
 
 applyOutput =
-#(define-music-function (ctx proc) (symbol? procedure?)
-   (_i "Apply function @code{proc} to every layout object in context @code{ctx}")
-   (make-music 'ApplyOutputEvent
-               'procedure proc
-               'context-type ctx))
+#(define-music-function (target proc) (symbol-list-or-symbol? procedure?)
+   (_i "Apply function @code{proc} to every layout object matched by
+@var{target} which takes the form @code{Context} or @code{Context.Grob}.")
+   (let ((p (check-grob-path target (*location*) #:max 2)))
+     (if p
+         (make-music 'ApplyOutputEvent
+                     'procedure proc
+                     'context-type (car p)
+                     (if (pair? (cdr p))
+                         (list (cons 'symbol (cadr p)))
+                         '()))
+         (make-music 'Music))))
 
 appoggiatura =
 #(def-grace-function startAppoggiaturaMusic stopAppoggiaturaMusic
@@ -175,11 +182,26 @@ assertBeamSlope =
    (make-grob-property-override 'Beam 'positions (check-slope-callbacks comp)))
 
 autochange =
-#(define-music-function (music) (ly:music?)
-   (_i "Make voices that switch between staves automatically")
-   (make-autochange-music (*parser*) music))
-
-
+#(define-music-function (pitch clef-1 clef-2 music)
+  ((ly:pitch? (ly:make-pitch 0 0)) (ly:context-mod?)(ly:context-mod?) ly:music?)
+  (_i "Make voices that switch between staves automatically.  As an option the
+pitch where to switch staves may be specified.  The clefs for the staves are
+optional as well.  Setting clefs  works only for implicitly instantiated
+staves.")
+  (let ;; keep the contexts alive for the full duration
+       ((skip (make-skip-music (make-duration-of-length
+                                     (ly:music-length music)))))
+    #{
+      <<
+        \context Staff = "up" $(or clef-1 #{ \with { \clef "treble" } #})
+          <<
+          #(make-autochange-music pitch music)
+          \new Voice { #skip }
+          >>
+        \context Staff = "down" $(or clef-2 #{ \with { \clef "bass" } #})
+          \new Voice { #skip }
+      >>
+    #}))
 
 balloonGrobText =
 #(define-music-function (grob-name offset text)
@@ -238,13 +260,13 @@ bendAfter =
 bookOutputName =
 #(define-void-function (newfilename) (string?)
    (_i "Direct output for the current book block to @var{newfilename}.")
-   (set! (paper-variable (*parser*) #f 'output-filename) newfilename))
+   (set! (paper-variable #f 'output-filename) newfilename))
 
 bookOutputSuffix =
 #(define-void-function (newsuffix) (string?)
    (_i "Set the output filename suffix for the current book block to
 @var{newsuffix}.")
-   (set! (paper-variable (*parser*) #f 'output-suffix) newsuffix))
+   (set! (paper-variable #f 'output-suffix) newsuffix))
 
 %% \breathe is defined as a music function rather than an event identifier to
 %% ensure it gets useful input location information: as an event identifier,
@@ -352,7 +374,7 @@ displayLilyMusic =
 to @var{port}, defaulting to the console.")
    (let ((port (or port (current-output-port))))
      (newline port)
-     (display-lily-music music (*parser*) port))
+     (display-lily-music music port))
    music)
 
 displayMusic =
@@ -399,7 +421,7 @@ eventChords =
    (_i "Compatibility function wrapping @code{EventChord} around
 isolated rhythmic events occuring since version 2.15.28, after
 expanding repeat chords @samp{q}.")
-   (event-chord-wrap! music (*parser*)))
+   (event-chord-wrap! music))
 
 featherDurations=
 #(define-music-function (factor argument) (ly:moment? ly:music?)
@@ -472,7 +494,7 @@ to the preceding note or rest as a post-event with @code{-}.")
                'automatically-numbered (not mark)
                'text (or mark (make-null-markup))
                'footnote-text footnote)))
-     #{ \once \tweak footnote-music #mus #item #}))
+     (once (tweak 'footnote-music mus item))))
 
 grace =
 #(def-grace-function startGraceMusic stopGraceMusic
@@ -531,7 +553,7 @@ If @var{item} is a symbol list of form @code{GrobName} or
 @code{Context.GrobName}, the result is an override for the grob name
 specified by it.  If @var{item} is a music expression, the result is
 the same music expression with an appropriate tweak applied to it.")
-   #{ \tweak transparent ##t #item #})
+   (tweak 'transparent #t item))
 
 inStaffSegno =
 #(define-music-function () ()
@@ -590,7 +612,7 @@ key =
 If both are null, just generate @code{KeyChangeEvent}.")
    (cond ((null? tonic) (make-music 'KeyChangeEvent))
          ((null? pitch-alist)
-          (ly:parser-error (*parser*) (_ "second argument must be pitch list")
+          (ly:parser-error (_ "second argument must be pitch list")
                            (*location*))
           (make-music 'SequentialMusic 'void #t))
          (else
@@ -626,13 +648,13 @@ label =
 language =
 #(define-void-function (language) (string?)
    (_i "Set note names for language @var{language}.")
-   (note-names-language (*parser*) language))
+   (note-names-language language))
 
 languageSaveAndChange =
 #(define-void-function (language) (string?)
   (_i "Store the previous pitchnames alist, and set a new one.")
   (set! previous-pitchnames pitchnames)
-  (note-names-language (*parser*) language))
+  (note-names-language language))
 
 languageRestore =
 #(define-void-function () ()
@@ -640,7 +662,7 @@ languageRestore =
    (if previous-pitchnames
        (begin
         (set! pitchnames previous-pitchnames)
-        (ly:parser-set-note-names (*parser*) pitchnames))
+        (ly:parser-set-note-names pitchnames))
       (ly:input-warning (*location*) (_ "No other language was defined previously. Ignoring."))))
 
 
@@ -808,7 +830,7 @@ transpose from @var{around} to @var{to}.")
 
 mark =
 #(define-music-function
-   (label) ((scheme? '()))
+   (label) ((number-or-markup?))
   "Make the music for the \\mark command."
   (let* ((set (and (integer? label)
                    (context-spec-music (make-property-set 'rehearsalMark label)
@@ -819,7 +841,7 @@ mark =
     (if set
         (make-sequential-music (list set ev))
         (begin
-          (set! (ly:music-property ev 'label) label)
+          (if label (set! (ly:music-property ev 'label) label))
           ev))))
 
 musicMap =
@@ -870,33 +892,27 @@ appropriate tweak applied.")
       (let ((prop-path (check-grob-path
                          (if (symbol? property)
                              (list property)
-                             property)
-                         (*parser*) (*location*)
+                             property) (*location*)
                          #:start 1 #:default #t #:min 2 #:max 2)))
         (if prop-path
             ; If the head of the grob property path is a symbol--i.e.,
             ; a grob name, produce a directed tweak.  Otherwise, create
             ; an ordinary tweak.
             (if (symbol? (car prop-path))
-                #{
-                  \tweak #prop-path #(offsetter (second prop-path) offsets) #item
-                #}
-                #{
-                  \tweak #(second prop-path) #(offsetter (second prop-path) offsets) #item
-                #})
+                (tweak prop-path (offsetter (second prop-path) offsets) item)
+                (tweak (second prop-path)
+                       (offsetter (second prop-path) offsets)
+                       item))
             item))
       ; In case of an override, grob property path is Context.Grob.property.
       (let ((prop-path (check-grob-path
                          (append item
                                  (if (symbol? property)
                                      (list property)
-                                     property))
-                         (*parser*) (*location*)
+                                     property)) (*location*)
                          #:default 'Bottom #:min 3 #:max 3)))
         (if prop-path
-            #{
-              \override #prop-path = #(offsetter (third prop-path) offsets)
-            #}
+            (propertyOverride prop-path (offsetter (third prop-path) offsets))
             (make-music 'Music)))))
 
 omit =
@@ -908,7 +924,7 @@ If @var{item} is a symbol list of form @code{GrobName} or
 @code{Context.GrobName}, the result is an override for the grob name
 specified by it.  If @var{item} is a music expression, the result is
 the same music expression with an appropriate tweak applied to it.")
-   #{ \tweak stencil ##f #item #})
+   (tweak 'stencil #f item))
 
 once =
 #(define-music-function (music) (ly:music?)
@@ -958,8 +974,14 @@ overrideProperty =
    (_i "Set the grob property specified by @var{grob-property-path} to
 @var{value}.  @var{grob-property-path} is a symbol list of the form
 @code{Context.GrobName.property} or @code{GrobName.property}, possibly
-with subproperties given as well.")
-   (let ((p (check-grob-path grob-property-path (*parser*) (*location*)
+with subproperties given as well.
+
+As opposed to @code{\\override} which overrides the context-dependent
+defaults with which a grob is created, this command uses
+@code{Output_property_engraver} at the grob acknowledge stage.  This
+may be necessary for overriding values set after the initial grob
+creation.")
+   (let ((p (check-grob-path grob-property-path (*location*)
                              #:default 'Bottom
                              #:min 3)))
      (if p
@@ -1030,6 +1052,9 @@ Example:
   B = { d d | e e | }
   C = { e e | f f | }
 @end verbatim
+
+The last bar checks in a sequence are not copied to the result in
+order to facilitate ending the last entry at non-bar boundaries.
 ")
    (define voice-count (length voice-ids))
    (define (bar-check? m)
@@ -1050,7 +1075,7 @@ Example:
              "Store the previously built sequence into the current voice and
 change to the following voice."
              (set-car! current-voices
-                       (cons (reverse! current-sequence)
+                       (cons current-sequence
                              (car current-voices)))
              (set! current-sequence '())
              (set! current-voices (cdr current-voices)))
@@ -1067,9 +1092,21 @@ change to the following voice."
                                (if (bar-check? m) (change-voice))))))
                      lst)
            (if (pair? current-sequence) (change-voice))
-           ;; un-circularize `voices' and reorder the voices
-           (set! voices (map reverse!
-                             (list-head voices voice-count)))
+           ;; Un-circularize voices
+           (set! voices (list-head voices voice-count))
+
+           ;; Remove trailing bar checks to facilitate ending a
+           ;; sequence on a non-bar, reverse partial sequences and sequences
+           (set! voices (map!
+                         (lambda (l)
+                           (map! reverse!
+                                 (reverse!
+                                  (if (and (pair? l) (pair? (car l))
+                                           (bar-check? (caar l)))
+                                      (cons (cdar l) (cdr l))
+                                      l))))
+                         voices))
+
            ;; check sequence length
            (apply for-each (lambda seqs
                              (define (seq-len seq)
@@ -1105,18 +1142,17 @@ change to the following voice."
        (and (or split-elt split-elts)
             (map
              (lambda (e es)
-               (apply music-clone music
-                      (append
-                       ;; reassigning the origin of the parent only
-                       ;; makes sense if the first expression in the
-                       ;; result is from a distributed origin
-                       (let ((origin
-                              (if (ly:music? elt)
-                                  (and (ly:music? e) (ly:music-property e 'origin #f))
-                                  (and (pair? es) (ly:music-property (car es) 'origin #f)))))
-                         (if origin (list 'origin origin) '()))
-                       (if (ly:music? e) (list 'element e) '())
-                       (if (pair? es) (list 'elements es) '()))))
+               (let ((m (ly:music-deep-copy music
+                       ;;; reassigning the origin of the parent only
+                       ;;; makes sense if the first expression in the
+                       ;;; result is from a distributed origin
+                                            (or (and (ly:music? e) e)
+                                                (and (pair? es) (car es))))))
+                 (if (ly:music? e)
+                     (set! (ly:music-property m 'element) e))
+                 (if (pair? es)
+                     (set! (ly:music-property m 'elements) es))
+                 m))
              (or split-elt (circular-list #f))
              (or split-elts (circular-list #f))))))
    (let ((voices (recurse-and-split music)))
@@ -1124,7 +1160,7 @@ change to the following voice."
          ;;
          ;; bind voice identifiers to the voices
          (for-each (lambda (voice-id voice)
-                     (ly:parser-define! (*parser*) voice-id voice))
+                     (ly:parser-define! voice-id voice))
                    voice-ids voices)
          (ly:music-warning music
                            (_ "ignoring parallel music without barchecks")))))
@@ -1146,19 +1182,47 @@ parenthesize =
        (set! (ly:music-property arg 'parenthesize) #t))
    arg)
 
-#(define (make-directed-part-combine-music
-          parser direction chord-range part1 part2
+#(define (make-directed-part-combine-music direction chord-range part1 part2
           one-context-settings
           two-context-settings
           shared-context-settings)
 
-   (let* ((pc-music (make-part-combine-music
-                     parser (list part1 part2) direction chord-range))
+   (let* ((pc-music (make-music 'PartCombineMusic))
+          (m1 (context-spec-music (make-non-relative-music part1) 'Voice "one"))
+          (m2 (context-spec-music (make-non-relative-music part2) 'Voice "two"))
+          (listener (ly:parser-lookup 'partCombineListener))
+          (evs2 (recording-group-emulate m2 listener))
+          (evs1 (recording-group-emulate m1 listener))
+          (split-list
+           (if (and (assoc "one" evs1) (assoc "two" evs2))
+               (determine-split-list (reverse! (assoc-get "one" evs1) '())
+                                     (reverse! (assoc-get "two" evs2) '())
+                                     chord-range)
+               '()))
           (L1 (ly:music-length part1))
           (L2 (ly:music-length part2))
           ;; keep the contexts alive for the full duration
           (skip (make-skip-music (make-duration-of-length
                                   (if (ly:moment<? L1 L2) L2 L1)))))
+
+     (set! (ly:music-property pc-music 'elements)
+           (list (make-music
+                  'PartCombinePartMusic
+                  'element m1
+                  'context-change-list
+                  (make-part-combine-context-changes
+                   default-part-combine-context-change-state-machine-one
+                   split-list))
+                 (make-music
+                  'PartCombinePartMusic
+                  'element m2
+                  'context-change-list
+                  (make-part-combine-context-changes
+                   default-part-combine-context-change-state-machine-two
+                   split-list))))
+
+     (set! (ly:music-property pc-music 'direction) direction)
+
      #{ \context Staff <<
           \context Voice = "one" \with #one-context-settings { #skip }
           \context Voice = "two" \with #two-context-settings { #skip }
@@ -1167,8 +1231,7 @@ parenthesize =
           \context NullVoice = "null" { #skip }
           #pc-music
           #(make-part-combine-marks
-            default-part-combine-mark-state-machine
-            (ly:music-property pc-music 'split-list))
+            default-part-combine-mark-state-machine split-list)
         >> #} ))
 
 partcombine =
@@ -1179,7 +1242,7 @@ a music expression containing simultaneous voices, where @var{part1}
 and @var{part2} are combined into one voice where appropriate.
 Optional @var{chord-range} sets the distance in steps between notes
 that may be combined into a chord or unison.")
-   (make-directed-part-combine-music (*parser*) #f chord-range part1 part2
+   (make-directed-part-combine-music #f chord-range part1 part2
     #{ \with { \voiceOne \override DynamicLineSpanner.direction = #UP } #}
     #{ \with { \voiceTwo \override DynamicLineSpanner.direction = #DOWN } #}
     #{ #} ))
@@ -1189,7 +1252,7 @@ partcombineUp =
    ((number-pair? '(0 . 8)) ly:music? ly:music?)
    (_i "Take the music in @var{part1} and @var{part2} and typeset so
 that they share a staff with stems directed upward.")
-   (make-directed-part-combine-music (*parser*) UP chord-range part1 part2
+   (make-directed-part-combine-music UP chord-range part1 part2
     #{ \with { \voiceOne \override DynamicLineSpanner.direction = #UP } #}
     #{ \with { \voiceThree \override DynamicLineSpanner.direction = #UP } #}
     #{ \with { \voiceOne \override DynamicLineSpanner.direction = #UP } #} ))
@@ -1199,7 +1262,7 @@ partcombineDown =
    ((number-pair? '(0 . 8)) ly:music? ly:music?)
    (_i "Take the music in @var{part1} and @var{part2} and typeset so
 that they share a staff with stems directed downward.")
-   (make-directed-part-combine-music (*parser*) DOWN chord-range part1 part2
+   (make-directed-part-combine-music DOWN chord-range part1 part2
     #{ \with { \voiceFour \override DynamicLineSpanner.direction = #DOWN } #}
     #{ \with { \voiceTwo \override DynamicLineSpanner.direction = #DOWN } #}
     #{ \with { \voiceTwo \override DynamicLineSpanner.direction = #DOWN } #} ))
@@ -1265,6 +1328,81 @@ print @var{secondary-note} as a stemless note head in parentheses.")
                            trill-events)))))
      main-note))
 
+propertyOverride =
+#(define-music-function (grob-property-path value)
+   (symbol-list? scheme?)
+   (_i "Set the grob property specified by @var{grob-property-path} to
+@var{value}.  @var{grob-property-path} is a symbol list of the form
+@code{Context.GrobName.property} or @code{GrobName.property}, possibly
+with subproperties given as well.  This music function is mostly intended
+for use from Scheme as a substitute for the built-in @code{\\override}
+command.")
+   (let ((p (check-grob-path grob-property-path (*location*)
+                             #:default 'Bottom
+                             #:min 3)))
+     (if p
+         (context-spec-music
+          (make-music 'OverrideProperty
+                      'symbol (cadr p)
+                      'origin (*location*)
+                      'grob-value value
+                      'grob-property-path (cddr p)
+                      'pop-first #t)
+          (car p))
+         (make-music 'Music))))
+
+propertyRevert =
+#(define-music-function (grob-property-path)
+   (symbol-list?)
+   (_i "Revert the grob property specified by @var{grob-property-path} to
+its previous value.  @var{grob-property-path} is a symbol list of the form
+@code{Context.GrobName.property} or @code{GrobName.property}, possibly
+with subproperties given as well.  This music function is mostly intended
+for use from Scheme as a substitute for the built-in @code{\\revert}
+command.")
+   (let ((p (check-grob-path grob-property-path (*location*)
+                             #:default 'Bottom
+                             #:min 3)))
+     (if p
+         (context-spec-music
+          (make-music 'RevertProperty
+                      'symbol (cadr p)
+                      'origin (*location*)
+                      'grob-property-path (cddr p))
+          (car p))
+         (make-music 'Music))))
+
+propertySet =
+#(define-music-function (property-path value)
+   (symbol-list-or-symbol? scheme?)
+   (_i "Set the context property specified by @var{property-path} to
+@var{value}.  This music function is mostly intended for use from
+Scheme as a substitute for the built-in @code{\\set} command.")
+   (let ((p (check-context-path property-path (*location*))))
+     (if p
+         (context-spec-music
+          (make-music 'PropertySet
+                      'symbol (cadr p)
+                      'value value
+                      'origin (*location*))
+          (car p))
+         (make-music 'Music))))
+
+propertyUnset =
+#(define-music-function (property-path)
+   (symbol-list-or-symbol?)
+   (_i "Unset the context property specified by @var{property-path}.
+This music function is mostly intended for use from Scheme as a
+substitute for the built-in @code{\\unset} command.")
+   (let ((p (check-context-path property-path (*location*))))
+     (if p
+         (context-spec-music
+          (make-music 'PropertyUnset
+                      'symbol (cadr p)
+                      'origin (*location*))
+          (car p))
+         (make-music 'Music))))
+
 pushToTag =
 #(define-music-function (tag more music)
    (symbol? ly:music? ly:music?)
@@ -1287,6 +1425,7 @@ usually contains spacers or multi-measure rests.")
                'element main-music
                'quoted-music-name what))
 
+
 relative =
 #(define-music-function (pitch music)
    ((ly:pitch?) ly:music?)
@@ -1449,7 +1588,7 @@ appropriate tweak applied.")
        (if (>= total-found 2)
            (helper siblings offsets)
            (offset-control-points (car offsets)))))
-   #{ \once \tweak control-points #shape-curve #item #})
+   (once (tweak 'control-points shape-curve item)))
 
 shiftDurations =
 #(define-music-function (dur dots arg)
@@ -1499,11 +1638,10 @@ spacingTweaks =
 #(define-music-function (parameters) (list?)
    (_i "Set the system stretch, by reading the 'system-stretch property of
 the `parameters' assoc list.")
-   #{
-     \overrideProperty Score.NonMusicalPaperColumn.line-break-system-details
-     #(list (cons 'alignment-extra-space (cdr (assoc 'system-stretch parameters)))
-             (cons 'system-Y-extent (cdr (assoc 'system-Y-extent parameters))))
-   #})
+   (overrideProperty
+    '(Score NonMusicalPaperColumn line-break-system-details)
+    (list (cons 'alignment-extra-space (cdr (assoc 'system-stretch parameters)))
+          (cons 'system-Y-extent (cdr (assoc 'system-Y-extent parameters))))))
 
 styledNoteHeads =
 #(define-music-function (style heads music)
@@ -1529,7 +1667,7 @@ tagGroup =
    (_i "Define a tag group comprising the symbols in the symbol list
 @var{tags}.  Tag groups must not overlap.")
    (let ((err (define-tag-group tags)))
-     (if err (ly:parser-error (*parser*) err (*location*)))))
+     (if err (ly:parser-error err (*location*)))))
 
 temporary =
 #(define-music-function (music)
@@ -1693,29 +1831,59 @@ convenient.
 @var{prop} can contain additional elements in which case a nested
 property (inside of an alist) is tweaked.")
    (if (ly:music? item)
-       (let ((p (check-grob-path prop (*parser*) (*location*)
+       (let ((p (check-grob-path prop (*location*)
                                  #:start 1
                                  #:default #t
                                  #:min 2)))
-         (if p
-             (set! (ly:music-property item 'tweaks)
-                   (acons (cond ((pair? (cddr p)) p)
-                                ((symbol? (car p))
-                                 (cons (car p) (cadr p)))
-                                (else (cadr p)))
-                          value
-                          (ly:music-property item 'tweaks))))
+         (cond ((not p))
+               ;; p now contains at least two elements.  The first
+               ;; element is #t when no grob has been explicitly
+               ;; specified, otherwise it is a grob name.
+               ((music-is-of-type? item 'context-specification)
+                ;; This is essentially dealing with the case
+                ;; \tweak color #red \tweak font-size #3 NoteHead
+                ;; namely when stacked tweaks end in a symbol list
+                ;; rather than a music expression.
+                ;;
+                ;; We have a tweak here to convert into an override,
+                ;; so we need to know the grob to apply it to.  That's
+                ;; easy if we have a directed tweak, and otherwise we
+                ;; need to find the symbol in the expression itself.
+                (let* ((elt (ly:music-property item 'element))
+                       (seq (if (music-is-of-type? elt 'sequential-music)
+                                elt
+                                (make-sequential-music (list elt))))
+                       (elts (ly:music-property seq 'elements))
+                       (symbol (if (symbol? (car p))
+                                   (car p)
+                                   (and (pair? elts)
+                                        (ly:music-property (car elts)
+                                                           'symbol)))))
+                  (if (symbol? symbol)
+                      (begin
+                        (set! (ly:music-property seq 'elements)
+                              (cons (make-music 'OverrideProperty
+                                                'symbol symbol
+                                                'grob-property-path (cdr p)
+                                                'pop-first #t
+                                                'grob-value value
+                                                'origin (*location*))
+                                    elts))
+                        (set! (ly:music-property item 'element) seq))
+                      (begin
+                        (ly:parser-error (_ "Cannot \\tweak")
+                                         (*location*))
+                        (ly:music-message item (_ "untweakable"))))))
+               (else
+                (set! (ly:music-property item 'tweaks)
+                      (acons (cond ((pair? (cddr p)) p)
+                                   ((symbol? (car p))
+                                    (cons (car p) (cadr p)))
+                                   (else (cadr p)))
+                             value
+                             (ly:music-property item 'tweaks)))))
          item)
-       ;; We could just throw this at \override and let it sort this
-       ;; out on its own, but this way we should get better error
-       ;; diagnostics.
-       (let ((p (check-grob-path
-                 (append item (if (symbol? prop) (list prop) prop))
-                 (*parser*) (*location*)
-                 #:default 'Bottom #:min 3)))
-         (if p
-             #{ \override #p = #value #}
-             (make-music 'Music)))))
+       (propertyOverride (append item (if (symbol? prop) (list prop) prop)) value)))
 
 undo =
 #(define-music-function (music)
@@ -1729,10 +1897,10 @@ unsets already in @var{music} cause a warning.  Non-property-related music is ig
      (let
          ((lst
            (fold-some-music
-            (lambda (m) (or (music-is-of-type? m 'layout-instruction-event)
-                            (music-is-of-type? m 'context-specification)
-                            (music-is-of-type? m 'apply-context)
-                            (music-is-of-type? m 'time-signature-music)))
+            (music-type-predicate '(layout-instruction-event
+                                    context-specification
+                                    apply-context
+                                    time-signature-music))
             (lambda (m overrides)
               (case (ly:music-property m 'name)
                 ((OverrideProperty)