Hi Stefan,
Am 20.09.22 um 13:00 schrieb Stefan Thomas:
Dear community,
sorry, it's a bit an off topic: If You are not interested in
pitch-class set theory, You don't have to read the following.
I've worked on a python module dealing with pitch-class set theory as
I've read in "The structure of atonal music" by Allen Forte.
I know that there already exists some modules like that but I want to
integrate it in lilypond.
I'm still working on this module, but at the moment I can do:
* Getting the normal form and prime form of a pcs.
* Transposing and inverting a pcs
* Finding subsets of a pcs given in primeform.
I'm working on:
* Finding different kinds of similarity of pcs.
* Getting subcomplexes k and kh of a pcs.
* Finding primeform and so on, when pitches in lilypond-style are given.
* Getting pitches in lilypond-style when pitches in midinote-nums or
as pcs are given.
Let me know it if You are interested. Maybee it's only something of
interest for nerdy persons like me, but maybee for others too.
I don't know if this is of use to you, but I created a bunch of LilyPond
routines for dealing with PC sets (like in Forte, but also with a finer
equivalence relation not identifying a set with its inverse) last year.
I attach it unchanged, as I don't have time to clean it up at the
moment; you'll probably want to uncomment some of the routines at the
end to see what the functions do.
I'm curious: You write that you are working on a Python module; how does
this integrate in LilyPond?
Lukas
\version "2.22.0"
\include "dodeka.ily"
% ---------------------- Forte number dictionaries ------------------------------
forte-distinct-inverses = % Taken from https://en.wikipedia.org/wiki/List_of_pitch-class_sets
#'((0-1 . ())
(1-1 . (0))
(2-1 . (0 1))
(2-2 . (0 2))
(2-3 . (0 3))
(2-4 . (0 4))
(2-5 . (0 5))
(2-6 . (0 6))
(3-1 . (0 1 2))
(3-2A . (0 1 3))
(3-2B . (0 2 3))
(3-3A . (0 1 4))
(3-3B . (0 3 4))
(3-4A . (0 1 5))
(3-4B . (0 4 5))
(3-5A . (0 1 6))
(3-5B . (0 5 6))
(3-6 . (0 2 4))
(3-7A . (0 2 5))
(3-7B . (0 3 5))
(3-8A . (0 2 6))
(3-8B . (0 4 6))
(3-9 . (0 2 7))
(3-10 . (0 3 6))
(3-11A . (0 3 7))
(3-11B . (0 4 7))
(3-12 . (0 4 8))
(4-1 . (0 1 2 3))
(4-2A . (0 1 2 4))
(4-2B . (0 2 3 4))
(4-3 . (0 1 3 4))
(4-4A . (0 1 2 5))
(4-4B . (0 3 4 5))
(4-5A . (0 1 2 6))
(4-5B . (0 4 5 6))
(4-6 . (0 1 2 7))
(4-7 . (0 1 4 5))
(4-8 . (0 1 5 6))
(4-9 . (0 1 6 7))
(4-10 . (0 2 3 5))
(4-11A . (0 1 3 5))
(4-11B . (0 2 4 5))
(4-12A . (0 2 3 6))
(4-12B . (0 3 4 6))
(4-13A . (0 1 3 6))
(4-13B . (0 3 5 6))
(4-14A . (0 2 3 7))
(4-14B . (0 4 5 7))
(4-z15A . (0 1 4 6))
(4-z15B . (0 2 5 6))
(4-16A . (0 1 5 7))
(4-16B . (0 2 6 7))
(4-17 . (0 3 4 7))
(4-18A . (0 1 4 7))
(4-18B . (0 3 6 7))
(4-19A . (0 1 4 8))
(4-19B . (0 3 4 8))
(4-20 . (0 1 5 8))
(4-21 . (0 2 4 6))
(4-22A . (0 2 4 7))
(4-22B . (0 3 5 7))
(4-23 . (0 2 5 7))
(4-24 . (0 2 4 8))
(4-25 . (0 2 6 8))
(4-26 . (0 3 5 8))
(4-27A . (0 2 5 8))
(4-27B . (0 3 6 8))
(4-28 . (0 3 6 9))
(4-z29A . (0 1 3 7))
(4-z29B . (0 4 6 7))
(5-1 . (0 1 2 3 4))
(5-2A . (0 1 2 3 5))
(5-2B . (0 2 3 4 5))
(5-3A . (0 1 2 4 5))
(5-3B . (0 1 3 4 5))
(5-4A . (0 1 2 3 6))
(5-4B . (0 3 4 5 6))
(5-5A . (0 1 2 3 7))
(5-5B . (0 4 5 6 7))
(5-6A . (0 1 2 5 6))
(5-6B . (0 1 4 5 6))
(5-7A . (0 1 2 6 7))
(5-7B . (0 1 5 6 7))
(5-8 . (0 2 3 4 6))
(5-9A . (0 1 2 4 6))
(5-9B . (0 2 4 5 6))
(5-10A . (0 1 3 4 6))
(5-10B . (0 2 3 5 6))
(5-11A . (0 2 3 4 7))
(5-11B . (0 3 4 5 7))
(5-z12 . (0 1 3 5 6))
(5-13A . (0 1 2 4 8))
(5-13B . (0 2 3 4 8))
(5-14A . (0 1 2 5 7))
(5-14B . (0 2 5 6 7))
(5-15 . (0 1 2 6 8))
(5-16A . (0 1 3 4 7))
(5-16B . (0 3 4 6 7))
(5-z17 . (0 1 3 4 8))
(5-z18A . (0 1 4 5 7))
(5-z18B . (0 2 3 6 7))
(5-19A . (0 1 3 6 7))
(5-19B . (0 1 4 6 7))
(5-20A . (0 1 5 6 8))
(5-20B . (0 2 3 7 8))
(5-21A . (0 1 4 5 8))
(5-21B . (0 3 4 7 8))
(5-22 . (0 1 4 7 8))
(5-23A . (0 2 3 5 7))
(5-23B . (0 2 4 5 7))
(5-24A . (0 1 3 5 7))
(5-24B . (0 2 4 6 7))
(5-25A . (0 2 3 5 8))
(5-25B . (0 3 5 6 8))
(5-26A . (0 2 4 5 8))
(5-26B . (0 3 4 6 8))
(5-27A . (0 1 3 5 8))
(5-27B . (0 3 5 7 8))
(5-28A . (0 2 3 6 8))
(5-28B . (0 2 5 6 8))
(5-29A . (0 1 3 6 8))
(5-29B . (0 2 5 7 8))
(5-30A . (0 1 4 6 8))
(5-30B . (0 2 4 7 8))
(5-31A . (0 1 3 6 9))
(5-31B . (0 2 3 6 9))
(5-32A . (0 1 4 6 9))
(5-32B . (0 3 5 8 9))
(5-33 . (0 2 4 6 8))
(5-34 . (0 2 4 6 9))
(5-35 . (0 2 4 7 9))
(5-z36A . (0 1 2 4 7))
(5-z36B . (0 3 5 6 7))
(5-z37 . (0 3 4 5 8))
(5-z38A . (0 1 2 5 8))
(5-z38B . (0 3 6 7 8))
(6-1 . (0 1 2 3 4 5))
(6-2A . (0 1 2 3 4 6))
(6-2B . (0 2 3 4 5 6))
(6-z3A . (0 1 2 3 5 6))
(6-z3B . (0 1 3 4 5 6))
(6-z4 . (0 1 2 4 5 6))
(6-5A . (0 1 2 3 6 7))
(6-5B . (0 1 4 5 6 7))
(6-z6 . (0 1 2 5 6 7))
(6-7 . (0 1 2 6 7 8))
(6-8 . (0 2 3 4 5 7))
(6-9A . (0 1 2 3 5 7))
(6-9B . (0 2 4 5 6 7))
(6-z10A . (0 1 3 4 5 7))
(6-z10B . (0 2 3 4 6 7))
(6-z11A . (0 1 2 4 5 7))
(6-z11B . (0 2 3 5 6 7))
(6-z12A . (0 1 2 4 6 7))
(6-z12B . (0 1 3 5 6 7))
(6-z13 . (0 1 3 4 6 7))
(6-14A . (0 1 3 4 5 8))
(6-14B . (0 3 4 5 7 8))
(6-15A . (0 1 2 4 5 8))
(6-15B . (0 3 4 6 7 8))
(6-16A . (0 1 4 5 6 8))
(6-16B . (0 2 3 4 7 8))
(6-z17A . (0 1 2 4 7 8))
(6-z17B . (0 1 4 6 7 8))
(6-18A . (0 1 2 5 7 8))
(6-18B . (0 1 3 6 7 8))
(6-z19A . (0 1 3 4 7 8))
(6-z19B . (0 1 4 5 7 8))
(6-20 . (0 1 4 5 8 9))
(6-21A . (0 2 3 4 6 8))
(6-21B . (0 2 4 5 6 8))
(6-22A . (0 1 2 4 6 8))
(6-22B . (0 2 4 6 7 8))
(6-z23 . (0 2 3 5 6 8))
(6-z24A . (0 1 3 4 6 8))
(6-z24B . (0 2 4 5 7 8))
(6-z25A . (0 1 3 5 6 8))
(6-z25B . (0 2 3 5 7 8))
(6-z26 . (0 1 3 5 7 8))
(6-27A . (0 1 3 4 6 9))
(6-27B . (0 3 5 6 8 9))
(6-z28 . (0 1 3 5 6 9))
(6-z29 . (0 2 3 6 7 9))
(6-30A . (0 1 3 6 7 9))
(6-30B . (0 2 3 6 8 9))
(6-31A . (0 1 4 5 7 9))
(6-31B . (0 2 4 5 8 9))
(6-32 . (0 2 4 5 7 9))
(6-33A . (0 2 3 5 7 9))
(6-33B . (0 2 4 6 7 9))
(6-34A . (0 1 3 5 7 9))
(6-34B . (0 2 4 6 8 9))
(6-35 . (0 2 4 6 8 10))
(6-z36A . (0 1 2 3 4 7))
(6-z36B . (0 3 4 5 6 7))
(6-z37 . (0 1 2 3 4 8))
(6-z38 . (0 1 2 3 7 8))
(6-z39A . (0 2 3 4 5 8))
(6-z39B . (0 3 4 5 6 8))
(6-z40A . (0 1 2 3 5 8))
(6-z40B . (0 3 5 6 7 8))
(6-z41A . (0 1 2 3 6 8))
(6-z41B . (0 2 5 6 7 8))
(6-z42 . (0 1 2 3 6 9))
(6-z43A . (0 1 2 5 6 8))
(6-z43B . (0 2 3 6 7 8))
(6-z44A . (0 1 2 5 6 9))
(6-z44B . (0 3 4 7 8 9))
(6-z45 . (0 2 3 4 6 9))
(6-z46A . (0 1 2 4 6 9))
(6-z46B . (0 3 5 7 8 9))
(6-z47A . (0 1 2 4 7 9))
(6-z47B . (0 2 5 7 8 9))
(6-z48 . (0 1 2 5 7 9))
(6-z49 . (0 1 3 4 7 9))
(6-z50 . (0 1 4 6 7 9))
(7-1 . (0 1 2 3 4 5 6))
(7-2A . (0 1 2 3 4 5 7))
(7-2B . (0 2 3 4 5 6 7))
(7-3A . (0 1 2 3 4 5 8))
(7-3B . (0 3 4 5 6 7 8))
(7-4A . (0 1 2 3 4 6 7))
(7-4B . (0 1 3 4 5 6 7))
(7-5A . (0 1 2 3 5 6 7))
(7-5B . (0 1 2 4 5 6 7))
(7-6A . (0 1 2 3 4 7 8))
(7-6B . (0 1 4 5 6 7 8))
(7-7A . (0 1 2 3 6 7 8))
(7-7B . (0 1 2 5 6 7 8))
(7-8 . (0 2 3 4 5 6 8))
(7-9A . (0 1 2 3 4 6 8))
(7-9B . (0 2 4 5 6 7 8))
(7-10A . (0 1 2 3 4 6 9))
(7-10B . (0 2 3 4 5 6 9))
(7-11A . (0 1 3 4 5 6 8))
(7-11B . (0 2 3 4 5 7 8))
(7-z12 . (0 1 2 3 4 7 9))
(7-13A . (0 1 2 4 5 6 8))
(7-13B . (0 2 3 4 6 7 8))
(7-14A . (0 1 2 3 5 7 8))
(7-14B . (0 1 3 5 6 7 8))
(7-15 . (0 1 2 4 6 7 8))
(7-16A . (0 1 2 3 5 6 9))
(7-16B . (0 1 3 4 5 6 9))
(7-z17 . (0 1 2 4 5 6 9))
(7-z18A . (0 1 4 5 6 7 9))
(7-z18B . (0 1 4 6 7 8 9))
(7-19A . (0 1 2 3 6 7 9))
(7-19B . (0 1 2 3 6 8 9))
(7-20A . (0 1 2 5 6 7 9))
(7-20B . (0 1 2 5 7 8 9))
(7-21A . (0 1 2 4 5 8 9))
(7-21B . (0 1 3 4 5 8 9))
(7-22 . (0 1 2 5 6 8 9))
(7-23A . (0 2 3 4 5 7 9))
(7-23B . (0 2 4 5 6 7 9))
(7-24A . (0 1 2 3 5 7 9))
(7-24B . (0 2 4 6 7 8 9))
(7-25A . (0 2 3 4 6 7 9))
(7-25B . (0 2 3 5 6 7 9))
(7-26A . (0 1 3 4 5 7 9))
(7-26B . (0 2 4 5 6 8 9))
(7-27A . (0 1 2 4 5 7 9))
(7-27B . (0 2 4 5 7 8 9))
(7-28A . (0 1 3 5 6 7 9))
(7-28B . (0 2 3 4 6 8 9))
(7-29A . (0 1 2 4 6 7 9))
(7-29B . (0 2 3 5 7 8 9))
(7-30A . (0 1 2 4 6 8 9))
(7-30B . (0 1 3 5 7 8 9))
(7-31A . (0 1 3 4 6 7 9))
(7-31B . (0 2 3 5 6 8 9))
(7-32A . (0 1 3 4 6 8 9))
(7-32B . (0 1 3 5 6 8 9))
(7-33 . (0 1 2 4 6 8 10))
(7-34 . (0 1 3 4 6 8 10))
(7-35 . (0 1 3 5 6 8 10))
(7-z36A . (0 1 2 3 5 6 8))
(7-z36B . (0 2 3 5 6 7 8))
(7-z37 . (0 1 3 4 5 7 8))
(7-z38A . (0 1 2 4 5 7 8))
(7-z38B . (0 1 3 4 6 7 8))
(8-1 . (0 1 2 3 4 5 6 7))
(8-2A . (0 1 2 3 4 5 6 8))
(8-2B . (0 2 3 4 5 6 7 8))
(8-3 . (0 1 2 3 4 5 6 9))
(8-4A . (0 1 2 3 4 5 7 8))
(8-4B . (0 1 3 4 5 6 7 8))
(8-5A . (0 1 2 3 4 6 7 8))
(8-5B . (0 1 2 4 5 6 7 8))
(8-6 . (0 1 2 3 5 6 7 8))
(8-7 . (0 1 2 3 4 5 8 9))
(8-8 . (0 1 2 3 4 7 8 9))
(8-9 . (0 1 2 3 6 7 8 9))
(8-10 . (0 2 3 4 5 6 7 9))
(8-11A . (0 1 2 3 4 5 7 9))
(8-11B . (0 2 4 5 6 7 8 9))
(8-12A . (0 1 3 4 5 6 7 9))
(8-12B . (0 2 3 4 5 6 8 9))
(8-13A . (0 1 2 3 4 6 7 9))
(8-13B . (0 2 3 5 6 7 8 9))
(8-14A . (0 1 2 4 5 6 7 9))
(8-14B . (0 2 3 4 5 7 8 9))
(8-z15A . (0 1 2 3 4 6 8 9))
(8-z15B . (0 1 3 5 6 7 8 9))
(8-16A . (0 1 2 3 5 7 8 9))
(8-16B . (0 1 2 4 6 7 8 9))
(8-17 . (0 1 3 4 5 6 8 9))
(8-18A . (0 1 2 3 5 6 8 9))
(8-18B . (0 1 3 4 6 7 8 9))
(8-19A . (0 1 2 4 5 6 8 9))
(8-19B . (0 1 3 4 5 7 8 9))
(8-20 . (0 1 2 4 5 7 8 9))
(8-21 . (0 1 2 3 4 6 8 10))
(8-22A . (0 1 2 3 5 6 8 10))
(8-22B . (0 1 2 3 5 7 9 10))
(8-23 . (0 1 2 3 5 7 8 10))
(8-24 . (0 1 2 4 5 6 8 10))
(8-25 . (0 1 2 4 6 7 8 10))
(8-26 . (0 1 3 4 5 7 8 10))
(8-27A . (0 1 2 4 5 7 8 10))
(8-27B . (0 1 2 4 6 7 9 10))
(8-28 . (0 1 3 4 6 7 9 10))
(8-z29A . (0 1 2 3 5 6 7 9))
(8-z29B . (0 2 3 4 6 7 8 9))
(9-1 . (0 1 2 3 4 5 6 7 8))
(9-2A . (0 1 2 3 4 5 6 7 9))
(9-2B . (0 2 3 4 5 6 7 8 9))
(9-3A . (0 1 2 3 4 5 6 8 9))
(9-3B . (0 1 3 4 5 6 7 8 9))
(9-4A . (0 1 2 3 4 5 7 8 9))
(9-4B . (0 1 2 4 5 6 7 8 9))
(9-5A . (0 1 2 3 4 6 7 8 9))
(9-5B . (0 1 2 3 5 6 7 8 9))
(9-6 . (0 1 2 3 4 5 6 8 10))
(9-7A . (0 1 2 3 4 5 7 8 10))
(9-7B . (0 1 2 3 4 5 7 9 10))
(9-8A . (0 1 2 3 4 6 7 8 10))
(9-8B . (0 1 2 3 4 6 8 9 10))
(9-9 . (0 1 2 3 5 6 7 8 10))
(9-10 . (0 1 2 3 4 6 7 9 10))
(9-11A . (0 1 2 3 5 6 7 9 10))
(9-11B . (0 1 2 3 5 6 8 9 10))
(9-12 . (0 1 2 4 5 6 8 9 10))
(10-1 . (0 1 2 3 4 5 6 7 8 9))
(10-2 . (0 1 2 3 4 5 6 7 8 10))
(10-3 . (0 1 2 3 4 5 6 7 9 10))
(10-4 . (0 1 2 3 4 5 6 8 9 10))
(10-5 . (0 1 2 3 4 5 7 8 9 10))
(10-6 . (0 1 2 3 4 6 7 8 9 10))
(11-1 . (0 1 2 3 4 5 6 7 8 9 10))
(12-1 . (0 1 2 3 4 5 6 7 8 9 10 11)))
forte =
#'((0-1 . ())
(1-1 . (0))
(2-1 . (0 1))
(2-2 . (0 2))
(2-3 . (0 3))
(2-4 . (0 4))
(2-5 . (0 5))
(2-6 . (0 6))
(3-1 . (0 1 2))
(3-10 . (0 3 6))
(3-11 . (0 3 7))
(3-11 . (0 4 7))
(3-12 . (0 4 8))
(3-2 . (0 1 3))
(3-2 . (0 2 3))
(3-3 . (0 1 4))
(3-3 . (0 3 4))
(3-4 . (0 1 5))
(3-4 . (0 4 5))
(3-5 . (0 1 6))
(3-5 . (0 5 6))
(3-6 . (0 2 4))
(3-7 . (0 2 5))
(3-7 . (0 3 5))
(3-8 . (0 2 6))
(3-8 . (0 4 6))
(3-9 . (0 2 7))
(4-1 . (0 1 2 3))
(4-10 . (0 2 3 5))
(4-11 . (0 1 3 5))
(4-11 . (0 2 4 5))
(4-12 . (0 2 3 6))
(4-12 . (0 3 4 6))
(4-13 . (0 1 3 6))
(4-13 . (0 3 5 6))
(4-14 . (0 2 3 7))
(4-14 . (0 4 5 7))
(4-16 . (0 1 5 7))
(4-16 . (0 2 6 7))
(4-17 . (0 3 4 7))
(4-18 . (0 1 4 7))
(4-18 . (0 3 6 7))
(4-19 . (0 1 4 8))
(4-19 . (0 3 4 8))
(4-20 . (0 1 5 8))
(4-21 . (0 2 4 6))
(4-22 . (0 2 4 7))
(4-22 . (0 3 5 7))
(4-23 . (0 2 5 7))
(4-24 . (0 2 4 8))
(4-25 . (0 2 6 8))
(4-26 . (0 3 5 8))
(4-27 . (0 2 5 8))
(4-27 . (0 3 6 8))
(4-28 . (0 3 6 9))
(4-2 . (0 1 2 4))
(4-2 . (0 2 3 4))
(4-3 . (0 1 3 4))
(4-4 . (0 1 2 5))
(4-4 . (0 3 4 5))
(4-5 . (0 1 2 6))
(4-5 . (0 4 5 6))
(4-6 . (0 1 2 7))
(4-7 . (0 1 4 5))
(4-8 . (0 1 5 6))
(4-9 . (0 1 6 7))
(4-z15 . (0 1 4 6))
(4-z15 . (0 2 5 6))
(4-z29 . (0 1 3 7))
(4-z29 . (0 4 6 7))
(5-1 . (0 1 2 3 4))
(5-10 . (0 1 3 4 6))
(5-10 . (0 2 3 5 6))
(5-11 . (0 2 3 4 7))
(5-11 . (0 3 4 5 7))
(5-13 . (0 1 2 4 8))
(5-13 . (0 2 3 4 8))
(5-14 . (0 1 2 5 7))
(5-14 . (0 2 5 6 7))
(5-15 . (0 1 2 6 8))
(5-16 . (0 1 3 4 7))
(5-16 . (0 3 4 6 7))
(5-19 . (0 1 3 6 7))
(5-19 . (0 1 4 6 7))
(5-20 . (0 1 5 6 8))
(5-20 . (0 2 3 7 8))
(5-21 . (0 1 4 5 8))
(5-21 . (0 3 4 7 8))
(5-22 . (0 1 4 7 8))
(5-23 . (0 2 3 5 7))
(5-23 . (0 2 4 5 7))
(5-24 . (0 1 3 5 7))
(5-24 . (0 2 4 6 7))
(5-25 . (0 2 3 5 8))
(5-25 . (0 3 5 6 8))
(5-26 . (0 2 4 5 8))
(5-26 . (0 3 4 6 8))
(5-27 . (0 1 3 5 8))
(5-27 . (0 3 5 7 8))
(5-28 . (0 2 3 6 8))
(5-28 . (0 2 5 6 8))
(5-29 . (0 1 3 6 8))
(5-29 . (0 2 5 7 8))
(5-2 . (0 1 2 3 5))
(5-2 . (0 2 3 4 5))
(5-30 . (0 1 4 6 8))
(5-30 . (0 2 4 7 8))
(5-31 . (0 1 3 6 9))
(5-31 . (0 2 3 6 9))
(5-32 . (0 1 4 6 9))
(5-32 . (0 3 5 8 9))
(5-33 . (0 2 4 6 8))
(5-34 . (0 2 4 6 9))
(5-35 . (0 2 4 7 9))
(5-3 . (0 1 2 4 5))
(5-3 . (0 1 3 4 5))
(5-4 . (0 1 2 3 6))
(5-4 . (0 3 4 5 6))
(5-5 . (0 1 2 3 7))
(5-5 . (0 4 5 6 7))
(5-6 . (0 1 2 5 6))
(5-6 . (0 1 4 5 6))
(5-7 . (0 1 2 6 7))
(5-7 . (0 1 5 6 7))
(5-8 . (0 2 3 4 6))
(5-9 . (0 1 2 4 6))
(5-9 . (0 2 4 5 6))
(5-z12 . (0 1 3 5 6))
(5-z17 . (0 1 3 4 8))
(5-z18 . (0 1 4 5 7))
(5-z18 . (0 2 3 6 7))
(5-z36 . (0 1 2 4 7))
(5-z36 . (0 3 5 6 7))
(5-z37 . (0 3 4 5 8))
(5-z38 . (0 1 2 5 8))
(5-z38 . (0 3 6 7 8))
(6-1 . (0 1 2 3 4 5))
(6-14 . (0 1 3 4 5 8))
(6-14 . (0 3 4 5 7 8))
(6-15 . (0 1 2 4 5 8))
(6-15 . (0 3 4 6 7 8))
(6-16 . (0 1 4 5 6 8))
(6-16 . (0 2 3 4 7 8))
(6-18 . (0 1 2 5 7 8))
(6-18 . (0 1 3 6 7 8))
(6-20 . (0 1 4 5 8 9))
(6-21 . (0 2 3 4 6 8))
(6-21 . (0 2 4 5 6 8))
(6-22 . (0 1 2 4 6 8))
(6-22 . (0 2 4 6 7 8))
(6-27 . (0 1 3 4 6 9))
(6-27 . (0 3 5 6 8 9))
(6-2 . (0 1 2 3 4 6))
(6-2 . (0 2 3 4 5 6))
(6-30 . (0 1 3 6 7 9))
(6-30 . (0 2 3 6 8 9))
(6-31 . (0 1 4 5 7 9))
(6-31 . (0 2 4 5 8 9))
(6-32 . (0 2 4 5 7 9))
(6-33 . (0 2 3 5 7 9))
(6-33 . (0 2 4 6 7 9))
(6-34 . (0 1 3 5 7 9))
(6-34 . (0 2 4 6 8 9))
(6-35 . (0 2 4 6 8 10))
(6-5 . (0 1 2 3 6 7))
(6-5 . (0 1 4 5 6 7))
(6-7 . (0 1 2 6 7 8))
(6-8 . (0 2 3 4 5 7))
(6-9 . (0 1 2 3 5 7))
(6-9 . (0 2 4 5 6 7))
(6-z10 . (0 1 3 4 5 7))
(6-z10 . (0 2 3 4 6 7))
(6-z11 . (0 1 2 4 5 7))
(6-z11 . (0 2 3 5 6 7))
(6-z12 . (0 1 2 4 6 7))
(6-z12 . (0 1 3 5 6 7))
(6-z13 . (0 1 3 4 6 7))
(6-z17 . (0 1 2 4 7 8))
(6-z17 . (0 1 4 6 7 8))
(6-z19 . (0 1 3 4 7 8))
(6-z19 . (0 1 4 5 7 8))
(6-z23 . (0 2 3 5 6 8))
(6-z24 . (0 1 3 4 6 8))
(6-z24 . (0 2 4 5 7 8))
(6-z25 . (0 1 3 5 6 8))
(6-z25 . (0 2 3 5 7 8))
(6-z26 . (0 1 3 5 7 8))
(6-z28 . (0 1 3 5 6 9))
(6-z29 . (0 2 3 6 7 9))
(6-z36 . (0 1 2 3 4 7))
(6-z36 . (0 3 4 5 6 7))
(6-z37 . (0 1 2 3 4 8))
(6-z38 . (0 1 2 3 7 8))
(6-z39 . (0 2 3 4 5 8))
(6-z39 . (0 3 4 5 6 8))
(6-z3 . (0 1 2 3 5 6))
(6-z3 . (0 1 3 4 5 6))
(6-z4 . (0 1 2 4 5 6))
(6-z40 . (0 1 2 3 5 8))
(6-z40 . (0 3 5 6 7 8))
(6-z41 . (0 1 2 3 6 8))
(6-z41 . (0 2 5 6 7 8))
(6-z42 . (0 1 2 3 6 9))
(6-z43 . (0 1 2 5 6 8))
(6-z43 . (0 2 3 6 7 8))
(6-z44 . (0 1 2 5 6 9))
(6-z44 . (0 3 4 7 8 9))
(6-z45 . (0 2 3 4 6 9))
(6-z46 . (0 1 2 4 6 9))
(6-z46 . (0 3 5 7 8 9))
(6-z47 . (0 1 2 4 7 9))
(6-z47 . (0 2 5 7 8 9))
(6-z48 . (0 1 2 5 7 9))
(6-z49 . (0 1 3 4 7 9))
(6-z50 . (0 1 4 6 7 9))
(6-z6 . (0 1 2 5 6 7))
(7-1 . (0 1 2 3 4 5 6))
(7-10 . (0 1 2 3 4 6 9))
(7-10 . (0 2 3 4 5 6 9))
(7-11 . (0 1 3 4 5 6 8))
(7-11 . (0 2 3 4 5 7 8))
(7-13 . (0 1 2 4 5 6 8))
(7-13 . (0 2 3 4 6 7 8))
(7-14 . (0 1 2 3 5 7 8))
(7-14 . (0 1 3 5 6 7 8))
(7-15 . (0 1 2 4 6 7 8))
(7-16 . (0 1 2 3 5 6 9))
(7-16 . (0 1 3 4 5 6 9))
(7-19 . (0 1 2 3 6 7 9))
(7-19 . (0 1 2 3 6 8 9))
(7-20 . (0 1 2 5 6 7 9))
(7-20 . (0 1 2 5 7 8 9))
(7-21 . (0 1 2 4 5 8 9))
(7-21 . (0 1 3 4 5 8 9))
(7-22 . (0 1 2 5 6 8 9))
(7-23 . (0 2 3 4 5 7 9))
(7-23 . (0 2 4 5 6 7 9))
(7-24 . (0 1 2 3 5 7 9))
(7-24 . (0 2 4 6 7 8 9))
(7-25 . (0 2 3 4 6 7 9))
(7-25 . (0 2 3 5 6 7 9))
(7-26 . (0 1 3 4 5 7 9))
(7-26 . (0 2 4 5 6 8 9))
(7-27 . (0 1 2 4 5 7 9))
(7-27 . (0 2 4 5 7 8 9))
(7-28 . (0 1 3 5 6 7 9))
(7-28 . (0 2 3 4 6 8 9))
(7-29 . (0 1 2 4 6 7 9))
(7-29 . (0 2 3 5 7 8 9))
(7-2 . (0 1 2 3 4 5 7))
(7-2 . (0 2 3 4 5 6 7))
(7-30 . (0 1 2 4 6 8 9))
(7-30 . (0 1 3 5 7 8 9))
(7-31 . (0 1 3 4 6 7 9))
(7-31 . (0 2 3 5 6 8 9))
(7-32 . (0 1 3 4 6 8 9))
(7-32 . (0 1 3 5 6 8 9))
(7-33 . (0 1 2 4 6 8 10))
(7-34 . (0 1 3 4 6 8 10))
(7-35 . (0 1 3 5 6 8 10))
(7-3 . (0 1 2 3 4 5 8))
(7-3 . (0 3 4 5 6 7 8))
(7-4 . (0 1 2 3 4 6 7))
(7-4 . (0 1 3 4 5 6 7))
(7-5 . (0 1 2 3 5 6 7))
(7-5 . (0 1 2 4 5 6 7))
(7-6 . (0 1 2 3 4 7 8))
(7-6 . (0 1 4 5 6 7 8))
(7-7 . (0 1 2 3 6 7 8))
(7-7 . (0 1 2 5 6 7 8))
(7-8 . (0 2 3 4 5 6 8))
(7-9 . (0 1 2 3 4 6 8))
(7-9 . (0 2 4 5 6 7 8))
(7-z12 . (0 1 2 3 4 7 9))
(7-z17 . (0 1 2 4 5 6 9))
(7-z18 . (0 1 4 5 6 7 9))
(7-z18 . (0 1 4 6 7 8 9))
(7-z36 . (0 1 2 3 5 6 8))
(7-z36 . (0 2 3 5 6 7 8))
(7-z37 . (0 1 3 4 5 7 8))
(7-z38 . (0 1 2 4 5 7 8))
(7-z38 . (0 1 3 4 6 7 8))
(8-1 . (0 1 2 3 4 5 6 7))
(8-10 . (0 2 3 4 5 6 7 9))
(8-11 . (0 1 2 3 4 5 7 9))
(8-11 . (0 2 4 5 6 7 8 9))
(8-12 . (0 1 3 4 5 6 7 9))
(8-12 . (0 2 3 4 5 6 8 9))
(8-13 . (0 1 2 3 4 6 7 9))
(8-13 . (0 2 3 5 6 7 8 9))
(8-14 . (0 1 2 4 5 6 7 9))
(8-14 . (0 2 3 4 5 7 8 9))
(8-16 . (0 1 2 3 5 7 8 9))
(8-16 . (0 1 2 4 6 7 8 9))
(8-17 . (0 1 3 4 5 6 8 9))
(8-18 . (0 1 2 3 5 6 8 9))
(8-18 . (0 1 3 4 6 7 8 9))
(8-19 . (0 1 2 4 5 6 8 9))
(8-19 . (0 1 3 4 5 7 8 9))
(8-20 . (0 1 2 4 5 7 8 9))
(8-21 . (0 1 2 3 4 6 8 10))
(8-22 . (0 1 2 3 5 6 8 10))
(8-22 . (0 1 2 3 5 7 9 10))
(8-23 . (0 1 2 3 5 7 8 10))
(8-24 . (0 1 2 4 5 6 8 10))
(8-25 . (0 1 2 4 6 7 8 10))
(8-26 . (0 1 3 4 5 7 8 10))
(8-27 . (0 1 2 4 5 7 8 10))
(8-27 . (0 1 2 4 6 7 9 10))
(8-28 . (0 1 3 4 6 7 9 10))
(8-2 . (0 1 2 3 4 5 6 8))
(8-2 . (0 2 3 4 5 6 7 8))
(8-3 . (0 1 2 3 4 5 6 9))
(8-4 . (0 1 2 3 4 5 7 8))
(8-4 . (0 1 3 4 5 6 7 8))
(8-5 . (0 1 2 3 4 6 7 8))
(8-5 . (0 1 2 4 5 6 7 8))
(8-6 . (0 1 2 3 5 6 7 8))
(8-7 . (0 1 2 3 4 5 8 9))
(8-8 . (0 1 2 3 4 7 8 9))
(8-9 . (0 1 2 3 6 7 8 9))
(8-z15 . (0 1 2 3 4 6 8 9))
(8-z15 . (0 1 3 5 6 7 8 9))
(8-z29 . (0 1 2 3 5 6 7 9))
(8-z29 . (0 2 3 4 6 7 8 9))
(9-1 . (0 1 2 3 4 5 6 7 8))
(9-10 . (0 1 2 3 4 6 7 9 10))
(9-11 . (0 1 2 3 5 6 7 9 10))
(9-11 . (0 1 2 3 5 6 8 9 10))
(9-12 . (0 1 2 4 5 6 8 9 10))
(9-2 . (0 1 2 3 4 5 6 7 9))
(9-2 . (0 2 3 4 5 6 7 8 9))
(9-3 . (0 1 2 3 4 5 6 8 9))
(9-3 . (0 1 3 4 5 6 7 8 9))
(9-4 . (0 1 2 3 4 5 7 8 9))
(9-4 . (0 1 2 4 5 6 7 8 9))
(9-5 . (0 1 2 3 4 6 7 8 9))
(9-5 . (0 1 2 3 5 6 7 8 9))
(9-6 . (0 1 2 3 4 5 6 8 10))
(9-7 . (0 1 2 3 4 5 7 8 10))
(9-7 . (0 1 2 3 4 5 7 9 10))
(9-8 . (0 1 2 3 4 6 7 8 10))
(9-8 . (0 1 2 3 4 6 8 9 10))
(9-9 . (0 1 2 3 5 6 7 8 10))
(10-1 . (0 1 2 3 4 5 6 7 8 9))
(10-2 . (0 1 2 3 4 5 6 7 8 10))
(10-3 . (0 1 2 3 4 5 6 7 9 10))
(10-4 . (0 1 2 3 4 5 6 8 9 10))
(10-5 . (0 1 2 3 4 5 7 8 9 10))
(10-6 . (0 1 2 3 4 6 7 8 9 10))
(11-1 . (0 1 2 3 4 5 6 7 8 9 10))
(12-1 . (0 1 2 3 4 5 6 7 8 9 10 11)))
% Setup of forte-dictionary as a vector of alists in which the
% keys are normal/prime forms of PC sets.
#(define forte-dictionary (make-vector 13))
#(define (set-pc-style all-sets)
(for-each
(lambda (n)
(let ((all-n-sets
(filter
(lambda (entry) (eq? (length (cdr entry)) n))
all-sets)))
(vector-set!
forte-dictionary n
(map
(lambda (number-set-pair)
(cons (cdr number-set-pair)
(symbol->string (car number-set-pair))))
all-n-sets))))
(iota 13)))
#(set-pc-style forte)
% ---------------------- General tools ----------------------------------------
#(define (assoc-list alist keys)
"Return the first entry in alist whose for the first possible key in keys. Keys are compared using equal?"
; "Vertical" searching (first choose a key and use assoc for this key) seems to be faster
; than "horizontal" searching.
(if (pair? keys)
(let* ((first-key (car keys))
(other-keys (cdr keys))
(instance (assoc first-key alist)))
(if instance
instance
(assoc-list alist other-keys)))
#f))
#(define* (comma-separate-strings lst #:optional (separator ","))
(if (pair? lst)
(if (pair? (cdr lst))
(string-append (car lst)
separator
(comma-separate-strings (cdr lst) separator))
(car lst))
""))
#(define (group-classes lst equivalent?)
; groups list in sublists of equivalent elements.
; equivalent? is assumed to be an equivalence relation
; TODO: Keep order (at the moment, reversed list is returned)
; (Solution: Wrap in let loop?)
(if (pair? lst)
(let* ((head
(car lst))
(tail-groups
(group-classes (cdr lst) equivalent?))
(group-of-head
(list-index (lambda (group) (equivalent? (car group) head))
tail-groups)))
(if group-of-head
(begin
(list-set! tail-groups group-of-head
(cons head (list-ref tail-groups group-of-head)))
tail-groups)
(cons (list head) tail-groups)))
'()))
#(define (all-numbers from to)
(iota (1+ (abs (- to from))) (min from to)))
#(define (sort-with-valuation lst val less)
; val is a valuation function on lst
; returns sorted list of pairs (x . (val x))
; The sort is stable.
(stable-sort!
(map (lambda (x) (cons x (val x))) lst)
(lambda (p q) (less (cdr p) (cdr q)))))
#(define (sort-by-valuation lst val less)
(map car (sort-with-valuation lst val less)))
#(define (print-and-return val)
(pretty-print val)
val)
#(define (range set) (- (last set) (first set)))
#(define (set-find-pattern set pattern)
; given a pattern (a b ...), return the first sublist
; of set starting with an element x such that
; x+a, x+b etc. are also in set.
; set is assumed to be sorted!
; Returns #f (not empty list) if pattern is not found!
(if (pair? set)
(if (every
(lambda (k) (member (+ (car set) k) set))
pattern)
set
(set-find-pattern (cdr set) pattern))
#f))
#(define (set-replace! set old new)
; replaces old by new in set.
; set is assumed to be (strictly) sorted and will be sorted!
; may operate destructively, but is NOT guaranteed to
; modify set in-place as desired!
; TODO: At the moment, no check is new is already in set!
(let ((sublist (member old set)))
(if (pair? sublist)
(begin
(list-set! sublist 0 new)
(sort! set <))
set)))
#(define (mark-duplicates els)
; given a list of elements, produces a same-length list of
; booleans indicating, for each element of the given list;
; if it has a twin in the list.
; e.g. input '(1 2 3 4 1 5 6 3)
; output '(#t #f #t #f #t #f #f #t)
(if (pair? els)
(let* ((head (car els))
(tail (cdr els))
(tail-duplicates (mark-duplicates tail))
(twin-in-tail (list-index (lambda (n) (eq? n head)) tail)))
(if twin-in-tail
(begin
(list-set! tail-duplicates twin-in-tail #t)
(cons #t tail-duplicates))
(cons #f tail-duplicates)))
'()))
#(define (lexicographic less) ; should less be assumed to be strictly less or less-or-equal?
(lambda (x y)
(cond ((null? x) #t)
((null? y) #f)
((equal? (car x) (car y)) ; which notion of equality should be used?
((lexicographic less) (cdr x) (cdr y)))
(else (less (car x) (car y))))))
#(define (lexicographic-by-size less) ; should less be assumed to be strictly less or less-or-equal?
(lambda (x y)
(let* ((length-x (length x))
(length-y (length y)))
(if (eq? length-x length-y)
(if (null? x)
#t ; arbitrary, might also be #f?
(if (equal? (car x) (car y)) ; which notion of equality should be used?
((lexicographic-by-size less) (cdr x) (cdr y))
(less (car x) (car y))))
(< length-x length-y)))))
#(define (strictly-sorted? numlist)
(cond ((null? numlist) #t)
((null? (cdr numlist)) #t)
(else (and (< (car numlist) (cadr numlist))
(strictly-sorted? (cdr numlist))))))
#(define (vector-increase-at! pos vec)
(vector-set! vec pos (1+ (vector-ref vec pos)))
vec)
% ---------------------- The chromatic_clash_engraver ----------------------------------
% This engraver, to be used in voice or staff contexts, forces the use of
% accidentals if diatonic clashes (such as c + cis) occur.
chromatic_clash_engraver =
#(lambda (ctx)
; writing the engraver as a context-dependent lambda
; makes sure that the notes are collected per-staff
; if the engraver is \consist'ed to all \Staff contexts.
(let ((note-events '()))
(make-engraver
(listeners
((note-event engraver event)
(set! note-events (cons event note-events))))
((process-music translator)
(let* ((pitches (map (lambda (ev) (ly:event-property ev 'pitch))
note-events))
(pitch-steps (map ly:pitch-steps pitches))
(clashes (mark-duplicates pitch-steps)))
(for-each (lambda (ev clash?)
(if clash? (ly:event-set-property! ev 'force-accidental #t)))
note-events clashes))
(set! note-events '())))))
% ---------------------- Basic PC set definitions -----------------------------
% "Chromatic pitches" are integers where 0 = middle c.
% A PC-set shall always be
% a) be sorted,
% b) made up of elements {0,...,11},
% c) without duplicates.
% normalize-pc-set guarantees a) and b).
% make-pc-set guarantees a)-c).
% PC sets made by make-pc-set are guaranteed to fulfill a)-c).
% A PC-set is _not_ assumed to always be rooted in 0.
% (i.e. a PC-set is an ordered subset of Z/12, but without
% identification of translates).
#(define (pitch-class? n)
(and (index? n)
(< n 12)))
#(define (pc-set? set)
(and (every pitch-class? set)
(strictly-sorted? set)))
% May be used instead of make-pc-set if input set is guaranteed
% not to contain duplicate pitch classes
#(define (normalize-pc-set set)
(sort (map (lambda (p) (modulo p 12)) set) <))
#(define (make-pc-set notes) (delete-duplicates (normalize-pc-set notes)))
% ---------------------- Elementary PC set operations --------------------------
#(define (set+ . sets) ; robust
(make-pc-set (apply append sets)))
#(define (set- set-a set-b) ; assumes conditions a)-c)
(filter (lambda (n) (not (memq n set-b))) set-a))
#(define chromatic-total (iota 12))
#(define (set-complement set) ; assumes conditions a)-c)
(filter (lambda (n) (not (memq n set))) chromatic-total))
% all-subsets does not produce a natural ordering of subsets.
% Maybe lexicographic-by-size?
#(define (all-subsets set)
(if (null? set)
(list set)
(let
((head (car set))
(subsets-without-head (all-subsets (cdr set))))
(append (map (lambda (subset) (cons head subset)) subsets-without-head)
subsets-without-head))))
#(define (all-supersets set)
(map (lambda (summand) (set+ set summand))
(all-subsets (set-complement set))))
% ---------------------- Interval vector operations ----------------------------
#(define (interval-class p1 p2)
; Calculates the intervall class in {0,...,6} between two chromatic pitches
(let ((diff (modulo (- p1 p2) 12)))
(if (> diff 6)
(- 12 diff)
diff)))
#(define (interval-vector set)
(if (pair? set)
(let* ((head (car set))
(tail (cdr set))
(work-vector (interval-vector tail)))
(for-each
(lambda (k)
(vector-increase-at! (1- (interval-class head k)) work-vector))
tail)
work-vector)
(make-vector 6 0)))
#(define (format-interval-vector vect)
(string-append
"<"
(string-concatenate (map number->string (vector->list vect)))
">"))
#(define (format-interval-vector-with-comma vect)
(string-append
"<"
(comma-separate-strings (map number->string (vector->list vect)))
">"))
% ---------------------- Unsorted operations -----------------------------------
#(define (zerobase-pc-set set)
(map (lambda (p) (- p (car set))) set))
#(define (rotate-pc-set set) ; TODO: Does not accept empty set at the moment.
(if (and (pair? set) ; test if set is non-empty ...
(pair? (cdr set))) ; ... and contains more than one element.
(normalize-pc-set (map (lambda (p) (- p (cadr set))) set))
set))
#(define (all-rotations set)
; generates list of all rotations of pitch class set, each zero-based.
(if (null? set)
(list set)
(fold (lambda (n lst)
(cons (rotate-pc-set (car lst)) lst)) ; ignore counter, add rotated version of list-head to list
(list (zerobase-pc-set set)) ; start with zerobased-version of set
(iota (1- (length set))))))
#(define (normal-forms set)
; Returns list of normal forms, i.e. rotations with minimal overall interval range
; expects a zero-based set!
; The list is unsorted (whereas a recursive lexicocraphic sorting might produce some kind of prime form as well).
; TODO: This function works with last's a lot; it might be more efficient to produce reversed rotations and work with car's.
(let* ((rotations (all-rotations set))
(ranges (map (lambda (rotation) (last rotation)) rotations))
(minimum-range (apply min ranges)))
(filter (lambda (rotation) (= (last rotation) minimum-range)) rotations)))
% What's the difference between sort and sort-list?
#(define (recurse-sets add-length base-part proc)
; In this function, pc-sets are ordered right-to-left for recursion.
; A right-hand base-part is amended at the start
; If recursion is complete, proc is called for the completed pc sets.
(if (zero? add-length)
(proc (reverse base-part))
(let ((new-minimum (1+ (car base-part))))
(for-each
(lambda (n) (recurse-sets (1- add-length) (cons n base-part) proc))
(iota (- 12 new-minimum) new-minimum)))))
#(define (for-each-zerobased-pc-set proc n)
(if (positive? n)
(recurse-sets (1- n) '(0) proc)
(proc '())))
#(define (set-equivalent? set-a set-b)
; tests transposition equivalence
; expects sets with condition a), b)
; returns empty list as #f value
(member (zerobase-pc-set set-b) (all-rotations set-a)))
#(define (set-invert set) (zerobase-pc-set (normalize-pc-set (map - set))))
#(define (set-transpose set k) (normalize-pc-set (map (lambda (p) (+ p k)) set)))
#(define (prime-form set) ; This is not the actual prime form in case of ambiguity!
(car (sort (normal-forms set) (lexicographic <))))
#(define (lookup-primeform-fortenumber set)
(let* ((rotations (all-rotations (make-pc-set set)))
(dictionary (vector-ref forte-dictionary (length (car rotations)))))
(assoc-list dictionary rotations)))
#(define (lookup-fortenumber set) (cdr (lookup-primeform-fortenumber set)))
#(define (lookup-primeform set) (car (lookup-primeform-fortenumber set)))
#(define (format-set set)
(string-append
"["
(comma-separate-strings (map number->string set))
"]"))
#(define (format-hex-set set)
(string-upcase (string-concatenate
(map (lambda (x) (number->string x 16)) set))))
#(define (generate-js-dictionary port)
(define* (format-js-variable-assignment variable-value)
(let ((variable (car variable-value))
(value (cdr variable-value)))
(if (list? value)
(string-append (format #f " ~s: [\n" variable)
(comma-separate-strings
(map (lambda (entry)
(format #f " ~s" entry))
value)
",\n")
(format #f "\n ]"))
(format #f " ~s: ~s" variable value))))
(define (format-js-variable-assignments variable-value-list)
(display (comma-separate-strings
(map format-js-variable-assignment variable-value-list) ",\n")
port))
(define (hex-prime-form set)
(format-hex-set (lookup-primeform set)))
(format port "export default {\n")
(map (lambda (n)
(map
(lambda (primeform-fortename)
(let* ((primeform (car primeform-fortename))
(fortename (cdr primeform-fortename))
(subsets (delete-duplicates (map hex-prime-form (all-subsets primeform))))
(supersets (delete-duplicates (map hex-prime-form (all-supersets primeform)))))
(format port " ~s: {\n" (format-hex-set primeform))
(format-js-variable-assignments
(list (cons "forteName" fortename)
(cons "complement" (lookup-fortenumber
(set-complement primeform)))
(cons "intervalVector" (format-interval-vector-with-comma
(interval-vector (car primeform-fortename))))
(cons "subSets" subsets)
(cons "superSets" supersets)))
(format port "\n },\n")))
(vector-ref forte-dictionary n)))
(iota 13))
(format port "};\n")
(ly:warning "Don't forget to remove last comma in .js output!"))
% ---------------------- LilyPond interface ------------------------------------
forteNumber =
#(define-scheme-function (set) (pc-set?) (lookup-fortenumber set))
tablePrimeForm =
#(define-scheme-function (set) (pc-set?)
(markup (format-set (car (lookup-primeform-fortenumber set)))))
addChordForteNumber =
#(define-music-function (mus) (ly:music?)
(if (music-is-of-type? mus 'event-chord)
#{
$mus - \forteNumber \PCset #mus #}
mus))
addChordPrimeForm =
#(define-music-function (mus) (ly:music?)
(if (music-is-of-type? mus 'event-chord)
#{
<<
$mus
\context Lyrics = "pf" \lyricmode {
\markup { \rotate #30 \tiny \typewriter \tablePrimeForm \PCset #mus }
}
>>
#}
mus))
addChordPrimeFormAndIntervalVector =
#(define-music-function (mus) (ly:music?)
(if (music-is-of-type? mus 'event-chord)
(let ((set (PCset mus)))
#{
<<
$mus
\context Lyrics = "pf" \lyricmode {
\markup { \rotate #30 \teeny \bold \typewriter \tablePrimeForm #set }
}
\context Lyrics = "iv" \lyricmode {
\markup { \rotate #30 \teeny \typewriter #(format-interval-vector (interval-vector set)) }
}
>>
#})
mus))
PCset =
#(define-scheme-function (notes) (ly:music?)
(make-pc-set (map ly:pitch-semitones (music-pitches notes))))
% ---------------------- Circle of fifths tools --------------------------------
% PC set = set of chromatic pitch classes in Z/12Z (c = 0, c# = 1 etc.)
% CFPC set = circle of fifth chromatic pitch classes in Z/12Z (d = 0, a = 1 etc.)
% SFPC set = stack of fifth diatonic pitch classes in Z (d = 0)
%
% C/SFPC pitch classes are centered on 0 = d since that way
% the white keys are
% { f = -3, c = -2, g = -1, d = 0, a = 1, e = 2, b = 3 }
#(define cfpc-set? pc-set?)
% Internally, both sets are represented by (wlog ordered) sets in Z/12Z
#(define (sfpc-set? set)
(and (every integer? set)
(strictly-sorted? set)))
#(define make-cfpc-set make-pc-set)
#(define (pc-set->cfpc-set set)
(make-cfpc-set (map (lambda (p) (* (- p 2) 7)) set)))
#(define (cfpc-set->pc-set set)
(make-pc-set (map (lambda (p) (+ (* p 7) 2)) set)))
#(define (accidental-count sfpc)
; sfpc is a stack-of-fifth class, 0 = d.
(round (/ sfpc 7)))
#(define (accidental-weight n)
; 0 accidentals: weight 0
; 1 accidental: weight 3
; 2 accidentals: weight 15
(1- (ash 1 (* 2 (abs n)))))
#(define (sfpc-set-accidental-weight set)
(apply +
(map (lambda (k) (accidental-weight (accidental-count k))) set)))
#(define (all-sfpc-liftings-of-cfpc-set set)
; expects a chromatic fifths set, sorted, elements in 0..11.
; the resulting list is sorted by range (last - first)
(sort
(fold
(lambda (n prev)
(cons
(cdr (append (car prev) (list (+ 12 (caar prev)))))
prev))
(list set)
(iota (1- (length set))))
(lambda (p q) (< (range p) (range q)))))
#(define (cfpc-set->sfpc-set set)
; Lifts a CFPC set to a SFPC set.
; The lifted set is guaranteed to map to cfpc-set via Z -> Z/12Z
; The lifting is chosen as follows:
; - First, a lifting with least possible fifth ranges is chosen
; - Then, we try to eliminate doubly-chromatic clashes
; - Lastly, we move by multiples of 12 in order to center around 0
; as far as possible
(let* ((lifted-set (car (all-sfpc-liftings-of-cfpc-set set))))
; (range (last lifted-set))
; (shift (truncate (/ range 2))))
(do ((i 1 (1+ i)))
((> i 3)) ; Three rounds of optimization seem to suffice ...
; TODO: The following four operations repeat code in a bad way...
; Turn (c cis d) into (his cis d), i.e. (0 2 7) into (2 7 12)
(let ((c-cis-d (set-find-pattern lifted-set '(2 7))))
(if c-cis-d
(set! lifted-set (set-replace! lifted-set (car c-cis-d) (+ 12 (car c-cis-d))))))
; Turn (h c cis) into (h c des), i.e. (0 5 7) into (0 5 -5)
(let ((c-h-cis (set-find-pattern lifted-set '(5 7))))
(if c-h-cis
(set! lifted-set (set-replace! lifted-set (+ 7 (car c-h-cis)) (+ -5 (car c-h-cis))))))
; Turn (c cis dis) into (his cis dis), i.e. (0 7 9) into (7 9 12)
(let ((c-cis-dis (set-find-pattern lifted-set '(7 9))))
(if c-cis-dis
(set! lifted-set (set-replace! lifted-set (car c-cis-dis) (+ 12 (car c-cis-dis))))))
; Turn (c d dis) into (c d es), i.e. (0 2 9) into (0 2 3)
(let ((c-d-dis (set-find-pattern lifted-set '(2 9))))
(if c-d-dis
(set! lifted-set (set-replace! lifted-set (+ 9 (car c-d-dis)) (+ -3 (car c-d-dis))))))
) ; end of stupid loop to do 4 passes of optimization
(let* ((mean (/ (+ (first lifted-set) (last lifted-set)) 2))
(correction (* 12 (round (/ mean 12)))))
(map (lambda (p) (- p correction)) lifted-set))))
#(define (whitecenter-sfpc-set set)
; transposes an sfpc set in such a way that we prefer white
; keys and make double accidentals expensive.
; Idea: Given a sorted sfpc-set (a b ... z),
; left-most candidate arises by shifting by z to the left,
; right-most candidate arises by shifting by a to the left.
; Hence: Add all indices from (-z, ..., -a)
(first (sort-by-valuation
(map (lambda (shift)
(map (lambda (x) (- x shift)) set))
(all-numbers (first set) (last set)))
sfpc-set-accidental-weight
<)))
#(define (sfpc->pitch p)
(let* ((mittle-d (ly:make-pitch 0 1))
(fifth-up (ly:make-pitch 0 4))
(fifth-down (ly:make-pitch -1 3))
(step (if (negative? p) fifth-down fifth-up)))
(car (fold
(lambda (n lst) (cons (ly:pitch-transpose (car lst) step) lst))
(list mittle-d)
(iota (abs p))))))
chordFromCFPCset =
#(define-music-function (set) (cfpc-set?)
#{
% Should add a function to sanitize chords
% in order to remove f-fis-clashes.
< $@(map normalize-pitch (map sfpc->pitch (whitecenter-sfpc-set (cfpc-set->sfpc-set set)))) >
#})
niceChordFromPCset =
#(define-music-function (set) (pc-set?)
(chordFromCFPCset (pc-set->cfpc-set set)))
% ---------------------- Tests and examples ------------------------------------
%{
#(map (lambda (entry)
(let ((prime-form (car entry))
(number (cdr entry)))
(format #t "~a ~a\n" number (lookup-fortenumber (set-complement prime-form)))))
(vector-ref forte-dictionary 5))
%}
#(set-pc-style forte-distinct-inverses)
%{
\fixed c'
{
\musicMap #addChordForteNumber {
\textLengthOn
\accidentalStyle modern
\time 1/4
\omit Stem
\omit Score.TimeSignature
\omit Score.BarLine
\override TextScript.baseline-skip = 2.4
<c es g>^Molldreiklang
<c e g>^Durdreiklang
<c e gis>^\markup\column{übermäßiger Dreiklang}
<c es ges>^\markup\column{verminderter Dreiklang}
<c d g>^"sus2"
<c f g>^"sus4"
<c f bes>^Doppelquarte
<c f b>^\markup\column{Viennese Trichord}
<c fis b>^\markup\column{Viennese Trichord?}
% <c e bes>
% <c ges bes>
% <c e b>
% <c g b>
\once\undo\omit Score.BarLine \bar "."
<cis e g b>^"Halbverminderter"
<c e g bes>^"Dominantseptakkord"
<c es g bes>^"moll7"
<cis e g bes>^"Verminderter"
<c e g b>^"maj7"
<c e g d'>^"add9"
<c es g d'>^"m-add9"
\once\undo\omit Score.BarLine \bar "."
}
}
%}
PCsetChord =
#(define-music-function (set) (pc-set?)
#{
<
$@(map (lambda (n)
(make-music
'NoteEvent
'duration
(ly:make-duration 2)
'pitch
(normalize-and-respell-pitch (ly:make-pitch 0 0 (/ n 2)))))
set)
>
#}
)
\paper {
system-system-spacing.padding = 5
}
\layout {
\context {
\Staff
\consists \chromatic_clash_engraver
}
\accidentalStyle modern
\textLengthOn
\omit Score.TimeSignature
\override TextScript.direction = #UP
\override TextScript.self-alignment-X = #CENTER
\override TextScript.parent-alignment-X = #CENTER
\override TextScript.X-align-on-main-noteheads = ##t
\override TextScript.font-size = -1
\omit Score.BarNumber
}
%{
\new Staff {
\time 1/4
$@
(map addChordForteNumber
(map addChordPrimeForm
(map niceChordFromPCset
(map car (vector-ref forte-dictionary 5)))))
}
%}
#(define (list-of-pc-set-tuples n)
; Yields the list of all n-sets in the current forte-dictionary
; in groups of set, inverse and (if n = 6) complement.
(group-classes
(map car (vector-ref forte-dictionary n))
(if (equal? n 6)
(lambda (set-a set-b)
(or (set-equivalent? set-a set-b)
(set-equivalent? set-a (set-invert set-b))
(set-equivalent? set-a (set-complement set-b))
(set-equivalent? set-a (set-complement (set-invert set-b)))))
(lambda (set-a set-b)
(or (set-equivalent? set-a set-b)
(set-equivalent? set-a (set-invert set-b)))))))
allSets =
#(define-void-function (n) (index?)
(add-score
#{
\score {
\layout {
indent = 0
ragged-last = ##t
\override Lyrics.VerticalAxisGroup.nonstaff-nonstaff-spacing.padding = 0.7
\override Lyrics.VerticalAxisGroup.nonstaff-nonstaff-spacing.minimum-distance = 0
}
\new Staff {
\set Timing.defaultBarType = ""
\textLengthOn
\time 1/4
$@(map (lambda (list-of-sets) #{
$@
(map addChordPrimeFormAndIntervalVector
(map addChordForteNumber
(map niceChordFromPCset list-of-sets)))
\bar "|" #})
(list-of-pc-set-tuples n))
}
} #}
))
%{
"027": {
"rahnPrimeForm": "027",
"fortePrimeForm": "027",
"intervalVector": "010020",
"forteName": "3-9",
"cardinality": 3,
"zMate": null
}
%}
#(format #t "~a ~a\n" (get-internal-real-time) (get-internal-run-time))
#(let ((port (open-output-file "list.js")))
(generate-js-dictionary port)
(close-port port))
#(format #t "~a ~a\n" (get-internal-real-time) (get-internal-run-time))
%{
\book {
\paper {
page-breaking = #ly:one-page-breaking
score-system-spacing.padding = 15
}
\header {
tagline = ##f
}
#(for-each allSets (iota 12 1))
}
%}