class MidiTrack { constructor(player, chunk) { this.player = player; this.events = chunk.events; this.length = 0; for (let i=0; i< this.events.length; i++) { this.length += this.events[i].deltaTime; } this.index = 0; this.lastTime = 0; } parseEvent(event) { if (event instanceof MidiVoiceEvent) { this.player.parseEvent(event); } else if (event instanceof MidiSetTempoEvent) { this.player.bpm = 60000000 / event.tempo; } } tick(delta) { while(true) { if (this.index >= this.events.length) { break; } let event = this.events[this.index]; if (this.lastTime + event.deltaTime - this.player.time <= 0) { this.lastTime += event.deltaTime; this.parseEvent(event); this.index++; } else { break; } } } } class MidiPlayer { constructor() { this.tracks = []; this.playing = false; this.speed = 1; this.bpm = 120; this.time = 0; } loadMidi(midi) { this.tracks = []; this.time = 0; this.length = 0; this.bpm = 120; this.midi = midi; if (!(this.midi.chunks[0] instanceof MidiHeaderChunk)) { throw "No header chunk"; } let header = this.midi.chunks[0]; this.format = header.format; if ((header.division & 0x0800) === 1) { throw "SMPTA time format not yet supported"; } else { this.ticksPerQuarterNote = header.division; } for (let i=1; i= this.length) { this.time = this.length; this.pause(); } } setTime(time) { // TODO skip notes this.time = time; } play() { if (this.playing) return; this.playing = true; let lastTime = Date.now(); this.interval = setInterval(() => { let delta = Date.now() - lastTime; lastTime = Date.now(); this.tick(delta * this.speed); }, 1000 / 60); } pause() { this.playing = false; clearInterval(this.interval); } } let midiPlayer = new MidiPlayer(); function uploadMidi() { let input = document.createElement("input"); input.type = "file"; input.accept = "audio/midi"; input.addEventListener("change", function() { let reader = new FileReader(); reader.addEventListener("load", function() { let midiFile = new MidiFile(reader.result); midiPlayer.loadMidi(midiFile); console.log(midiFile); }); reader.readAsArrayBuffer(input.files[0]); }); input.click(); }