mikmod icon indicating copy to clipboard operation
mikmod copied to clipboard

UltraTracker fixes

Open neumatho opened this issue 4 years ago • 29 comments
trafficstars

Just made some small fixes:

  1. Fixed sample speed to calculate it correctly by using the original formula with floating points. "Break the beat" have a beat that runs too fast, because of miscalculation before.
  2. Portamento to note fixed. According to the documentation, it should keep run until it reaches the note, even if other effects are running. "Consolidation" module has two porta in the beginning of the song and before the fix, it reached a false tone, because it didn't continue.
  3. Clear invalid loop values. Some modules has values when loop is not set.

UltraTracker.zip

neumatho avatar May 25 '21 12:05 neumatho

@AliceLR: can you please review?

sezero avatar May 25 '21 13:05 sezero

I took the liberty of re-basing this patch to current git (@neumatho: please verify)

I wonder whether we can avoid libm:pow(). I also wonder why finetune is read as a signed short and not unsigned..

@AliceLR ?

sezero avatar Jun 05 '21 01:06 sezero

I haven't thoroughly checked this patch (or the Farandole Composer patch) yet but I think pulling in libm and/or using floating point math in MikMod is not a very good idea. Part of the appeal of MikMod (to me anyway) is that it has sort of lingered in the same state for 20 years, so while it was maybe considered "slow" in 2001, it's probably the fastest of the main mod libraries today (not verified...). It hasn't accumulated floating point cruft and the same bizarre indirection e.g. libxmp has for some features.

Modern PCs can do floating point math just fine, but for example, one of MegaZeux's target platforms I've been looking at MikMod for is the Nintendo DS, an ARMv5 machine with no hardware floating point support that will almost certainly need to have a hardware mixer driver written for it.

I don't have any particular suggestions for getting rid of this particular call right now (unless MikMod has a base-2 exponent table sitting around somewhere already). In this case it's at least not as big of a performance concern as the one in the Farandole Composer patch, but I still don't like it. I could also see if it's possible to calculate a better interpolant than the original.

AliceLR avatar Jun 05 '21 02:06 AliceLR

I took the liberty of re-basing this patch to current git (@neumatho: please verify)

Its look fine by me.

neumatho avatar Jun 05 '21 04:06 neumatho

Re: the wrong speed calculation without pow() usage, i.e. this part of the patch:

		   pow(2,(double)s.finetune/OCTAVE/32768), but to avoid floating point
		   here, we'll use a first order approximation here.
		   1/567290 == Ln(2)/OCTAVE/32768 */
-		q->speed=s.speed+s.speed*(((SLONG)s.speed*(SLONG)s.finetune)/567290);
+		/* Thomas Neumann: Uncommented the original code and use the
+		   right formula. Today's computers are fast enough to calculate
+		   a little bit floating point.
+		   The reason is because the module "Break the beat.ult" have a
+		   finetune value of -169, so the original code calculates the
+		   speed to -8363 and set it on an usigned variable, so we got a
+		   really high value. By using the original formula, we get a
+		   speed of 8360 */
+//		q->speed=s.speed+s.speed*(((SLONG)s.speed*(SLONG)s.finetune)/567290);
+		q->speed = s.speed * pow(2, (double)s.finetune / (OCTAVE * 32768));

Is it naive of me to think that abs()ing the calculated result would be a legitimate solution here?

sezero avatar Jul 15 '21 09:07 sezero

It will be positive, but not the exactly right value. According to the comment, the old formula result in -8363 and you will then abs()ing it to 8363. The right value should be 8360. If that is close enough and will not be out of tune, I do not know.

Maybe there could also be some other corner cases where it will be wrong.

neumatho avatar Jul 15 '21 11:07 neumatho

You are right, there are several others than display significant differences.

sezero avatar Jul 15 '21 13:07 sezero

You are right, there are several others than display significant differences.

Took all the files from http://ftp.modland.com/pub/modules/Ultratracker/ and ran a test on them printing the finetune and calculated speed values:

Apparently, abs() changes only three cases and only two of them actually correctly...

sezero avatar Jul 15 '21 13:07 sezero

I wonder if it is possible to retrieve the original source from somewhere (either a player or the editor) and see what they do in this case. It is probably written in assembly, so the use of floating point is minimal.

