Roman numerals for analysis
This function is designed to simplify the creation of Roman numerals for harmonic analysis.
Syntax: \markup \rN { ...list of symbols... }
Enter a Roman numeral as a list of symbols, with each element separated by spaces. List symbols in order of appearance, omitting those not needed: Roman numeral; letter for a quality requiring a special indicator (i.e., diminished, half-diminished, etc.); top (or only) number of inversion symbol; bottom number; "/" (if secondary function); Roman numeral.
Preceding either Roman numeral with "s" or "b" or "n" will attach a sharp, flat, or natural: for example, "svi" or "bVII" (quotation marks not needed). Note names are possible: Cs, Dn, Eb.
Use the following symbols for qualities (if a superscript indication is needed): "o" for diminished, "h" for half-diminished, "+" for augmented, "b" for flat. You may use any combination of "M" and "m" here: M, mm, MM7, Mm, Mmm9, etc. Added notes are also possible: add6, add9, etc.
The analysis can be created in a Lyrics context.
\version "2.24.0"
%% http://lsr.di.unimi.it/LSR/Item?id=710
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A function to create Roman numerals for harmonic analysis.
%%
%% Syntax: \markup \rN { ...list of symbols... }
%%
%% List symbols in this order (as needed): Roman numeral, quality, top number of
%% inversion symbol, bottom number, "/" (if secondary function), Roman numeral.
%%
%% "bVII" creates flat VII; "svi" creates sharp vi; "Ab" creates A-flat; "As" A-sharp
%%
%% Qualities: use "o" for diminished, "h" for half-diminished,
%% "+" for augmented, "b" for flat. Use any combination of "M" and "m":
%% M, m, MM7, Mm, mm, Mmm9, etc. Added-note chords: add, add6, etc.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(define rN-size -1) %% change to vary size of numerals
#(define scaling (magstep rN-size))
%%% change constant to adjust distance between characters
#(define X-separation (* scaling 0.2))
%%% symmetrical distance between inversion figures and midline
#(define inversion-Y-separation (* scaling 0.1))
#(define dim
(markup
#:override `(thickness . ,scaling)
#:draw-circle (* scaling 0.25) (* scaling 0.1) #f))
#(define half-dim
(markup
#:override `(thickness . ,scaling)
#:combine
(#:combine dim
#:draw-line `(,(* scaling -0.3) . ,(* scaling -0.3)))
#:draw-line `(,(* scaling 0.3) . ,(* scaling 0.3))))
#(define augmented
(markup
#:override `(thickness . ,scaling)
#:combine
(#:combine #:draw-line `(,(* scaling -0.25) . 0)
#:draw-line `(0 . ,(* scaling -0.25)))
(#:combine #:draw-line `(,(* scaling 0.25) . 0)
#:draw-line `(0 . ,(* scaling 0.25)))))
#(define (acc? str num) (string? number?)
(eq? num (string-index str (char-set #\b #\s #\n)))) %% checks for accidental
#(define acc `((#\b . ,(markup #:flat))
(#\s . ,(markup #:sharp))
(#\n . ,(markup #:natural))))
#(define-markup-command (rN layout props symbols) (markup-list?)
;; isolate and normalize segment of list before slash (if any)
(let* ((up-to-slash (car (split-list-by-separator symbols (lambda (x) (equal? x "/")))))
(first-part (append up-to-slash (make-list (- 4 (length up-to-slash)) "")))
(normalized
(if (or (string-index (cadr first-part) (string->char-set "mMaAdD"))
(not (null? (lset-intersection equal? '("o" "h" "+" "b") (cdr first-part)))))
first-part
(list (car first-part) "" (cadr first-part) (caddr first-part))))
(base (car normalized))
(quality (cadr normalized))
(quality-marker
(cond ((equal? "o" quality) (markup #:raise (* 0.5 scaling) dim))
((equal? "h" quality) (markup #:raise (* 0.5 scaling) half-dim))
((equal? "+" quality) (markup #:raise (* 0.5 scaling) augmented))
((equal? "b" quality) (markup #:raise (* 0.5 scaling) #:flat))
((equal? "" quality) (markup #:null))
(else (markup quality))))
(upper (caddr normalized))
(lower (cadddr normalized))
;; isolate slash and what follows
(second-part (if (member "/" symbols) (member "/" symbols) '("" "")))
(rN-two (cadr second-part))
(base-stencil
(interpret-markup layout
(cons (list `(word-space . ,X-separation) `(font-size . ,rN-size)) props)
(markup base)))
;; calculate Y midpoint of base (for positioning quality and inversion)
(vertical-offset (/ (interval-length (ly:stencil-extent base-stencil Y)) 2))
(inversion-stencil
(if (equal? lower "")
(ly:stencil-translate-axis (interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
(markup #:fontsize (- rN-size 5) upper))
inversion-Y-separation Y)
(ly:stencil-aligned-to
(ly:stencil-combine-at-edge
(interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
(markup #:fontsize (- rN-size 5) upper))
Y DOWN
(interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
(markup #:fontsize (- rN-size 5) lower))
(* 2 inversion-Y-separation))
Y CENTER)))
(quality-marker-stencil
(ly:stencil-translate-axis (interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
(markup #:fontsize (- rN-size 5) quality-marker))
inversion-Y-separation Y))
;; base, quality marker, and inversion
(one
(ly:stencil-combine-at-edge
(ly:stencil-combine-at-edge
(interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
;; accommodates an accidental either before or after
(cond ((acc? base 0)
(markup #:fontsize (- rN-size 4)
#:raise (* 2 vertical-offset) #:vcenter
(assoc-ref acc (string-ref base 0))
#:fontsize rN-size (substring base 1)))
((acc? base (1- (string-length base)))
(markup #:fontsize rN-size
(substring base 0 (1- (string-length base)))
#:fontsize (- rN-size 4) #:raise (/ scaling 2)
(assoc-ref acc (string-ref base (1- (string-length base))))))
(else (markup #:fontsize rN-size base))))
X RIGHT
(ly:stencil-translate-axis quality-marker-stencil vertical-offset Y)
(if (equal? "" quality) 0 X-separation))
X RIGHT
(ly:stencil-translate-axis inversion-stencil vertical-offset Y)
(if (equal? "" upper) 0 X-separation)))
;; slash and after
(two
(ly:stencil-combine-at-edge
(interpret-markup layout
(cons (list `(word-space . ,X-separation) `(font-size . ,rN-size)) props)
(if (equal? "" lower)
(markup (car second-part))
(markup #:hspace X-separation (car second-part))))
X RIGHT
(interpret-markup layout
(cons (list `(word-space . ,X-separation)) props)
(cond ((acc? rN-two 0)
(markup #:fontsize (- rN-size 4)
#:raise scaling (assoc-ref acc (string-ref rN-two 0))
#:fontsize rN-size (substring rN-two 1)))
((acc? rN-two (1- (string-length rN-two)))
(markup #:fontsize rN-size
(substring rN-two 0 (1- (string-length rN-two)))
#:fontsize (- rN-size 4) #:raise (/ scaling 2)
(assoc-ref acc (string-ref rN-two (1- (string-length rN-two))))))
(else (markup #:fontsize rN-size rN-two))))
X-separation)))
(if (equal? rN-two "")
one
(ly:stencil-combine-at-edge one X RIGHT two 0))))
bassline = \relative c' {
\clef bass
\key g \major
\time 3/4
g4 fis f
e es es
d2 d,4
g2.
\bar "||"
}
analysis = \lyricmode {
\set stanza = #"G: " % use spaces to adjust position of key indication
\markup \rN { I } \markup \rN { V 6 5 } \markup \rN { vii o 4 3 / IV }
\markup \rN { IV 6 } \markup \rN { ii h 4 3 } \markup \rN { Fr + 6 }
\markup \rN { I 6 4 } \markup \rN { V 7 }
\markup \rN { I }
}
\score {
\new Staff <<
\new Voice = "bass" { \bassline }
\new Lyrics \lyricsto "bass" { \analysis }
>>
\layout {
\context {
\Score
\override SpacingSpanner.shortest-duration-space = #5
}
}
}