[Impromptu] Floating point rounding and make-metre

Andrew Sorensen andrew at moso.com.au
Tue Mar 3 00:09:56 GMT 2009


Hi Glen,

Thanks for that, your post is timely, floating point error was  
discussed a few days ago (see post from couple of days ago). The  
general gist is that you should be using rational numbers (1/8 1/5 3/2  
etc..) rather than reals (floating point) if you need precision.  The  
fmod function is deprecated, you should be using the modulo function  
which accepts rational numbers, as well as integers and reals.

Sorry that the make-metre function is out of date.  It should use  
modulo and rational numbers internally.  The metro that ships with  
impromptu does make use of rationals internally but you are  
responsible for making sure that you pass rationals into metro (i.e.  
beat should be a rational or an integer, not a real).

Cheers,
Andrew.



On 03/03/2009, at 4:40 AM, Glen F wrote:

> 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.
>
> _______________________________________________
> Impromptu mailing list
> Impromptu at lists.moso.com.au
> http://lists.moso.com.au/mailman/listinfo/impromptu



More information about the Impromptu mailing list