const fs = require('fs') const { Midi } = require('@tonejs/midi') const instrumentOffsets = [ 54, // harp 0, // basedrum 0, // snare 0, // hat 30, // bass 66, // flute 78, // bell 42, // guitar 78, // chime 78, // xylophone 54, // iron xylophone 66, // cow bell 30, // didgeridoo 54, // bit 54, // banjo 54 // electric piano ] const instrumentNames = require('./instrument-names.json') const path = require('path/posix') function convertMidi (filepath, lyrics = undefined) { const midiData = fs.readFileSync(filepath) const midi = new Midi(midiData) const noteList = [] let length = 0 for (const track of midi.tracks) { for (const note of track.notes) { const mcNote = convertNote(track, note) if (mcNote !== undefined) { noteList.push(mcNote) length = Math.max(length, mcNote.tick) } } } noteList.sort((a, b) => { return a.tick - b.tick }) // normalize volume let maxVolume = 0.001 for (const note of noteList) { if (note.volume > maxVolume) { maxVolume = note.volume } } for (const note of noteList) { note.volume /= maxVolume } if (lyrics) { parseLyrics(midiData, midi, lyrics) } return { name: midi?.meta?.name ?? path.basename(filepath), notes: noteList, loop: false, loopPosition: 0, length } } function convertNote (track, note) { const midiPitch = note.midi const midiInstrument = track.instrument.number let mcInstrumentID = 0 let mcPitchID = 0 if (track.instrument.percussion) { if (midiPitch === 35 || midiPitch === 36 || midiPitch === 41 || midiPitch === 43 || midiPitch === 45 || midiPitch === 57) { mcInstrumentID = 1 // bass drum } else if (midiPitch === 38 || midiPitch === 39 || midiPitch === 40 || midiPitch === 54 || midiPitch === 69 || midiPitch === 70 || midiPitch === 73 || midiPitch === 74 || midiPitch === 78 || midiPitch === 79) { mcInstrumentID = 2 // snare } else if (midiPitch === 37 || midiPitch === 42 || midiPitch === 44 || midiPitch === 46 || midiPitch === 49 || midiPitch === 51 || midiPitch === 52 || midiPitch === 55 || midiPitch === 57 || midiPitch === 59) { mcInstrumentID = 3 // hat } else { return undefined } mcPitchID = 0 } else { if ((midiInstrument >= 0 && midiInstrument <= 7) || (midiInstrument >= 24 && midiInstrument <= 31)) { // normal if (midiPitch >= 54 && midiPitch <= 78) { mcInstrumentID = 0 // piano } else if (midiPitch >= 30 && midiPitch <= 54) { mcInstrumentID = 4 // bass } else if (midiPitch >= 78 && midiPitch <= 102) { mcInstrumentID = 6 // bells } } else if (midiInstrument >= 8 && midiInstrument <= 15) { // chromatic percussion if (midiPitch >= 54 && midiPitch <= 78) { mcInstrumentID = 10 // iron xylophone } else if (midiPitch >= 78 && midiPitch <= 102) { mcInstrumentID = 9 // xylophone } else if (midiPitch >= 30 && midiPitch <= 54) { mcInstrumentID = 4 // bass } } else if ((midiInstrument >= 16 && midiInstrument <= 23) || (midiInstrument >= 32 && midiInstrument <= 71) || (midiInstrument >= 80 && midiInstrument <= 111)) { // synth if (midiPitch >= 54 && midiPitch <= 78) { // bit mcInstrumentID = 13 } else if (midiPitch >= 30 && midiPitch <= 54) { // didgeridoo mcInstrumentID = 12 } else if (midiPitch >= 78 && midiPitch <= 102) { // bells mcInstrumentID = 6 } } else if (midiInstrument >= 72 && midiInstrument <= 79) { // woodwind if (midiPitch >= 66 && midiPitch <= 90) { mcInstrumentID = 5 // flute } else if (midiPitch >= 30 && midiPitch <= 54) { // didgeridoo mcInstrumentID = 12 } else if (midiPitch >= 54 && midiPitch <= 78) { // bit mcInstrumentID = 13 } } else { return undefined } mcPitchID = midiPitch - instrumentOffsets[mcInstrumentID] } return { time: Math.floor(note.time * 1000), instrument: instrumentNames[mcInstrumentID], pitch: mcPitchID, volume: note.velocity } } function parseLyrics (midiArray, midi, lyrics) { midiArray = new Uint8Array(midiArray) const midiData = require('midi-file').parseMidi(midiArray) midiData.tracks.forEach(track => { let lastTick = 0 let currentTicks = 0 track.forEach(event => { currentTicks += event.deltaTime if (event.meta && event.type === 'lyrics') { const time = midi.header.ticksToSeconds(currentTicks) const tick = Math.floor(time * 20) if (!lyrics[tick]) { lyrics[tick] = event.text } else if (tick === lastTick) { lyrics[tick] += event.text } lastTick = tick } }) }) } module.exports = convertMidi