151 lines
4.8 KiB
JavaScript
151 lines
4.8 KiB
JavaScript
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
|