auto-sequence examples and analysis
The following examples process a set-palette created from a harmonic reduction by Emilios Cambouropoulos of Messiaen’s Quartet for the End of Time (Quartet VII).* The examples intermingle explanations of Common Lisp code and print statements with music notation output created directly from calls to slippery chicken’s Common Music Notation (CMN) routines.
The code can also be downloaded (try “save link as” rather than viewing the code in your browser—web browsers tend to garble line breaks when showing code written in plain text). Just be sure to run (update-app-src) before trying as I had to write some new code in order to simplify these examples.
Note that in order to show what might be called the raw algorithm at work, the repeating-bass avoidance algorithm (see part 1) is turned off in all examples. It was however turned on for all generations of material in jitterbug.
First of all we create the set-palette object we’ll be interrogating:
;;; Reduction of the first six bars of Messiaen’s Quartet for the End of Time, ;;; Quartet VII* (let* ((sp (make-set-palette 'sp '((1 ((bf3 c4 e4 df5))) (2 ((bf3 df4 gf4 c5))) (3 ((bf3 ef3 g4 a4))) (4 ((g3 c4 e4 a4))) (5 ((e3 a3 cs4 fs4))) (6 ((g3 a3 cs4 bf4))) (7 ((g3 bf3 ef4 a4))) (8 ((g3 c4 e4 fs4))) (9 ((cs3 fs3 as3 ds4)))))) (count 0)) ; just for naming graphics files
Next we define two local helper functions which allow us to avoid repeating function arguments, etc.:
(flet ((display (what) (cmn-display what :include-missing-chromatic nil :break-line-each-set nil :text-y-offset 1 :include-missing-non-chromatic nil)) (seq-map (centroid-env dissonance-env permutate) (let ((sm (make-set-map (format nil "set-map-~a" (incf count)) (auto-sequence sp :map-section 1 :permutate permutate :dissonance-env dissonance-env ;; we'll allow bass notes to repeat so we ;; can see the raw algorithm at work :verbose nil :repeating-bass t :centroid-env centroid-env)))) (bind-palette sm sp) sm))) (set-sc-config 'default-spectra 'akoustik-piano-spectra)
Now we’ll simply display the set-palette in the given order:
The following calls create printed output for information:
(format t "~&*** Dissonance values ascending (default piano spectra): ~%~a" (calculate-dissonance sp :sort t))
*** Dissonance values ascending (default piano spectra): (((3) 0.943890409317768d0) ((2) 0.9853051681697161d0) ((1) 1.0157344064133076d0) ((4) 1.0184718989548012d0) ((5) 1.141358173235601d0) ((7) 1.2290550594195926d0) ((8) 1.2717449079752114d0) ((6) 1.3435047613383702d0) ((9) 1.4817037428595998d0))
(format t "~&Centroid values ascending: ~%~a" (calculate-spectral-centroid sp :sort t))
Centroid values ascending: (((9) 540.8613295909009d0) ((5) 588.8115115588994d0) ((3) 610.0100103269881d0) ((6) 621.9567770851219d0) ((7) 626.8594112316986d0) ((8) 641.7953398271236d0) ((4) 642.5722062493335d0) ((1) 676.9391516417646d0) ((2) 689.4536760174341d0))
We know from the above sorted spectral centroid values that if we sequence our set-palette from lowest to highest then we should get the order 9 5 3 6 7 8 4 1 2. The (0 0 100 1) envelope passed as the desired spectral centroid movement represents this as best we can with such a data type (remember that the X axis can have any arbitrary range so we’re thinking in percent here; the Y axis ranges over 0.0 to 1.0, representing minimum to maximum detected values). So what we’re showing in the next graphic is that, in this instance at least, our auto-sequence method is aligned with the sorting method when using the default successive method:
(display (seq-map '(0 0 100 1) nil nil))
Now we’ll try the same thing with the dissonance envelope. From the printed values above, and continuing to use the piano spectra with 12 partials by default, we know that 9 6 8 7 5 4 1 2 3 is the decreasing order of dissonance. The successive method, going down in dissonance, aligns perfectly with this:
(display (seq-map nil '(0 1 100 0) nil))
Let’s see what happens when we get auto-sequence to go the other way: from lowest to highest dissonance. Again, from the printed values above we know that the order of dissonance is 3 2 1 4 5 7 8 6 9. What we can see below (3 1 4 5 7 8 6 9 2) is that auto-sequence’s default successive method returns something comparable, though not quite the same. The problem here, to the extent that there is one, lies in the way the successive method chooses the set which best fits the current point in the curve, without taking a global view of where perhaps that set might be best placed over the whole duration of the curve. This goes most spectacularly wrong in the placement of set 2, which our analysis method determined to be the 2nd most consonant set but which auto-sequence places at the very end of our ascending dissonance progression:
(display (seq-map nil '(0 0 100 1) nil))
This is where the question of the permutation method comes into focus. This takes permutations of all the sets from the palette and scores each ordering against the curves using a deviation method, as a form of fitness test. It returns the ordering with the best score (least deviation overall). By default it looks at 2000 permutations. This is what it returns when trying, again, to go from lowest to highest dissonance:
(display (seq-map nil '(0 0 100 1) t))
Not so impressive. (Remember again that the order of dissonance from low to high is 3 2 1 4 5 7 8 6 9).
However the default 2000 permutations processed doesn’t nearly cover the 362880 (9 factorial) possible permutations of our nine sets. Clearly we’re missing our ideal solution in those first 2000 permutations and so we end up with a suboptimal result. What happens if we score all possible permutations?
(display (seq-map nil '(0 0 100 1) 'all))
This is significantly better, especially as set 2 is in 2nd place, as with the sorting routine, but it’s still not the same result as the sorting routine. So what’s going wrong? Well, nothing, actually, and that’s why I’ve been writing about our sequencing routines aligning with the sort routine, not being right or wrong. To put it more plainly, we’re not sorting here, even when our curve is a straight line from minimum to maximum or vice-versa. What we’re doing is trying to find the best sequence with relation to one or two curves, that is, the sequence which hugs the curves with the least deviation from them. When looking at the deviation scores of all permutations, the above result is indeed the closest fit we have to the dissonance curve. (The sort order is actually in place 30.) That may still sound wrong but it’s all about the approach and what it is you’re trying to do. If you want to sort by dissonance or spectral centroid, that’s simple enough. But if you want to pass a curve with several or even many break points, the sorting routine is no longer capable (without considerable kneading) but our successive and permutation methods very much are.
Now let’s go back and look at the original ordering of dissonance. Our default analysis—again, using piano spectra and 12 partials—places set 9 as the most dissonant. This might come as a bit of a surprise, given that it’s essentially a 2nd inversion F# major triad with added sixth, a chord most jazz musicians would consider pretty consonant, at least in a different inversion. Surely set 1 for example, with its Bflat-C-Dflat or set 6, with its G-A-Bflat, are more dissonant? Well, again, thinking intervallically or functionally that may be true, but thinking and arguing spectrally for a moment, set 9 is the lowest set (as our spectral centroid analysis shows) so more of its partials will be down in the easily audible range wreaking havoc, as it were, and creating all sorts of interferences and beatings.
But what happens to the sets’ dissonance values when we look at each pitch as a sine wave? The following call shows that dissonance values are overall a lot lower than those calculated from 12 partials, as we might expect when thinking spectrally, and also that set 9 has moved from first to third place in the dissonance table:
;; it somehow feels wrong that 9 is the most dissonant set. what happens if ;; we only use 1 partial? (format t "~&Dissonance values descending with only one partial: ~%~a" (reverse (calculate-dissonance sp :num-partials 1 :sort t)))
Dissonance values descending with only one partial: (((8) 0.16283403717366568d0) ((6) 0.15741114923293267d0) ((9) 0.15612688076448514d0) ((1) 0.13720045634452163d0) ((5) 0.11952484697275971d0) ((7) 0.11363826513535183d0) ((3) 0.09297959042575726d0) ((2) 0.09041846555744328d0) ((4) 0.08807535888419944d0))
In the last call we were still using piano spectra, albeit with only one partial playing a role in the calculations. The following shows what happens when we switch to the violin ensemble spectra, namely, pretty much nothing:
(set-sc-config 'default-spectra 'violin-ensemble-spectra) ;; see bottom of spectra.lsp for the available spectra (format t "~&Dissonance values descending with only one partial and using ~ violin spectra: ~%~a" (reverse (calculate-dissonance sp :num-partials 1 :sort t)))
Dissonance values descending with only one partial and using violin spectra: (((8) 0.15847954089203245d0) ((6) 0.1551409849509306d0) ((9) 0.15328308985631217d0) ((1) 0.13457628730614093d0) ((5) 0.11542805529804544d0) ((7) 0.11092087330375007d0) ((3) 0.09110252740542797d0) ((2) 0.08797409664316036d0) ((4) 0.08477404741277736d0))
Yes, the dissonance values are slightly different—as we would expect from the slightly different and always imperfect tunings of real instruments—but the order is the same. With only one partial being used in the calculation, even on different instruments, if they’re pitched instruments we would hope to get the same or close to the same ordering of dissonance for any arbitrary collection/palette of different sets.
This raises another question: what’s the situation like with the violin spectra but using 12-partials again? The following calls create this output:
(format t "~&Dissonance values descending with 12 partials and using ~ violin spectra: ~%~a" (reverse (calculate-dissonance sp :num-partials 12 :sort t)))
Dissonance values descending with 12 partials and using violin spectra: (((8) 1.2382532227777618d0) ((1) 1.1352177902493907d0) ((6) 1.0851793051430236d0) ((9) 1.069710267705214d0) ((2) 1.048683042376949d0) ((5) 1.009007934804796d0) ((7) 0.9590334868795312d0) ((3) 0.9325752925750109d0) ((4) 0.8873721480041312d0))
This is then reflected in our successive ordering of dissonance values from high to low using the violin spectra and the successive method:
(display (seq-map nil '(0 1 100 0) nil))
This moves set 9 from third to fourth place—getting closer perhaps to where our intuition would have placed it. But before we start feeling vindicated we should note that the two lowest notes of set 9 are below the violin’s range so spectral data will be provided using the lowest available note (G3). This means that the relationship between the two faked lowest notes will be a perfect—from the perspective of the equal tempered scale at least, which is far from perfect, but that’s another story—perfect 4th, instead of a slightly detuned one, which it inevitably would be if we had actual data for the two separate notes. (For example, the first partial multiplier for G3 in our violin data is 0.989; for B-flat-3 it’s 1.012. If these were perfectly in tune both multipliers would be 1.0.)
and the moral of the story is?
The moral of the piano vs. violin spectra story is that dissonance values for any arbitrary set will be roughly comparable using different instruments’ spectral data as long as we only look at one partial. This means that sequencing based on one partial will generally be the same no matter which pitched instrument’s data we use. However, that is an artificial situation and probably not very useful for creating music. When we use 12 partials’ data from different instruments we get quite significant differences in both dissonance values and therefore sequence ordering using dissonance envelopes (I leave it to the reader to experiment with the influence of different instruments’ data on sequencing using spectral centroids) . So if you’re going to use auto-sequence to gain practical results, use spectral data for the (types of) instruments you’re writing for and, where possible, have data for the whole range of your sets. The latter point means it would probably be worthwhile using data from several instruments to cover the necessary range—a hybrid spectrum, as it were.
In lieu of an overall conclusion I might answer the obvious question of why I’d go to so much trouble to order a mere nine sets. Well, of course I wouldn’t. The whole point of auto-sequence is to deal with significantly larger and, more importantly, algorithmically generated set-palettes, such as the one used in jitterbug (below). This was generated by ring modulation routines, filtered, then auto-sequenced, as documented in another blog post and (…ahem..) clearly audible in the music.
* Taken from Cambouropoulos, Emilios. ‘The Harmonic Musical Surface and Two Novel Chord Representation Schemes’. In Computational Music Analysis, edited by David Meredith, 31–56. Cham: Springer International Publishing, 2016. http://link.springer.com/10.1007/978-3-319-25931-4_2.