[Impromptu] Floating point rounding and make-metre

Glen F holaglen at gmail.com
Mon Mar 2 18:40:50 GMT 2009


Hi there -- I've recently started playing with Impromptu, and am thoroughly
enjoying it (thanks!).  However, I've found that the make-metre function
doesn't always "sync" properly to the metronome.  Often, when I ask for beat
1 (for example), it returns #f at a time when it should return #t.  I
debugged the problem and found that the problem seems to be in
floating-point rounding.
I notice that in the documentation for fmod (which make-metre uses) you say
Impromptu rounds to 4 decimal places to "try to avoid some floating point
precision problems."  I'm still seeing them, in my programs (basically
variants on the Messiaen tutorial).  Rather than doing rounding, perhaps you
might consider using a tolerance/epsilon value in time comparisons.

I modified make-metre to a version which "always" (so far, at least ;-)
returns the expected result, regardless of the metronome value and the total
metre length (I'm using a length of 12 beats -- one bar in 12/8 time).  In
case it's useful for anyone, I'll post it here (this can replace the version
in time-lib.scm)...  With this, my examples work reliably -- without it,
every now and then (depending on what value the metronome has at time of
first call) my "composing" callback function just does nothing, missing all
the beats, especially beat 1!  The other problem, specific to queries for
beat 1.0 was that often (if the metronome time was a fractional bit below
the next beat, e.g. 11.9999), it would compare with "metre-length + 1", i.e.
13, rather than with 1, and so return #f.  I added a test for this specific
case, which solves all the problems I was having.

;=============== (a bit of time-lib.scm)

; x and y are the numbers to compare
; tol is the fractional tolerance of the smaller number below
; which they will be considered "close enough"
(define close-rel (lambda (x y tol)
                     (<= (abs (- x y)) (* (abs tol) (min (abs x) (abs y)) )
)
                     )
   )

; this is another version, with an absolute
; tolerance (in units of x and y, not relative in percentage)
(define close-abs (lambda (x y tol)
                     (<= (abs (- x y)) (abs tol))
                     )
   )

(define *metre-tol* 1.0e-3)

; creates a meter where metre is a list of numerators
; and base is a shared denominator (relative to impromptu beats. i.e. 1 =
crotchet,  0.5 = eighth etc.)
;
; e.g.  (define *metre* (make-metre '(2 3 2) 0.5)) = 2/8 3/8 2/8 rotating
cycle.
;
; then call meter with time and beat
; if beat matches time then #t else #f
;
; e.g. give the above define
;      (*metre* 2.5 1.0) => #t because 0.0 = 1, 0.5 = 2, 1.0 = 1, 1.5 = 2,
2.0 = 3, 2.5 = 1, 3.0 = 2 and repeat.
(define make-metre
   (lambda (metre base)
      (let ((metre-length (apply + metre)))
         (lambda (time beat)
            (close-abs (let loop ((qtime (fmod (/ time base) metre-length))
                              (lst metre)
                              (valuea (car metre))
                              (valueb 0))
                          (if (close-abs qtime metre-length *metre-tol*)
                             1.0
                             (if (< qtime valuea)
                                (+ 1.0 (- qtime valueb))
                                (loop qtime (cdr lst) (+ valuea (cadr lst))
(+ valueb (car lst)) ) )
                          )
                       )
                       beat *metre-tol*)
         )
      )
   )
)

;===============

Hope this is helpful.  Keep up the great work with Impromptu,
Glen F.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.moso.com.au/pipermail/impromptu/attachments/20090302/3783a721/attachment.htm 


More information about the Impromptu mailing list