1 ;;; chord.scm -- to be included in/to replace chord-name.scm
2 ;;; 2000 janneke@gnu.org
7 ;; urg, these two only to guess if a '/' is needed to separate
8 ;; user-chord-name and additions/subtractions
13 ;; The regex module may not be available, or may be broken.
14 (define chord-use-regex
15 (let ((os (string-downcase (vector-ref (uname) 0))))
16 (not (equal? "cygwin" (substring os 0 (min 6 (string-length os)))))))
18 ;; If you have trouble with regex, define #f
19 (define chord-use-regex #t)
20 ;;(define chord-use-regex #f)
23 ;; (octave notename accidental)
28 ;; word: string + optional list of property
29 ;; property: size, style, font, super, offset
34 ;; * clean split of base/banter/american stuff
35 ;; * text definition is rather ad-hoc
36 ;; * do without format module
37 ;; * finish and check american names
38 ;; * make notename (tonic) configurable from mudela
39 ;; * fix append/cons stuff in inner-name-banter
44 (define chord::names-alist-banter '())
45 (set! chord::names-alist-banter
51 (((0 . 0) (2 . 0)) . #f)
53 (((0 . 0) (2 . -1)) . ("m"))
55 (((0 . 0) (1 . 0) (4 . 0)) . (("2" (type . "super"))))
57 (((0 . 0) (3 . 0) (4 . 0)) . (("4" (type . "super"))))
59 (((0 . 0) (2 . -1) (4 . -1)) . ("dim"))
62 (((0 . 0) (2 . -1) (4 . -1) (6 . -2)) . (("o" (type . "super"))))
64 (((0 . 0) (2 . -1) (4 . -1) (6 . -2) (1 . -1)) . ("dim" ("9" (type . "super"))))
65 (((0 . 0) (2 . -1) (4 . -1) (6 . -2) (1 . -1) (3 . -1)) . ("dim" ("11" (type . "super"))))
67 chord::names-alist-banter))
70 (define chord::names-alist-american '())
71 (set! chord::names-alist-american
75 (((0 . 0) (2 . 0)) . #f)
76 (((0 . 0) (2 . -1)) . ("m"))
77 (((0 . 0) (2 . -1) (4 . -1)) . ("dim"))
78 (((0 . 0) (4 . 0)) . (("5" (type . "super"))))
79 (((0 . 0) (3 . 0) (4 . 0)) . ("sus"))
80 (((0 . 0) (2 . -1) (4 . -1)) . (("o" (type . "super"))))
82 (((0 . 0) (2 . -1) (4 . -1) (6 . -2)) . (("o7" (type . "super"))))
83 ;jazz: the delta, see jazz-chords.ly
84 ;(((0 . 0) (2 . -1) (4 . -1) (6 . -2)) . (("N" (type . "super") (style . "msam") (size . -3))))
86 ;(((0 . 0) (2 . -1) (4 . -1) (6 . -1)) . (("x7" (type . "super"))))
88 (((0 . 0) (2 . -1) (4 . -1) (6 . -1)) . (("o" (type . "super")) ("/" (size . -2) (offset . (-0.58 . 0.5))) ("7" (type . "super"))))
90 (((0 . 0) (2 . 0) (4 . 1)) . ("aug"))
91 (((0 . 0) (2 . 0) (4 . 1) (6 . -1)) . (("aug" ("7" (type . "super")))))
93 (((0 . 0) (2 . 0) (4 . -1) (6 . 0)) . (("maj7" (type . "super")) ("accidentals--1" (font . "feta") (type . "super")) ("5" (type . "super"))))
95 (((0 . 0) (3 . 0) (4 . 0) (6 . -1)) . (("7sus4" (type . "super"))))
97 (((0 . 0) (2 . 0) (4 . 0) (5 . 0)) . (("maj6" (type . "super"))))
99 ;(((0 . 0) (2 . -1) (4 . 0) (5 . 0)) . ("m6" . ""))
102 ;;(((0 . 0) (2 . 0) (4 . 0) (8 . 0)) . ("add9" . ""))
103 ;;(((0 . 0) (2 . 0) (4 . 0) (1 . 0)) . ("" . (("script" . "add9"))))
105 ;; we don't want the '/no7'
106 ;;(((0 . 0) (2 . 0) (4 . 0) (5 . 0) (8 . 0)) . ("6/9" . ""))
107 ;;(((0 . 0) (2 . 0) (4 . 0) (5 . 0) (1 . 0)) . (("script" . "6/9"))))
110 ;;(((0 . 0) (2 . 0) (4 . 0) (6 . 0) (1 . 0)) . ("maj9" . ""))
113 chord::names-alist-american))
118 (define (pitch->note-name pitch)
119 (cons (cadr pitch) (caddr pitch)))
121 (define (pitch->text pitch)
123 (make-string 1 (integer->char (+ (modulo (+ (cadr pitch) 2) 7) 65)))
124 (if (= (caddr pitch) 0)
126 (list (list (string-append "accidentals-"
127 (number->string (caddr pitch)))
128 '(font . "feta"))))))
130 (define (step->text pitch)
132 (number->string (+ (cadr pitch) (if (= (car pitch) 0) 1 8)))
140 (define (pitch->text-banter pitch)
143 (define (step->text-banter pitch)
144 (if (= (cadr pitch) 6)
153 (define pitch::semitone-vec (list->vector '(0 2 4 5 7 9 11)))
155 (define (pitch::semitone pitch)
156 (+ (* (car pitch) 12)
157 (vector-ref pitch::semitone-vec (modulo (cadr pitch) 7))
160 (define (pitch::transpose pitch delta)
161 (let ((simple-octave (+ (car pitch) (car delta)))
162 (simple-notename (+ (cadr pitch) (cadr delta))))
163 (let ((octave (+ simple-octave (quotient simple-notename 7)))
164 (notename (modulo simple-notename 7)))
165 (let ((accidental (- (+ (pitch::semitone pitch) (pitch::semitone delta))
166 (pitch::semitone `(,octave ,notename 0)))))
167 `(,octave ,notename ,accidental)))))
169 (define (pitch::diff pitch tonic)
170 (let ((simple-octave (- (car pitch) (car tonic)))
171 (simple-notename (- (cadr pitch) (cadr tonic))))
172 (let ((octave (+ simple-octave (quotient simple-notename 7)
173 (if (< simple-notename 0) -1 0)))
174 (notename (modulo simple-notename 7)))
175 (let ((accidental (- (pitch::semitone pitch)
176 (pitch::semitone tonic)
177 (pitch::semitone `(,octave ,notename 0)))))
178 `(,octave ,notename ,accidental)))))
180 (define (pitch::note-pitch pitch)
181 (+ (* (car pitch) 7) (cadr pitch)))
183 (define (chord::step tonic pitch)
184 (- (pitch::note-pitch pitch) (pitch::note-pitch tonic)))
186 ;; text: list of word
187 ;; word: string + optional list of property
188 ;; property: align, kern, font (?), size
190 (define chord::minor-major-vec (list->vector '(0 -1 -1 0 -1 -1 0)))
192 ;; compute the relative-to-tonic pitch that goes with 'step'
193 (define (chord::step-pitch tonic step)
194 ;; urg, we only do this for thirds
195 (if (= (modulo step 2) 0)
197 (let loop ((i 1) (pitch tonic))
201 pitch `(0 2 ,(vector-ref chord::minor-major-vec
202 ;; -1 (step=1 -> vector=0) + 7 = 6
203 (modulo (+ i 6) 7)))))))))
205 ;; find the pitches that are not part of `normal' chord
206 (define (chord::additions chord-pitches)
207 (let ((tonic (car chord-pitches)))
208 ;; walk the chord steps: 1, 3, 5
209 (let loop ((step 1) (pitches chord-pitches) (additions '()))
211 (let* ((pitch (car pitches))
212 (p-step (+ (- (pitch::note-pitch pitch)
213 (pitch::note-pitch tonic))
215 ;; pitch is an addition if
217 ;; it comes before this step or
219 ;; its step is even or
220 (= (modulo p-step 2) 0)
221 ;; has same step, but different accidental or
223 (not (equal? pitch (chord::step-pitch tonic step))))
224 ;; is the last of the chord and not one of base thirds
226 (= (length pitches) 1)))
227 (loop step (cdr pitches) (cons pitch additions))
229 (loop step (cdr pitches) additions)
230 (loop (+ step 2) pitches additions))))
231 (reverse additions)))))
233 ;; find the pitches that are missing from `normal' chord
234 (define (chord::subtractions chord-pitches)
235 (let ((tonic (car chord-pitches)))
236 (let loop ((step 1) (pitches chord-pitches) (subtractions '()))
238 (let* ((pitch (car pitches))
239 (p-step (+ (- (pitch::note-pitch pitch)
240 (pitch::note-pitch tonic))
242 ;; pitch is an subtraction if
243 ;; a step is missing or
245 (loop (+ step 2) pitches
246 (cons (chord::step-pitch tonic step) subtractions))
247 ;; there are no pitches left, but base thirds are not yet done and
249 (= (length pitches) 1))
250 ;; present pitch is not missing step
252 (loop (+ step 2) pitches subtractions)
253 (loop (+ step 2) pitches
254 (cons (chord::step-pitch tonic step) subtractions)))
256 (loop (+ step 2) (cdr pitches) subtractions)
257 (loop step (cdr pitches) subtractions)))))
258 (reverse subtractions)))))
260 ;; combine tonic, user-specified chordname,
261 ;; additions, subtractions and base or inversion to chord name
263 (define (chord::inner-name-banter tonic user-name additions subtractions base-and-inversion)
264 (apply append (pitch->text-banter tonic)
265 (if user-name user-name '())
266 ;; why does list->string not work, format seems only hope...
267 (if (and chord-use-regex
268 (string-match "super" (format "~s" user-name))
269 (or (pair? additions)
270 (pair? subtractions)))
271 '(("/" (type . "super")))
273 (let loop ((from additions) (to '()))
275 (let ((p (car from)))
279 (cons (step->text-banter p) '((type . "super")))
280 (if (or (pair? (cdr from))
281 (pair? subtractions))
282 '(("/" (type . "super")))
285 (let loop ((from subtractions) (to '()))
287 (let ((p (car from)))
290 (cons '("no" (type . "super"))
292 (cons (step->text-banter p) '((type . "super")))
293 (if (pair? (cdr from))
294 '(("/" (type . "super")))
297 (if (and (pair? base-and-inversion)
298 (or (car base-and-inversion)
299 (cdr base-and-inversion)))
301 (if (car base-and-inversion)
303 (car base-and-inversion))
305 (cdr base-and-inversion)))
310 (define (chord::name-banter tonic user-name pitches base-and-inversion)
311 (let ((additions (chord::additions pitches))
312 (subtractions (chord::subtractions pitches)))
313 (chord::inner-name-banter tonic user-name additions subtractions base-and-inversion)))
315 ;; american chordnames use no "no",
316 ;; but otherwise very similar to banter for now
317 (define (chord::name-american tonic user-name pitches base-and-inversion)
318 (let ((additions (chord::additions pitches))
320 (chord::inner-name-banter tonic user-name additions subtractions base-and-inversion)))
324 ;; Check for each subset of chord, full chord first, if there's a
325 ;; user-override. Split the chord into user-overridden and to-be-done
326 ;; parts, complete the missing user-override matched part with normal
327 ;; chord to be name-calculated.
329 (define (chord::user-name style pitches base-and-inversion)
330 ;(display "pitches:") (display pitches) (newline)
331 ;(display "style:") (display style) (newline)
332 ;(display "b&i:") (display base-and-inversion) (newline)
333 (let ((diff (pitch::diff '(0 0 0) (car pitches)))
335 (ly-eval (string->symbol (string-append "chord::name-" style))))
337 (ly-eval (string->symbol (string-append "chord::names-alist-" style)))))
338 (let loop ((note-names (reverse pitches))
341 (if (pair? note-names)
345 (pitch->note-name (pitch::transpose x diff)))
349 ;; urg? found: break loop
350 (loop '() chord (cdr entry))
351 (loop (cdr note-names) (cons (car note-names) chord) #f)))
352 (let* ((transposed (if pitches
353 (map (lambda (x) (pitch::transpose x diff)) chord)
355 (matched (if (= (length chord) 0)
357 (- (length pitches) (length chord))))
359 (append (do ((i matched (- i 1))
360 (base '() (cons `(0 ,(* (- i 1) 2) 0) base)))
364 (name-func (car pitches) user-name completed base-and-inversion))))))