On Sat, 2006-09-16 at 13:41 +0200, Karl Hammar wrote: > > In this case inexactness doesn't matter. The term 2**tolerance_exp makes > > sure that notes that are slightly shorter than the next whole duration > > are rounded to the whole duration instead of somethin like "c8.....". In > > some cases floating point arithmetic can cause some randomness regarding > > whether the note becomes c8... or c4 (if the duration is exactly 1/32 > > from the next whole duration), but I don't think that matters -- such > > note length are weird enough to start with. > > > > When determining the number of dots the use of floating point arithmetic > > doesn't obviously harm, because the result is rounded anyway. > > > > I can't think of any simple way to achieve the same result without using > > math.log(). > > > > The attached diff for musicxml.py now has an explanatory comment. > > > > I don't understand this: > > tolerance_exp = -4 > a = - math.log(self._duration) / math.log(2) > b = 2**tolerance_exp > c = a - b > > 1: > if tolerance_exp is a constant, why don't you simply say b = 1/16 > (another constant), which is clearly a duration(?) value. > > 2: > why are you taking the difference between the log of a duration and a > duration? > > In physics there is something which is called a dimension test: you > simply cannot do 1 meter - 1 second. In math, you don't easily agree to > do log(entity) - entity. And in this case I read your code as > -2log( seconds ) - seconds which is clearly wrong. > > Now b seems to be an implementation detail caused by using floating > point values, the same with math.ceil, is it so? > > If you simply want to map a duration to its -2log, why don't you use > something like arr = [ 1, 2, 4, ...]; f(x) = arr[x]; which is a clear > statement about what you are doing (although incomplete, I sense that > self._duration is a rational number). > > 3: > I don't understand what you are trying to do and your comment does not > match the code
Maybe I should explain what get_duration_log does. It returns the type of the base note of the note object. So if, for instance, the note is c8..., get_duration_log returns 3. If you think the function is badly named, I agree, but I didn't create it, I only added those two lines to calculate the type if there is no <type> flag in the xml file. But you are right in some respect, because I now realise my formula is incorrect. My intention was to add the difference between a whole duration (e.g. c4) and a note with a shorter base duration and four dots (e.g. c8....) to (a) (as you call it), so that if the duration is very close to the next whole duration, the function returns the next duration (e.g. c4 instead of c8) (This is to avoid rounding errors and prevent the note from having an insane number of dots). The difference _is_ constant in logarithmic scale, but i'm using a wrong constant. I was thinking that for example c4->2, c4.->2.5, c4..->2.75 so the length of the fourth dot in the logarithmic scale is 1/16. This is of course wrong but I didn't realise it because my formula worked as desired (!). The following should be correct: I have a note with n dots, with duration d = 2h - (2^-n) h where h is the length of the base note. I want a factor x which lengthens it to the next whole duration 2h, so d*x = 2h => x = 1 / (1 - 2^-(n+1) ) Thus log2 (d*x) = log2 (d) + log2 (x). In this case, n = 4. the correct formula is: lengthening_factor = 1 / (1 - 2^-5 ) a = - math.log (self._duration * lengthening_factor) / math.log(2) result = math.ceil(a) result is the length of the base note. It will be as long or shorter than than the real duration, except if duration is just slightly shorter than the next whole duration. Rounding errors may occur if _duration*x is exactly 2h, but I don't see that as a problem. Such durations are so unusual that it doesn't matter much if they get 4 dots or are rounded up. It would be possible to implement this with rational numbers like this: i=0 while not (Rational(1, 2**i) < _duration*x and _duration*x < Rational(1, 2**(i+1) )) i++ return i but I'm using logarithms in get_num_dots anyway, and actually i should start from -2, so this wouldn't be as simple after all. > > 4: > using floating point values for exact things usually open up enough > nasty bugs and is very hard to verify due to rounding errors. Unless > you have VERY good reason, just don't do it, it is not worth the > trouble. > > =========== > > Later in your patch: > > + factor = self._duration / 2**(-self.get_duration_log()) > > + return int (round (-math.log(2 - factor) / math.log(2))) > > If: > > a = 1/2**(-self.get_duration_log()) > > then: > > a = 2**self.get_duration_log() > > assuming that get_duration_log() returns a 2log and > that get_duration() exists, then This is where this goes wrong. get_duration_log doesn't return 2log of _duration. > > a = self.get_duration() > > get_duration() look suspiciously like _duration(), so my guess is that > > factor = self._duration ** 2 > > by quickly reading the code as a math guy. > > Now, if duration usually is less than 1 (we don't see many breves and > longas today), then > > -math.log(2 - factor) / math.log(2) > > more or less reduces to a near constant -1. > > Is that your intention, or am I reading your code false? > I assume that duration is the lenght of a note, like whole note is 1, > a quater note is 1/4, ... > > Regards, > /Karl 2**-(get_duration_log) is the length of the base note. So factor is the real duration relative to the length of the base note. So if you have c16.., factor is 1.75. You subtract it from 2 and you have 0.25, log2 of which is -2. And 2 is the number of dots. This could also be implementer with rational numbers, but only if durations from input correspond with a proper whole duration or a dotted length. The advantage of using logarithm is that any duration will be rounded to the closest representation. So if you have an xml file generated from music played with a midi keyboard for example, the scripts does its best to find as best representation for it as possible. I attached a diff, now with the correction and additional comments. I hope this cleared things. Tuukka
--- lilypond/python/musicxml.py 2006-08-26 01:04:08.000000000 +0300 +++ musicxml.py 2006-09-17 13:23:09.000000000 +0300 @@ -1,4 +1,5 @@ import new +import math from rational import * class Xml_node: @@ -193,7 +194,33 @@ 'long': -2, 'whole': 0} [log] else: - return 0 + # Using floating point arithmetic here + # Lengthening_factor means that durations between + # quarter note * (1-lengthening factor) and + # half note * (1-lengthening factor) + # --> quarter note (return 2) + # etc. Thus rounding errors shouldn't cause problems. + + # With dot_limit=4 get_num_dots won't return more than 4, because + # for example for duration c4..... this function returns 1 (half note). + # (Floating point arithmetics can cause some randomness whether a + # note becomes c4.... or c2) + dot_limit = 4 + lengthening_factor = 1 / (1 - 2**-(dot_limit+1) ) + print self._duration*lengthening_factor + return int (math.ceil + (-math.log (self._duration*lengthening_factor) / math.log (2))) + + def get_num_dots(self): + if self.get_maybe_exist_typed_child (class_dict['type']): + return len (self.get_typed_children (Dot)) + + else: + # factor means how many times duration is the length of the base note. + # 2 - factor tells how far duration is from the next whole duration + # by rounding we can also handle weird durations in input + factor = self._duration / 2**(-self.get_duration_log()) + return int (round (-math.log(2 - factor) / math.log(2))) def get_factor (self): return 1 @@ -351,27 +378,26 @@ elements.extend (m.get_all_children ()) start_attr = None - for n in elements: - voice_id = n.get_maybe_exist_typed_child (class_dict['voice']) - - if not (voice_id or isinstance (n, Attributes)): - continue - - if isinstance (n, Attributes) and not start_attr: - start_attr = n - continue - - if isinstance (n, Attributes): - for v in voices.values (): + for n in elements: + if (isinstance (n, Note)): + voice_id = n.get_maybe_exist_typed_child (get_class('voice')) + if voice_id: + id = voice_id.get_text () + else: + id = '1' + + if not voices.has_key (id): + voices[id] = Musicxml_voice() + + voices[id].add_element (n) + + elif isinstance (n, Attributes): + if not start_attr: + start_attr = n + + for v in voices.values (): v.add_element (n) - continue - - id = voice_id.get_text () - if not voices.has_key (id): - voices[id] = Musicxml_voice() - - voices[id].add_element (n) - + if start_attr: for (k,v) in voices.items (): v.insert (0, start_attr)
_______________________________________________ lilypond-devel mailing list lilypond-devel@gnu.org http://lists.gnu.org/mailman/listinfo/lilypond-devel