neumatho avatar Jul 15 '21 14:07 neumatho

I don't know.

Playing with the non-zero finetune values, I don't know how I can get an acceptable result without using pow():

--- original.txt
+++ with-pow.txt
@@ -1,45 +1,45 @@
 u1/cyboccultation.ult
-678 -> 83630
+678 -> 8373
 
 u1/dance of the electric willows.ult
--1 -> 8363
+-1 -> 8362
 
 u1/dark force.ult
--536 -> 4294917118 (50178 with abs())
+-536 -> 8355
 
 u1/fight hunting!.ult
 50 -> 8363
--178 -> 4294958933 (8363 with abs())
+-178 -> 8360
--78 -> 0
+-78 -> 8361
--20 -> 8363
+-20 -> 8362
--200 -> 4294958933 (8363 with abs())
+-200 -> 8360
 
 u1/further.ult
-105 -> 16726
+105 -> 8364
 
 u1/i can't stop - ecstasy remix.ult
-600 -> 75267
+600 -> 8371
 
 u1/love starts with an l.ult
-522 -> 66904
+522 -> 8370
-100 -> 16726
+100 -> 8364
 
 u1/rack of lam.ult
 30 -> 8363
--40 -> 8363
+-40 -> 8362
-175 -> 25089
+175 -> 8365
 
 u1/sir pranz-a-lot.ult
--18 -> 8363
+-18 -> 8362
 
 u1/solar-dance.ult
 30 -> 8363
-80 -> 16726
+80 -> 8364
--133 -> 0
+-133 -> 8361
 50 -> 8363
 
 u1/spherical glide.ult
-25000 -> 3085947
+25000 -> 8739
 
 u1/the earth's last defender theme.ult
-500 -> 66904
+500 -> 8370

Unless someone provides another solution (@AliceLR ?), I will have to accept pow()?

sezero avatar Jul 15 '21 16:07 sezero

@AliceLR: what do you suggest?

sezero avatar Aug 16 '21 20:08 sezero

Maybe you can use this code:

https://gist.github.com/Madsy/1088393

It contains pow and others using fixed points. I have not tried it, but maybe you can create a test project with it and test with the numbers from above and see if it can produce the right results?

neumatho avatar Aug 18 '21 12:08 neumatho

I still think a numerical approximation could be used here depending on the domain of this function. I haven't looked close enough at it, sorry. I will get back to you soon re: this. edit: that said, including math.h isn't really the end of the world, and this isn't particularly high-traffic code.

AliceLR avatar Aug 31 '21 02:08 AliceLR

I still think a numerical approximation could be used here depending on the domain of this function.

Indeed - If you have a solution, that'd be nice.

edit: that said, including math.h isn't really the end of the world, and this isn't particularly high-traffic code.

Yeah

sezero avatar Aug 31 '21 03:08 sezero

Sorry for the delay (again). The linear interpolant used in the original code is fundamentally correct but, like many other things in MikMod, was implemented in a totally baffling and wrong way. Try this:

q->speed = s.speed * ((double)s.finetune / 567290.0 + 1.0);

Comparison of the exponent vs. the interpolant shows that, using the range of s.finetune as the domain, they are extremely close. I don't think a second order interpolant would improve very much. image https://www.wolframalpha.com/input/?i=plot+2%5E(x/(12*32768)),+x/567290+1+on+%5B-32768,32767%5D

AliceLR avatar Sep 22 '21 04:09 AliceLR

With Alice's version, Break The Beat sounds very close to libxmp's output. Thomas, what do you say?

sezero avatar Sep 22 '21 17:09 sezero

Alice: apart from the pow approximation (which I intend to use yours, waiting to hear from Thomas), is the rest of the patch OK?

sezero avatar Sep 23 '21 06:09 sezero

With Alice's version, Break The Beat sounds very close to libxmp's output. Thomas, what do you say?

It seems to work.

neumatho avatar Sep 23 '21 13:09 neumatho

Alice: apart from the pow approximation (which I intend to use yours, waiting to hear from Thomas), is the rest of the patch OK?

After having given it a look, I'm not sure. I think the fixed interpolant is fine but unrolling a continuous effect seems unusual and prone to breaking in edge cases. I'll need to familiarize myself with UltraTracker and that effect to have a better idea of how to deal with it.

