Skip to content

Commit 5deb909

Browse files
authored
Add support for tempo changes (#5)
Co-authored-by: Emmett <a>
1 parent 1d022d5 commit 5deb909

File tree

3 files changed

+58
-49
lines changed

3 files changed

+58
-49
lines changed

index.html

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ <h2><label for="midifile">MIDI file</label></h2>
3535
<span>Slide mode</span>
3636
<div class="flexrow spacebetween">
3737
<span>
38-
<input type="radio" id="slidemidi2tc" name="slidemode" checked>
38+
<input type="radio" id="slidemidi2tc" name="slidemode">
3939
<label for="slidemidi2tc">midi2tc</label>
4040
</span>
4141
<span>
42-
<input type="radio" id="slidetccc" name="slidemode">
42+
<input type="radio" id="slidetccc" name="slidemode" checked>
4343
<label for="slidetccc">tccc</label>
4444
</span>
4545
</div>
@@ -104,7 +104,7 @@ <h3>- Song info -</h3>
104104

105105
<h3>- Chart info -</h3>
106106

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

@@ -144,7 +144,7 @@ <h3>- Other -</h3>
144144

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

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

281281
<div class="container">
282282
<h2>Version history</h2>
283+
<p>
284+
v1.9<br>
285+
Added support for tempo changes
286+
</p>
283287
<p>
284288
v1.8a:<br>
285289
Changed "Song Folder" to "trackRef"
@@ -379,7 +383,7 @@ <h2>Version history</h2>
379383
</div>
380384
<div class="footer">
381385
<p>
382-
<a href="https://github.com/TC-Chart-Converter/TC-Chart-Converter.github.io/">Trombone Champ Chart Converter</a> by RShields, Gloomhonk, and contributors<br>
386+
<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>
383387
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>
384388
<br>
385389
<a href="https://github.com/colxi/midi-parser-js">MidiParser.js</a> by Sergi Guzman and contributors<br>

src/midiToNotes.js

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const MidiToNotes = (function () {
1616
/** All the events in the midi file, sorted by time */
1717
const sortedMidiEvents = getSortedMidiEvents(midi);
1818

19-
collectPitchBendEvents(sortedMidiEvents, timeDivision);
20-
generateWarnings(sortedMidiEvents, timeDivision);
19+
adjustForTempoChanges(sortedMidiEvents);
20+
collectPitchBendEvents(sortedMidiEvents);
2121

2222
// Calculate endpoint
2323
for (let i = sortedMidiEvents.length - 1; i >= 0; i--) {
@@ -86,6 +86,11 @@ const MidiToNotes = (function () {
8686
let bPriority = 0;
8787
const aType = getEventType(a);
8888
const bType = getEventType(b);
89+
90+
// Tempo change events
91+
if (aType === "meta" && a.metaType == 81) aPriority = 2;
92+
if (bType === "meta" && b.metaType == 81) bPriority = 2;
93+
8994
if (
9095
(aType === "noteOn" || aType === "noteOff") &&
9196
(bType === "noteOn" || bType === "noteOff")
@@ -110,6 +115,17 @@ const MidiToNotes = (function () {
110115
return allMidiEvents;
111116
}
112117

118+
function pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch) {
119+
const tcPitchDelta = tcEndPitch - tcStartPitch;
120+
MidiToNotes.notes.push([
121+
startTime / timeDivision,
122+
length > 0 ? length / timeDivision : defaultNoteLength,
123+
tcStartPitch,
124+
tcPitchDelta,
125+
tcEndPitch,
126+
]);
127+
}
128+
113129
function generateNotesMidi2Tc(sortedMidiEvents, timeDivision) {
114130
/** Note that we're currently creating */
115131
let currentNote;
@@ -128,15 +144,7 @@ const MidiToNotes = (function () {
128144
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
129145
const tcEndPitch =
130146
convertPitch(pitch, endPitchBend, event.time, timeDivision);
131-
const tcPitchDelta = tcEndPitch - tcStartPitch;
132-
133-
MidiToNotes.notes.push([
134-
startTime / timeDivision,
135-
length > 0 ? length / timeDivision : defaultNoteLength,
136-
tcStartPitch,
137-
tcPitchDelta,
138-
tcEndPitch,
139-
]);
147+
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);
140148

141149
currentNote = undefined;
142150
}
@@ -151,15 +159,7 @@ const MidiToNotes = (function () {
151159
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
152160
const tcEndPitch =
153161
convertPitch(pitch, pitchBend, event.time, timeDivision);
154-
const tcPitchDelta = tcEndPitch - tcStartPitch;
155-
156-
MidiToNotes.notes.push([
157-
startTime / timeDivision,
158-
length > 0 ? length / timeDivision : defaultNoteLength,
159-
tcStartPitch,
160-
tcPitchDelta,
161-
tcEndPitch,
162-
]);
162+
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);
163163
}
164164

165165
currentNote = {
@@ -189,15 +189,7 @@ const MidiToNotes = (function () {
189189
convertPitch(startPitch, startPitchBend, startTime, timeDivision);
190190
const tcEndPitch =
191191
convertPitch(endPitch, endPitchBend, event.time, timeDivision);
192-
const tcPitchDelta = tcEndPitch - tcStartPitch;
193-
194-
MidiToNotes.notes.push([
195-
startTime / timeDivision,
196-
length > 0 ? length / timeDivision : defaultNoteLength,
197-
tcStartPitch,
198-
tcPitchDelta,
199-
tcEndPitch,
200-
]);
192+
pushNote(startTime, timeDivision, length, tcStartPitch, tcEndPitch);
201193

202194
currentNote = undefined;
203195
}
@@ -238,11 +230,38 @@ const MidiToNotes = (function () {
238230
value: midiPitchBend * Settings.getSetting("pitchbendrange")
239231
};
240232

241-
MidiToNotes.pitchBendEvents.push(pitchEvent)
233+
MidiToNotes.pitchBendEvents.push(pitchEvent);
242234
}
243235
}
244236
}
245237

238+
/**
239+
* Adjust note and event times based on tempo changes.
240+
* Operates in-place!
241+
*/
242+
function adjustForTempoChanges(sortedMidiEvents) {
243+
/**
244+
* Value from the first tempo change, measured in microseconds per quarter note.
245+
* MIDI files use a default of 500,000 (120 bpm) if no tempo event is provided.
246+
*/
247+
let baseTempo = 500000;
248+
/** Value from the last tempo change, measured in microseconds per quarter note */
249+
let currTempo = baseTempo;
250+
251+
let currTime = 0;
252+
253+
for (const event of sortedMidiEvents) {
254+
if (getEventType(event) === "meta" && event.metaType === 81) {
255+
if (event.time === 0) baseTempo = event.data;
256+
currTempo = event.data;
257+
}
258+
259+
let adjustedDeltaTime = event.deltaTime * currTempo / baseTempo;
260+
currTime += adjustedDeltaTime;
261+
event.time = currTime;
262+
}
263+
}
264+
246265
/**
247266
* Finds the pitch adjust amount at a given time in the MIDI. If the MIDI time
248267
* is between two pitch bend events then the amount is found by a linear
@@ -303,20 +322,6 @@ const MidiToNotes = (function () {
303322
}
304323
}
305324

306-
function generateWarnings(sortedMidiEvents, timeDivision) {
307-
for (const event of sortedMidiEvents) {
308-
const eventType = getEventType(event);
309-
if (eventType === "meta") {
310-
if (event.metaType === 81 && event.time !== 0) {
311-
// tempo change
312-
midiWarnings.add("Tempo change (unsupported)", {
313-
beat: Math.floor(event.time / timeDivision),
314-
});
315-
}
316-
}
317-
}
318-
}
319-
320325
/** Returns whether a note is snapped (quantized) */
321326
function warnIfUnsnapped(eventTime, timeDivision, snaps) {
322327
for (const snap of snaps) {

test/bpmtest.mid

649 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)