Customized italian style chords
Implements a personalized way to write chords (in italian). The format is: (root)(root alteration)(root modifier)(root other)_(bass)(bass alteration).(duration)(dots)*(ratio numerator)/(ratio denominator) -Chords must be written in a long string. -Note names and alterations must be written in italian. -Correspondances between alteration codes and engraved symbols are: d=sharp, dd=double sharp, b=flat, bb=double flat (you can change them in the snippet). -Correspondances between modifier codes and engraved symbols are: "-" (minor)=en dash, "dim" (diminished)="o", "semidim" (half diminished)="ø" (you can change them in the snippet). -(root other) is free text. -The chord duration follows the standard lilypond format, you can also add dots and a fraction for the ratio multiplier. -If a bass is present, the chord is written in fractional form with numerator=root and denominator=bass (like italian style music sheets).
\version "2.24.0"
%Il formato dell'accordo è <radice>_<basso>.<durata con eventuali punti di valore>*<num>/<den>.
%music-map prende la musica come se fosse un albero e ci applica <funzione> in modo gerarchico a partire dalle foglie.
%I parametri passati via via a <funzione> dunque partono dalle singole foglie (LyricEvent) per poi passare al livello superiore (SequentialMusic)
%che comprende la lista dei LyricEvent (le singole foglie sono passate di nuovo ma questa volta raggruppate in una lista).
%Se modifico un livello basso, ritrovo la modifica a tutti i livelli superiori.
%Nel mio caso ho un unico evento di tipo LyricEvent che è la stringa contenente tutti gli accordi.
%Da questa stringa devo isolare i singoli accordi e aggiungerli in una lista di LyricEvent (opportunamente modificati).
%La lista dei LyricEvent poi va sostituita nel livello superiore SequentialMusic sotto la voce 'elements.
%Visto che con la prima chiamata costruisco la lista e con la seconda sostituisco 'elements di SequentialMusic,
%ho bisogno di una variabile globale che mi memorizzi la lista tra la prima e la seconda chiamata.
%
%Per capire bene come funziona music-map si può aggiungere all'inizio e alla fine della funzione il debug
% (display "MUSIC IN\n")
% (display (ly:music-property stringa-in 'name))
% (display " ")
% (display (length (ly:music-property stringa-in 'elements)))
% (display "\n")
% (display-scheme-music stringa-in)
% (display "\n")
%
% (display "MUSIC OUT\n")
% (display (ly:music-property stringa-out 'name))
% (display " ")
% (display (length (ly:music-property stringa-out 'elements)))
% (display "\n")
% (display-scheme-music stringa-out)
% (display "\n")
%
%Per visualizzare come viene memorizzata la musica in lylipond si usa la funzione
% (display-scheme-music <variabile>)
\language "italiano"
AccordiMio =
#(define-music-function (stringa-in) (ly:music?)
(define stringa-out "")
(set! stringa-out (music-map elabora-stringa-accordi stringa-in))
stringa-out
)
%variabili globali
#(begin
(define lista "")
(define durata 4)
(define numpunti 0)
(define num 1)
(define den 1)
)
#(define (elabora-stringa-accordi stringa-in)
(define stringa-out "")
(define elemento "")
(define p 0)
(define s "")
;imposto l'uscita uguale all'ingresso (nel caso non debba modificare nulla)
(set! stringa-out stringa-in)
;quando la stringa-in è al livello di LyricEvent...
(when (eq? (ly:music-property stringa-in 'name) 'LyricEvent)
;ricavo la stringa con tutti gli accordi
(set! elemento (ly:music-property stringa-in 'text))
;sostituisco i caratteri non stampabili (#\tab, #\return e #\newline) con lo spazio
(do ((p 0 (+ p 1))) ((>= p (string-length elemento)))
(cond
((equal? #\tab (string-ref elemento p)) (set! s (string-append s " ")))
((equal? #\return (string-ref elemento p)) (set! s (string-append s " ")))
((equal? #\newline (string-ref elemento p)) (set! s (string-append s " ")))
(else (set! s (string-append s (string (string-ref elemento p)))))
)
)
(set! elemento s)
;cancello gli spazi multipli
(set! s "")
(set! elemento (string-trim-both elemento))
(do ((p 0 (+ p 1))) ((>= p (string-length elemento))) ;parto dal primo carattere, non può essere uno spazio perché prima ho fatto il trim (dunque nel primo carattere non leggerò mai il carattere p-1 che non esiste)
(cond
((equal? #\space (string-ref elemento p)) (when (not (equal? #\space (string-ref elemento (- p 1)))) (set! s (string-append s (string (string-ref elemento p))))))
((not (equal? #\space (string-ref elemento p))) (set! s (string-append s (string (string-ref elemento p)))))
)
)
(set! elemento s)
;costruisco la lista dei LyricEvent in uscita
(set! lista (map elabora-accordo (string-split elemento #\ )))
)
;quando la stringa-in è al livello 'SequentialMusic...
(when (eq? (ly:music-property stringa-in 'name) 'SequentialMusic)
;sostituisco 'elements con la lista formata da tanti LyricEvent
(ly:music-set-property! stringa-out 'elements lista)
)
;ritorna stringa-out
stringa-out
)
#(define (elabora-accordo stringa-in)
(define stringa-out "")
;definisce le variabili per il parsing dell'accordo
(define radice-basso "")
(define durata-punti-num-den "")
(define durata-punti "")
(define durata-num-den "")
;durata è una variabile globale
;numpunti è una variabile globale
;num è una variabile globale
;den è una variabile globale
(define durataexp 0)
(define num-den "")
(define tmp "")
;definisce la variabili di cui si compone l'accordo
(define accordo "")
(define puntatore 0)
(define radice "")
(define radice-alterazione "") ;d, dd, b, bb
(define radice-modificatore "") ;dim, semidim, minore
(define radice-altro "")
(define basso "")
(define basso-alterazione "") ;d, dd, b, bb
;definisce i divisori
(define DIV-DURATA #\.)
(define DIV-SCALA #\*)
(define DIV-FRAZIONE #\/)
(define DIV-BASSO #\_)
;definisce i modificatori
(define MOD-DIMINUITO "dim")
(define MOD-SEMIDIMINUITO "semidim")
(define MOD-MINORE "-")
;definisce le alterazioni (si segue la notazione standard di Lylipond)
(define ALT-DIESIS "d")
(define ALT-DOPPIODIESIS "dd")
(define ALT-BEMOLLE "b")
(define ALT-DOPPIOBEMOLLE "bb")
;definisce i markup
(define MKP-SEMIDIMINUITO (markup (#:raise 1 "ø")))
(define MKP-DIMINUITO (markup (#:raise 1 "o")))
(define MKP-MINORE (markup (#:simple (ly:wide-char->utf-8 #x2013)))) ;il trattino è EN DASH
(define MKP-DOPPIODIESIS (markup (#:line (#:raise 1.5 (#:doublesharp)))))
(define MKP-DOPPIOBEMOLLE (markup (#:line (#:raise 0.5 (#:doubleflat)))))
(define MKP-DIESIS (markup (#:line (#:raise 1 (#:sharp)))))
(define MKP-BEMOLLE (markup (#:line (#:raise 0.5 (#:flat)))))
;PARSING DELL'ACCORDO
(set! tmp (string-split stringa-in DIV-DURATA)) ;divido l'accordo dalla durata (il delimitatore di string-split deve essere di tipo char)
(set! radice-basso (car tmp))
(when (>= (length tmp) 2) ;quando ho uno (o più) punti allora ho anche la durata
;resetta durata, numpunti, num, den
(set! durata 4)
(set! numpunti 0)
(set! num 1)
(set! den 1)
;parsing della durata
(set! durata-punti-num-den (string-join (cdr tmp) (make-string 1 DIV-DURATA))) ;ricompongo la parte della durata compresi i punti (il delimitatore di string-join deve essere di tipo string)
(set! tmp (string-split durata-punti-num-den DIV-SCALA)) ;divido la durata dalla frazione
(set! durata-punti (car tmp))
(when (= (length tmp) 2) ;quando ho (esattamente) un asterisco allora ho anche la frazione
;parsing del num e del den
(set! num-den (cadr tmp))
(set! tmp (string-split num-den DIV-FRAZIONE)) ;divido num da den
(set! num (string->number (car tmp)))
(when (= (length tmp) 2) ;quando ho (esattamente) una barra allora ho anche il den
(set! den (string->number (cadr tmp)))
)
)
;parsing della durata e del numpunti
(set! tmp (string-split durata-punti DIV-DURATA)) ;divido la durata dai punti di valore
(set! durata (string->number (car tmp)))
(set! numpunti (- (length tmp) 1)) ;conto i punti di valore
)
;parsing della radice e del basso
(set! tmp (string-split radice-basso DIV-BASSO)) ;divido la radice dal basso
(set! radice (car tmp))
(when (= (length tmp) 2) ;quando ho (esattamente) un underscore allora ho anche il basso
(set! basso (cadr tmp))
)
;PARSING DELLA RADICE
;sostituisci i simboli di alterazione (bemolle, doppio bemolle, diesis, doppio diesis) e i modificatori (minore, dim e semidim)
(set! tmp radice)
(when (not (string-null? tmp))
;gestisci il caso "s" come skip
(when (equal? "s" tmp)
(set! radice "")
(set! tmp "")
)
;se c'è il modificatore semidiminuito, impostalo e cancellalo
(when (string-contains tmp MOD-SEMIDIMINUITO)
(set! radice-modificatore MKP-SEMIDIMINUITO)
(set! tmp (ly:string-substitute MOD-SEMIDIMINUITO "" tmp))
)
;se c'è il modificatore diminuito, impostalo e cancellalo
(when (string-contains tmp MOD-DIMINUITO)
(set! radice-modificatore MKP-DIMINUITO)
(set! tmp (ly:string-substitute MOD-DIMINUITO "" tmp))
)
;ricava il nome della nota (primi 2 caratteri o primi 3 se la nota è SOL), impostala e cancellala
(when (>= (string-length tmp) 2)
(set! puntatore 2)
(when (equal? "so" (substring tmp 0 puntatore))
(set! puntatore 3)
)
(set! radice (substring tmp 0 puntatore))
(set! radice (markup (#:simple (string-upcase radice))))
(set! tmp (substring tmp puntatore (string-length tmp)))
)
;se c'è il modificatore minore, impostalo e cancellalo (devo farlo dopo che ho cancellato la nota altrimenti rischio di confondere la m di minore con la m di mi)
(when (string-contains tmp MOD-MINORE)
(set! radice-modificatore MKP-MINORE)
(set! tmp (ly:string-substitute MOD-MINORE "" tmp))
)
;verifica che la stringa abbia almeno 2 caratteri, ricava le alterazioni doppie (che ora stanno nei caratteri iniziali) e poi le cancella
(when (>= (string-length tmp) 2)
(cond
((equal? ALT-DOPPIODIESIS (substring tmp 0 (string-length ALT-DOPPIODIESIS))) (set! radice-alterazione MKP-DOPPIODIESIS) (set! tmp (substring tmp (string-length ALT-DOPPIODIESIS) (string-length tmp))))
((equal? ALT-DOPPIOBEMOLLE (substring tmp 0 (string-length ALT-DOPPIOBEMOLLE))) (set! radice-alterazione MKP-DOPPIOBEMOLLE) (set! tmp (substring tmp (string-length ALT-DOPPIOBEMOLLE) (string-length tmp))))
)
)
;verifica che la stringa abbia almeno 1 carattere, ricava le alterazioni singole (che ora stanno nei caratteri iniziali) e poi le cancella
(when (>= (string-length tmp) 1)
(cond
((equal? ALT-DIESIS (substring tmp 0 (string-length ALT-DIESIS))) (set! radice-alterazione MKP-DIESIS) (set! tmp (substring tmp (string-length ALT-DIESIS) (string-length tmp))))
((equal? ALT-BEMOLLE (substring tmp 0 (string-length ALT-BEMOLLE))) (set! radice-alterazione MKP-BEMOLLE) (set! tmp (substring tmp (string-length ALT-BEMOLLE) (string-length tmp))))
)
)
;ricava tutto il resto
(set! radice-altro (markup (#:simple tmp)))
)
;PARSING DEL BASSO
;sostituisci i simboli di alterazione (bemolle, doppio bemolle, diesis, doppio diesis)
(set! tmp basso)
(when (not (string-null? tmp))
;ricava il nome della nota (primi 2 caratteri o primi 3 se la nota è SOL), impostala e cancellala
(set! puntatore 2)
(when (equal? "so" (substring tmp 0 puntatore))
(set! puntatore 3)
)
(set! basso (substring tmp 0 puntatore))
(set! basso (markup (#:simple (string-upcase basso))))
(set! tmp (substring tmp puntatore (string-length tmp)))
;verifica che la stringa abbia almeno 2 caratteri, ricava le alterazioni doppie (che ora stanno nei caratteri iniziali) e poi le cancella
(when (>= (string-length tmp) 2)
(cond
((equal? ALT-DOPPIODIESIS (substring tmp 0 (string-length ALT-DOPPIODIESIS))) (set! basso-alterazione MKP-DOPPIODIESIS) (set! tmp (substring tmp (string-length ALT-DOPPIODIESIS) (string-length tmp))))
((equal? ALT-DOPPIOBEMOLLE (substring tmp 0 (string-length ALT-DOPPIOBEMOLLE))) (set! basso-alterazione MKP-DOPPIOBEMOLLE) (set! tmp (substring tmp (string-length ALT-DOPPIOBEMOLLE) (string-length tmp))))
)
)
;verifica che la stringa abbia almeno 1 carattere, ricava le alterazioni singole (che ora stanno nei caratteri iniziali) e poi le cancella
(when (>= (string-length tmp) 1)
(cond
((equal? ALT-DIESIS (substring tmp 0 (string-length ALT-DIESIS))) (set! basso-alterazione MKP-DIESIS) (set! tmp (substring tmp (string-length ALT-DIESIS) (string-length tmp))))
((equal? ALT-BEMOLLE (substring tmp 0 (string-length ALT-BEMOLLE))) (set! basso-alterazione MKP-BEMOLLE) (set! tmp (substring tmp (string-length ALT-BEMOLLE) (string-length tmp))))
)
)
)
;CALCOLA LA DURATA
;converti la durata in esponente della potenza di 2
(set! durataexp (ly:intlog2 durata))
;DEBUG
;(display (string-append "RADICE=" (markup->string radice) ", RADICE-ALT=" (markup->string radice-alterazione) ", RADICE-MOD=" (markup->string radice-modificatore) ", RADICE-ALTRO=" (markup->string radice-altro) ", BASSO=" (markup->string basso) ", BASSO-ALT=" (markup->string basso-alterazione) "\nDURATAEXP=" (number->string durataexp) ", NUMPUNTI=" (number->string numpunti) ", NUM=" (number->string num) ", DEN=" (number->string den) "\n\n"))
;costruisce l'accordo radice
(set! radice (markup #:concat (radice radice-alterazione radice-modificatore radice-altro)))
;costruisce l'accordo basso
(set! basso (markup #:concat (basso basso-alterazione)))
;costruisce la frazione
(if (string-null? (markup->string basso))
(set! accordo radice)
(set! accordo (markup #:line (#:fraction radice basso)))
)
;imposta e ritorna la stringa in uscita
(set! stringa-out (make-music 'LyricEvent 'duration (ly:make-duration durataexp numpunti num den) 'text accordo))
stringa-out
)
Accordi = \new Lyrics { \override LyricText.font-name = "JazzChords"
\lyricmode { \AccordiMio {
" soldd-7_rebb.4 s.8 do
redim7 misemidim7"
}
}
}
Melodia = \new Staff \new Voice { do'8 8 8 8 8 8 8 8 }
Parole = \new Lyrics \lyricmode { uno4 due tre quattro }
<<
\Accordi
\Melodia
\Parole
>>