chipmunkbot3/util/convert-midi.js
2024-02-11 21:23:41 -05:00

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