lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Rootless slash chords, 2017 edition


From: Thomas Morley
Subject: Re: Rootless slash chords, 2017 edition
Date: Tue, 28 Mar 2017 19:49:08 +0200

2017-03-28 0:39 GMT+02:00 Dmitry <address@hidden>:
> Hi,
>
> There's been a long standing feature request:
>
> https://sourceforge.net/p/testlilyissues/issues/3909/
>
> #3909 a feature to disable the chord root name printing - just print
> the slash and inversion
>
> Over the years, it has been addressed more or less. Time to give it
> another try?
>
> In a few words, we need a mechanism to suppress printing repeated root
> chord name over a progression of slash chords. That means, we'd like to
> see this:
>
> F#min9 /F /E /D#
>
> instead of this:
>
> F#min9 F#min9/F F#min9/E F#min9/D#
>
> The latter is much less readable, occupies more space and bloats the
> score.
>
> To say this feature is demanded in jazz means say nothing! I've
> recently started working on a bunch of jazz scores, and the very first
> piece features three (!) different progressions with changing bass
> notes, like the above. OK, there is a well-known hack that uses
> \whiteout: http://lsr.di.unimi.it/LSR/Item?id=776
>
> Unfortunately, for the real-life scores it is simply a no-go. It
> requires a chord exception for each and every rootless slash chord, it
> messes up MIDI output (as slash chords are not recognized in chord
> exceptions), not to mention that it produces unclean output.
>
> There is another workaround from 2011: http://www.mail-archive.com/lily
> pond-user%40gnu.org/msg67087.html
> Needless to say, it doesn't work in 2017 :) but I've managed to fix it.
> Here we go,
>
> #(define (rootless-chord-names in-pitches bass inversion context)
>    (ignatzek-chord-names `(,(ly:make-pitch 0 0 0) ,(ly:make-pitch 0 0
> 0)) bass inversion context))
>
> #(define (empty-namer pitch lower?) (make-simple-markup ""))
>
> retainChordNoteNamer =
> \applyContext
>   #(lambda (context)
>      (let ((rn (ly:context-property context 'chordRootNamer)))
>        (ly:context-set-property! context 'chordNoteNamer rn)))
>
> rootless = {
>   \retainChordNoteNamer
>   \once \set chordNameFunction = #rootless-chord-names
>   \once \set chordRootNamer = #empty-namer
> }
>
> After that, one can use \rootless as follows:
>
> fis2:m7.9 \rootless fis4:m7.9/f \rootless fis4:m7.9/e \rootless
> fis1:m7.9/dis
>
> This produces perfect output, but the code is ugly as hell. What I'm
> doing here is basically the following:
> - kill off chordRootNamer, but retain chordNoteNamer;
> - after that, the root note note wouldn't be printed, but the suffix
> (like m9, etc.) would be. To suppress it, we supply a
> proxy chordNameFunction that would guarantee a suffix-less chord.
>
> It should be easy to convert this one-shot semantics to something like
> \rootlessOn and \rootlessOff. In fact, it would be nice if mainstream
> Lilypond had something similar to "\set chordChanges = ..." but for
> slash chords; but of course nobody would like to see ugly hacks like
> the above in the mainstream :)
>
> Off the top of my head, I could propose the following solution:
>
> - inside Scheme code for chord name functions ({ignatzek,banter,jazz}-
> chord-names), allow for NIL "pitches" argument. That should mean
> "produce output for bass note only, omitting everything before the
> slash";
> - in Chord_name_engraver::process_music, track repeating root parts of
> slash chords and pass NIL pitches to a chordNameFunction in case of
> repetition. Everything should be similar to handling chordChanges,
> however this time we should remember and compare whole chord
> structures, not markup.
>
> What do you think?
>
> Cheers,
> Dmitry



Hi Dmitry,

below my approch.
I'm afraid you'll not have problems to break it...
Comments inline.

\version "2.19.57"

%% Tries to compare the relevant parts of the markups for current and previous
%% chord.
%%
%% Probably it would be far easier to compare actual pitches in the
%% chordNameFunction as delivered there, but 'lastChord' returns only a markup
%% not pitches.
#(define (test-engraver ctx)
  (let ((prev '()))
    (make-engraver
      (acknowledgers
        ((chord-name-interface engraver grob source-engraver)
           ;; As of the 'ignatzek-chord-names'-procedure:
           ;; 'current' will always be a list, as well as '(last current)'
           ;; So it seems safe to apply list-modifying procedures to them.
           ;; Ofcourse this may change, when 'ignatzek-chord-names' is changed
           ;; or a different chordNameFunction is used.
           (let* ((current (ly:grob-property grob 'text))
                  (current-string-lists
                    (split-list-by-separator
                      (filter
                        (lambda (arg)
                          (and (string? arg) (not (string-null? arg))))
                        (flatten-list current))
                      (lambda (x) (string=? x "/"))))
                  (prev-string-lists
                    (split-list-by-separator
                      (filter
                        (lambda (arg)
                          (and (string? arg) (not (string-null? arg))))
                        (if (pair? prev)
                            (flatten-list (car prev))
                            '()))
                      (lambda (x) (string=? x "/"))))
                  (current-bass (take-right (last current) 1)))

             ;; print 'current-bass' if the root of current and previous chord
             ;; are the same, the bass is different and present at all.
             ;; Otherwise do nothing.
             (if (and (pair? prev)
                      (equal? (car current-string-lists) (car
prev-string-lists))
                      (not (equal? (last current-string-lists) (last
prev-string-lists)))
                      (pair? (cdr current-string-lists)))
                 (ly:grob-set-property! grob 'text
                   (make-line-markup
                     ;; reinject "/", which is not caught
                     ;; by 'current-bass'
                     (cons "/" current-bass))))
             (set! prev (cons current prev)))))
      ((finalize translator)
       ;; clear 'prev'
       (set! prev '())))))

\layout {
  \context {
    \ChordNames
    \consists \test-engraver
  }
}


\new ChordNames
\chordmode {
    c:m/e c:m/+g c:m/fes c/fes c/d c:7/d c:7/+g d e e:m f:m f:m f
}
\new ChordNames
\chordmode {
    c c
    c:m7
    c:m7/bes
    c:m7 c:m7.9/cis
    c:m7/cis
}


HTH a bit,
  Harm



reply via email to

[Prev in Thread] Current Thread [Next in Thread]