UNPKG

@perry-rylance/midi-to-milliseconds

Version:

TypeScript library for resolving MIDI event times to milliseconds. Useful for syncing things like graphics to MIDI.

72 lines (71 loc) 3.42 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const midi_1 = require("@perry-rylance/midi"); const TimeResolvedTrack_1 = __importDefault(require("./TimeResolvedTrack")); const TimeResolvedEvent_1 = __importDefault(require("./TimeResolvedEvent")); const InjectedSetTempoEvent_1 = __importDefault(require("./InjectedSetTempoEvent")); class TimeResolver { constructor(file, options) { var _a; if (file.resolution.units !== midi_1.ResolutionUnits.PPQ) throw new Error("Only PPQ resolution is supported presently"); this.options = options; // NB: Get absolute ticks for all events this.tracks = file.tracks.map(track => new TimeResolvedTrack_1.default(track, options)); // NB: Get resolved tempo events from all tracks const resolvedSetTempoEvents = this.tracks.map(track => { return track.events.filter(event => event.original instanceof midi_1.SetTempoEvent); }) .flat(); // NB: Inject the tempo events into each track for (const track of this.tracks) { this.injectResolvedSetTempoEvents(track, resolvedSetTempoEvents); if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.stable) this.indexEvents(track); } // NB: Walk the events for each track resolving time const ppqn = file.resolution.ticksPerQuarterNote; let trackIndex = 0; for (const track of this.tracks) { let milliseconds = 0; let bpm = 120; let prev = null; for (const event of track.events) { // NB: Deltas won't work here because we've injected set tempos. Need to track our own delta from absolute times. const delta = event.absolute.ticks - (prev ? prev.absolute.ticks : 0); milliseconds += delta * 60000 / ppqn / bpm; // NB: Round down to avoid floating-point comparison issues. Milliseconds is accurate enough for the purposes of this library. event.absolute.milliseconds = Math.floor(milliseconds); if (event.original instanceof midi_1.SetTempoEvent) bpm = event.original.bpm; prev = event; } trackIndex++; } } injectResolvedSetTempoEvents(track, events) { const cloned = events.map(event => { const cloned = new InjectedSetTempoEvent_1.default(event.original.delta); cloned.bpm = event.original.bpm; return new TimeResolvedEvent_1.default(cloned, event.absolute); }); // NB: Important to unshift these so that the set tempo events come first track.events.unshift(...cloned); // TODO: Could speed this up with an insertion sort or something more specific than JS's native sort track.events.sort((a, b) => { if (a.absolute.ticks === b.absolute.ticks) return 0; if (a.absolute.ticks > b.absolute.ticks) return 1; return -1; }); } indexEvents(track) { for (let i = 0; i < track.events.length; i++) track.events[i].index = i; } } exports.default = TimeResolver;