Hi!I've been working on a automatic way to engrave a 12 tone row matrix in Lilypond (See, for instance, http://unitus.org/FULL/12tone.pdf for an explanation of what it is). I'm doing it as a project to teach myself Scheme. I wanted to have something concrete to work with wile learning and create something useful at the same time.
There are some websites and softwares that already generate this kind of matrix:
http://composertools.com/Tools/matrix/MatrixCalc.html http://in.music.sc.edu/fs/bain/software/tta-v2.22d/default.htm http://www.musictheory.net/calculators/matrixbut I don't know any that outputs proper engraved sheet music, which would be very convenient for me. I any case the point is not to create something new, but to have some project that would require me to learn Scheme and be useful.
It's being a very interesting experience, sometimes fun, sometimes frustrating, a lot of times obsessive, but I finally arrived at something which I'm satisfied and wanted to share. So here it is! Just put all files in the same directory and run 12tone-matrix.ly to see what it looks like. All criticisms and suggestions are very much appreciated.
You can read all the 12 transpositions and inversions of the rows. For the retrogrades, just read it from right to left. The inversions are read vertically, delimited by the dashed lines, and retrograde-inversion is read from bottom to top. I'll explain some (nasty) details in the following lines.
-------I found it easier to separate the pure scheme calculations in one file and the lilypond specific variables in another one (these correspond to 12tone-calc.scm and 12tone-engravers.ily as you may gess). This would allow me possibly to use the same Scheme functions with other software, although lilypond requires placing a # before each scheme function, which make the task a little harder.
I tried to start building some very generic and abstract pitch calculations and define functions progressively more specific, so I can expand the code more easily and possibly experiment with other types or pitch sets or even other musical parameters like durations, etc. That's why I have a different concept of an inversion and an inversion for 12 tone rows, for instance. I will not go into details of how the calculations work since I think it's not appropriate to this list, maybe I'll do it in the future in another place. What is interesting is that in the process I had to learn and apply the concepts of higher-order functions and recursion which are very convenient in Scheme.
The engravers were interesting. Since I was basically going to engrave 12 rows which are equal in all aspects except the pitches I wanted to create some function that would create a new staff with some specific configurations and recursively call itself 12 times. The Scheme tutorial by Urs Liska (lilypondblog.org/category/using-lilypond/advanced/scheme-tutorials/) was very useful, I had a hard time trying to imagine how I could do it with "the Lilypond way" of defining functions, but with the help of the tutorial I was able to arrive at something that works, although I'm not 100% satisfied with it yet.
I have the code being controlled by git (another thanks to the lilypond blog for showing me the importance of learning git and have version control) but it is not published yet since it is mainly a personal project, I may do it in the next days, though.
Enjoy! And please send me some feedback. Caio Barros
\version "2.18.2" % "square" transpositions linked to the defined row #(define (squareNumber n) (squareTranspose row n)) % List the rows that will be written #(define (squareRows n) (pset-op 12transpose row (squareTranspose row n))) % Convert the numbers in the row to Lilypond pitches #(define (number->note n) (cond ((= n 0) (ly:make-pitch 1 0)) ((= n 1) (ly:make-pitch 1 0 1/2)) ((= n 2) (ly:make-pitch 1 1)) ((= n 3) (ly:make-pitch 1 1 1/2)) ((= n 4) (ly:make-pitch 0 2)) ((= n 5) (ly:make-pitch 0 3)) ((= n 6) (ly:make-pitch 0 3 1/2)) ((= n 7) (ly:make-pitch 0 4)) ((= n 8) (ly:make-pitch 0 4 1/2)) ((= n 9) (ly:make-pitch 0 5)) ((= n 10) (ly:make-pitch 0 5 1/2)) ((= n 11) (ly:make-pitch 0 6)) (else (error "Pitch not found")))) % Write elements (markups or note) writeElements = #(define-music-function (parser location sym1 sym2 fn1 ls) (symbol? symbol? procedure? list?) (make-music 'SequentialMusic 'elements (let loop ((ls ls)) (if (null? ls) '() (cons (make-music sym1 ;'LyricEvent / 'NoteEvent sym2 ; 'text / 'pitch (fn1 (car ls)) ; (markup #:concat (#:larger "RI" #:sub (number->string (car ls)))) / (number->note (car ls)) 'duration (ly:make-duration 2)) (loop (cdr ls))))))) % customBarLine by Janek Warchoł % http://stackoverflow.com/questions/18538793/lilypond-markup-text-next-to-barline % changed to write the Retrograde inversion customBarLine = #(define-music-function (parser location n) (number?) #{ \once \override Staff.BarLine #'stencil = #(lambda (grob) (ly:stencil-combine-at-edge (ly:bar-line::print grob) X RIGHT (grob-interpret-markup grob #{ \markup { \whiteout \vcenter \concat { \larger "R" \sub #(number->string (squareNumber n))} } #}) 0))#}) % Write a new Staff with the transposition number: %% - As an instrument name on the left %% - As a custom bar line on the right writeRow = #(define-music-function (parser location n) (number?) #{ \new Staff \with { instrumentName = \markup { \concat { \larger O \sub #(number->string (squareNumber n)) } } } { \writeElements #'NoteEvent #'pitch #(lambda (x) (number->note x)) #(squareRows n) \stopStaff \bar "|" s4 \customBarLine #n } #}) % Write markups with a large string and a subtexted number writeRowMarkups = #(define-music-function (parser location str ls) (string? list?) #{ \writeElements #'LyricEvent #'text #(lambda (x) (markup #:concat (#:larger str #:sub (number->string x)))) #ls #}) % Use writeRowMarkups to write the inversion transpositions markupI = #(define-music-function (parser location) () #{ \writeRowMarkups #"I" #(zero-pset row 12) #}) % Use writeRowMarkups to write the retrograde-inversion transpositions markupRI = #(define-music-function (parser location) () #{ \writeRowMarkups #"RI" #(zero-pset row 12) #}) % Recursive function to write the 12 rows with all the transpositions writeRowRec = #(define-music-function (parser location n) (number?) (if (= 12 n) #{ #} #{ << \writeRow #n \writeRowRec #(+ n 1) >> #})) writeSquare = \writeRowRec #0
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Pitch manipulations in Scheme % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Creating Transpositions and inversions % Transpositions #(define (transpose ls int mod) (map (lambda (x) (modulo (+ int x) mod)) ls)) % Set first pitch as "0" and maintain interval relations #(define (zero-pset ls mod) (transpose ls (* (car ls) -1) mod)) % Inversion #(define (invert ls mod) (map (lambda (x) (modulo x mod)) ; keep inside modulos (e.g. 12 for 12-tone rows) (map (lambda (y) (+ y (car ls))) ; sum the inverted intervals with the first pitch of the original pitch set (map (lambda (z) (* z -1)) (zero-pset ls mod))))) % invert zeroed pitch set signals % Apply the chosen operation in different ways depending on the number of arguments #(define (pset-op op pset . ls) (cond ((null? ls) (op pset)) ((null? (cdr ls)) (op pset (car ls))) (else (op pset (car ls) (cadr ls))))) % 12 tone specific operations (always inside modulos 12) #(define (12transpose pset n) (transpose pset n 12)) #(define (12invert pset) (invert pset 12)) #(define (12retrograde pset) (reverse pset)) #(define (12retinvert pset) (reverse (invert pset 12))) % Transpose the original 12 tone row according to the intervals of the inverted form #(define (squareTranspose 12set n) (list-ref (invert (zero-pset 12set 12) 12) n)) %%%%%%%%% Some further pitch manipulations (no used yet) % List all transpositions #(define (all-transpositions pset mod) (let loop ((x pset) (y '())) (if (null? x) (reverse y) (loop (cdr x) (cons (transpose (zero-pset pset) (car x) mod) y))))) % list all transpositions in ascending order #(define (all-transpositions-order pset mod) (let loop ((x pset) (y '()) (z 0)) (if (null? x) (reverse y) (loop (cdr x) (cons (transpose pset z mod) y) (+ z 1))))) % list all inversions #(define (all-inversions pset mod) (let loop ((x (all-transpositions pset mod)) (y '())) (if (null? x) (reverse y) (loop (cdr x) (cons (invert (car x) mod) y))))) % List all inversions in ascending order #(define (all-inversions-order pset mod) (let loop ((x (all-transpositions-order pset mod)) (y '())) (if (null? x) (reverse y) (loop (cdr x) (cons (row-invert (car x) mod) y)))))
\version "2.18.2" #(set-global-staff-size 16) % the row should be written as a list of numbers (0 = c, 1 = c-sharp, etc.) #(define row '(6 5 9 3 0 11 10 1 2 4 8 7)) \include "12tone-calc.scm" \include "12tone-engravers.ily" \layout { \context { \Score \override TimeSignature.stencil = ##f \override Stem.stencil = ##f \override BarLine.layer = 1 } } \paper { left-margin = 3\cm right-margin = 2\cm indent = 1.5\cm line-width = 16\cm tagline = "" ragged-last = ##f } \score { \new StaffGroup << \time 1/4 \set Timing.defaultBarType = "!" \set StaffGroup.systemStartDelimiter = #'SystemStartBar %Write indications of Inversions \new Lyrics { \markupI } %Write all Rows plus indications of transpositions \writeSquare %Write indications of Retrogrades fo Inversions \new Lyrics { \markupRI } >> }
_______________________________________________ lilypond-user mailing list lilypond-user@gnu.org https://lists.gnu.org/mailman/listinfo/lilypond-user