/************************************************************************ * Circular Web Audio Buffer Queue */ function CircularAudioBuffer(slots) { slots = slots || 24; // number of buffers this.slots = slots; this.buffers = new Array(slots); this.reset(); for (var i = 0; i < this.slots; i++) { var buffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE); this.buffers[i] = buffer; } } // pseudo empty all buffers CircularAudioBuffer.prototype.reset = function () { this.used = 0; this.filled = 0; }; // returns number of buffers that are filled CircularAudioBuffer.prototype.filledBuffers = function () { var fills = this.filled - this.used; if (fills < 0) fills += this.slots; return fills; } // returns whether buffers are all filled CircularAudioBuffer.prototype.full = function () { //console.debug(this.filledBuffers()); return this.filledBuffers() >= this.slots - 1; } // returns a reference to next available buffer to be filled CircularAudioBuffer.prototype.prepare = function () { if (this.full()) { //console.log('buffers full!!') return } var buffer = this.buffers[ this.filled++]; this.filled %= this.slots; return buffer; } // returns the next buffer in the queue CircularAudioBuffer.prototype.use = function () { if (! this.filledBuffers()) return; var buffer = this.buffers[ this.used++]; this.used %= this.slots; return buffer; } /************************************************************************ * Web Audio Stuff */ var SAMPLE_RATE = 44100; var BUFFER = 4096; var channels = 2; var audioCtx; var source; var scriptNode; var circularBuffer; var emptyBuffer; function initAudio() { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); scriptNode = audioCtx.createScriptProcessor(BUFFER, 0, channels); scriptNode.onaudioprocess = onAudioProcess; source = audioCtx.createBufferSource(); circularBuffer = new CircularAudioBuffer(8); emptyBuffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE); source.connect(scriptNode); source.start(0); console.debug("initAudio"); } function startAudio() { scriptNode.connect(audioCtx.destination); console.debug("startAudio"); } function pauseAudio() { circularBuffer.reset(); scriptNode.disconnect(); console.debug("pauseAudio"); } /************************************************************************ * Emscripten variables and callback - cannot be renamed */ var ULONG_MAX = 4294967295; var _EM_signalStop = 0; var _EM_seekSamples = ULONG_MAX; function processAudio(buffer_loc, size) { var buffer = circularBuffer.prepare(); var left_buffer_f32 = buffer.getChannelData(0); var right_buffer_f32 = buffer.getChannelData(1); // Copy emscripten memory (OpenAL stereo16 format) to JS for (var i = 0; i < size; i++) { left_buffer_f32[i] = MidiPlayer.HEAP16[(buffer_loc >> 1) + 2 * i + 0] / 32768; right_buffer_f32[i] = MidiPlayer.HEAP16[(buffer_loc >> 1) + 2 * i + 1] / 32768; } } function updateProgress(current, total) { midiPlayer_currentSamples = current; midiPlayer_totalSamples = total; midiPlayer_progress.style.width = (current / total * 100) + '%'; midiPlayer_playingTime.innerHTML = samplesToTime(current); midiPlayer_totalTime.innerHTML = samplesToTime(total); var millisec = Math.floor(current * 1000 / SAMPLE_RATE / midiPlayer_updateRate); if (midiPlayer_lastMillisec > millisec) { midiPlayer_lastMillisec = 0; } if (millisec > midiPlayer_lastMillisec) { if (midiPlayer_onUpdate != null) midiPlayer_onUpdate(millisec * midiPlayer_updateRate); //console.log(millisec * UPDATE_RATE); } midiPlayer_lastMillisec = millisec; } function completeConversion(status) { midiPlayer_drainBuffer = true; console.debug('completeConversion'); midiPlayer_convertionJob = null; // Not a pause if (_EM_signalStop != 2) { setTimeout(stop, 1000); } } /************************************************************************ * Global player variables and functions */ // html elements var midiPlayer_width; var midiPlayer_bar; var midiPlayer_progress; var midiPlayer_playingTime; var midiPlayer_play; var midiPlayer_pause; var midiPlayer_stop; var midiPlayer_totalTime; // variables var midiPlayer_isLoaded = false; var midiPlayer_isAudioInit = false; var midiPlayer_input = null; var midiPlayer_lastMillisec = 0; var midiPlayer_midiName = '' var midiPlayer_convertionJob = null; var midiPlayer_currentSamples = ULONG_MAX; var midiPlayer_totalSamples = 0; var midiPlayer_updateRate = 50; var midiPlayer_drainBuffer = false; var BASE64_MARKER = ';base64,'; // callbacks var midiPlayer_onStop = null; var midiPlayer_onUpdate = null; var MidiPlayer = { noInitialRun: true, totalDependencies: 1, monitorRunDependencies: function(left) { //console.log(this.totalDependencies); //console.log(left); if ((left == 0) && !midiPlayer_isLoaded) { console.log("MidiPlayer is loaded"); midiPlayer_isLoaded = true; } } }; function onAudioProcess(audioProcessingEvent) { var generated = circularBuffer.use(); if (!generated && midiPlayer_drainBuffer) { // wait for remaining buffer to drain before disconnect audio pauseAudio(); midiPlayer_drainBuffer = false; return; } if (!generated) { //console.log('buffer under run!!') generated = emptyBuffer; } var outputBuffer = audioProcessingEvent.outputBuffer; var offset = 0; if (outputBuffer.copyToChannel !== undefined) { // Firefox -> about 50% faster than decoding outputBuffer.copyToChannel(generated.getChannelData(0), 0, offset); outputBuffer.copyToChannel(generated.getChannelData(1), 1, offset); } else { // Other browsers -> about 20 - 70% slower than decoding var leftChannel = outputBuffer.getChannelData(0); var rightChannel = outputBuffer.getChannelData(1); var generatedLeftChannel = generated.getChannelData(0); var generatedRightChannel = generated.getChannelData(1); var i; for (i = 0; i < BUFFER; i++) { leftChannel[i] = generatedLeftChannel[i]; rightChannel[i] = generatedRightChannel[i]; } } } function samplesToTime(at) { var in_s = Math.floor(at / SAMPLE_RATE); var s = in_s % 60; var min = in_s / 60 | 0; return min + ':' + (s === 0 ? '00': s < 10 ? '0' + s: s); } function convertDataURIToBinary(dataURI) { var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length; var base64 = dataURI.substring(base64Index); var raw = window.atob(base64); var rawLength = raw.length; var array = new Uint8Array(new ArrayBuffer(rawLength)); for (var i = 0; i < rawLength; i++) { array[i] = raw.charCodeAt(i); } return array; } function convertFile(file, data) { midiPlayer_midiName = file; midiPlayer_input = null; console.log('open ', midiPlayer_midiName); MidiPlayer['FS'].writeFile(midiPlayer_midiName, data, { encoding: 'binary' }); play(); } function pause() { _EM_signalStop = 2; circularBuffer.reset(); midiPlayer_play.style.display = 'inline-block'; midiPlayer_pause.style.display = 'none'; } function play() { if (!midiPlayer_isLoaded) { console.error("MidiPlayer is not loaded yet"); return; } if (!midiPlayer_isAudioInit) { initAudio(); midiPlayer_isAudioInit = true; } _EM_seekSamples = midiPlayer_currentSamples; if (midiPlayer_convertionJob) { return; } _EM_signalStop = 0; midiPlayer_play.style.display = 'none'; midiPlayer_pause.style.display = 'inline-block'; midiPlayer_stop.style.display = 'inline-block'; // add small delay so UI can update. setTimeout(runConversion, 100); } function stop() { _EM_signalStop = 1; _EM_seekSamples = 0; circularBuffer.reset(); midiPlayer_totalSamples = 0; midiPlayer_currentSamples = ULONG_MAX; midiPlayer_progress.style.width = '0%'; midiPlayer_playingTime.innerHTML = "00.00"; midiPlayer_totalTime.innerHTML = "00.00"; midiPlayer_play.style.display = 'none'; midiPlayer_pause.style.display = 'none'; midiPlayer_stop.style.display = 'none'; if (midiPlayer_onStop != null) midiPlayer_onStop(); } function runConversion() { midiPlayer_convertionJob = { sourceMidi: midiPlayer_midiName, targetWav: midiPlayer_midiName.replace(/\.midi?$/i, '.wav'), targetPath: '', conversion_start: Date.now() }; var sleep = 10; circularBuffer.reset(); startAudio(); console.log(midiPlayer_convertionJob); MidiPlayer.ccall('wildwebmidi', null,[ 'string', 'string', 'number'],[midiPlayer_convertionJob.sourceMidi, midiPlayer_convertionJob.targetPath, sleep], { async: true }); } /************************************************************************ * jQuery player plugin */ (function ($) { $.fn.midiPlayer = function (options) { var options = $.extend({ // These are the defaults. color: "#556b2f", backgroundColor: "white", width: 500, onStop: null, onUpdate: null, updateRate: 50, }, options); // width should not be less than 150 options.width = Math.max(options.width, 150); // update rate should not be less than 10 milliseconds options.updateRate = Math.max(options.updateRate, 10); if(options.locateFile) MidiPlayer.locateFile = options.locateFile; MidiModule(MidiPlayer); $.fn.midiPlayer.play = function (song) { if (midiPlayer_isLoaded == false) { midiPlayer_input = song; } else { var byteArray = convertDataURIToBinary(song); if (midiPlayer_totalSamples > 0) { stop(); // a timeout is necessary because otherwise writing to the disk is not done setTimeout(function() {convertFile("player.midi", byteArray);}, 200); } else { convertFile("player.midi", byteArray); } } }; $.fn.midiPlayer.pause = function () { pause(); }; $.fn.midiPlayer.seek = function (millisec) { if (midiPlayer_totalSamples == 0) return; var samples = millisec * SAMPLE_RATE / 1000; midiPlayer_currentSamples = Math.min(midiPlayer_totalSamples, samples); play(); }; $.fn.midiPlayer.stop = function () { stop(); }; // Create the player this.append("
"); $("#midiPlayer_div").append("
0:00
") .append("
") .append("
0:00
") .append("") .append("") .append(""); $("#midiPlayer_div").css("width", options.width + 200); $("#midiPlayer_bar").css("width", options.width); $("#midiPlayer_progress").css("background", options.color); // Assign the global variables midiPlayer_onStop = options.onStop; midiPlayer_onUpdate = options.onUpdate; midiPlayer_updateRate = options.updateRate; midiPlayer_bar = document.getElementById('midiPlayer_bar'); midiPlayer_progress = document.getElementById('midiPlayer_progress'); midiPlayer_playingTime = document.getElementById('midiPlayer_playingTime'); midiPlayer_play = document.getElementById('midiPlayer_play'); midiPlayer_pause = document.getElementById('midiPlayer_pause'); midiPlayer_stop = document.getElementById('midiPlayer_stop'); midiPlayer_totalTime = document.getElementById('midiPlayer_totalTime'); var pageDragStart = 0; var barDragStart = 0; midiPlayer_bar.addEventListener('mousedown', function (e) { if (midiPlayer_totalSamples == 0) return; pageDragStart = e.pageX; barDragStart = e.offsetX; updateDragging(e.pageX); }); window.addEventListener('mousemove', function (e) { if (pageDragStart != 0) { pause(); updateDragging(e.pageX); } }); window.addEventListener('mouseup', function (e) { if (pageDragStart == 0) return; if (midiPlayer_totalSamples == 0) return; pageDragStart = 0; play(); }); function updateDragging(pageX) { var posX = barDragStart + (pageX - pageDragStart); if (posX >= 0 && posX <= options.width) { var percent = posX / options.width; midiPlayer_currentSamples = percent * midiPlayer_totalSamples | 0; updateProgress(midiPlayer_currentSamples, midiPlayer_totalSamples); } } return; }; } (jQuery));