const MIDI = require("./MIDI"); const trackDeltaToAbs = events => { let tick = 0; events.forEach(event => { tick += event.deltaTime; event.tick = tick; }); }; const trackAbsToDelta = events => { let lastTick = 0; events.sort((e1, e2) => e1.tick - e2.tick).forEach(event => { event.deltaTime = event.tick - lastTick; lastTick = event.tick; }); }; const sliceTrack = (track, startTick, endTick) => { trackDeltaToAbs(track); const events = []; const status = {}; track.forEach(event => { if (event.tick >= startTick && event.tick <= endTick && event.subtype !== "endOfTrack") events.push({ ...event, tick: event.tick - startTick, }); else if (event.tick < startTick) { switch (event.type) { case "meta": status[event.subtype] = event; break; } } }); Object.values(status).forEach(event => events.push({ ...event, tick: 0, })); events.push({ tick: endTick - startTick, type: "meta", subtype: "endOfTrack", }); trackAbsToDelta(events); return events; }; const sliceMidi = (midi, startTick, endTick) => ({ header: midi.header, tracks: midi.tracks.map(track => sliceTrack(track, startTick, endTick)), }); const TICKS_PER_BEATS = 480; const EXCLUDE_MIDI_EVENT_SUBTYPES = [ "endOfTrack", "trackName", "noteOn", "noteOff", ]; function encodeToMIDIData(notation, {startTime, unclosedNoteDuration = 30e+3} = {}) { notation.microsecondsPerBeat = notation.microsecondsPerBeat || 500000; const ticksPerBeat = TICKS_PER_BEATS; const msToTicks = ticksPerBeat * 1000 / notation.microsecondsPerBeat; const header = { formatType: 0, ticksPerBeat }; const track = []; if (!Number.isFinite(startTime)) { if (!notation.notes || !notation.notes[0]) throw new Error("encodeToMidiData: no start time specificed"); startTime = notation.notes[0].start; } track.push({ time: startTime, type: "meta", subtype: "copyrightNotice", text: `Composed by MusicWdigets. BUILT on ${new Date(Number(process.env.VUE_APP_BUILD_TIME)).toDateString()}` }); const containsTempo = notation.events && notation.events.find(event => event.subtype == "setTempo"); if (!containsTempo) { track.push({ time: startTime, type: "meta", subtype: "timeSignature", numerator: 4, denominator: 4, thirtyseconds: 8 }); track.push({ time: startTime, type: "meta", subtype: "setTempo", microsecondsPerBeat: notation.microsecondsPerBeat }); } //if (notation.correspondences) // track.push({ time: startTime, type: "meta", subtype: "text", text: "find-corres:" + notation.correspondences.join(",") }); let endTime = startTime || 0; if (notation.notes) { for (const note of notation.notes) { track.push({ time: note.start, type: "channel", subtype: "noteOn", channel: note.channel || 0, noteNumber: note.pitch, velocity: note.velocity, finger: note.finger, }); endTime = Math.max(endTime, note.start); if (Number.isFinite(unclosedNoteDuration)) note.duration = note.duration || unclosedNoteDuration; if (note.duration) { track.push({ time: note.start + note.duration, type: "channel", subtype: "noteOff", channel: note.channel || 0, noteNumber: note.pitch, velocity: 0, }); endTime = Math.max(endTime, note.start + note.duration); } } } if (notation.events) { const events = notation.events.filter(event => !EXCLUDE_MIDI_EVENT_SUBTYPES.includes(event.data.subtype)); for (const event of events) { track.push({ time: event.time, ...event.data, }); endTime = Math.max(endTime, event.time); } } track.push({ time: endTime + 100, type: "meta", subtype: "endOfTrack" }); track.sort(function (e1, e2) { return e1.time - e2.time; }); // append finger event after every noteOn event track.map((event, index) => ({event, index})) .filter(({event}) => event.subtype == "noteOn" && event.finger != null) .reverse() .forEach(({event, index}) => track.splice(index + 1, 0, { time: event.time, type: "meta", subtype: "text", text: `fingering(${event.finger})`, })); track.forEach(event => event.ticks = Math.round((event.time - startTime) * msToTicks)); track.forEach((event, i) => event.deltaTime = (event.ticks - (i > 0 ? track[i - 1].ticks : 0))); return {header, tracks: [track]}; }; function encodeToMIDI(notation, options) { const data = encodeToMIDIData(notation, options); return MIDI.encodeMidiFile(data); }; module.exports = { sliceMidi, encodeToMIDIData, encodeToMIDI, };