Slight revision. Some confusion about when functions can be called with
optional parameters. Anyway, works better now.
Joel
; scala.scm
; 20/12/2011
; joel at matthysmusic dot com
;-------------------------------------------------------------------
; LOAD SCALA FILES INTO fluxus
; the basic keynumber->frequency function is
; (scala-note [keynum])
; by default, the function is set up for equal temperament, so
; (scala-note 60)
; > 261.6255653006
;
; This means key number 60 = 261.62... Hz
; * (scala-note also accepts lists of notes)
; There are a number of preset scale options:
;
; equal - equal temperament (12 notes)
; just - just intonation (12 notes)
; mean - meantone temperament (12 notes)
; pythag - diatonic pythagorean scale (7 notes)
; partch - Harry Partch scale (43 notes)
;
; To see the available scales:
; (list-scales)
;
; To change scales, use:
; (set-scale "scale-name")
; You can also change the base frequency (diapason) and keynumber with
; (set-diapason) and (set-diapason-key)
;
; Here, I choose the Pythagorean scale, set the diapason to 200 Hz and set
; the keynum to 0:
; (set-scale "pythag")
; (set-diapason 200)
; (set-diapason-key 0)
; (scala-note 0)
; > 200
; To load a scala file, use
; (load-scala-file "scalafilename.scl" "scale-nickname")
; * You choose a nickname to keep track of the scale.
; * If you don't choose a nickname, the scale will be
; * nicknamed "scala"
;
; You can find out the description of the file with:
; scala-scale-description
;
; Example:
; (load-scala-file "scala/scl/bohlen-eg.scl" "bohlen")
; (set-scale "bohlen")
; (set-diapason 100)
; (set-diapason-key 0)
; (scala-note '(0 2 3 5 6 8 9 11 12 14 15))
; > (100 109.05... ... 436.203)
;
; * keynumbers must be integers except in equal temperament
;
;--------------------------------------------------------------------------
(define scala-scale-size 12)
(define scala-scale #f)
(define scala-scale-description "12 note equal tempered scale")
(define diapason 261.6255653006)
(define diapason-key 60)
(define (set-diapason n) (set! diapason n))
(define (set-diapason-key n) (set! diapason-key n))
(define scale-list '(("equal"
"12 note equal tempered scale"
12
#f)
("just"
"12 note just intoned scale"
12
(1 16/15 9/8 6/5 5/4 4/3 45/32 3/2 8/5 5/3 9/5 15/8 2))
("mean"
"12 note meantone scale"
12
(1 1.069984 1.118034 1.196279 1.25 1.337481 1.397542
1.495349 1.5625 1.671851 1.746928 1.869186 2))
("pythag"
"7 note Pure Pythagorean scale"
7
(1 9/8 81/64 4/3 3/2 27/16 243/128 2))
("partch"
"43 note Harry Partch scale"
43
(1 81/80 33/32 21/20 16/15 12/11
11/10 10/9 9/8 8/7 7/6 32/27 6/5 11/9 5/4 14/11
9/7 21/16 4/3 27/20 11/8 7/5 10/7 16/11 40/27 3/2
32/21 14/9 11/7 8/5 18/11 5/3 27/16 12/7 7/4 16/9
9/5 20/11 11/6 15/8 40/21 64/33 160/81 2))))
(define (set-scale name)
(set-scale-helper name scale-list))
(define (set-scale-helper name temp-scales)
(cond ((null? temp-scales) ;end of list, so it must be equal temp
(begin (set! scala-scale #f)
(set! diapason 261.6255653006)
(set! diapason-key 60)
(set! scala-scale-description "Equal temperament")))
((equal? name (caar temp-scales))
(set! scala-scale-size (list-ref (car temp-scales) 2))
(set! scala-scale (list-ref (car temp-scales) 3))
(set! scala-scale-description (list-ref (car temp-scales) 1)))
(else (set-scale-helper name (cdr temp-scales)))))
(define (parse-scale keynum)
(if (not scala-scale)
; equal temperament
(* diapason (expt 2 (/ (- keynum diapason-key) 12)))
; else custom scale
(if (integer? keynum)
(let ((scale-degree (modulo (- keynum diapason-key) scala-scale-size))
(scale-octave (floor (/ (- keynum diapason-key) scala-scale-size))))
(* diapason
(expt (list-ref scala-scale (- (length scala-scale) 1))
scale-octave)
(list-ref scala-scale scale-degree)))
(print "error: custom scales cannot use fractional key values."))))
(define (parse-scala-file filename)
(begin
(let ((output '()))
(call-with-input-file filename
(lambda (input-port)
(let loop ((x (read-line input-port)))
(if (not (eof-object? x))
(begin
(set! output (append output
(list x)))
(loop (read-line input-port))) #t))))
output)))
(define (remove-comment-lines input [result '()])
(cond ((null? input) result)
((equal? #\! (string-ref (car input) 0))
(remove-comment-lines (cdr input) result))
(else (remove-comment-lines (cdr input)
(append result
(list (car input)))))))
(define (log10 n) (/ (log n) (log 10)))
(define (cents->ratio c)
(expt 10 (* c (/ (log10 2) 1200))))
(define (scala-intervals input-file [result '(1)])
(if (null? input-file)
result
(let ((test (scala-numberize (string->list (car input-file)))))
(if (number? test)
(scala-intervals (cdr input-file)
(append result
(list test)))
(scala-intervals (cdr input-file) result)))))
; ok, this is ugly, but basically (scala-numberize) converts a list of
; characters into a number
(define (scala-numberize input [found-number #f] [found-period #f] [result '()])
(cond ((null? input) ; end of list
(cond ((not found-number) #f) ; no number found; therefore a comment
(found-period (cents->ratio ; per scala documentation, . = cents
(string->number (list->string result))))
(else (string->number (list->string result))))) ; ratio
(else
(let* ((current-char (car input))
(char-val (- (char->integer current-char) 48)))
(cond ((and (not found-number)
(= char-val -16)) ; leading spaces get skipped
(scala-numberize (cdr input) #f #f result))
((and (not found-number)
(or (< char-val -3) (> char-val 9))) #f)
; if we find non-numbers at the
; beginning of a line, we assume it's a comment
(else
(scala-numberize (cdr input)
(or found-number ; have we found a number?
(and (>= char-val 0) (<= char-val 9)))
(or found-period ; does it have a period in it?
(= char-val -2))
(if (and (>= char-val -3) ; is it a number?
(<= char-val 9)) ; then add it to list
(append result (list current-char))
; otherwise, don't add it
result))))))))
(define (load-scala-file filename [new-name "scala"])
(let ((scale-definition '())
(scala-file '()))
(begin
(set! scala-file (remove-comment-lines (parse-scala-file filename)))
(set! scale-definition (list new-name ; scale name
(car scala-file) ; scale description
(scala-numberize
(string->list (list-ref scala-file 1)))
; scale size
(scala-intervals (cddr scala-file))))
(set! scale-list (append (list scale-definition) scale-list))
(set-scale new-name))))
(define (scala-note input [result '()])
(cond ((number? input) (parse-scale input))
((null? input) result)
(else (scala-note (cdr input)
(append result
(list (scala-note (car input))))))))
(define (list-scales [count 0] [result '()])
(if (= count (length scale-list)) result
(list-scales (+ 1 count)
(append result
(list (list-ref (list-ref scale-list count) 0))))))