Skip to content

Instantly share code, notes, and snippets.

@AliceLR
Last active January 9, 2022 02:26
Show Gist options
  • Save AliceLR/a5b7ace9d678ab407f974d9663236cc2 to your computer and use it in GitHub Desktop.
Save AliceLR/a5b7ace9d678ab407f974d9663236cc2 to your computer and use it in GitHub Desktop.
FAR effects info, player comparison

FAR module testing.

Module xmp (libxmp/libxmp#500) MikMod (sezero/mikmod#37) OpenMPT Effects
4Go10/m31.far OK OK OK B F
Dafydd/pond in the palace of pythagoras.far OK OK OK B
Daniel Potter/residual ambient amperage.far OK OK OK 1 F
Dirty Jester/techno riders.far OK (a2cc3bd) OK OK F
Guillermo Luijk Delgado/to the polish of valencia.far OK (32a338f) OK (b2a5c13) [1], [2] D E F (+zero pattern)
Haj/before.far OK, [3] OK, [3], [6] @ 16h [1], [3], [6] @ 16h 1 2 4 B D E F
Haj/cold recognition.far OK? :( OK? :( [8] [1], [8] 1 2 3 4 7 8 A B E
Haj/optimists push mood toward the fear.far OK? :( OK? :( [1], pitch wrong near end? 1 2 3 4 B E F
Haj/vanish.far OK (53a90e7) OK? (3c49302) [1], [4] 2 3 4 B E F
Jamie Watts/amazon dawn.far OK (b3a6b1b) OK (d5c0ec8) [4] ★ 3 B F
Krystall/emerald dream.far OK (d2ad428) OK (f407753) [1], [4], [5] @ 38 ★ 3 4 B D E F
Krystall/just another mystical song.far OK OK [4], [5] @ 10 ★ 3 4 F
Krystall/once upon a time.far OK OK [4] ([5], but no issues) 3 4 B F
Krystall/our world.far OK OK [1], [4], [5] ★ 3 4 B D E F
Krystall/paracidize.far OK (32a338f) OK (b2a5c13) [2], [4] 3 B F (+zero pattern)
Krystall/symphony 1.far OK OK [1], [4] 3 B D E F
Krystall/twenty million miles to venus.far OK [6] @ 4 ★ [1], [6] @ 4 ★ 1 2 B E F
Kueppenshreck/a journey into sound.far OK [6] @ 6, [7] @ 2 ★ [1], [6] @ 6, [7] @ 2 ★ 2 5 6 B D E F
Luigi Smythe/heir-born.far OK OK [1] ★ B D E F
MHz/dark dreams.far OK (2a11f4a) OK (b2a5c13) OK B F (+loop to 2)
Maelcum/budda on a bicycle.far OK OK OK
Marc/no answer.far OK OK OK B
Marc/second-hand communion.far OK OK (b2a5c13) OK B F (+loop to 12)
Prescience/aurora.far OK OK [1] ★ B D F (relies on D? underflow!)
Prescience/etheral nebula.far OK [6] ★ [4] [6] ★ 1 2 3 4 B (1? interrupts 3?!)
Ryan Cramer/thunder dream.far OK OK [1] ★ D E F
Sc'en/alterations of time.far OK OK (b2a5c13) OK, [2] ★ (+zero pattern)
Silent Knight/beyond the shores of avalon.far OK OK (b2a5c13) [1], [2] ★ D E (+two zero patterns)
Soundwave (US)/the palace festival.far OK OK (b2a5c13) OK, [2] ★ B C F (+zero pattern)
The Kiefdevil/nonshlen tustokken.far OK OK [1] 4 E F
Zowie/backfire.far OK OK, [6] [4], [6] 1 3 B
Zowie/far from earth.far OK OK, [6] [4], [6] 1 3 B
Zowie/invasion part 1.far OK OK [4] 3
Zowie/invation part 2.far OK OK [4] 3 B
Zowie/the rain in the ruin.far OK (17609ef) OK (3c49302) [1], [4] ★ 3 B D F
Zowie/the vagabond.far OK OK [5] 4 B
Effects test module (effects.far) [1b], [5] (old tempo only) [1b 6 7 8 10 11] ★ [1 1b 4 5 6 7 8 10 11] ★ 03 04 05 1 2 3 4 5 6 7 8 9 A B C D E F
  1. New tempo mode inaccuracies. Affects all modules, but only marked for usages where it matters due to unimplemented fine tempo. OpenMPT: it seems to use 80 BPM and puts coarse tempo as speed, which unfortunately doesn't line up with reality. Also seems to ignore fine tempo. Fixed in libxmp patch as of a2cc3bd, fixed in MikMod patch as of 3c49302. 1b) Old tempo mode inaccuracies. These are pretty much unavoidable in libxmp due to the necessary small tick sizes sounding terrible and XMP_MIN_BPM. Other players may not bother implementing this because it's kind of bad and nothing uses it.
  2. When the order list references a zero-length pattern, a blank pattern is created with a break line equal to the default break value configured by the user. By default, this is 3F, i.e. these blank patterns are USUALLY 64 rows long. MikMod and OpenMPT both ignore these, libxmp patch supports as of 32a338f.
  3. Relies on overflowing or underflowing GUS pitch using effect 1? or 2?, which is unsupported by this replayer.
  4. Tone portamento not implemented correctly. It might not be persistent tone portamento, or the timing just might not be quite right.
  5. Problems related to 4? Retrigger. This may be caused by [1] or it might just be due to not emulating the weird delay calculation.
  6. Tone slide issues. Usually tone slide is too powerful. In at least one case a module relies on 1? or 2? interrupting 3?.
  7. Vibrato issues. "a journey into sound.far" is the only FAR I know of that uses effects 5? and 6? (nothing beside my test FAR uses 9?, but it's easy to support). Effect 5? is used to set the global vibrato depth in pattern 9/order 0, then several other patterns use 6? in multiple different channels, including pattern 0. Vibrato is used mostly on major/minor string chords that are pretty noticeable during playback in Farandole Composer.
  8. This module uses effect A? "Slide to vol". It works more or less like tone portamento, but with the volume column value as the target instead of the note. For libxmp I ended up putting the volume in the high nibble of the parameter and left the volume column blank when this effect is used (to make things easier). It's probably a good idea to use the FAR effects test module to implement this because "cold recognition.far" is painful and it's the only one I found that uses it.
  9. C? Note Offset is a strange command: it seems like it should be equivalent to note delay in other formats, but it plays a note on tick 0 and a second note at the specified offset. The specified offset is supposed to work like retrigger, but due to the high divisor and no tempos ever having more than 15 ticks, the parameter effectively represents the tick number the second note should play on. Convenient! The only module that uses this is the palace festival.far, but its usage seems to be an accidental substitution for effect B? and can be ignored. Use the effects test module instead.
  10. 03 Fulfill Loop is meant to act like a sustain release but I haven't 100% figured out the mechanics of how it works. Often it just cuts the sample. It might be guaranteed to work if the sample loop starts at 0 and ends at the end of the sample, but more experimentation is required.

★: Module is well made and is a good example of these issues.

Tempo

  • Coarse tempos can just be a precomputed table. There's no reason to add yet another integer division at runtime here. The precomputed table is:
/* tempo[0] = 256; tempo[i] = floor(128 / i). */
static const int far_tempos[16] =
{
	256, 128, 64, 42, 32, 25, 21, 18, 16, 14, 12, 11, 10, 9, 9, 8
};
  • Effect 04 switches to old tempo mode, effect 05 switches to new tempo mode. I don't see handling for old tempo mode in this patch, but I don't think anything actually uses it.
  • New tempo mode: "Speed" and "BPM" are derived by taking speed = 0; bpm = far_tempos[coarse] + fine; divisor = 1197255 / bpm;. If it's greater than 0xffff (the maximum divisor supported by the Programmable Interval Timer), divide divisor by 2, multiply bpm by 2, and increment speed by 1 until the divisor is less than or equal to 0xffff. If speed >= 2, increment it again. Finally, increment speed by 4 (the original code adds 3, but sloppy code when checking and decrementing the tick counter obscures that there's effectively an extra tick).
  • Old tempo mode: speed = 4; bpm = far_tempos[coarse] + fine;.
  • New tempo mode tempos 0 through 6 should work out to 4 ticks, tempos 7 through C to 5 ticks, and tempos D through F to 7 ticks. Fine tempo can alter the effective number of ticks. Old tempo mode tempos all effectively have 4 ticks (the original player uses 32 to appease the Programmable Interval Timer but ignores all but every 8th).
  • Effects D? and E? decrease/increase the fine tempo by ? once per row.
  • libxmp supports variable time rates, so it can use the computed "speed" and "bpm" directly with a time factor of 4.01373. MikMod and OpenMPT should probably instead multiply the final "bpm" by 2.5.

Tempo edge cases

  • If the sum int eax = far_tempos[coarse] + fine is negative, 1197255 / eax should be negative, then underflow when stored to uint32_t divisor. If eax is zero, return without modifying the tempo, but don't modify coarse or fine tempo.
  • Effect F? should never modify fine tempo. Effect D? should only clamp fine tempo to 0 when far_tempos[coarse] + fine <= 0. Effect E? should clamp fine tempo to 100 when far_tempos[coarse] + fine >= 100.
  • I think that's adequate to properly emulate FAR tempo. :(

Portamento and Vibrato

  • Effects 1? and 2? increment/decrement frequency once per row by GUS_FREQUENCY_STEP*4, where GUS_FREQUENCY_STEP is roughly 36318/1024. I don't remember if MikMod even has support for a frequency-based "period" mode, so this might have to be very roughly approximated.
  • Effect 3? slides to the target note within ? rows. I don't think changing tempo changes the slide rate, fortunately. A parameter of 0 is translated to 1. The "within ? rows" statement only holds for tempo 4: the calculation is actually performed using the tempo, so this scales for different tempos:
// This works out to completing in (param) rows at speed 4, but
// (param * 2) rows at speed 2, (param * 4) rows at speed 1, etc.
porta_step = diff * 8 / (param * interrupts_per_second);
  • New notes and effects 1? and 2? interrupt 3?.
  • Effect 5? (vibrato depth) is global and this value defaults to 4. Attempting to unroll it in the loader is an extremely bad idea. It should be another global player variable like the FAR tempo. This effect should not initiate vibrato by itself, it should just set the rate.
  • Effect 6? is sort of like regular vibrato. Multiply the rate parameter by 3 relative to standard vibrato, multiply the LFO output by GUS_FREQUENCY_STEP to get the correct depth.
  • Effect 9? is the same as 6? but persistent until cancelled with 60 or 90.
  • Effect 6? when 9? is active sets the vibrato rate but doesn't cancel persistent vibrato.

Volslides and balance

  • These behave like normal effects for a change!
  • Effects 7? and 8? are effectively fine volume slide i.e. increment/decrement once per row.
  • Effect A? slides to the target volume "within ? rows". A parameter of 0 is translated to 1. The actual formula for this doesn't actually make the slide end within that number of rows:
start = current_volume*4;
target = row_volume*4 + 3; // bug
...
// This works out to completing in (param / 2) rows at speed 4.
volslide_step = diff * 16 / (param * interrupts_per_second);
  • New volumes interrupt A?.
  • Effect B? is equivalent to MOD E8x.

Retrigger and "note offset"

  • Effects 4? and C? do nothing when no note is present.
  • Effect 4? depends on the current FAR tempo (ugh). It's supposed to approximately subdivide the current row by the ? and play ? notes during the row, including the initial note. Due to bizarre/amateurish tick handling this can play a number of notes equal to the effective "speed" plus 1 (a note on the start of the next row). I don't think the note on the start of the next row is worth trying to support.
  • Effect C? "Note offset" is strange. It seems like it's intended to be a delay effect, but it also plays a note on the first tick of the row it's used on. This is supposed to work similar to 4?, but due to the poor precision when subdividing the row and the way retriggers occur, this effectively plays the second note on tick ?.
  • Effect 40 crashes Farandole Composer, don't try to play it unless you've saved your work. :(

"Fulfill loop"

  • edit: forgot to mention this one. Effect 03 "Fulfill Loop" is supposed to be sustain release but due to what looks like misusage of the GUS, it doesn't work most of the time, and instead just cuts the note. I implemented it as note cut in libxmp.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment