grimmdude / MidiWriterJS

♬ A JavaScript library which provides an API for programmatically generating and creating expressive multi-track MIDI files and JSON.
MIT License
557 stars 60 forks source link

Fix delta slippage after use of triplets/complex durations #78

Closed niieani closed 3 years ago

niieani commented 3 years ago

Fixes #69.

First track from the Chopin MIDI generated from the examples directory:

Screen Shot 2021-02-01 at 2 15 05 AM

There were 2 issues I had to tackle to fix this problem.

First was low precision in the addition of triplet and dotted durations causing rounding issues.

Second, harder part was delta slippage - even though it was calculated correctly, rounding was being done per note, and that meant that every 33.66 would always round up to 34. In reality this caused a slight shift forward or backwards whenever a complex duration event happened. Use 3 triplets and you were already 3 ticks ahead. To mitigate, I've added a concept of precision loss tracking within a track, which will apply the loss to each subsequent event in effort to correct it. This means that the same duration will sometimes last 34 and sometimes 33 ticks and so the rounding issue self-corrects with every subsequent event.

Incidentally I've added support more flexible durations (all durations are calculated dynamically).

This enables things like quintuplets (or any other custom tuplet), by adding a classifier after the duration name (e.g. 8t5). Triplets are the implied tuplet type - so same behavior as previously: 8t is same as 8t3.

Also, any power of 2 note duration is supported, so 128ths will work with all combinations too. And one can do triple dotted notes or quadruple dotted if one's into that 😄 (e.g. dddd4).

Finally, you can also do tuples of dotted notes (d4t) and more complex things like dd8t7 - a septuplet of double dotted eights 😂.

Hope this is satisfactory! Added a bunch of tests for these fixes and new behaviors too.

grimmdude commented 3 years ago

Wow, amazing job on this @niieani! Thanks very much for this contribution, adds so much value to the library. Will create new 1.7.5 release to include this.