/* ---------------------------------------------------------- midi.Player : 0.3.1 : 2015-03-26 ---------------------------------------------------------- https://github.com/mudcube/midi.js ---------------------------------------------------------- */ import { MidiFile, Replayer } from './jasmid' import root from './root' (function () { root.Player = {} const player = root.Player player.currentTime = 0 player.endTime = 0 player.restart = 0 player.playing = false player.timeWarp = 1 player.startDelay = 0 player.BPM = 120 player.start = player.resume = function (onsuccess) { if (player.currentTime < -1) { player.currentTime = -1 } startAudio(player.currentTime, null, onsuccess) } player.pause = function () { var tmp = player.restart stopAudio() player.restart = tmp } player.stop = function () { stopAudio() player.restart = 0 player.currentTime = 0 } player.addListener = function (onsuccess) { onMidiEvent = onsuccess } player.removeListener = function () { onMidiEvent = undefined } player.clearAnimation = function () { if (player.animationFrameId) { window.cancelAnimationFrame(player.animationFrameId) } } player.setAnimation = function (callback) { var currentTime = 0 var tOurTime = 0 var tTheirTime = 0 // player.clearAnimation() // / var frame = function () { player.animationFrameId = window.requestAnimationFrame(frame) // / if (player.endTime === 0) { return } if (player.playing) { currentTime = (tTheirTime === player.currentTime) ? tOurTime - Date.now() : 0 if (player.currentTime === 0) { currentTime = 0 } else { currentTime = player.currentTime - currentTime } if (tTheirTime !== player.currentTime) { tOurTime = Date.now() tTheirTime = player.currentTime } } else { // paused currentTime = player.currentTime } // / var endTime = player.endTime // var percent = currentTime / endTime var total = currentTime / 1000 var minutes = total / 60 var seconds = total - (minutes * 60) var t1 = minutes * 60 + seconds var t2 = (endTime / 1000) // / if (t2 - t1 < -1.0) { return } else { callback({ now: t1, end: t2, events: noteRegistrar }) } } // / window.requestAnimationFrame(frame) } // helpers player.loadMidiFile = function (onsuccess, onprogress, onerror) { try { // console.log(MidiFile(player.currentData), new Replayer(MidiFile(player.currentData), player.timeWarp, null, player.BPM)) player.replayer = new Replayer(MidiFile(player.currentData), player.timeWarp, null, player.BPM) player.data = player.replayer.getData() player.endTime = getLength() // / root.loadPlugin({ // instruments: player.getFileInstruments(), onsuccess: onsuccess, onprogress: onprogress, onerror: onerror }) } catch (event) { console.error(event) onerror && onerror(event) } } player.loadFile = function (file, onsuccess, onprogress, onerror) { player.stop() if (file.indexOf('base64,') !== -1) { var data = window.atob(file.split(',')[1]) player.currentData = data player.loadMidiFile(onsuccess, onprogress, onerror) } else { var fetch = new window.XMLHttpRequest() fetch.open('GET', file) fetch.overrideMimeType('text/plain; charset=x-user-defined') fetch.onreadystatechange = function () { if (this.readyState === 4) { if (this.status === 200) { var t = this.responseText || '' var ff = [] var mx = t.length var scc = String.fromCharCode for (var z = 0; z < mx; z++) { ff[z] = scc(t.charCodeAt(z) & 255) } // / var data = ff.join('') player.currentData = data player.loadMidiFile(onsuccess, onprogress, onerror) } else { onerror && onerror('Unable to load MIDI file') } } } fetch.send() } } player.getFileInstruments = function () { var instruments = {} var programs = {} for (var n = 0; n < player.data.length; n++) { var event = player.data[n][0].event if (event.type !== 'channel') { continue } var channel = event.channel switch (event.subtype) { case 'controller': // console.log(event.channel, root.defineControl[event.controllerType], event.value) break case 'programChange': programs[channel] = event.programNumber break case 'noteOn': var program = programs[channel] var gm = root.GM.byId[isFinite(program) ? program : channel] instruments[gm.id] = true break } } var ret = [] for (var key in instruments) { ret.push(key) } return ret } // Playing the audio var eventQueue = [] // hold events to be triggered var queuedTime // var startTime = 0 // to measure time elapse var noteRegistrar = {} // get event for requested note var onMidiEvent // listener var scheduleTracking = function (channel, note, currentTime, offset, message, velocity, time) { return setTimeout(function () { var data = { channel: channel, note: note, now: currentTime, end: player.endTime, message: message, velocity: velocity } // if (message === 128) { delete noteRegistrar[note] } else { noteRegistrar[note] = data } if (onMidiEvent) { onMidiEvent(data) } player.currentTime = currentTime // / eventQueue.shift() // / if (eventQueue.length < 1000) { startAudio(queuedTime, true) } else if (player.currentTime === queuedTime && queuedTime < player.endTime) { // grab next sequence startAudio(queuedTime, true) } }, currentTime - offset) } var getContext = function () { if (root.api === 'webaudio') { return root.WebAudio.getContext() } else { player.ctx = {currentTime: 0} } return player.ctx } var getLength = function () { var data = player.data var length = data.length var totalTime = 0.5 for (var n = 0; n < length; n++) { totalTime += data[n][1] } return totalTime } var __now var getNow = function () { if (window.performance && window.performance.now) { return window.performance.now() } else { return Date.now() } } var startAudio = function (currentTime, fromCache, onsuccess) { if (!player.replayer) { return } if (!fromCache) { if (typeof currentTime === 'undefined') { currentTime = player.restart } // / player.playing && stopAudio() player.playing = true player.data = player.replayer.getData() player.endTime = getLength() } // / var note var offset = 0 var messages = 0 var data = player.data var ctx = getContext() var length = data.length // queuedTime = 0.5 // / // var interval = eventQueue[0] && eventQueue[0].interval || 0 var foffset = currentTime - player.currentTime // / if (root.api !== 'webaudio') { // set currentTime on ctx var now = getNow() __now = __now || now ctx.currentTime = (now - __now) / 1000 } // / startTime = ctx.currentTime // / for (var n = 0; n < length && messages < 100; n++) { var obj = data[n] if ((queuedTime += obj[1]) <= currentTime) { offset = queuedTime continue } // / currentTime = queuedTime - offset // / var event = obj[0].event if (event.type !== 'channel') { continue } // / var channelId = event.channel var channel = root.channels[channelId] var delay = ctx.currentTime + ((currentTime + foffset + player.startDelay) / 1000) var queueTime = queuedTime - offset + player.startDelay switch (event.subtype) { case 'controller': root.setController(channelId, event.controllerType, event.value, delay) break case 'programChange': root.programChange(channelId, event.programNumber, delay) break case 'pitchBend': root.pitchBend(channelId, event.value, delay) break case 'noteOn': if (channel.mute) break note = event.noteNumber - (player.MIDIOffset || 0) eventQueue.push({ event: event, time: queueTime, source: root.noteOn(channelId, event.noteNumber, event.velocity, delay), interval: scheduleTracking(channelId, note, queuedTime + player.startDelay, offset - foffset, 144, event.velocity) }) messages++ break case 'noteOff': if (channel.mute) break note = event.noteNumber - (player.MIDIOffset || 0) eventQueue.push({ event: event, time: queueTime, source: root.noteOff(channelId, event.noteNumber, delay), interval: scheduleTracking(channelId, note, queuedTime, offset - foffset, 128, 0) }) break default: break } } // / onsuccess && onsuccess(eventQueue) } var stopAudio = function () { var ctx = getContext() player.playing = false player.restart += (ctx.currentTime - startTime) * 1000 // stop the audio, and intervals while (eventQueue.length) { let o = eventQueue.pop() window.clearInterval(o.interval) if (!o.source) continue // is not webaudio if (typeof (o.source) === 'number') { window.clearTimeout(o.source) } else { // webaudio o.source.disconnect(0) } } // run callback to cancel any notes still playing for (var key in noteRegistrar) { let o = noteRegistrar[key] if (noteRegistrar[key].message === 144 && onMidiEvent) { onMidiEvent({ channel: o.channel, note: o.note, now: o.now, end: o.end, message: 128, velocity: o.velocity }) } } // reset noteRegistrar noteRegistrar = {} } })()