[yoshimi] insidious rounding problem

  • From: Ichthyostega <prg@xxxxxxxxxxxxxxx>
  • To: yoshimi@xxxxxxxxxxxxx
  • Date: Sun, 27 May 2018 22:46:05 +0200


Hello Will,

the LAC and the new release motivated me to dig into a problem I'm aware of
since some time. Up to now I dealt with it the "pragmatic way". But what I found
now makes me feel somewhat queasy ;-)

Some time ago, I worked on a string-like voice. Rather by accident, I found some
LFO, which gave the voice a specific warm yet tangy quality, which I imeediately
fell in love with. After some (a lot more tweaking), this specific flavor seemed
to have vanished. First, I put that off as an effect of self-suggestion, but
somehow I couldn't forget about it.

Now I happen to check in everything into Git. I am a Git addict (as most
developers are). So I went back and looked at my tweaks, and found, that the
LFO-Frequency had a runaway. It became a tiny little bit smaller on each save.

So for example:
 - Original Parameter: 0.64   (note: can not be represented in binary float!!)
 - after 1st save:     0.639999
 - after 2nd save:     0.639998
 - after 2nd save:     0.639997

and so on. You see: after saving maybe 20 times, we get into a region where
the change becomes audible, at least for a trained ear.


My pragmatic takaway from this "close encounter" was just never to trust
Yoshimi, and explicitly look at each tweak and revert anything I know I hadn't
done myself.


Well. With the new 1.5.x fileformat, we got the problem somewhat under control,
since there is now the exact bitstring for each float parameter saved. Anyway,
today I wanted to know what's the reason all of this, and especially I wanted
to know, if I run any risk when converting an Instrument written with 1.4.x
with Yoshimi 1.5.x


To make a long story short, my findings are:
(1) the runaway error happens on the conversion float -> string
    This means, Yoshimi 1.5.x reads old files in and gets the same float values
    in memory as an old instance got. Which means, they sound the same

(2) When saving, the correct bitstring is saved. However the human-readable
    decimal value is still off by the same amount as previously
    (Delta=-5.96046e-08)

(3) The reason is a very old addition in the Config() ctor
    by Cal from Changeset 387c6ece (19.03.2011)

    // We need lrintf() to round toward zero. Special thanks go to
    // Lars Luthman for this one!!
    fesetround(FE_TOWARDZERO);

(4) Today, this global setting *not* in effect for LV2.
    The code today reads as follows:


    if (synth->getIsLV2Plugin())
    {
        rtprio = 4; // To force internal threads below LV2 host
    }
    else
        fesetround(FE_TOWARDZERO); // Special thanks to Lars Luthman for 
conquering
                               // the heffalump. We need lrintf() to round
                               // toward zero.
    //^^^^^^^^^^^^^^^ This call is not needed aymore (at least for lv2 plugin)
    //as all calls to lrintf() are replaced with (int)truncf()
    //which befaves exactly the same when flag FE_TOWARDZERO is set


Yoshimi source code is available from either: 
https://sourceforge.net/projects/yoshimi
Or: https://github.com/Yoshimi/yoshimi
Our list archive is at: https://www.freelists.org/archive/yoshimi
To post, email to yoshimi@xxxxxxxxxxxxx

Other related posts: