Returns:
diff --git a/docs/CopyrightEvent.html b/docs/CopyrightEvent.html index 2a8e699..f0dc67b 100644 --- a/docs/CopyrightEvent.html +++ b/docs/CopyrightEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/CuePointEvent.html b/docs/CuePointEvent.html index a6ad767..1a2bbf1 100644 --- a/docs/CuePointEvent.html +++ b/docs/CuePointEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/CuePointEvent.html b/docs/CuePointEvent.html index a6ad767..1a2bbf1 100644 --- a/docs/CuePointEvent.html +++ b/docs/CuePointEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/EndTrackEvent.html b/docs/EndTrackEvent.html index c0f92b8..e854a28 100644 --- a/docs/EndTrackEvent.html +++ b/docs/EndTrackEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/EndTrackEvent.html b/docs/EndTrackEvent.html index c0f92b8..e854a28 100644 --- a/docs/EndTrackEvent.html +++ b/docs/EndTrackEvent.html @@ -24,7 +24,7 @@
@@ -186,7 +186,7 @@
diff --git a/docs/HeaderChunk.html b/docs/HeaderChunk.html index 72904b8..e9f5525 100644 --- a/docs/HeaderChunk.html +++ b/docs/HeaderChunk.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/HeaderChunk.html b/docs/HeaderChunk.html index 72904b8..e9f5525 100644 --- a/docs/HeaderChunk.html +++ b/docs/HeaderChunk.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/InstrumentNameEvent.html b/docs/InstrumentNameEvent.html index 1e95d73..454c1bb 100644 --- a/docs/InstrumentNameEvent.html +++ b/docs/InstrumentNameEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/InstrumentNameEvent.html b/docs/InstrumentNameEvent.html index 1e95d73..454c1bb 100644 --- a/docs/InstrumentNameEvent.html +++ b/docs/InstrumentNameEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/KeySignatureEvent.html b/docs/KeySignatureEvent.html index 2744614..7758dcc 100644 --- a/docs/KeySignatureEvent.html +++ b/docs/KeySignatureEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/KeySignatureEvent.html b/docs/KeySignatureEvent.html index 2744614..7758dcc 100644 --- a/docs/KeySignatureEvent.html +++ b/docs/KeySignatureEvent.html @@ -24,7 +24,7 @@
@@ -186,7 +186,7 @@
diff --git a/docs/LyricEvent.html b/docs/LyricEvent.html index bf04ae3..e916e08 100644 --- a/docs/LyricEvent.html +++ b/docs/LyricEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/LyricEvent.html b/docs/LyricEvent.html index bf04ae3..e916e08 100644 --- a/docs/LyricEvent.html +++ b/docs/LyricEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/MarkerEvent.html b/docs/MarkerEvent.html index 35cafcd..2aa01e9 100644 --- a/docs/MarkerEvent.html +++ b/docs/MarkerEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/MarkerEvent.html b/docs/MarkerEvent.html index 35cafcd..2aa01e9 100644 --- a/docs/MarkerEvent.html +++ b/docs/MarkerEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/NoteEvent.html b/docs/NoteEvent.html index 56a3039..26c04eb 100644 --- a/docs/NoteEvent.html +++ b/docs/NoteEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/NoteEvent.html b/docs/NoteEvent.html index 56a3039..26c04eb 100644 --- a/docs/NoteEvent.html +++ b/docs/NoteEvent.html @@ -24,7 +24,7 @@
@@ -346,7 +346,7 @@
diff --git a/docs/NoteOffEvent.html b/docs/NoteOffEvent.html index bf892d5..807b342 100644 --- a/docs/NoteOffEvent.html +++ b/docs/NoteOffEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/NoteOffEvent.html b/docs/NoteOffEvent.html index bf892d5..807b342 100644 --- a/docs/NoteOffEvent.html +++ b/docs/NoteOffEvent.html @@ -24,7 +24,7 @@
@@ -431,7 +431,7 @@ getStatu
getStatu
Source:
@@ -504,7 +504,7 @@ Returns:
diff --git a/docs/NoteOnEvent.html b/docs/NoteOnEvent.html
index 14d8044..f6015dc 100644
--- a/docs/NoteOnEvent.html
+++ b/docs/NoteOnEvent.html
@@ -24,7 +24,7 @@
@@ -431,7 +431,7 @@ getStatu
Source:
@@ -504,7 +504,7 @@ Returns:
diff --git a/docs/ProgramChangeEvent.html b/docs/ProgramChangeEvent.html
index 3770620..a8625b9 100644
--- a/docs/ProgramChangeEvent.html
+++ b/docs/ProgramChangeEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TempoEvent.html b/docs/TempoEvent.html
index 98a73e4..6777467 100644
--- a/docs/TempoEvent.html
+++ b/docs/TempoEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TextEvent.html b/docs/TextEvent.html
index 561125e..c602f41 100644
--- a/docs/TextEvent.html
+++ b/docs/TextEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TimeSignatureEvent.html b/docs/TimeSignatureEvent.html
index bafcb24..8961b6e 100644
--- a/docs/TimeSignatureEvent.html
+++ b/docs/TimeSignatureEvent.html
@@ -24,7 +24,7 @@
@@ -186,7 +186,7 @@ Returns:
diff --git a/docs/Track.html b/docs/Track.html
index 5d47bc2..a926960 100644
--- a/docs/Track.html
+++ b/docs/Track.html
@@ -24,7 +24,7 @@
@@ -273,7 +273,7 @@ addCopyri
Source:
@@ -429,7 +429,7 @@ addCuePoin
Source:
@@ -772,7 +772,7 @@ addI
Source:
@@ -928,7 +928,7 @@ addLyricSource:
@@ -1084,7 +1084,7 @@ addMarkerSource:
@@ -1240,7 +1240,7 @@ addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
getStatu
Source:
@@ -504,7 +504,7 @@ Returns:
diff --git a/docs/ProgramChangeEvent.html b/docs/ProgramChangeEvent.html
index 3770620..a8625b9 100644
--- a/docs/ProgramChangeEvent.html
+++ b/docs/ProgramChangeEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TempoEvent.html b/docs/TempoEvent.html
index 98a73e4..6777467 100644
--- a/docs/TempoEvent.html
+++ b/docs/TempoEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TextEvent.html b/docs/TextEvent.html
index 561125e..c602f41 100644
--- a/docs/TextEvent.html
+++ b/docs/TextEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/TimeSignatureEvent.html b/docs/TimeSignatureEvent.html
index bafcb24..8961b6e 100644
--- a/docs/TimeSignatureEvent.html
+++ b/docs/TimeSignatureEvent.html
@@ -24,7 +24,7 @@
@@ -186,7 +186,7 @@ Returns:
diff --git a/docs/Track.html b/docs/Track.html
index 5d47bc2..a926960 100644
--- a/docs/Track.html
+++ b/docs/Track.html
@@ -24,7 +24,7 @@
@@ -273,7 +273,7 @@ addCopyri
Source:
@@ -429,7 +429,7 @@ addCuePoin
Source:
@@ -772,7 +772,7 @@ addI
Source:
@@ -928,7 +928,7 @@ addLyricSource:
@@ -1084,7 +1084,7 @@ addMarkerSource:
@@ -1240,7 +1240,7 @@ addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
Returns:
diff --git a/docs/TempoEvent.html b/docs/TempoEvent.html index 98a73e4..6777467 100644 --- a/docs/TempoEvent.html +++ b/docs/TempoEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/TextEvent.html b/docs/TextEvent.html index 561125e..c602f41 100644 --- a/docs/TextEvent.html +++ b/docs/TextEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/TextEvent.html b/docs/TextEvent.html index 561125e..c602f41 100644 --- a/docs/TextEvent.html +++ b/docs/TextEvent.html @@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@
diff --git a/docs/TimeSignatureEvent.html b/docs/TimeSignatureEvent.html index bafcb24..8961b6e 100644 --- a/docs/TimeSignatureEvent.html +++ b/docs/TimeSignatureEvent.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/TimeSignatureEvent.html b/docs/TimeSignatureEvent.html index bafcb24..8961b6e 100644 --- a/docs/TimeSignatureEvent.html +++ b/docs/TimeSignatureEvent.html @@ -24,7 +24,7 @@
@@ -186,7 +186,7 @@
diff --git a/docs/Track.html b/docs/Track.html index 5d47bc2..a926960 100644 --- a/docs/Track.html +++ b/docs/Track.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/Track.html b/docs/Track.html index 5d47bc2..a926960 100644 --- a/docs/Track.html +++ b/docs/Track.html @@ -24,7 +24,7 @@
@@ -273,7 +273,7 @@ addCopyri
addCopyri
Source:
@@ -429,7 +429,7 @@ addCuePoin
Source:
@@ -772,7 +772,7 @@ addI
Source:
@@ -928,7 +928,7 @@ addLyricSource:
@@ -1084,7 +1084,7 @@ addMarkerSource:
@@ -1240,7 +1240,7 @@ addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
addI
Source:
@@ -928,7 +928,7 @@ addLyricSource:
@@ -1084,7 +1084,7 @@ addMarkerSource:
@@ -1240,7 +1240,7 @@ addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
addMarkerSource:
@@ -1240,7 +1240,7 @@ addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
addTextSource:
@@ -1396,7 +1396,7 @@ addTrackN
Source:
@@ -1656,7 +1656,7 @@ contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
contr
Source:
@@ -1838,7 +1838,7 @@ merge
Source:
@@ -1997,7 +1997,7 @@ mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
mergeTrack<
Source:
@@ -2153,7 +2153,7 @@ polyModeOn<
Source:
@@ -2257,7 +2257,7 @@ rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
rem
Source:
@@ -2413,7 +2413,7 @@ setKey
Source:
@@ -2595,7 +2595,7 @@ setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
setPitchB
Source:
@@ -2751,7 +2751,7 @@ setTempoSource:
@@ -2907,7 +2907,7 @@ setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
setTi
Source:
@@ -3110,7 +3110,7 @@ Returns:
diff --git a/docs/TrackNameEvent.html b/docs/TrackNameEvent.html
index 3a4fe5d..3775f46 100644
--- a/docs/TrackNameEvent.html
+++ b/docs/TrackNameEvent.html
@@ -24,7 +24,7 @@
@@ -238,7 +238,7 @@ Returns:
diff --git a/docs/Utils.html b/docs/Utils.html
index 0c143ed..abee480 100644
--- a/docs/Utils.html
+++ b/docs/Utils.html
@@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getPrecisionLoss(tick) → {number}
+
+
+
+
+
+
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (static) getRoundedIfClose(tick) → {number}
+
+
+
+
+
+
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ tick
+
+
+
+
+
+number
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Returns:
@@ -674,7 +989,7 @@ (static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
Returns:
diff --git a/docs/Utils.html b/docs/Utils.html index 0c143ed..abee480 100644 --- a/docs/Utils.html +++ b/docs/Utils.html @@ -24,7 +24,7 @@
@@ -200,7 +200,7 @@ (static) Source:
+
+
+(static) Source:
@@ -357,7 +357,7 @@ (stati
Source:
@@ -600,6 +600,321 @@ Parameters:
+
+Returns:
+
+
+
+
+ -
+ Type:
+
+ -
+
+
number
+
+
+
+
+
+
+
+
+
+
+
+
Parameters:
+
+
+
+
+
+Returns:
+ + + +-
+
- + Type: + +
-
+
+
number
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+(static) getPrecisionLoss(tick) → {number}
+ + + + + +
+ Due to low precision of MIDI,
+we need to keep track of rounding errors in deltas.
+This function will calculate the rounding error for a given duration.
+
+
+
+
+
+
+-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- Source: +
- + + + + + + + +
Parameters:
+ + +Name | + + +Type | + + + + + +Description | +
---|---|---|
tick |
+
+
+
+
+
+number
+
+
+
+ |
+
+
+
+
+
+ + + + | +
+
+
+
+
+Returns:
+ + + +-
+
- + Type: + +
-
+
+
number
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(static) Source:
(static) getRoundedIfClose(tick) → {number}
+ + + + + +
+ Due to rounding errors in JavaScript engines,
+it's safe to round when we're very close to the actual tick number
+
+
+
+
+
+
+-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- Source: +
- + + + + + + + +
Parameters:
+ + +Name | + + +Type | + + + + + +Description | +
---|---|---|
tick |
+
+
+
+
+
+number
+
+
+
+ |
+
+
+
+
+
+ + + + | +
Returns:
@@ -674,7 +989,7 @@(static) Source:
@@ -989,7 +1304,7 @@ (static) Source:
@@ -1145,7 +1460,7 @@ (static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
(static) Source:
@@ -1494,7 +1809,7 @@ (static) Source:
@@ -1806,7 +2121,7 @@ (static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
(static) toAr
Source:
@@ -2035,7 +2350,7 @@ Returns:
diff --git a/docs/Writer.html b/docs/Writer.html
index d07f4ae..27deb08 100644
--- a/docs/Writer.html
+++ b/docs/Writer.html
@@ -24,7 +24,7 @@
@@ -796,7 +796,7 @@ Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html
index 1ae418c..2e78196 100644
--- a/docs/constants.js.html
+++ b/docs/constants.js.html
@@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@ constants.js
diff --git a/docs/global.html b/docs/global.html
index e8a91e5..dc12908 100644
--- a/docs/global.html
+++ b/docs/global.html
@@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@ Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html
index 74f6f75..b863ac0 100644
--- a/docs/header-chunk.js.html
+++ b/docs/header-chunk.js.html
@@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@ header-chunk.js
diff --git a/docs/index.html b/docs/index.html
index 69feaa8..60beb31 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@ VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html
index 3f184e1..fc42260 100644
--- a/docs/meta-events_controller-change-event.js.html
+++ b/docs/meta-events_controller-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html
index 29d8d19..5a9caeb 100644
--- a/docs/meta-events_copyright-event.js.html
+++ b/docs/meta-events_copyright-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html
index 542b757..6f857bb 100644
--- a/docs/meta-events_cue-point-event.js.html
+++ b/docs/meta-events_cue-point-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html
index c37d7bc..c32c5c3 100644
--- a/docs/meta-events_end-track-event.js.html
+++ b/docs/meta-events_end-track-event.js.html
@@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@ meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html
index 8a642ed..88d8471 100644
--- a/docs/meta-events_instrument-name-event.js.html
+++ b/docs/meta-events_instrument-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html
index 13c412e..40aab44 100644
--- a/docs/meta-events_key-signature-event.js.html
+++ b/docs/meta-events_key-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@ meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html
index c9dd39c..ba70552 100644
--- a/docs/meta-events_lyric-event.js.html
+++ b/docs/meta-events_lyric-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html
index 3dac53b..ef88e16 100644
--- a/docs/meta-events_marker-event.js.html
+++ b/docs/meta-events_marker-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html
index 615d8a2..1604a6a 100644
--- a/docs/meta-events_pitch-bend-event.js.html
+++ b/docs/meta-events_pitch-bend-event.js.html
@@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@ meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html
index b11bc65..8590210 100644
--- a/docs/meta-events_program-change-event.js.html
+++ b/docs/meta-events_program-change-event.js.html
@@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@ meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html
index bdf0764..5635413 100644
--- a/docs/meta-events_tempo-event.js.html
+++ b/docs/meta-events_tempo-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html
index 4af7e4c..4a58412 100644
--- a/docs/meta-events_text-event.js.html
+++ b/docs/meta-events_text-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html
index a693df5..d46397a 100644
--- a/docs/meta-events_time-signature-event.js.html
+++ b/docs/meta-events_time-signature-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html
index ec9bb3f..619ef9c 100644
--- a/docs/meta-events_track-name-event.js.html
+++ b/docs/meta-events_track-name-event.js.html
@@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@ meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html
index 4191fbb..efbf037 100644
--- a/docs/note-events_note-event.js.html
+++ b/docs/note-events_note-event.js.html
@@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@ note-events/note-event.js
restDuration = 0;
}
- // If duration is 8th triplets we need to make sure that the total ticks == quarter note.
- // So, the last one will need to be the remainder
- if (this.duration === '8t' && i == this.pitch.length - 1) {
- let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- tickDuration = quarterTicks - (tickDuration * 2);
- }
-
var noteOnNew = new NoteOnEvent({
channel: this.channel,
wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition
@@ -218,7 +211,7 @@ note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html
index d155caa..c3c93d6 100644
--- a/docs/note-events_note-off-event.js.html
+++ b/docs/note-events_note-off-event.js.html
@@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@ note-events/note-off-event.js
* @param {Track} track - parent track
* @return {NoteOffEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
if (this.tick === null) {
- this.tick = this.delta + track.tickPointer;
+ this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -109,7 +111,7 @@ note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html
index f7a89ee..d855bfa 100644
--- a/docs/note-events_note-on-event.js.html
+++ b/docs/note-events_note-on-event.js.html
@@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@ note-events/note-on-event.js
* @param {Track} track - parent track
* @return {NoteOnEvent}
*/
- buildData(track) {
+ buildData(track, precisionDelta) {
this.data = [];
// Explicitly defined startTick event
if (this.startTick) {
- this.tick = this.startTick;
+ this.tick = Utils.getRoundedIfClose(this.startTick);
// If this is the first event in the track then use event's starting tick as delta.
if (track.tickPointer == 0) {
@@ -88,10 +88,12 @@ note-events/note-on-event.js
} else {
this.delta = Utils.getTickDuration(this.wait);
- this.tick = track.tickPointer + this.delta;
+ this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta);
}
- this.data = Utils.numberToVariableLength(this.delta)
+ this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta);
+
+ this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection)
.concat(
this.getStatusByte(),
this.midiNumber,
@@ -123,7 +125,7 @@ note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html
index b8a4e46..5ddf40d 100644
--- a/docs/track.js.html
+++ b/docs/track.js.html
@@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@ track.js
this.size = [];
this.tickPointer = 0;
+ let precisionLoss = 0;
+
this.events.forEach((event, eventIndex) => {
// Build event & add to total tick duration
if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) {
- this.data = this.data.concat(event.buildData(this).data);
- this.tickPointer = event.tick;
-
+ const built = event.buildData(this, precisionLoss);
+ precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0);
+ this.data = this.data.concat(built.data);
+ this.tickPointer = Utils.getRoundedIfClose(event.tick);
} else {
this.data = this.data.concat(event.data);
}
@@ -380,7 +383,7 @@ track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html
index ef5f838..9d7e35b 100644
--- a/docs/utils.js.html
+++ b/docs/utils.js.html
@@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@ utils.js
* @return {array} - Bytes that form the MIDI time value
*/
static numberToVariableLength(ticks) {
+ ticks = Math.round(ticks);
var buffer = ticks & 0x7F;
while (ticks = ticks >> 7) {
@@ -217,9 +218,36 @@ utils.js
}
// Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- // Rounding only applies to triplets, which the remainder is handled below
var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION);
- return Math.round(quarterTicks * Utils.getDurationMultiplier(duration));
+ const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration);
+ return Utils.getRoundedIfClose(tickDuration)
+ }
+
+ /**
+ * Due to rounding errors in JavaScript engines,
+ * it's safe to round when we're very close to the actual tick number
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getRoundedIfClose(tick) {
+ const roundedTick = Math.round(tick);
+ return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick;
+ }
+
+ /**
+ * Due to low precision of MIDI,
+ * we need to keep track of rounding errors in deltas.
+ * This function will calculate the rounding error for a given duration.
+ *
+ * @static
+ * @param {number} tick
+ * @return {number}
+ */
+ static getPrecisionLoss(tick) {
+ const roundedTick = Math.round(tick);
+ return roundedTick - tick;
}
/**
@@ -229,49 +257,36 @@ utils.js
* @return {number}
*/
static getDurationMultiplier(duration) {
- // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION
- switch (duration) {
- case '0':
- return 0;
- case '1':
- return 4;
- case '2':
- return 2;
- case 'd2': // Dotted half
- return 3;
- case 'dd2': // Double dotted half
- return 3.5;
- case '4':
- return 1;
- case '4t':
- return 0.666;
- case 'd4': // Dotted quarter
- return 1.5;
- case 'dd4': // Double dotted quarter
- return 1.75;
- case '8':
- return 0.5;
- case '8t':
- // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one.
- return 0.33;
- case 'd8': // Dotted eighth
- return 0.75;
- case 'dd8': // Double dotted eighth
- return 0.875;
- case '16':
- return 0.25;
- case '16t':
- return 0.166;
- case '32':
- return 0.125;
- case '64':
- return 0.0625;
- default:
- // Notes default to a quarter, rests default to 0
- //return type === 'note' ? 1 : 0;
+ // Need to apply duration here.
+ // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks.
+
+ if (duration === '0') return 0;
+
+ const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/);
+ if (match) {
+ const base = Number(match.groups.base);
+ // 1 or any power of two:
+ const isValidBase = base === 1 || ((base & (base - 1)) === 0);
+ if (isValidBase) {
+ // how much faster or slower is this note compared to a quarter?
+ const ratio = base / 4;
+ let durationInQuarters = 1 / ratio;
+ const {dotted, tuplet} = match.groups;
+ if (dotted) {
+ const thisManyDots = dotted.length;
+ const divisor = Math.pow(2, thisManyDots);
+ durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor));
+ }
+ if (typeof tuplet === 'string') {
+ const fitInto = durationInQuarters * 2;
+ // default to triplet:
+ const thisManyNotes = Number(tuplet || '3');
+ durationInQuarters = fitInto / thisManyNotes;
+ }
+ return durationInQuarters
+ }
}
-
- throw duration + ' is not a valid duration.';
+ throw new Error(duration + ' is not a valid duration.');
}
}
@@ -288,7 +303,7 @@ utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html
index 514fe6f..aeed1a1 100644
--- a/docs/vexflow.js.html
+++ b/docs/vexflow.js.html
@@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@ vexflow.js
class VexFlow {
- constructor() {
- // code...
- }
-
/**
* Support for converting VexFlow voice into MidiWriterJS track
- * @return MidiWritier.Track object
+ * @return MidiWriter.Track object
*/
trackFromVoice(voice) {
- var track = new Track();
- var wait;
- var pitches = [];
+ const track = new Track();
+ let wait = [];
voice.tickables.forEach(tickable => {
- pitches = [];
-
if (tickable.noteType === 'n') {
- tickable.keys.forEach(key => {
- // build array of pitches
- pitches.push(this.convertPitch(key));
- });
-
+ track.addEvent(new NoteEvent({
+ pitch: tickable.keys.map(this.convertPitch),
+ duration: this.convertDuration(tickable),
+ wait
+ }));
+ // reset wait
+ wait = [];
} else if (tickable.noteType === 'r') {
- // move on to the next tickable and use this rest as a `wait` property for the next event
- wait = this.convertDuration(tickable);
+ // move on to the next tickable and add this to the stack
+ // of the `wait` property for the next note event
+ wait.push(this.convertDuration(tickable));
return;
}
-
- track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait}));
-
- // reset wait
- wait = 0;
});
+ // There may be outstanding rests at the end of the track,
+ // pad with a ghost note (zero duration and velocity), just to capture the wait.
+ if(wait.length > 0) {
+ track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'}));
+ }
+
return track;
}
-
/**
* Converts VexFlow pitch syntax to MidiWriterJS syntax
* @param pitch string
@@ -90,7 +86,6 @@ vexflow.js
return pitch.replace('/', '');
}
-
/**
* Converts VexFlow duration syntax to MidiWriterJS syntax
* @param note struct from VexFlow
@@ -124,7 +119,7 @@ vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html
index d3d3a60..4039586 100644
--- a/docs/writer.js.html
+++ b/docs/writer.js.html
@@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@ writer.js
Returns:
diff --git a/docs/constants.js.html b/docs/constants.js.html index 1ae418c..2e78196 100644 --- a/docs/constants.js.html +++ b/docs/constants.js.html @@ -24,7 +24,7 @@
@@ -83,7 +83,7 @@
diff --git a/docs/global.html b/docs/global.html index e8a91e5..dc12908 100644 --- a/docs/global.html +++ b/docs/global.html @@ -24,7 +24,7 @@
constants.js
diff --git a/docs/global.html b/docs/global.html index e8a91e5..dc12908 100644 --- a/docs/global.html +++ b/docs/global.html @@ -24,7 +24,7 @@
@@ -354,7 +354,7 @@
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html index 74f6f75..b863ac0 100644 --- a/docs/header-chunk.js.html +++ b/docs/header-chunk.js.html @@ -24,7 +24,7 @@
Returns:
diff --git a/docs/header-chunk.js.html b/docs/header-chunk.js.html index 74f6f75..b863ac0 100644 --- a/docs/header-chunk.js.html +++ b/docs/header-chunk.js.html @@ -24,7 +24,7 @@
@@ -75,7 +75,7 @@
diff --git a/docs/index.html b/docs/index.html index 69feaa8..60beb31 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@
header-chunk.js
diff --git a/docs/index.html b/docs/index.html index 69feaa8..60beb31 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,7 +24,7 @@
@@ -222,7 +222,7 @@
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html index 3f184e1..fc42260 100644 --- a/docs/meta-events_controller-change-event.js.html +++ b/docs/meta-events_controller-change-event.js.html @@ -24,7 +24,7 @@
VexFlow Integration
diff --git a/docs/meta-events_controller-change-event.js.html b/docs/meta-events_controller-change-event.js.html index 3f184e1..fc42260 100644 --- a/docs/meta-events_controller-change-event.js.html +++ b/docs/meta-events_controller-change-event.js.html @@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html index 29d8d19..5a9caeb 100644 --- a/docs/meta-events_copyright-event.js.html +++ b/docs/meta-events_copyright-event.js.html @@ -24,7 +24,7 @@
meta-events/controller-change-event.js
diff --git a/docs/meta-events_copyright-event.js.html b/docs/meta-events_copyright-event.js.html index 29d8d19..5a9caeb 100644 --- a/docs/meta-events_copyright-event.js.html +++ b/docs/meta-events_copyright-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html index 542b757..6f857bb 100644 --- a/docs/meta-events_cue-point-event.js.html +++ b/docs/meta-events_cue-point-event.js.html @@ -24,7 +24,7 @@
meta-events/copyright-event.js
diff --git a/docs/meta-events_cue-point-event.js.html b/docs/meta-events_cue-point-event.js.html index 542b757..6f857bb 100644 --- a/docs/meta-events_cue-point-event.js.html +++ b/docs/meta-events_cue-point-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html index c37d7bc..c32c5c3 100644 --- a/docs/meta-events_end-track-event.js.html +++ b/docs/meta-events_end-track-event.js.html @@ -24,7 +24,7 @@
meta-events/cue-point-event.js
diff --git a/docs/meta-events_end-track-event.js.html b/docs/meta-events_end-track-event.js.html index c37d7bc..c32c5c3 100644 --- a/docs/meta-events_end-track-event.js.html +++ b/docs/meta-events_end-track-event.js.html @@ -24,7 +24,7 @@
@@ -71,7 +71,7 @@
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html index 8a642ed..88d8471 100644 --- a/docs/meta-events_instrument-name-event.js.html +++ b/docs/meta-events_instrument-name-event.js.html @@ -24,7 +24,7 @@
meta-events/end-track-event.js
diff --git a/docs/meta-events_instrument-name-event.js.html b/docs/meta-events_instrument-name-event.js.html index 8a642ed..88d8471 100644 --- a/docs/meta-events_instrument-name-event.js.html +++ b/docs/meta-events_instrument-name-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html index 13c412e..40aab44 100644 --- a/docs/meta-events_key-signature-event.js.html +++ b/docs/meta-events_key-signature-event.js.html @@ -24,7 +24,7 @@
meta-events/instrument-name-event.js
diff --git a/docs/meta-events_key-signature-event.js.html b/docs/meta-events_key-signature-event.js.html index 13c412e..40aab44 100644 --- a/docs/meta-events_key-signature-event.js.html +++ b/docs/meta-events_key-signature-event.js.html @@ -24,7 +24,7 @@
@@ -117,7 +117,7 @@
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html index c9dd39c..ba70552 100644 --- a/docs/meta-events_lyric-event.js.html +++ b/docs/meta-events_lyric-event.js.html @@ -24,7 +24,7 @@
meta-events/key-signature-event.js
diff --git a/docs/meta-events_lyric-event.js.html b/docs/meta-events_lyric-event.js.html index c9dd39c..ba70552 100644 --- a/docs/meta-events_lyric-event.js.html +++ b/docs/meta-events_lyric-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html index 3dac53b..ef88e16 100644 --- a/docs/meta-events_marker-event.js.html +++ b/docs/meta-events_marker-event.js.html @@ -24,7 +24,7 @@
meta-events/lyric-event.js
diff --git a/docs/meta-events_marker-event.js.html b/docs/meta-events_marker-event.js.html index 3dac53b..ef88e16 100644 --- a/docs/meta-events_marker-event.js.html +++ b/docs/meta-events_marker-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html index 615d8a2..1604a6a 100644 --- a/docs/meta-events_pitch-bend-event.js.html +++ b/docs/meta-events_pitch-bend-event.js.html @@ -24,7 +24,7 @@
meta-events/marker-event.js
diff --git a/docs/meta-events_pitch-bend-event.js.html b/docs/meta-events_pitch-bend-event.js.html index 615d8a2..1604a6a 100644 --- a/docs/meta-events_pitch-bend-event.js.html +++ b/docs/meta-events_pitch-bend-event.js.html @@ -24,7 +24,7 @@
@@ -82,7 +82,7 @@
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html index b11bc65..8590210 100644 --- a/docs/meta-events_program-change-event.js.html +++ b/docs/meta-events_program-change-event.js.html @@ -24,7 +24,7 @@
meta-events/pitch-bend-event.js
diff --git a/docs/meta-events_program-change-event.js.html b/docs/meta-events_program-change-event.js.html index b11bc65..8590210 100644 --- a/docs/meta-events_program-change-event.js.html +++ b/docs/meta-events_program-change-event.js.html @@ -24,7 +24,7 @@
@@ -68,7 +68,7 @@
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html index bdf0764..5635413 100644 --- a/docs/meta-events_tempo-event.js.html +++ b/docs/meta-events_tempo-event.js.html @@ -24,7 +24,7 @@
meta-events/program-change-event.js
diff --git a/docs/meta-events_tempo-event.js.html b/docs/meta-events_tempo-event.js.html index bdf0764..5635413 100644 --- a/docs/meta-events_tempo-event.js.html +++ b/docs/meta-events_tempo-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html index 4af7e4c..4a58412 100644 --- a/docs/meta-events_text-event.js.html +++ b/docs/meta-events_text-event.js.html @@ -24,7 +24,7 @@
meta-events/tempo-event.js
diff --git a/docs/meta-events_text-event.js.html b/docs/meta-events_text-event.js.html index 4af7e4c..4a58412 100644 --- a/docs/meta-events_text-event.js.html +++ b/docs/meta-events_text-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html index a693df5..d46397a 100644 --- a/docs/meta-events_time-signature-event.js.html +++ b/docs/meta-events_time-signature-event.js.html @@ -24,7 +24,7 @@
meta-events/text-event.js
diff --git a/docs/meta-events_time-signature-event.js.html b/docs/meta-events_time-signature-event.js.html index a693df5..d46397a 100644 --- a/docs/meta-events_time-signature-event.js.html +++ b/docs/meta-events_time-signature-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html index ec9bb3f..619ef9c 100644 --- a/docs/meta-events_track-name-event.js.html +++ b/docs/meta-events_track-name-event.js.html @@ -24,7 +24,7 @@
meta-events/time-signature-event.js
diff --git a/docs/meta-events_track-name-event.js.html b/docs/meta-events_track-name-event.js.html index ec9bb3f..619ef9c 100644 --- a/docs/meta-events_track-name-event.js.html +++ b/docs/meta-events_track-name-event.js.html @@ -24,7 +24,7 @@
@@ -76,7 +76,7 @@
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html index 4191fbb..efbf037 100644 --- a/docs/note-events_note-event.js.html +++ b/docs/note-events_note-event.js.html @@ -24,7 +24,7 @@
meta-events/track-name-event.js
diff --git a/docs/note-events_note-event.js.html b/docs/note-events_note-event.js.html index 4191fbb..efbf037 100644 --- a/docs/note-events_note-event.js.html +++ b/docs/note-events_note-event.js.html @@ -24,7 +24,7 @@
@@ -174,13 +174,6 @@
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html index d155caa..c3c93d6 100644 --- a/docs/note-events_note-off-event.js.html +++ b/docs/note-events_note-off-event.js.html @@ -24,7 +24,7 @@
note-events/note-event.js
restDuration = 0; } - // If duration is 8th triplets we need to make sure that the total ticks == quarter note. - // So, the last one will need to be the remainder - if (this.duration === '8t' && i == this.pitch.length - 1) { - let quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION); - tickDuration = quarterTicks - (tickDuration * 2); - } - var noteOnNew = new NoteOnEvent({ channel: this.channel, wait: (i > 0 ? 0 : this.wait), // wait only applies to first note in repetition @@ -218,7 +211,7 @@note-events/note-event.js
diff --git a/docs/note-events_note-off-event.js.html b/docs/note-events_note-off-event.js.html index d155caa..c3c93d6 100644 --- a/docs/note-events_note-off-event.js.html +++ b/docs/note-events_note-off-event.js.html @@ -24,7 +24,7 @@
@@ -72,12 +72,14 @@
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html index f7a89ee..d855bfa 100644 --- a/docs/note-events_note-on-event.js.html +++ b/docs/note-events_note-on-event.js.html @@ -24,7 +24,7 @@
note-events/note-off-event.js
* @param {Track} track - parent track * @return {NoteOffEvent} */ - buildData(track) { + buildData(track, precisionDelta) { if (this.tick === null) { - this.tick = this.delta + track.tickPointer; + this.tick = Utils.getRoundedIfClose(this.delta + track.tickPointer); } - this.data = Utils.numberToVariableLength(this.delta) + this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta); + + this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection) .concat( this.getStatusByte(), this.midiNumber, @@ -109,7 +111,7 @@note-events/note-off-event.js
diff --git a/docs/note-events_note-on-event.js.html b/docs/note-events_note-on-event.js.html index f7a89ee..d855bfa 100644 --- a/docs/note-events_note-on-event.js.html +++ b/docs/note-events_note-on-event.js.html @@ -24,7 +24,7 @@
@@ -74,12 +74,12 @@
diff --git a/docs/track.js.html b/docs/track.js.html index b8a4e46..5ddf40d 100644 --- a/docs/track.js.html +++ b/docs/track.js.html @@ -24,7 +24,7 @@
note-events/note-on-event.js
* @param {Track} track - parent track * @return {NoteOnEvent} */ - buildData(track) { + buildData(track, precisionDelta) { this.data = []; // Explicitly defined startTick event if (this.startTick) { - this.tick = this.startTick; + this.tick = Utils.getRoundedIfClose(this.startTick); // If this is the first event in the track then use event's starting tick as delta. if (track.tickPointer == 0) { @@ -88,10 +88,12 @@note-events/note-on-event.js
} else { this.delta = Utils.getTickDuration(this.wait); - this.tick = track.tickPointer + this.delta; + this.tick = Utils.getRoundedIfClose(track.tickPointer + this.delta); } - this.data = Utils.numberToVariableLength(this.delta) + this.deltaWithPrecisionCorrection = Utils.getRoundedIfClose(this.delta - precisionDelta); + + this.data = Utils.numberToVariableLength(this.deltaWithPrecisionCorrection) .concat( this.getStatusByte(), this.midiNumber, @@ -123,7 +125,7 @@note-events/note-on-event.js
diff --git a/docs/track.js.html b/docs/track.js.html index b8a4e46..5ddf40d 100644 --- a/docs/track.js.html +++ b/docs/track.js.html @@ -24,7 +24,7 @@
@@ -142,12 +142,15 @@
diff --git a/docs/utils.js.html b/docs/utils.js.html index ef5f838..9d7e35b 100644 --- a/docs/utils.js.html +++ b/docs/utils.js.html @@ -24,7 +24,7 @@
track.js
this.size = []; this.tickPointer = 0; + let precisionLoss = 0; + this.events.forEach((event, eventIndex) => { // Build event & add to total tick duration if (event instanceof NoteOnEvent || event instanceof NoteOffEvent) { - this.data = this.data.concat(event.buildData(this).data); - this.tickPointer = event.tick; - + const built = event.buildData(this, precisionLoss); + precisionLoss = Utils.getPrecisionLoss(event.deltaWithPrecisionCorrection || 0); + this.data = this.data.concat(built.data); + this.tickPointer = Utils.getRoundedIfClose(event.tick); } else { this.data = this.data.concat(event.data); } @@ -380,7 +383,7 @@track.js
diff --git a/docs/utils.js.html b/docs/utils.js.html index ef5f838..9d7e35b 100644 --- a/docs/utils.js.html +++ b/docs/utils.js.html @@ -24,7 +24,7 @@
@@ -93,6 +93,7 @@
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html index 514fe6f..aeed1a1 100644 --- a/docs/vexflow.js.html +++ b/docs/vexflow.js.html @@ -24,7 +24,7 @@
utils.js
* @return {array} - Bytes that form the MIDI time value */ static numberToVariableLength(ticks) { + ticks = Math.round(ticks); var buffer = ticks & 0x7F; while (ticks = ticks >> 7) { @@ -217,9 +218,36 @@utils.js
} // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION - // Rounding only applies to triplets, which the remainder is handled below var quarterTicks = Utils.numberFromBytes(Constants.HEADER_CHUNK_DIVISION); - return Math.round(quarterTicks * Utils.getDurationMultiplier(duration)); + const tickDuration = quarterTicks * Utils.getDurationMultiplier(duration); + return Utils.getRoundedIfClose(tickDuration) + } + + /** + * Due to rounding errors in JavaScript engines, + * it's safe to round when we're very close to the actual tick number + * + * @static + * @param {number} tick + * @return {number} + */ + static getRoundedIfClose(tick) { + const roundedTick = Math.round(tick); + return Math.abs(roundedTick - tick) < 0.000001 ? roundedTick : tick; + } + + /** + * Due to low precision of MIDI, + * we need to keep track of rounding errors in deltas. + * This function will calculate the rounding error for a given duration. + * + * @static + * @param {number} tick + * @return {number} + */ + static getPrecisionLoss(tick) { + const roundedTick = Math.round(tick); + return roundedTick - tick; } /** @@ -229,49 +257,36 @@utils.js
* @return {number} */ static getDurationMultiplier(duration) { - // Need to apply duration here. Quarter note == Constants.HEADER_CHUNK_DIVISION - switch (duration) { - case '0': - return 0; - case '1': - return 4; - case '2': - return 2; - case 'd2': // Dotted half - return 3; - case 'dd2': // Double dotted half - return 3.5; - case '4': - return 1; - case '4t': - return 0.666; - case 'd4': // Dotted quarter - return 1.5; - case 'dd4': // Double dotted quarter - return 1.75; - case '8': - return 0.5; - case '8t': - // For 8th triplets, let's divide a quarter by 3, round to the nearest int, and substract the remainder to the last one. - return 0.33; - case 'd8': // Dotted eighth - return 0.75; - case 'dd8': // Double dotted eighth - return 0.875; - case '16': - return 0.25; - case '16t': - return 0.166; - case '32': - return 0.125; - case '64': - return 0.0625; - default: - // Notes default to a quarter, rests default to 0 - //return type === 'note' ? 1 : 0; + // Need to apply duration here. + // Quarter note == Constants.HEADER_CHUNK_DIVISION ticks. + + if (duration === '0') return 0; + + const match = duration.match(/^(?<dotted>d+)?(?<base>\d+)(?:t(?<tuplet>\d*))?/); + if (match) { + const base = Number(match.groups.base); + // 1 or any power of two: + const isValidBase = base === 1 || ((base & (base - 1)) === 0); + if (isValidBase) { + // how much faster or slower is this note compared to a quarter? + const ratio = base / 4; + let durationInQuarters = 1 / ratio; + const {dotted, tuplet} = match.groups; + if (dotted) { + const thisManyDots = dotted.length; + const divisor = Math.pow(2, thisManyDots); + durationInQuarters = durationInQuarters + (durationInQuarters * ((divisor - 1) / divisor)); + } + if (typeof tuplet === 'string') { + const fitInto = durationInQuarters * 2; + // default to triplet: + const thisManyNotes = Number(tuplet || '3'); + durationInQuarters = fitInto / thisManyNotes; + } + return durationInQuarters + } } - - throw duration + ' is not a valid duration.'; + throw new Error(duration + ' is not a valid duration.'); } } @@ -288,7 +303,7 @@utils.js
diff --git a/docs/vexflow.js.html b/docs/vexflow.js.html index 514fe6f..aeed1a1 100644 --- a/docs/vexflow.js.html +++ b/docs/vexflow.js.html @@ -24,7 +24,7 @@
@@ -44,44 +44,40 @@
diff --git a/docs/writer.js.html b/docs/writer.js.html index d3d3a60..4039586 100644 --- a/docs/writer.js.html +++ b/docs/writer.js.html @@ -24,7 +24,7 @@
vexflow.js
class VexFlow { - constructor() { - // code... - } - /** * Support for converting VexFlow voice into MidiWriterJS track - * @return MidiWritier.Track object + * @return MidiWriter.Track object */ trackFromVoice(voice) { - var track = new Track(); - var wait; - var pitches = []; + const track = new Track(); + let wait = []; voice.tickables.forEach(tickable => { - pitches = []; - if (tickable.noteType === 'n') { - tickable.keys.forEach(key => { - // build array of pitches - pitches.push(this.convertPitch(key)); - }); - + track.addEvent(new NoteEvent({ + pitch: tickable.keys.map(this.convertPitch), + duration: this.convertDuration(tickable), + wait + })); + // reset wait + wait = []; } else if (tickable.noteType === 'r') { - // move on to the next tickable and use this rest as a `wait` property for the next event - wait = this.convertDuration(tickable); + // move on to the next tickable and add this to the stack + // of the `wait` property for the next note event + wait.push(this.convertDuration(tickable)); return; } - - track.addEvent(new NoteEvent({pitch: pitches, duration: this.convertDuration(tickable), wait: wait})); - - // reset wait - wait = 0; }); + // There may be outstanding rests at the end of the track, + // pad with a ghost note (zero duration and velocity), just to capture the wait. + if(wait.length > 0) { + track.addEvent(new NoteEvent({pitch: '[c4]', duration: '0', wait, velocity: '0'})); + } + return track; } - /** * Converts VexFlow pitch syntax to MidiWriterJS syntax * @param pitch string @@ -90,7 +86,6 @@vexflow.js
return pitch.replace('/', ''); } - /** * Converts VexFlow duration syntax to MidiWriterJS syntax * @param note struct from VexFlow @@ -124,7 +119,7 @@vexflow.js
diff --git a/docs/writer.js.html b/docs/writer.js.html index d3d3a60..4039586 100644 --- a/docs/writer.js.html +++ b/docs/writer.js.html @@ -24,7 +24,7 @@
@@ -125,7 +125,7 @@