UNPKG

@tonejs/midi

Version:

Convert binary midi into JSON

256 lines 8.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Header = exports.keySignatureKeys = void 0; var BinarySearch_1 = require("./BinarySearch"); var privatePPQMap = new WeakMap(); /** * @hidden */ exports.keySignatureKeys = [ "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "F#", "C#", ]; /** * The parsed MIDI file header. */ var Header = /** @class */ (function () { function Header(midiData) { var _this = this; /** * The array of all the tempo events. */ this.tempos = []; /** * The time signatures. */ this.timeSignatures = []; /** * The time signatures. */ this.keySignatures = []; /** * Additional meta events. */ this.meta = []; /** * The name of the MIDI file; */ this.name = ""; // Look through all the tracks for tempo changes. privatePPQMap.set(this, 480); if (midiData) { privatePPQMap.set(this, midiData.header.ticksPerBeat); // Check time signature and tempo events from all of the tracks. midiData.tracks.forEach(function (track) { track.forEach(function (event) { if (event.meta) { if (event.type === "timeSignature") { _this.timeSignatures.push({ ticks: event.absoluteTime, timeSignature: [ event.numerator, event.denominator, ], }); } else if (event.type === "setTempo") { _this.tempos.push({ bpm: 60000000 / event.microsecondsPerBeat, ticks: event.absoluteTime, }); } else if (event.type === "keySignature") { _this.keySignatures.push({ key: exports.keySignatureKeys[event.key + 7], scale: event.scale === 0 ? "major" : "minor", ticks: event.absoluteTime, }); } } }); }); // Check the first track for other relevant data. var firstTrackCurrentTicks_1 = 0; // Used for absolute times. midiData.tracks[0].forEach(function (event) { firstTrackCurrentTicks_1 += event.deltaTime; if (event.meta) { if (event.type === "trackName") { _this.name = event.text; } else if (event.type === "text" || event.type === "cuePoint" || event.type === "marker" || event.type === "lyrics") { _this.meta.push({ text: event.text, ticks: firstTrackCurrentTicks_1, type: event.type, }); } } }); this.update(); } } /** * This must be invoked after any changes are made to the tempo array * or the timeSignature array for the updated values to be reflected. */ Header.prototype.update = function () { var _this = this; var currentTime = 0; var lastEventBeats = 0; // Make sure it's sorted; this.tempos.sort(function (a, b) { return a.ticks - b.ticks; }); this.tempos.forEach(function (event, index) { var lastBPM = index > 0 ? _this.tempos[index - 1].bpm : _this.tempos[0].bpm; var beats = event.ticks / _this.ppq - lastEventBeats; var elapsedSeconds = (60 / lastBPM) * beats; event.time = elapsedSeconds + currentTime; currentTime = event.time; lastEventBeats += beats; }); this.timeSignatures.sort(function (a, b) { return a.ticks - b.ticks; }); this.timeSignatures.forEach(function (event, index) { var lastEvent = index > 0 ? _this.timeSignatures[index - 1] : _this.timeSignatures[0]; var elapsedBeats = (event.ticks - lastEvent.ticks) / _this.ppq; var elapsedMeasures = elapsedBeats / lastEvent.timeSignature[0] / (lastEvent.timeSignature[1] / 4); lastEvent.measures = lastEvent.measures || 0; event.measures = elapsedMeasures + lastEvent.measures; }); }; /** * Convert ticks into seconds based on the tempo changes. */ Header.prototype.ticksToSeconds = function (ticks) { // Find the relevant position. var index = (0, BinarySearch_1.search)(this.tempos, ticks); if (index !== -1) { var tempo = this.tempos[index]; var tempoTime = tempo.time; var elapsedBeats = (ticks - tempo.ticks) / this.ppq; return tempoTime + (60 / tempo.bpm) * elapsedBeats; } else { // Assume 120. var beats = ticks / this.ppq; return (60 / 120) * beats; } }; /** * Convert ticks into measures based off of the time signatures. */ Header.prototype.ticksToMeasures = function (ticks) { var index = (0, BinarySearch_1.search)(this.timeSignatures, ticks); if (index !== -1) { var timeSigEvent = this.timeSignatures[index]; var elapsedBeats = (ticks - timeSigEvent.ticks) / this.ppq; return (timeSigEvent.measures + elapsedBeats / (timeSigEvent.timeSignature[0] / timeSigEvent.timeSignature[1]) / 4); } else { return ticks / this.ppq / 4; } }; Object.defineProperty(Header.prototype, "ppq", { /** * The number of ticks per quarter note. */ get: function () { return privatePPQMap.get(this); }, enumerable: false, configurable: true }); /** * Convert seconds to ticks based on the tempo events. */ Header.prototype.secondsToTicks = function (seconds) { // Find the relevant position. var index = (0, BinarySearch_1.search)(this.tempos, seconds, "time"); if (index !== -1) { var tempo = this.tempos[index]; var tempoTime = tempo.time; var elapsedTime = seconds - tempoTime; var elapsedBeats = elapsedTime / (60 / tempo.bpm); return Math.round(tempo.ticks + elapsedBeats * this.ppq); } else { // Assume 120. var beats = seconds / (60 / 120); return Math.round(beats * this.ppq); } }; /** * Convert the header into an object. */ Header.prototype.toJSON = function () { return { keySignatures: this.keySignatures, meta: this.meta, name: this.name, ppq: this.ppq, tempos: this.tempos.map(function (t) { return { bpm: t.bpm, ticks: t.ticks, }; }), timeSignatures: this.timeSignatures, }; }; /** * Parse a header json object. */ Header.prototype.fromJSON = function (json) { this.name = json.name; // Clone all the attributes. this.tempos = json.tempos.map(function (t) { return Object.assign({}, t); }); this.timeSignatures = json.timeSignatures.map(function (t) { return Object.assign({}, t); }); this.keySignatures = json.keySignatures.map(function (t) { return Object.assign({}, t); }); this.meta = json.meta.map(function (t) { return Object.assign({}, t); }); privatePPQMap.set(this, json.ppq); this.update(); }; /** * Update the tempo of the midi to a single tempo. Will remove and replace * any other tempos currently set and update all of the event timing. * @param bpm The tempo in beats per second. */ Header.prototype.setTempo = function (bpm) { this.tempos = [ { bpm: bpm, ticks: 0, }, ]; this.update(); }; return Header; }()); exports.Header = Header; //# sourceMappingURL=Header.js.map