Fixes Issue 1504, allowing feather beam line breaking.
[lilypond/patrick.git] / scm / modal-transforms.scm
blob151fb8c3be06edcea7e213291989398428d43fea
1 ;;; modal-transforms.scm --- Modal transposition, inversion, and retrograde.
3 ;; Copyright (C) 2011 Ellis & Grant, Inc.
5 ;; Author: Michael Ellis <michael.f.ellis@gmail.com>
7 ;; COPYRIGHT NOTICE
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
17 ;; for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program; if not, write to the Free Software
21 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
24 (define (transposer-factory scale)
25   "Returns a transposer for the specified @var{scale}.
26 It is an error if either argument to a transposer is not in the scale
27 it was created with.  A transposer knows nothing about LilyPond
28 internals.  It treats scales as an ordered list of arbitrary items and
29 pitches as members of a scale.
32   (define (index item lis)
33     (list-index (lambda (x) (equal? item x)) lis))
35   (lambda (from-pitch to-pitch pitch)
36     (cond
37      ((not (member from-pitch scale))
38       (ly:warning (_i "'from' pitch not in scale; ignoring"))
39       pitch)
41      ((not (member to-pitch scale))
42       (ly:warning (_i "'to' pitch not in scale; ignoring"))
43       pitch)
45      ((not (member pitch scale))
46       (ly:warning (_i "pitch to be transposed not in scale; ignoring"))
47       pitch)
49      (else
50       (list-ref scale
51                 (modulo
52                  (+ (index pitch scale)
53                     (- (index to-pitch scale)
54                        (index from-pitch scale)))
55                  (length scale)))))))
57 (define (inverter-factory scale)
58   "Returns an inverter for the specified @var{scale}.
59 It is an error if either argument to an inverter
60 is not in the scale it was created with.  An inverter knows nothing
61 about LilyPond internals.  It treats scales as an ordered list of
62 arbitrary items and pitches as members of a scale.
65   (define (index item lis)
66     (list-index (lambda (x) (equal? item x)) lis))
68   (lambda (around-pitch to-pitch pitch)
69     (cond
70      ((not (member around-pitch scale))
71       (ly:warning (_i "'around' pitch not in scale; ignoring"))
72       pitch)
74      ((not (member to-pitch scale))
75       (ly:warning (_i "'to' pitch not in scale; ignoring"))
76       pitch)
78      ((not (member pitch scale))
79       (ly:warning (_i "pitch to be inverted not in scale; ignoring"))
80       pitch)
82      (else
83       (list-ref scale
84                 (modulo
85                  (+ (index to-pitch scale)
86                     (- (index around-pitch scale)
87                        (index pitch scale)))
88                  (length scale)))))))
90 (define (replicate-modify lis n mod-proc)
91   "Apply @code{(mod-proc lis n)} to each element of a list and
92 concatenate the results.  Knows nothing of LilyPond internals."
93   (cond
94    ((< n 0)
95     (ly:warning (_i "negative replication count; ignoring")))
96    ((= n 0)
97     '())
98    ((= n 1)
99     (mod-proc lis 1))
100    ((> n 1)
101     (append
102      (replicate-modify lis (- n 1) mod-proc)
103      (mod-proc lis n)))))
107 (define-public (change-pitches music converter)
108   "Recurse through @var{music}, applying @var{converter} to pitches.
109 Converter is typically a transposer or an inverter as defined above in
110 this module, but may be user-defined.  The converter function must take
111 a single pitch as its argument and return a new pitch.  These are
112 LilyPond scheme pitches, e.g. @code{(ly:make-pitch 0 2 0)}
114   (let ((elements (ly:music-property music 'elements))
115         (element (ly:music-property music 'element))
116         (pitch (ly:music-property music 'pitch)))
118     (cond
119      ((ly:pitch? pitch)
120       (ly:music-set-property! music 'pitch (converter pitch)))
122      ((pair? elements)
123       (map (lambda (x) (change-pitches x converter)) elements))
125      ((ly:music? element)
126       (change-pitches element converter)))))
129 (define (extract-pitch-sequence music)
130   "Recurse through @var{music}, extracting pitches.
131 Returns a list of pitch objects, e.g
132 @code{'((ly:make-pitch 0 2 0) (ly:make-pitch 0 4 0) ... )}
133 Typically used to construct a scale for input to transposer-factory
134 (see).
137   (let ((elements (ly:music-property music 'elements))
138         (element (ly:music-property music 'element))
139         (pitch (ly:music-property music 'pitch)))
141     (cond
142      ((ly:pitch? pitch)
143       pitch)
145      ((pair? elements)
146       (map
147        (lambda (x) (extract-pitch-sequence x))
148        elements))
150      ((ly:music? element)
151       (extract-pitch-sequence element)))))
153 (define (make-scale music)
154   "Convenience wrapper for extract-pitch-sequence."
155   (map car (extract-pitch-sequence music)))
158 (define (make-extended-scale music)
159   "Extend scale given by @var{music} by 5 octaves up and down."
160   ;; This is a bit of a hack since, in theory, someone might want to
161   ;; transpose further than 5 octaves from the original scale
162   ;; definition.  In practice this seems unlikely to occur very often.
163   (define extender
164     (lambda (lis n)
165       (map
166        (lambda (i)
167          (ly:make-pitch
168           (+ (- n 6) (ly:pitch-octave i))
169           (ly:pitch-notename i)
170           (ly:pitch-alteration i)))
171        lis)))
173   (let ((scale (make-scale music)))
174     (replicate-modify scale 11 extender)))
177 ;; ------------- PUBLIC FUNCTIONS -----------------------------
179 (define-public (make-modal-transposer from-pitch to-pitch scale)
180   "Wrapper function for transposer-factory."
181   (let ((transposer (transposer-factory (make-extended-scale scale)))
182         (from (car (extract-pitch-sequence from-pitch)))
183         (to (car (extract-pitch-sequence to-pitch))))
185     (lambda (p)
186       (transposer from to p))))
188 (define-public (make-modal-inverter around-pitch to-pitch scale)
189   "Wrapper function for inverter-factory"
190   (let ((inverter (inverter-factory (make-extended-scale scale)))
191         (around (car (extract-pitch-sequence around-pitch)))
192         (to (car (extract-pitch-sequence to-pitch))))
194     (lambda (p)
195       (inverter around to p))))
198 (define-public (retrograde-music music)
199   "Returns @var{music} in retrograde (reversed) order."
200   ;; Copied from LSR #105 and renamed.
201   ;; Included here to allow this module to provide a complete set of
202   ;; common formal operations on motives, i.e transposition,
203   ;; inversion and retrograding.
205   (let* ((elements (ly:music-property music 'elements))
206          (reversed (reverse elements))
207          (element (ly:music-property music 'element))
208          (span-dir (ly:music-property music 'span-direction)))
210     (ly:music-set-property! music 'elements reversed)
212     (if (ly:music? element)
213         (ly:music-set-property!
214          music 'element
215          (retrograde-music element)))
217     (if (ly:dir? span-dir)
218         (ly:music-set-property! music 'span-direction (- span-dir)))
220     (map retrograde-music reversed)
222     music))
224 (define-public (pitch-invert around to music)
225   "If @var{music} is a single pitch, inverts it about @var{around}
226 and transposes from @var{around} to @var{to}."
227   (let ((p (ly:music-property music 'pitch)))
228     (if (ly:pitch? p)
229         (ly:music-set-property!
230          music 'pitch
231          (ly:pitch-transpose to (ly:pitch-diff around p))))
232     music))
234 (define-public (music-invert around-pitch to-pitch music)
235   "Applies pitch-invert to all pitches in @var{music}."
236   (let ((around (car (extract-pitch-sequence around-pitch)))
237         (to (car (extract-pitch-sequence to-pitch))))
238      (music-map (lambda (x) (pitch-invert around to x)) music)))