Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ <h2><label for="midifile">MIDI file</label></h2>
<span>Slide mode</span>
<div class="flexrow spacebetween">
<span>
<input type="radio" id="slidemidi2tc" name="slidemode" checked>
<input type="radio" id="slidemidi2tc" name="slidemode">
<label for="slidemidi2tc">midi2tc</label>
</span>
<span>
<input type="radio" id="slidetccc" name="slidemode">
<input type="radio" id="slidetccc" name="slidemode" checked>
<label for="slidetccc">tccc</label>
</span>
</div>
Expand Down Expand Up @@ -104,7 +104,7 @@ <h3>- Song info -</h3>

<h3>- Chart info -</h3>

<abbr title="Variable BPM and non-integer BPM are not supported">i</abbr>
<abbr title="Variable BPM and non-integer BPM are supported">i</abbr>
<label for="bpm">BPM</label>
<input type="number" id="bpm" name="bpm" min="1" max="1000">

Expand Down Expand Up @@ -144,7 +144,7 @@ <h3>- Other -</h3>

<abbr title="Whether TCCC should generate a random prefix for trackRef. This ensures trackRef will be globally unique.">i</abbr>
<label for="prefixTrackRef">Prefix trackRef?</label>
<input type="checkbox" id="prefixTrackRef" name="prefixTrackRef" checked>
<input type="checkbox" id="prefixTrackRef" name="prefixTrackRef">

<abbr title="The length of the song in measures. Leave this blank unless you need to manually set the endpoint.">i</abbr>
<label for="songendpoint">Song Endpoint</label>
Expand Down Expand Up @@ -280,6 +280,10 @@ <h3>Examples</h3>

