Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Special pages
LilyPond wiki
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Coloring successive intervals
Page
Discussion
English
Read
Edit
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
Edit source
View history
General
What links here
Related changes
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
This scheme engraver searches for specified successive intervals in a voice, e.g. a minor third. If the interval occurs somewhere, the note heads of both notes defining the interval are colored. The engraver can be added to every Voice context. Usage: <code> \color_interval_engraver #intervaldefs #debug? #intervals-given </code> '''intervaldefs''': alist containing information about semitonical distances for certain intervals, diatonical distance is calculated in the engraver using `string-diatonic-semi-tonic-list`, relying on the key. '''debug?''': (optional) boolean, if true, output information about the processed pitches '''intervals-given''': list of the form <pre>#`((interval1 ,dir1 enh1 ,color1) (interval2 ,dir2 enh2 ,color2) ... (intervalN ,dirN enhN ,colorN))</pre> with<br /> '''intervalN''': string - specifying the interval to search after<br /> '''dirN''': integer - <code>UP</code> (=1) <code>DOWN</code> (=-1) or 0 (up and down)<br /> '''enhN''': boolean - search for enharmonically equivalent intervals, too?<br /> '''colorN''': lilypond color value, see NR A.7. Example: <pre>\layout { \context { \Voice \consists \color_interval_engraver #intervaldefs #`(("2--" ,UP #f ,green) ("3+" ,DOWN #t ,blue)) } }</pre> Debug mode: With <code>debug?</code> set to <code>#t</code>, the engraver does output * The preprocessed list of intervals to search after * Detailed information about all note head grobs the engraver has acknowledged Warnings: The engraver tries to provide helpful warning messages when fed with invalid input or if other issues prevent the engraver from working correctly. Some examples: * <code>Warning: Color_interval_engraver: In interval (2++ 2 #f (0.5 0.5 0.0)), wrong type argument: 2, needs to be a direction.</code> * <code>Warning: Color_interval_engraver: Recoloring note head in Voice N/A, bar number 1 #<Pitch ees'> belongs to intervals ((2 . 3) 1 #f (0.0 1.0 0.0)) and ((3 . 5) -1 #f (1.0 0.0 0.0))</code> * <code>Warning: Adding color_interval_engraver to a Staff context may lead to unexpected results if the Staff contains more than one voice.</code> <lilypond version="2.24.0" full> %%% Create an engraver that colors note heads depending on the %%% intervals between successive pitches %% Interval definitions alist %% %% Key: %% number determines the interval type, 1=prime, 2=second, 3=third ... %% plus and minus signs determine variant, no sign=perfect interval, +=major, %% ++=augmented, -=minor, --=diminished %% This naming scheme is arbitrary, it is possible to label the interval-types %% differently, like %% %% '(("A1" . (0 . 1)) %% ("P1" . (0 . 0)) %% ("m2" . (1 . 1)) etc. %% %% if an argument list using the same labels is passed to the engraver. %% %% Value: %% the car represents the diatonic, the cdr the semitonic steps. %% Only positive values are specified, negative values for %% intervals downwards are generated in the engraver. %% This list may be extended or completely overwritten %% Usage: #(display (assoc-get "4--" intervaldefs)) #(define intervaldefs '(("1++" . (0 . 1)) ("1" . (0 . 0)) ("2-" . (1 . 1)) ("2--" . (1 . 0)) ("2+" . (1 . 2)) ("2++" . (1 . 3)) ("3-" . (2 . 3)) ("3--" . (2 . 2)) ("3+" . (2 . 4)) ("3++" . (2 . 5)) ("4--" . (3 . 4)) ("4++" . (3 . 6)) ("4" . (3 . 5)) ("5--" . (4 . 6)) ("5++" . (4 . 8)) ("5" . (4 . 7)) ("6-" . (5 . 8)) ("6--" . (5 . 7)) ("6+" . (5 . 9)) ("6++" . (5 . 10)) ("7-" . (6 . 10)) ("7--" . (6 . 9)) ("7+" . (6 . 11)) ("7++" . (6 . 12)) ("8--" . (7 . 11)) ("8++" . (7 . 13)) ("8" . (7 . 12)) ("9-" . (8 . 13)) ("9--" . (8 . 12)) ("9+" . (8 . 14)) ("9++" . (8 . 15)) ("10-" . (9 . 15)) ("10--" . (9 . 14)) ("10+" . (9 . 16)) ("10++" . (9 . 17)) ("11--" . (10 . 16)) ("11++" . (10 . 18)) ("11" . (10 . 17)) ("12--" . (11 . 18)) ("12" . (11 . 19)))) %% Create an engraver that compares the intervals between sequential pitches %% of a voice with a given list of intervals. %% If a specified interval is found, the heads of both notes encompassing %% the interval are colored. %% %% Mode of operation: %% Intervals are defined by two integers representing the diatonic %% resp. semitonic distance between two pitches. %% It is necessary to take both distances into account to distinguish %% between enharmonically identical intervals, e.g. a major third %% and a diminished fourth. %% Example: %% d -> f# : diatonic distance = 2 steps (f# is derived from f natural), %% semitonic distance = 4 steps %% d -> gb: diatonic distance = 3 steps (gb is derived from g natural), %% semitonic distance = 4 steps %% %% The engraver consists of two parts: %% %% color_interval_engraver: checks, whether the given parameters are valid, %% looks up the interval in the interval definitions alist and hands %% the determined interval distances together with the other unchanged %% parameters over to the actual engraver color-interval-engraver-core. %% %% color-interval-engraver-core: creates a scheme-engraver which %% acknowledges note head grobs and stores the last and %% current grob locally. Then the pitches are extracted and the interval between %% the last and current pitch is compared to the specified interval. %% %% Usage: %% \color_interval_engraver #intervaldefs #debug? intervals-given %% %% intervaldefs: alist containing information about semitonical distances for %% certain intervals, diatonical distance is calculated in the engraver using %% `string-diatonic-semi-tonic-list`, relying on the key. %% %% debug?: (optional) boolean, if true, output information about the processed %% pitches %% %% intervals-given: list of the form %% #`((interval1 ,dir1 enh1 ,color1) %% (interval2 ,dir2 enh2 ,color2) %% ... %% (intervalN ,dirN enhN ,colorN)) %% with %% intervalN: string - specifying the interval to search after %% dirN: integer - UP (=1) DOWN (=-1) or 0 (up and down) %% enhN: boolean - search for enharmonically equivalent intervals, too? %% colorN: lilypond color value, see NR A.7. %% %% Constructing the argument list with `(= quasiquote) provides %% an elegant shorthand for (list (list interval1 dir1 enh1 color1) %% (list interval2 dir2 enh2 color2)) %% This would not work with '(= quote), because this special form does %% not allow to unquote certain list elements with the comma , %% The directions UP and DOWN and the color values, however, need %% to be evaluated to the corresponding integer values resp. %% RGB values. %% %% \layout { %% \context { %% \Voice %% \consists \color_interval_engraver #intervaldefs %% `(("2--" ,UP #f ,green) %% ("3+" ,DOWN #t ,blue)) %% } %% } color_interval_engraver = #(define-scheme-function (interval-defs debug? intervals-given) (list? (boolean?) list?) ;; debug? is optional, defaults to #f (let* ((msg-header "Color_interval_engraver:") ;; 2.18.2 does not accept an empty list as engraver, unlike 2.19.x (empty-engraver (make-engraver ((initialize translator) '()))) (type-check-interval (lambda (interval) ;; basic check for amount of args (if (not (= 4 (length interval))) (begin (ly:warning "~a Interval ~a must have 4 entries" msg-header interval) #f) ;; check every entry for type, additionally the first entry ;; whether it's a key in intervaldefs (let ((name (car interval)) (dir (second interval)) (enh? (third interval)) (color (fourth interval))) (and ;; check first entry for string? and ;; whether it's in intervaldefs (if (and (string? name) (assoc-get name intervaldefs)) #t (begin (ly:warning "~a In interval ~a, ~a not found in interval definitions" msg-header interval (car interval)) #f)) ;; check second entry for ly:dir? ;; As opposed to the normal meaning of 0 (=CENTER), ;; 0 means up >and< down here (if (ly:dir? dir) #t (begin (ly:warning "~a In interval ~a, wrong type argument: ~a, needs to be a direction." msg-header interval dir) #f)) ;; check third entry for boolean? (if (boolean? enh?) #t (begin (ly:warning "~a In interval ~a, wrong type argument: ~a, needs to be a boolean." msg-header interval enh?) #f)) ;; check fourth entry for color? (if (color? color) #t (begin (ly:warning "~a In interval ~a, wrong type argument: ~a, needs to be a color." msg-header interval color) #f))))))) (cleaned-intervals-given (filter type-check-interval intervals-given)) (search-intervals (map (lambda (interval) (let ((diatonic-semitonic-pair (assoc-get (car interval) interval-defs))) (cons diatonic-semitonic-pair (cdr interval)))) cleaned-intervals-given))) (if debug? (begin (ly:message "~a Preprocessed intervals:\n" msg-header) (for-each (lambda (search-interval) (format (current-error-port) "Distances (DT/ST):~a, direction:~a, enharmonic:~a, color:~a\n" (car search-interval) (second search-interval) (third search-interval) (fourth search-interval))) search-intervals))) (if (null? search-intervals) (begin (ly:warning "~a No valid interval found. Returning empty engraver" msg-header) empty-engraver) ;; Instantiate actual engraver (color-interval-engraver-core search-intervals debug?)))) #(define (color-interval-engraver-core search-intervals debug?) (lambda (context) ;; Context type: Staff, Voice, etc. ;; Context id: arbitrary string ;; \new <context-type> = <context-id> \music ;; \new Voice = "soprano" \music (let ((engraver-name "Color_interval_engraver") (context-type (ly:context-name context)) (context-id (let ((id (ly:context-id context))) (if (string-null? id) "N/A" id))) ;; Later we want to extract the current bar number from there (score-context (ly:context-find context 'Score)) (noteheads-to-process '()) (ready-to-process? #f) (last-noteheads-color #f) (last-interval #f)) (make-engraver ((initialize translator) ;; Output a warning if the engraver has been added to a Staff context ;; If the Staff consists of more than one Voice, the engraver cannot ;; distinguish the different voices and will mix them up (if (eq? context-type 'Staff) (ly:warning (string-append "Adding color_interval_engraver to a Staff context may lead " "to unexpected results if the Staff contains more than one " "voice.")))) ;; This engraver does not listen to events, thus it does not ;; define listeners. It does only acknowledge grobs, ;; specifically note heads created by other engravers. (acknowledgers ((note-head-interface engraver grob source-engraver) (if ready-to-process? ;; if we have two note heads already, push the old one out (set! noteheads-to-process (list grob (car noteheads-to-process))) ;; We need two note heads to compare the underlying pitches ;; -> store note heads until we have two (begin (set! noteheads-to-process (cons grob noteheads-to-process)) (if (= (length noteheads-to-process) 2) (set! ready-to-process? #t)))) ;; Check for grobs in the queue, before continuing (if ready-to-process? ;; Note head grobs store a reference to the ;; event that caused their generation ;; Thus we can extract the pitch (let* ((current-bar-number (ly:context-property score-context 'currentBarNumber)) (current-moment (ly:context-current-moment context)) (grob-causes (map (lambda (grob) (ly:grob-property grob 'cause)) noteheads-to-process)) (pitches (map (lambda (cause) (ly:event-property cause 'pitch)) grob-causes)) ;; Calculate interval distances, diatonic and semitonic (current-interval-dist-diatonic (apply - (map ly:pitch-steps pitches))) (current-interval-dist-semitonic (apply - (map ly:pitch-semitones pitches))) ;; Check if a given interval matches the current interval (interval-match? (lambda (search-interval) (let* ((search-interval-dist (car search-interval)) (search-interval-dir (second search-interval)) (search-interval-enh? (third search-interval)) (search-interval-dist-diatonic (car search-interval-dist)) (search-interval-dist-semitonic (cdr search-interval-dist))) ;; if search-interval-enh? was set to true for ;; the current interval, compare only the semitonic ;; distances, e.g. c#-f would also match a major 3rd, ;; not only a diminished 4th ;; ;; search-interval-dir can only be -1, 0, 1 ;; other values are excluded by typechecking, ;; thus 0 needs special casing, ;; for other cases multiplying relevant value with ;; search-interval-dir is enough ;; -- harm (if (zero? search-interval-dir) (and ;; if direction does not matter, compare ;; with absolute values (= search-interval-dist-semitonic (abs current-interval-dist-semitonic)) (if (not search-interval-enh?) (= search-interval-dist-diatonic (abs current-interval-dist-diatonic)) #t)) (and (= search-interval-dist-semitonic (* search-interval-dir current-interval-dist-semitonic)) (if (not search-interval-enh?) (= search-interval-dist-diatonic (* search-interval-dir current-interval-dist-diatonic)) #t)))))) ;; Get first occurrence of a matching interval (matching-interval (find interval-match? search-intervals)) ;; Extract color from matching interval (search-interval-color (if matching-interval (fourth matching-interval) #f))) (if debug? (let* ((cep (current-error-port))) (format cep "\n*** This is ~a from ~a ~a ***\n" engraver-name context-type context-id) (format cep "\nBar number ~a, moment ~a\n" current-bar-number current-moment) (format cep "\nPitches (last/current): ~a/~a\n" (second pitches) (first pitches)) (format cep "\nDistance (diatonic/semitonic): ~a/~a\n" current-interval-dist-diatonic current-interval-dist-semitonic) (if matching-interval (begin (format cep "\nMatch! Found interval ~a, coloring ~a\n" matching-interval search-interval-color) (if last-noteheads-color (format cep "\nRecoloring - Last note heads color: ~a\n" last-noteheads-color)))) (display "\n---------------------\n" cep))) (if search-interval-color (begin ;; Check if the note heads directly preceding were ;; colored, too. If true, the last note head belongs ;; to two distinct intervals ;; ;; <noteheads-to-process> ;; (grobB grobA) ;; interval grobB<->grobA matches -> color! ;; (grobB_colored grobA_colored) ;; <next iteration> ;; (grobC grobB_colored) ;; interval grobC<->grobB matches -> color! ;; (grobC_colored grobB_colored_colored (!)) ;; -> information about interval grobA<->grobB gets lost ;; In this case, print a warning (if last-noteheads-color (ly:warning (string-append "~a: Recoloring note head in ~a ~a, bar number ~a\n" "~a belongs to intervals ~a and ~a") engraver-name context-type context-id current-bar-number (second pitches) last-interval matching-interval)) ;; Color current and last note head grob (for-each (lambda (grob) (ly:grob-set-property! grob 'color search-interval-color)) noteheads-to-process))) ;; Preserve the current color (if any) for recoloring check ;; (see above) (set! last-noteheads-color search-interval-color) (set! last-interval matching-interval))))))))) \markup \column { \line { "Diminished second," \with-color #green "up" "and" \with-color #blue "down" } \line { "Minor second," \with-color #yellow "up" "and" \with-color #cyan "down" } \line { "Major second," \with-color #red "up" "and" \with-color #darkgreen "down" } \line { "Augmented second," \with-color #darkcyan "up" "and" \with-color #darkyellow "down" } } \score { \new Voice \relative c'' { fis4 g e d as gis cis bes f g cis des des, e g fis } \layout { \context { \Voice \consists \color_interval_engraver #intervaldefs #`(("2--" ,UP #f ,green) ("2--" ,DOWN #f ,blue) ("2-" ,UP #f ,yellow) ("2-" ,DOWN #f ,cyan) ("2+" ,UP #f ,red) ("2+" ,DOWN #f ,darkgreen) ("2++" ,UP #f ,darkcyan) ("2++" ,DOWN #f ,darkyellow) ;; Not specified interval ;("2+++" ,DOWN #f ,darkyellow) ;; Direction not suitable ;("2++" 2 #f ,darkyellow) ;; Wrong type argument for 'searching enharmonically equivalent, too?' ;("2++" ,DOWN foo ,darkyellow) ;; Wrong type for color ;("2++" ,DOWN #f (1 2 3 4 5)) ;; Wrong amount of entries ;("2++" ,DOWN #f) ) } } } \markup \column { "Color intervals regardless of direction" \with-color #green "Diminished third" \with-color #yellow "Minor third" \with-color #red "Major third" \with-color #darkcyan "Augmented third" } \score { \new Staff \relative c' { d4 f e cis gis' e f a d bis cis as e ges des fis } \layout { \context { \Voice \consists \color_interval_engraver #intervaldefs #`(("3--" 0 #f ,green) ("3-" 0 #f ,yellow) ("3+" 0 #f ,red) ("3++" 0 #f ,darkcyan)) } } } \markup \column { "Color enharmonically equivalent intervals, too" \with-color #green "Augmented second, minor third" } \score { \new Staff \relative c' { d4 f e a ges } \layout { \context { \Voice \consists \color_interval_engraver #intervaldefs #`(("3-" 0 #t ,green)) } } } \markup \column { "Output warning, if note belongs to two intervals" \line { \with-color #green "Minor third" and \with-color #red "perfect fourth" } } \score { \new Staff \relative c' { c4 es bes des } \layout { \context { \Voice \consists \color_interval_engraver #intervaldefs #`(("3-" ,UP #f ,green) ("4" ,DOWN #f ,red)) } } } \markup \column { "Output debug information" \line { \with-color #green "Minor second" and \with-color #red "perfect fourth" } } \score { << \new Voice = "Soprano" \relative c' { \key b \minor \partial 2 fis2 e2. fis4 b2 a4 r cis fis, b a gis2 fis4 r } \new Voice = "Alto" \relative c' { \key b \minor \partial 2 d2 cis2. d4 d (e) fis r fis (fis) eis fis fis eis fis r } >> \layout { \context { \Voice \consists \color_interval_engraver #intervaldefs ##t #`(("2-" ,DOWN #f ,green) ("4" ,UP #f ,red)) } } } \markup \column { "Output warning, if engraver has been added to staff instead of voice context" \line { \with-color #green "Minor second" and \with-color #red "perfect fourth" } } \score { \new Staff << \new Voice = "Soprano" \relative c' { \voiceOne \key b \minor \partial 2 fis2 e2. fis4 b2 a4 r cis fis, b a gis2 fis4 r } \new Voice = "Alto" \relative c' { \voiceTwo \key b \minor \partial 2 d2 cis2. d4 d (e) fis r fis (fis) eis fis fis eis fis r } >> \layout { \context { \Staff \consists \color_interval_engraver #intervaldefs #`(("4" ,UP #f ,red) ("2-" ,DOWN #f ,green)) } } } \paper { tagline = ##f } </lilypond> [[Category:Pitches]] [[Category:Contexts and engravers]] [[Category:Editorial annotations]]
Summary:
Please note that all contributions to LilyPond wiki may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Meta:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Search
Search
Editing
Coloring successive intervals
Add topic