OK, here's rev 3. Now with optional microtonal support. This can be
disabled with (set-scala-microtonal #f).

Should accept fractional/float values anytime now.

Joel

On Wed, 2011-12-21 at 17:14 +0100, Kassen wrote:

> On 21/12/2011, Joel Matthys <[email protected]> wrote:
> 
> > I think most people who want to do microtonal stuff will want to do it
> > with equal temperament, and that's already implemented. Scala files
> > aren't really suitable for microtonal because it's possible to define
> > vastly different interval sizes from one note to the next, so I think
> > anyone who tries it should be happy with what they get. The "purist"
> > approach would be your "brutal" method, (floor ) the keynum, but I think
> > a little interpolation would be nice.
> >
> 
> I agree. More strongly even; when we do want to be in a tuning and
> want to round floats we will most likely want to do that relative to
> some scale, not to the tuning itself.
> That way we could -for example- turn a scaled (sin (time)) in to a
> sort of melody.
> 
> Here is a part of a toolset I'm working on;
> 
> 
> 
> (define major (list 0 2 4 5 7 9 11))
> (define minor (list 0 2 3 5 7 9 11))
> (define acoustic (list 0 2 4 6 7 9 10))
> (define whole (list 0 2 4 6 8 10))
> 
> ;gets the nth note of a given scale
> (define (in-scale myscale note)
>     (let* ( (note (inexact->exact (round note)))
>             (l (length myscale))
>             (r (remainder note l)))
>         (+
>             (* (quotient note l) 12)
>             (if (negative? r)
>                 (- (list-ref myscale (+ l r)) 12)
>                 (list-ref myscale r)))))
> 
> ;rounds to the nearest note that is in the given scale
> (define (to-scale myscale x)
>     (let*  ((myscale (append myscale '(12)))
>             (ret 0)
>             (over (quotient (inexact->exact (round x)) 12))
>             (over (if (negative? x) (- over 1) over))
>             (x (- x (* 12 over))))
>         (for ((n (in-range 0 (length myscale))))
>             (when (<
>                     (abs (- (list-ref myscale n) x))
>                     (abs (- ret x)))
>                 (set! ret (list-ref myscale n))))
>         (+ ret (* over 12))))
> 
> 
> Might be interesting/fun/helpful. I'll publish the whole thing once
> I'm at least sure of it all myself and we can see whether others like
> it and perhaps push it in.
> 
> Yours,
> Kas.
> 


; scala.scm, rev. 3
; 21/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-base-keynum)
;
; 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-base-keynum 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-description)
;
; Example:
; (load-scala-file "scala/scl/bohlen-eg.scl" "bohlen")
; (set-scale "bohlen")
; (set-diapason 100)
; (set-base-keynum 0)
; (scala-note '(0 2 3 5 6 8 9 11 12 14 15))
; > (100 109.05...  ... 436.203)
;
; By default, microtuning is on, allowing quarter-tones, etc. For instance,
; in equal temperament, (scala-note 60.5) is a quarter step above middle C.
; In scala scales, the frequency is linear interpolated; that is,
; (scala-note 60.5) returns the frequency halfway between notes 60 and 61.
; To disable microtuning, use:
; (set-scala-microtonal #f)
; Now all fractional values are rounded to the nearest integer.
;
;--------------------------------------------------------------------------
; CHANGES
;
; 20 Dec 2011 - created file
;               fixed error with optional parameters
; 21 Dec 2011 - rev 2 - changed set-diapason-key to set-base-keynum
;               changed global var names, eg diapson -> *diapason*
;               created table lookup methods to reduce cpu load
;               rev 3 - linear interpolation allows microtuning
;--------------------------------------------------------------------------
; TO DO
;
; eliminate duplicate scale definition if scala file reloaded
;--------------------------------------------------------------------------
(define *scala-size* 12)
(define *scala* #f)
(define *scala-description* "12 note equal tempered scale")
(define *diapason* 261.6255653006)
(define *diapason-key* 60)
(define (set-diapason n)
  (set! *diapason* n)
  (fill-scala-table (- *diapason-key* 60) (+ *diapason-key* 67)))
(define (set-base-keynum n)
  (set! *diapason-key* n)
  (fill-scala-table (- *diapason-key* 60) (+ *diapason-key* 67)))
(define *scala-table* '())
(define *scala-microtonal* #t)
(define (set-scala-microtonal input)
  (set! *scala-microtonal* input))

(define *scala-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 (scala-description) *scala-description*)

(define (set-scale name)
  (set-scale-helper name *scala-list*)
  (fill-scala-table (- *diapason-key* 60) (+ *diapason-key* 67)))

(define (set-scale-helper name temp-scales)
  (cond ((null? temp-scales) ;end of list, so it must be equal temp
         (begin (set! *scala* #f)
              		(set! *diapason* 261.6255653006)
              		(set! *diapason-key* 60)
			(set! *scala-description* "Equal temperament")))
        ((equal? name (caar temp-scales))
         (set! *scala-size* (list-ref (car temp-scales) 2))
         (set! *scala* (list-ref (car temp-scales) 3))
	 (set! *scala-description* (list-ref (car temp-scales) 1)))
	(else (set-scale-helper name (cdr temp-scales)))))

(define (scala-lint val low-source high-source low-target high-target)
  (let ((norm-val (/ (- val low-source) (- high-source low-source))))
    (+ low-target (* norm-val (- high-target low-target)))))

(define (scala-microtonal input)
  (let* ((low-key (inexact->exact (floor input)))
	 (high-key (inexact->exact (ceiling input)))
	 (low-freq (parse-scale low-key))
	 (high-freq (parse-scale high-key)))
    (scala-lint input low-key high-key low-freq high-freq)))

(define (parse-scale input)
  (let ((keynum (if *scala-microtonal* input
		    (round input))))
    (if (not *scala*)
					; equal temperament
	(* *diapason* (expt 2 (/ (- keynum *diapason-key*) 12)))
					; else custom scale
	(if (integer? keynum)
	    (let ((scale-degree (modulo (- keynum *diapason-key*) *scala-size*))
		  (scale-octave (floor (/ (- keynum *diapason-key*) *scala-size*))))
	      (* *diapason*
		 (expt (list-ref *scala* (- (length *scala*) 1))
		       scale-octave)
		 (list-ref *scala* scale-degree)))
	    (scala-microtonal keynum)))))

(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! *scala-list* (append (list scale-definition) *scala-list*))
      (set-scale new-name))))

(define (scala-note input [result '()])
  (let ((test (find-in-table input *scala-table*)))
    (cond (test test)
	  ((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 *scala-list*)) result
      (list-scales (+ 1 count)
		   (append result
			   (list (list-ref (list-ref *scala-list* count) 0))))))

(define (fill-scala-table low high [current #f] [result '()])
  (cond ((not current) (fill-scala-table low high low '()))
	((> current high)
	 (set! *scala-table* result))
	(else (fill-scala-table low high (+ current 1)
		    (append result
			    (list (list current
				  (parse-scale current))))))))

(define (find-in-table num table)
  (if (null? table) #f
      (let ((result (member num (car table))))
	(if (and (list? result)
		 (= 2 (length result)))
	    (list-ref result 1)
	    (find-in-table num (cdr table))))))

; default keynum->frequency table (equal temperament)

(define *scala-table* '((0 8.17579891564375) (1 8.661957218027297)
 (2 9.177023997419036) (3 9.722718241315079) (4 10.300861153527237)
 (5 10.91338223228143) (6 11.562325709738635) (7 12.249857374429727)
 (8 12.978271799373355) (9 13.75) (10 14.567617547440383)
 (11 15.43385316425396) (12 16.3515978312875) (13 17.323914436054597)
 (14 18.354047994838066)
 (15 19.445436482630157)
 (16 20.601722307054477)
 (17 21.826764464562853)
 (18 23.12465141947727)
 (19 24.49971474885946)
 (20 25.956543598746702)
 (21 27.5)
 (22 29.135235094880773)
 (23 30.867706328507914)
 (24 32.703195662575)
 (25 34.647828872109194)
 (26 36.70809598967613)
 (27 38.890872965260314)
 (28 41.20344461410895)
 (29 43.653528929125706)
 (30 46.24930283895454)
 (31 48.99942949771892)
 (32 51.913087197493404)
 (33 55)
 (34 58.27047018976155)
 (35 61.73541265701583)
 (36 65.40639132515)
 (37 69.29565774421839)
 (38 73.41619197935228)
 (39 77.78174593052063)
 (40 82.4068892282179)
 (41 87.30705785825143)
 (42 92.49860567790908)
 (43 97.99885899543783)
 (44 103.82617439498682)
 (45 110)
 (46 116.54094037952308)
 (47 123.47082531403167)
 (48 130.8127826503)
 (49 138.59131548843678)
 (50 146.83238395870455)
 (51 155.56349186104126)
 (52 164.8137784564358)
 (53 174.61411571650285)
 (54 184.99721135581817)
 (55 195.99771799087566)
 (56 207.65234878997364)
 (57 220)
 (58 233.08188075904616)
 (59 246.94165062806334)
 (60 261.6255653006)
 (61 277.18263097687355)
 (62 293.6647679174091)
 (63 311.1269837220825)
 (64 329.6275569128716)
 (65 349.2282314330057)
 (66 369.99442271163633)
 (67 391.9954359817513)
 (68 415.30469757994723)
 (69 440)
 (70 466.1637615180923)
 (71 493.88330125612663)
 (72 523.2511306012)
 (73 554.3652619537471)
 (74 587.3295358348182)
 (75 622.253967444165)
 (76 659.2551138257433)
 (77 698.4564628660114)
 (78 739.9888454232727)
 (79 783.9908719635026)
 (80 830.6093951598946)
 (81 880)
 (82 932.3275230361846)
 (83 987.7666025122534)
 (84 1046.5022612024)
 (85 1108.7305239074942)
 (86 1174.6590716696362)
 (87 1244.50793488833)
 (88 1318.5102276514865)
 (89 1396.9129257320226)
 (90 1479.9776908465453)
 (91 1567.9817439270055)
 (92 1661.218790319789)
 (93 1760)
 (94 1864.6550460723695)
 (95 1975.5332050245065)
 (96 2093.0045224048)
 (97 2217.4610478149884)
 (98 2349.3181433392724)
 (99 2489.01586977666)
 (100 2637.020455302973)
 (101 2793.825851464045)
 (102 2959.9553816930907)
 (103 3135.963487854011)
 (104 3322.437580639578)
 (105 3520)
 (106 3729.310092144739)
 (107 3951.066410049013)
 (108 4186.0090448096)
 (109 4434.922095629976)
 (110 4698.636286678547)
 (111 4978.03173955332)
 (112 5274.040910605945)
 (113 5587.651702928092)
 (114 5919.910763386181)
 (115 6271.92697570802)
 (116 6644.8751612791575)
 (117 7040)
 (118 7458.620184289476)
 (119 7902.132820098028)
 (120 8372.0180896192)
 (121 8869.844191259952)
 (122 9397.272573357093)
 (123 9956.06347910664)
 (124 10548.08182121189)
 (125 11175.303405856184)
 (126 11839.821526772363)
 (127 12543.85395141604)))

Reply via email to