<div class="container">
<h2>Version history</h2>
<p>
v1.9<br>
Added support for tempo changes
</p>
<p>
v1.8a:<br>
Changed "Song Folder" to "trackRef"
Expand Down Expand Up @@ -379,7 +383,7 @@ <h2>Version history</h2>
</div>
<div class="footer">
<p>
<a href="https://github.com/TC-Chart-Converter/TC-Chart-Converter.github.io/">Trombone Champ Chart Converter</a> by RShields, Gloomhonk, and contributors<br>
<a href="https://github.com/TC-Chart-Converter/TC-Chart-Converter.github.io/">Trombone Champ Chart Converter</a> by RShields, Gloomhonk, Emmett, and contributors<br>
Licensed under the <a href="https://github.com/TC-Chart-Converter/TC-Chart-Converter.github.io/blob/main/LICENSE">GNU Affero General Public License v3.0</a><br>
<br>
<a href="https://github.com/colxi/midi-parser-js">MidiParser.js</a> by Sergi Guzman and contributors<br>
Expand Down
93 changes: 49 additions & 44 deletions src/midiToNotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const MidiToNotes = (function () {
/** All the events in the midi file, sorted by time */
const sortedMidiEvents = getSortedMidiEvents(midi);

collectPitchBendEvents(sortedMidiEvents, timeDivision);
generateWarnings(sortedMidiEvents, timeDivision);
adjustForTempoChanges(sortedMidiEvents);
collectPitchBendEvents(sortedMidiEvents);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tempo changes need to come first so pitch bend events get retimed correctly


// Calculate endpoint
for (let i = sortedMidiEvents.length - 1; i >= 0; i--) {
Expand Down Expand Up @@ -86,6 +86,11 @@ const MidiToNotes = (function () {
let bPriority = 0;
const aType = getEventType(a);
const bType = getEventType(b);

// Tempo change events
if (aType === "meta" && a.metaType == 81) aPriority = 2;
if (bType === "meta" && b.metaType == 81) bPriority = 2;

if (
(aType === "noteOn" || aType === "noteOff") &&
(bType === "noteOn" || bType === "noteOff")
Expand All @@ -110,6 +115,17 @@ const MidiToNotes = (function () {
return allMidiEvents;
}

function pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch) {
const tcPitchDelta = tcEndPitch - tcStartPitch;
MidiToNotes.notes.push([
startTime / timeDivision,
length > 0 ? length / timeDivision : defaultNoteLength,
tcStartPitch,
tcPitchDelta,
tcEndPitch,
]);
}

function generateNotesMidi2Tc(sortedMidiEvents, timeDivision) {
/** Note that we're currently creating */
let currentNote;
Expand All @@ -128,15 +144,7 @@ const MidiToNotes = (function () {
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
const tcEndPitch =
convertPitch(pitch, endPitchBend, event.time, timeDivision);
const tcPitchDelta = tcEndPitch - tcStartPitch;

MidiToNotes.notes.push([
startTime / timeDivision,
length > 0 ? length / timeDivision : defaultNoteLength,
tcStartPitch,
tcPitchDelta,
tcEndPitch,
]);
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);

currentNote = undefined;
}
Expand All @@ -151,15 +159,7 @@ const MidiToNotes = (function () {
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
const tcEndPitch =
convertPitch(pitch, pitchBend, event.time, timeDivision);
const tcPitchDelta = tcEndPitch - tcStartPitch;

MidiToNotes.notes.push([
startTime / timeDivision,
length > 0 ? length / timeDivision : defaultNoteLength,
tcStartPitch,
tcPitchDelta,
tcEndPitch,
]);
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);
}

currentNote = {
Expand Down Expand Up @@ -189,15 +189,7 @@ const MidiToNotes = (function () {
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
const tcEndPitch =
convertPitch(endPitch, endPitchBend, event.time, timeDivision);
const tcPitchDelta = tcEndPitch - tcStartPitch;

MidiToNotes.notes.push([
startTime / timeDivision,
length > 0 ? length / timeDivision : defaultNoteLength,
tcStartPitch,
tcPitchDelta,
tcEndPitch,
]);
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);

currentNote = undefined;
}
Expand Down Expand Up @@ -238,11 +230,38 @@ const MidiToNotes = (function () {
value: midiPitchBend * Settings.getSetting("pitchbendrange")
};

MidiToNotes.pitchBendEvents.push(pitchEvent)
MidiToNotes.pitchBendEvents.push(pitchEvent);
}
}
}

/**
* Adjust note and event times based on tempo changes.
* Operates in-place!
*/
function adjustForTempoChanges(sortedMidiEvents) {
/**
* Value from the first tempo change, measured in microseconds per quarter note.
* MIDI files use a default of 500,000 (120 bpm) if no tempo event is provided.
*/
let baseTempo = 500000;
/** Value from the last tempo change, measured in microseconds per quarter note */
let currTempo = baseTempo;

let currTime = 0;

for (const event of sortedMidiEvents) {
if (getEventType(event) === "meta" && event.metaType === 81) {
if (event.time === 0) baseTempo = event.data;
currTempo = event.data;
}

let adjustedDeltaTime = event.deltaTime * currTempo / baseTempo;
currTime += adjustedDeltaTime;
event.time = currTime;
}
}

/**
* Finds the pitch adjust amount at a given time in the MIDI. If the MIDI time
* is between two pitch bend events then the amount is found by a linear
Expand Down Expand Up @@ -303,20 +322,6 @@ const MidiToNotes = (function () {
}
}

function generateWarnings(sortedMidiEvents, timeDivision) {
for (const event of sortedMidiEvents) {
const eventType = getEventType(event);
if (eventType === "meta") {
if (event.metaType === 81 && event.time !== 0) {
// tempo change
midiWarnings.add("Tempo change (unsupported)", {
beat: Math.floor(event.time / timeDivision),
});
}
}
}
}

/** Returns whether a note is snapped (quantized) */
function warnIfUnsnapped(eventTime, timeDivision, snaps) {
for (const snap of snaps) {
Expand Down
Binary file added test/bpmtest.mid
Binary file not shown.