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))))))

Reply via email to