Skip to content

how can i change pitches algorithmically?

Michael Edwards edited this page Jun 1, 2024 · 1 revision

How can I change pitches algorithmically?

I've made quite a few pieces with make-slippery-chicken where I'm not actually interested in the pitches generated, but I am very much interested in the rhythmic and related structures offered by the function and resultant object. In those cases it's necessary to change instruments' pitches post hoc, using post-generation editing techniques.

Below is an interesting example, prompted by Diana Ortiz, where a piece is first of all generated with make-slippery-chicken, then, according to a chance-curve which is automagically extrapolated over the whole piece, instruments' notes are changed to those of another player. In this way we can very easily control the amount of unison playing over all notes of a piece.

(in-package :sc)

;;; First let's generate an existing piece that we can clone and then act
;;; upon. Comment out and load tempus-perfectum.lsp by hand if working in
;;; another directory.
(load-from-same-dir "tempus-perfectum.lsp")
;;; To avoid warnings about globals in the code below.
(proclaim '(special +tempus-perfectum+))

(defmethod tendency-unison ((sc slippery-chicken)
                            &optional
                            (main-player 'ob)
                            ;; x values can range over whatever you like but y
                            ;; values are expressed as a percentage where 0 = no
                            ;; pitch change and 100 = definite pitch
                            ;; change. Change 'em all by default, just to prove
                            ;; it works.
                            (change-chance-env '(0 100 100 100)))
  (let ((all-events (get-events-sorted-by-time sc))
        (chance 0.0)
        (do-it nil)
        (main-player-note nil)
        (event-count 0)
        (changed-count 0))
    ;; we've used an x-axis from 0-100 (or whatever), but need to stretch this
    ;; over all the notes in the whole piece (including, for simplicity's sake
    ;; the main-player)
    (setq change-chance-env (new-lastx change-chance-env (1- (num-notes sc))))
    (loop for event in all-events do
             (when (needs-new-note event) ; only process attacked notes
               (if (eq main-player (player event))
                   ;; for simplicity's sake we'll just grab the last main-player
                   ;; note and use that to change subsequent non-main-player
                   ;; notes, accepting or even enjoying that, depending on
                   ;; rhythmic structure, we might be a little behind.
                   (setq main-player-note (pitch-or-chord event))
                   ;; don't change main-player's notes, and only change other
                   ;; player's notes if we've seen a main-player note already
                   (when main-player-note
                     ;; get the chance of changing 
                     (setq chance (interpolate event-count change-chance-env)
                           ;; determine whether to change the note to the
                           ;; main-player's note using fixed-seed
                           ;; randomness. Note that we reset the random
                           ;; generator when event-count is 0, i.e. on the first
                           ;; time through the loop. Note also the less-than
                           ;; test assumes that the Y values of the
                           ;; change-chance-env range from 0 to 100
                           do-it (< (random-rep 100.0 (zerop event-count))
                                    chance))
                     (when do-it
                       ;; bear in mind that no checks are made here that the
                       ;; instrument we're dealing with can actually play the
                       ;; main player's note! If we wanted to be more
                       ;; sophisticated, then we could use the in-range method
                       ;; and octavise where necessary.
                       (setf (pitch-or-chord event) main-player-note)
                       (incf changed-count))))
               (incf event-count)))
    ;; we've only changed attacked notes' pitches, so any tied-to notes will
    ;; be wrong! Helpfully the check-ties method takes care of fixing these :-)
    (check-ties sc t)
    ;; these two for statistics' sake really
    (update-slots sc)
    (update-instrument-slots sc)
    (format t "~&Changed ~a notes." changed-count)
    sc))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; now actually do it, cloning the original so as not to have to reload, and/or
;;; to enable easier comparisons.
(let ((sc (tendency-unison (clone +tempus-perfectum+))))
  (cmn-display sc :size 10
                  :in-c t))
Clone this wiki locally