summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Ellis <michael.f.ellis@gmail.com>2011-02-17 11:06:14 +0000
committerTrevor Daniels <t.daniels@treda.co.uk>2011-02-17 11:06:14 +0000
commitfd7ad42f98d7e6117958a41e74cd736e396fb933 (patch)
tree8850d26f1f6564859cc3f093ef5020fb7d409592
parent37b7e122c049a954e5c5a91d5cac78004e77bb35 (diff)
Add modal transformations
-rw-r--r--Documentation/changes.tely30
-rw-r--r--Documentation/notation/pitches.itely168
-rw-r--r--input/regression/modal-transforms.ly34
-rw-r--r--ly/music-functions-init.ly40
-rw-r--r--scm/lily.scm1
-rw-r--r--scm/modal-transforms.scm222
6 files changed, 484 insertions, 11 deletions
diff --git a/Documentation/changes.tely b/Documentation/changes.tely
index f2a4b4c797..002f6a89cf 100644
--- a/Documentation/changes.tely
+++ b/Documentation/changes.tely
@@ -62,6 +62,36 @@ which scares away people.
@end ignore
@item
+A minimal composer toolkit of modal transformations is provided.
+A motif may be @notation{transposed}, @notation{inverted} and/or
+converted to its @notation{retrograde} within any scale.
+
+@lilypond
+pentatonicScale = \relative a' { a c d f g }
+motif = \relative c'' { d8 c f,4 <a f'> <a f'> }
+
+\new Staff <<
+ {
+ \partial 4
+ \pentatonicScale
+ \motif
+ \modalTranspose c a, \pentatonicScale \motif
+ \modalInversion d'' a' \pentatonicScale \motif
+ \retrograde \motif
+ }
+ {
+ \partial 4
+ s4^"pentatonic scale"
+ s1
+ s1^"motif"
+ s1^"transposition"
+ s1^"inversion"
+ s1^"retrograde"
+ }
+>>
+@end lilypond
+
+@item
Black mensural notation has minimal support.
@item
diff --git a/Documentation/notation/pitches.itely b/Documentation/notation/pitches.itely
index 0db01c8bde..78a222794d 100644
--- a/Documentation/notation/pitches.itely
+++ b/Documentation/notation/pitches.itely
@@ -594,6 +594,7 @@ This section discusses how to modify pitches.
@menu
* Octave checks::
* Transpose::
+* Modal transformations::
@end menu
@node Octave checks
@@ -788,6 +789,7 @@ see @ref{Instrument transpositions}.
@seealso
Notation Reference:
@ref{Relative octave entry},
+@ref{Modal transformations},
@ref{Instrument transpositions}.
Snippets:
@@ -811,6 +813,170 @@ The relative conversion will not affect @code{\transpose},
To use relative mode within transposed music, an additional
@code{\relative} must be placed inside @code{\transpose}.
+@node Modal transformations
+@unnumberedsubsubsec Modal transformations
+
+@cindex modal transformations
+@cindex transformations, modal
+@cindex operations, modal
+
+In a musical composition that is based on a scale, a motif is
+frequently transformed in various ways. It may be
+@notation{transposed} to start at different places in the scale, it
+may be @notation{inverted} around a pivot point in the scale, and/or
+it may be converted to its @notation{retrograde} (written backwards).
+
+@warning{Any note that does not lie within the given scale will be
+left untransformed.}
+
+@subsubheading Modal transposition
+
+@cindex modal transposition
+@cindex transposition, modal
+@cindex operation, transposition
+@funindex \modalTranspose
+@funindex modalTranspose
+
+A motif can be transposed within a given scale with:
+
+@example
+\modalTranspose @var{from-pitch} @var{to-pitch} @var{scale} @var{motif}
+@end example
+
+The notes of @var{motif} are shifted within the @var{scale} by the
+number of scale degrees given by the interval between @var{to-pitch}
+and @var{from-pitch}:
+
+@lilypond[verbatim,quote]
+diatonicScale = \relative c' { c d e f g a b }
+motif = \relative c' { c8 d e f g a b c }
+
+\new Staff {
+ \motif
+ \modalTranspose c f \diatonicScale \motif
+ \modalTranspose c b, \diatonicScale \motif
+}
+@end lilypond
+
+An ascending scale of any length and with any intervals may be
+specified:
+
+@lilypond[verbatim,quote]
+pentatonicScale = \relative c' { ges aes bes des ees }
+motif = \relative c' { ees8 des ges,4 <ges' bes,> <ges bes,> }
+
+\new Staff {
+ \motif
+ \modalTranspose ges ees' \pentatonicScale \motif
+}
+@end lilypond
+
+When used with a chromatic scale @code{\modalTranspose} has a
+similar effect to @code{\transpose}, but with the ability to
+specify the names of the notes to be used:
+
+@lilypond[verbatim,quote]
+chromaticScale = \relative c' { c cis d dis e f fis g gis a ais b }
+motif = \relative c' { c8 d e f g a b c }
+
+\new Staff {
+ \motif
+ \transpose c f \motif
+ \modalTranspose c f \chromaticScale \motif
+}
+@end lilypond
+
+@subsubheading Modal inversion
+
+@cindex modal inversion
+@cindex inversion, modal
+@cindex operation, inversion
+@funindex \modalInversion
+@funindex modalInversion
+
+A motif can be inverted within a given scale around a given pivot
+note and transposed in a single operation with:
+
+@example
+\modalInversion @var{around-pitch} @var{to-pitch} @var{scale} @var{motif}
+@end example
+
+The notes of @var{motif} are placed the same number of scale degrees
+from the @var{around-pitch} note within the @var{scale}, but in the
+opposite direction, and the result is then shifted within the
+@var{scale} by the number of scale degrees given by the interval between
+@var{to-pitch} and @var{around-pitch}.
+
+So to simply invert around a note in the scale use the same value for
+@var{around-pitch} and @var{to-pitch}:
+
+@lilypond[verbatim,quote]
+octatonicScale = \relative c' { ees f fis gis a b c d }
+motif = \relative c' { c8. ees16 fis8. a16 b8. gis16 f8. d16 }
+
+\new Staff {
+ \motif
+ \modalInversion fis' fis' \octatonicScale \motif
+}
+@end lilypond
+
+To invert around a pivot between two notes in the scale, invert around
+one of the notes and then transpose by one scale degree. The two notes
+specified can be interpreted as bracketing the pivot point:
+
+@lilypond[verbatim,quote]
+scale = \relative c' { c g' }
+motive = \relative c' { c c g' c, }
+
+\new Staff {
+ \motive
+ \modalInversion c' g' \scale \motive
+}
+@end lilypond
+
+
+@subsubheading Retrograde transformation
+
+@cindex retrograde transformation
+@cindex transformation, retrograde
+@cindex operation, retrograde
+@funindex \retrograde
+@funindex retrograde
+
+A motif can be reversed to produce its retrograde:
+
+@lilypond[verbatim,quote]
+motif = \relative c' { c8. ees16( fis8. a16 b8.) gis16 f8. d16 }
+
+\new Staff {
+ \motif
+ \retrograde \motif
+}
+@end lilypond
+
+The combined operation of inversion and retrograde produce the
+retrograde-inversion:
+
+@lilypond[verbatim,quote]
+octatonicScale = \relative c' { ees f fis gis a b c d }
+motif = \relative c' { c8. ees16 fis8. a16 b8. gis16 f8. d16 }
+
+\new Staff {
+ \motif
+ \retrograde \modalInversion c' c' \octatonicScale \motif
+}
+@end lilypond
+
+@seealso
+Notation Reference:
+@ref{Transpose}.
+
+@knownissues
+Manual ties inside @code{\retrograde} will be broken and
+generate warnings. Some ties can be generated automatically
+by enabling @ref{Automatic note splitting}.
+
+
@node Displaying pitches
@subsection Displaying pitches
@@ -2791,5 +2957,3 @@ Internals Reference:
@rinternals{Pitch_squash_engraver},
@rinternals{Voice},
@rinternals{RhythmicStaff}.
-
-
diff --git a/input/regression/modal-transforms.ly b/input/regression/modal-transforms.ly
new file mode 100644
index 0000000000..9070601510
--- /dev/null
+++ b/input/regression/modal-transforms.ly
@@ -0,0 +1,34 @@
+\version "2.13.51"
+\header {
+ texidoc = "\modalTranspose, \retrograde and \modalInversion work
+for an octatonic motif."
+}
+
+cOctatonicScale = {
+ c' d' ees' f'
+ ges' aes' a' b'
+}
+motif = {
+ c'8. ees'16 ges'8. a'16
+ b'8. aes'16 f'8. d'16
+}
+
+\score {
+ \new Staff {
+ \time 4/4
+ <<
+ {
+ \motif
+ \modalTranspose c' f' \cOctatonicScale \motif
+ \retrograde \motif
+ \modalInversion aes' b' \cOctatonicScale \motif
+ }
+ {
+ s1-"Octatonic motif" |
+ s1-"motif transposed from c to f" |
+ s1-"motif in retrograde" |
+ s1-"motif inverted around aes to b" |
+ }
+ >>
+ }
+}
diff --git a/ly/music-functions-init.ly b/ly/music-functions-init.ly
index fc49725f07..34261c114b 100644
--- a/ly/music-functions-init.ly
+++ b/ly/music-functions-init.ly
@@ -214,14 +214,14 @@ clef =
compoundMeter =
#(define-music-function (parser location args) (pair?)
- (_i "Create compound time signatures. The argument is a Scheme list of
-lists. Each list describes one fraction, with the last entry being the
-denominator, while the first entries describe the summands in the
-enumerator. If the time signature consists of just one fraction,
+ (_i "Create compound time signatures. The argument is a Scheme list of
+lists. Each list describes one fraction, with the last entry being the
+denominator, while the first entries describe the summands in the
+enumerator. If the time signature consists of just one fraction,
the list can be given directly, i.e. not as a list containing a single list.
-For example, a time signature of (3+1)/8 + 2/4 would be created as
-@code{\\compoundMeter #'((3 1 8) (2 4))}, and a time signature of (3+2)/8
-as @code{\\compoundMeter #'((3 2 8))} or shorter
+For example, a time signature of (3+1)/8 + 2/4 would be created as
+@code{\\compoundMeter #'((3 1 8) (2 4))}, and a time signature of (3+2)/8
+as @code{\\compoundMeter #'((3 2 8))} or shorter
@code{\\compoundMeter #'(3 2 8)}.")
(let* ((mlen (calculate-compound-measure-length args))
(beat (calculate-compound-base-beat args))
@@ -462,13 +462,29 @@ makeClusters =
(_i "Display chords in @var{arg} as clusters.")
(music-map note-to-cluster arg))
+modalInversion =
+#(define-music-function (parser location around to scale music)
+ (ly:music? ly:music? ly:music? ly:music?)
+ (_i "Invert @var{music} about @var{around} using @var{scale} and
+transpose from @var{around} to @var{to}.")
+ (let ((inverter (make-modal-inverter around to scale)))
+ (change-pitches music inverter)
+ music))
+
+modalTranspose =
+#(define-music-function (parser location from to scale music)
+ (ly:music? ly:music? ly:music? ly:music?)
+ (_i "Transpose @var{music} from pitch @var{from} to pitch @var{to}
+using @var{scale}.")
+ (let ((transposer (make-modal-transposer from to scale)))
+ (change-pitches music transposer)
+ music))
+
musicMap =
#(define-music-function (parser location proc mus) (procedure? ly:music?)
(_i "Apply @var{proc} to @var{mus} and all of the music it contains.")
(music-map proc mus))
-
-
%% noPageBreak and noPageTurn are music functions (not music indentifiers),
%% because music identifiers are not allowed at top-level.
noPageBreak =
@@ -780,6 +796,12 @@ resetRelativeOctave =
reference-note))
+retrograde =
+#(define-music-function (parser location music)
+ (ly:music?)
+ (_i "Return @var{music} in reverse order.")
+ (retrograde-music music))
+
revertTimeSignatureSettings =
#(define-music-function
(parser location time-signature)
diff --git a/scm/lily.scm b/scm/lily.scm
index 08e6561058..97b6c34c26 100644
--- a/scm/lily.scm
+++ b/scm/lily.scm
@@ -403,6 +403,7 @@ LilyPond safe mode. The syntax is the same as `define*-public'."
"chord-generic-names.scm"
"stencil.scm"
"markup.scm"
+ "modal-transforms.scm"
"music-functions.scm"
"part-combiner.scm"
"autochange.scm"
diff --git a/scm/modal-transforms.scm b/scm/modal-transforms.scm
new file mode 100644
index 0000000000..cdaa015ccc
--- /dev/null
+++ b/scm/modal-transforms.scm
@@ -0,0 +1,222 @@
+;;; modal-transforms.scm --- Modal transposition, inversion, and retrograde.
+
+;; Copyright (C) 2011 Ellis & Grant, Inc.
+
+;; Author: Michael Ellis <michael.f.ellis@gmail.com>
+
+;; COPYRIGHT NOTICE
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+;; for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program; if not, write to the Free Software
+;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+
+(define (transposer-factory scale)
+ "Returns a transposer for the specified @var{scale}.
+It is an error if either argument to a transposer is not in the scale
+it was created with. A transposer knows nothing about LilyPond
+internals. It treats scales as an ordered list of arbitrary items and
+pitches as members of a scale.
+"
+
+ (define (index item lis)
+ (list-index (lambda (x) (equal? item x)) lis))
+
+ (lambda (from-pitch to-pitch pitch)
+ (cond
+ ((not (member from-pitch scale))
+ (ly:warning (_i "'from' pitch not in scale; ignoring"))
+ pitch)
+
+ ((not (member to-pitch scale))
+ (ly:warning (_i "'to' pitch not in scale; ignoring"))
+ pitch)
+
+ ((not (member pitch scale))
+ (ly:warning (_i "pitch to be transposed not in scale; ignoring"))
+ pitch)
+
+ (else
+ (list-ref scale
+ (modulo
+ (+ (index pitch scale)
+ (- (index to-pitch scale)
+ (index from-pitch scale)))
+ (length scale)))))))
+
+(define (inverter-factory scale)
+ "Returns an inverter for the specified @var{scale}.
+It is an error if either argument to an inverter
+is not in the scale it was created with. An inverter knows nothing
+about LilyPond internals. It treats scales as an ordered list of
+arbitrary items and pitches as members of a scale.
+"
+
+ (define (index item lis)
+ (list-index (lambda (x) (equal? item x)) lis))
+
+ (lambda (around-pitch to-pitch pitch)
+ (cond
+ ((not (member around-pitch scale))
+ (ly:warning (_i "'around' pitch not in scale; ignoring"))
+ pitch)
+
+ ((not (member to-pitch scale))
+ (ly:warning (_i "'to' pitch not in scale; ignoring"))
+ pitch)
+
+ ((not (member pitch scale))
+ (ly:warning (_i "pitch to be inverted not in scale; ignoring"))
+ pitch)
+
+ (else
+ (list-ref scale
+ (modulo
+ (+ (index to-pitch scale)
+ (- (index around-pitch scale)
+ (index pitch scale)))
+ (length scale)))))))
+
+(define (replicate-modify lis n mod-proc)
+ "Apply @code{(mod-proc lis n)} to each element of a list and
+concatenate the results. Knows nothing of LilyPond internals."
+ (cond
+ ((< n 0)
+ (ly:warning (_i "negative replication count; ignoring")))
+ ((= n 0)
+ '())
+ ((= n 1)
+ (mod-proc lis 1))
+ ((> n 1)
+ (append
+ (replicate-modify lis (- n 1) mod-proc)
+ (mod-proc lis n)))))
+
+
+
+(define-public (change-pitches music converter)
+ "Recurse through @var{music}, applying @var{converter} to pitches.
+Converter is typically a transposer or an inverter as defined above in
+this module, but may be user-defined. The converter function must take
+a single pitch as its argument and return a new pitch. These are
+LilyPond scheme pitches, e.g. @code{(ly:make-pitch 0 2 0)}
+"
+ (let ((elements (ly:music-property music 'elements))
+ (element (ly:music-property music 'element))
+ (pitch (ly:music-property music 'pitch)))
+
+ (cond
+ ((ly:pitch? pitch)
+ (ly:music-set-property! music 'pitch (converter pitch)))
+
+ ((pair? elements)
+ (map (lambda (x) (change-pitches x converter)) elements))
+
+ ((ly:music? element)
+ (change-pitches element converter)))))
+
+
+(define (extract-pitch-sequence music)
+ "Recurse through @var{music}, extracting pitches.
+Returns a list of pitch objects, e.g
+@code{'((ly:make-pitch 0 2 0) (ly:make-pitch 0 4 0) ... )}
+Typically used to construct a scale for input to transposer-factory
+(see).
+"
+
+ (let ((elements (ly:music-property music 'elements))
+ (element (ly:music-property music 'element))
+ (pitch (ly:music-property music 'pitch)))
+
+ (cond
+ ((ly:pitch? pitch)
+ pitch)
+
+ ((pair? elements)
+ (map
+ (lambda (x) (extract-pitch-sequence x))
+ elements))
+
+ ((ly:music? element)
+ (extract-pitch-sequence element)))))
+
+(define (make-scale music)
+ "Convenience wrapper for extract-pitch-sequence."
+ (map car (extract-pitch-sequence music)))
+
+
+(define (make-extended-scale music)
+ "Extend scale given by @var{music} by 5 octaves up and down."
+ ;; This is a bit of a hack since, in theory, someone might want to
+ ;; transpose further than 5 octaves from the original scale
+ ;; definition. In practice this seems unlikely to occur very often.
+ (define extender
+ (lambda (lis n)
+ (map
+ (lambda (i)
+ (ly:make-pitch
+ (+ (- n 6) (ly:pitch-octave i))
+ (ly:pitch-notename i)
+ (ly:pitch-alteration i)))
+ lis)))
+
+ (let ((scale (make-scale music)))
+ (replicate-modify scale 11 extender)))
+
+
+;; ------------- PUBLIC FUNCTIONS -----------------------------
+
+(define-public (make-modal-transposer from-pitch to-pitch scale)
+ "Wrapper function for transposer-factory."
+ (let ((transposer (transposer-factory (make-extended-scale scale)))
+ (from (car (extract-pitch-sequence from-pitch)))
+ (to (car (extract-pitch-sequence to-pitch))))
+
+ (lambda (p)
+ (transposer from to p))))
+
+(define-public (make-modal-inverter around-pitch to-pitch scale)
+ "Wrapper function for inverter-factory"
+ (let ((inverter (inverter-factory (make-extended-scale scale)))
+ (around (car (extract-pitch-sequence around-pitch)))
+ (to (car (extract-pitch-sequence to-pitch))))
+
+ (lambda (p)
+ (inverter around to p))))
+
+
+(define-public (retrograde-music music)
+ "Returns @var{music} in retrograde (reversed) order."
+ ;; Copied from LSR #105 and renamed.
+ ;; Included here to allow this module to provide a complete set of
+ ;; common formal operations on motives, i.e transposition,
+ ;; inversion and retrograding.
+
+ (let* ((elements (ly:music-property music 'elements))
+ (reversed (reverse elements))
+ (element (ly:music-property music 'element))
+ (span-dir (ly:music-property music 'span-direction)))
+
+ (ly:music-set-property! music 'elements reversed)
+
+ (if (ly:music? element)
+ (ly:music-set-property!
+ music 'element
+ (retrograde-music element)))
+
+ (if (ly:dir? span-dir)
+ (ly:music-set-property! music 'span-direction (- span-dir)))
+
+ (map retrograde-music reversed)
+
+ music))