edit: note libxmp has the same tone portamento unrolling hack.

AliceLR avatar Sep 23 '21 23:09 AliceLR

Proof of concept for why the portamento unrolling that EVERY player seems to do (libxmp, MikMod with this patch, OpenMPT) isn't sufficient. PORTA.ULT.zip

The initial C-2 should start tone portamento at row 15 of order 0 and reach F#1 by the end of order 1. Instead, it decrements slightly each time the tone portamento command is encountered.

AliceLR avatar Sep 23 '21 23:09 AliceLR

OK, I pushed ed3e5b3394a07669055a694f0ce3e60ebb905c48 for the sample speed issue. With this, I have only two modules giving different results than pow():

cyboccultation.ult finetune: 678 -> 8373 (pow) -> 8372

spherical glide.ult finetune: 25000 -> 8739 (pow) -> 8731

sezero avatar Sep 24 '21 02:09 sezero

Waiting for the solution for portamento issue and the rest (i.e. items 2 and 3 on Thomas' original list).

sezero avatar Sep 24 '21 02:09 sezero

I have a working tone portamento fix for libxmp that handles various minor quirks. Presumably this could be ported to MikMod. It might be more work since, as far as I know, it doesn't have persistent tone portamento implemented outside of the FAR branch (and FAR tone portamento IIRC behaves different compared to libxmp's persistent tone portamento implementations).

AliceLR avatar Sep 24 '21 02:09 AliceLR

I have a working tone portamento fix for libxmp that handles various minor quirks. Presumably this could be ported to MikMod. It might be more work since, as far as I know, it doesn't have persistent tone portamento implemented outside of the FAR branch (and FAR tone portamento IIRC behaves different compared to libxmp's persistent tone portamento implementations).

I'd like to see that in mikmod if it's doable. Does #37 help in any way?

sezero avatar Sep 24 '21 02:09 sezero

After some lengthy work on my module tools I was able to find two Ultra Tracker modules in my local ModLand copy that unambiguously require actual persistent tone portamento:

the earth's last defender theme.ult. Affected portamento occurs between orders 9 and 10. consolidation.ult. Affected portamento occurs between orders 23 and 24.

AliceLR avatar Sep 29 '21 07:09 AliceLR

On further inspection of the original finetune algorithm, it seems to be wrong: in Ultra Tracker, finetune steps of 512 seem to roughly correspond to a semitone, so the "correct" formula might be 2**(finetune / (12 * 512)). (I don't know how correct this is, someone with more knowledge re: the GUS might have more insight.)

edit: it might be adding the finetune value to the Hz after octave calculations, because 512 at an octave lower sounds more like a whole step. WTF...?

AliceLR avatar Sep 29 '21 08:09 AliceLR

On further inspection of the original finetune algorithm, it seems to be wrong: in Ultra Tracker, finetune steps of 512 seem to roughly correspond to a semitone, so the "correct" formula might be 2**(finetune / (12 * 512)). (I don't know how correct this is, someone with more knowledge re: the GUS might have more insight.)

edit: it might be adding the finetune value to the Hz after octave calculations, because 512 at an octave lower sounds more like a whole step. WTF...?

Can @sagamusix be of some help?

sezero avatar Sep 29 '21 11:09 sezero

I don't have any specific insight here (it's very well possible that what OpenMPT currently does is wrong), but it is well-known that the GUS suffers in terms of frequency precision of lower notes in many trackers. This may cause issues in particular with slide precision if slides are directly computed in terms of GUS frequency units, which is a natural thing to do in a GUS-only tracker. The effective resolution of finetune and slides may in fact differ based on the number of initialized hardware channels - I don't know if UltraTracker always initializes the card with 32 channels, but if it does, that means that the slide precision is the lowest possible. I don't know the formula by heart, but it can be lifted easily from e.g. DOSBox' GUS emulation. I think one finetune step was 1/1024th of the GUS mixer frequency (which depends on the number of channels).

sagamusix avatar Sep 29 '21 12:09 sagamusix

I rebased this to current master: Reviews? Comments?

sezero avatar Mar 02 '22 09:03 sezero

@AliceLR: Can you review this? (Several parts had already been merged, it is now a small single patch.)

sezero avatar Jan 10 '23 05:01 sezero