OK, implemented the table lookup methods. Not too painful.

Also cleaned up some of the variable names and made method names more
consistent. (Still, hardly elegant and definitely not optimized!)

Joel

On Wed, 2011-12-21 at 13:28 +0100, Kassen wrote:

> On 21/12/2011, Joel Matthys <[email protected]> wrote:
> > Slight revision. Some confusion about when functions can be called with
> > optional parameters. Anyway, works better now.
> 
> Great work, and fast too!
> 
> I had a look at this now, nothing to deep, mind you, and like most
> people my knowledge of tuning is a bit limited, it's a bit of a black
> art after all.
> 
> Do I understand correctly that your (scale-note ) -which I think will
> eventualy replace the plain note- is a lot more involved than the old
> one because it parses most of the scale stuff evey time it is called?
> And do I understand correctly that the advantage of this is that we
> can now have fractional notes that will be correct?
> 
> I'll defer to Dave, but my current gut instinct is wondering how much
> of thise we could pre-calculate at picking our scale to save cpu
> during realtime usage. Would you say, for example, that it might make
> sense to pre-calculate a table like (note ) uses now and use that in
> case of integer arguments while your function would take care of the
> fractional ones? That would save cpu in the common case while
> preserving the option to accurately use quarter-tone equal-tempered
> notes, I imagine.
> 
> I am open to the possibility that none of the above makes any sense
> and I completely misunderstood it all.
> 
> Yours,
> Kas.
> 


; scala.scm, rev. 2
; 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)
;
; * keynumbers must be integers except in equal temperament
;
;--------------------------------------------------------------------------
; 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
;--------------------------------------------------------------------------
; TO DO
;
; linear interpolation methods for fraction keynums in scala scales
; 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-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 (parse-scale keynum)
	(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)))
		(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! *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