1 /************************************************************************
2 * Circular Web Audio Buffer Queue
4 function CircularAudioBuffer(slots) {
8 this.buffers = new Array(slots);
12 for (var i = 0; i < this.slots; i++) {
13 var buffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE);
14 this.buffers[i] = buffer;
18 // pseudo empty all buffers
19 CircularAudioBuffer.prototype.reset = function () {
24 // returns number of buffers that are filled
25 CircularAudioBuffer.prototype.filledBuffers = function () {
26 var fills = this.filled - this.used;
27 if (fills < 0) fills += this.slots;
31 // returns whether buffers are all filled
32 CircularAudioBuffer.prototype.full = function () {
33 //console.debug(this.filledBuffers());
34 return this.filledBuffers() >= this.slots - 1;
37 // returns a reference to next available buffer to be filled
38 CircularAudioBuffer.prototype.prepare = function () {
40 //console.log('buffers full!!')
43 var buffer = this.buffers[ this.filled++];
44 this.filled %= this.slots;
48 // returns the next buffer in the queue
49 CircularAudioBuffer.prototype.use = function () {
50 if (! this.filledBuffers()) return;
51 var buffer = this.buffers[ this.used++];
52 this.used %= this.slots;
56 /************************************************************************
60 var SAMPLE_RATE = 44100;
70 function initAudio() {
71 audioCtx = new (window.AudioContext || window.webkitAudioContext)();
72 scriptNode = audioCtx.createScriptProcessor(BUFFER, 0, channels);
73 scriptNode.onaudioprocess = onAudioProcess;
75 source = audioCtx.createBufferSource();
76 circularBuffer = new CircularAudioBuffer(8);
77 emptyBuffer = audioCtx.createBuffer(channels, BUFFER, SAMPLE_RATE);
79 source.connect(scriptNode);
81 console.debug("initAudio");
84 function startAudio() {
85 scriptNode.connect(audioCtx.destination);
86 console.debug("startAudio");
89 function pauseAudio() {
90 circularBuffer.reset();
91 scriptNode.disconnect();
92 console.debug("pauseAudio");
96 /************************************************************************
97 * Emscripten variables and callback - cannot be renamed
100 var ULONG_MAX = 4294967295;
101 var _EM_signalStop = 0;
102 var _EM_seekSamples = ULONG_MAX;
104 function processAudio(buffer_loc, size) {
105 var buffer = circularBuffer.prepare();
106 var left_buffer_f32 = buffer.getChannelData(0);
107 var right_buffer_f32 = buffer.getChannelData(1);
109 // Copy emscripten memory (OpenAL stereo16 format) to JS
110 for (var i = 0; i < size; i++) {
111 left_buffer_f32[i] = MidiPlayer.HEAP16[(buffer_loc >> 1) + 2 * i + 0] / 32768;
112 right_buffer_f32[i] = MidiPlayer.HEAP16[(buffer_loc >> 1) + 2 * i + 1] / 32768;
116 function updateProgress(current, total) {
117 midiPlayer_currentSamples = current;
118 midiPlayer_totalSamples = total;
119 midiPlayer_progress.style.width = (current / total * 100) + '%';
120 midiPlayer_playingTime.innerHTML = samplesToTime(current);
121 midiPlayer_totalTime.innerHTML = samplesToTime(total);
123 var millisec = Math.floor(current * 1000 / SAMPLE_RATE / midiPlayer_updateRate);
124 if (midiPlayer_lastMillisec > millisec) {
125 midiPlayer_lastMillisec = 0;
127 if (millisec > midiPlayer_lastMillisec) {
128 if (midiPlayer_onUpdate != null) midiPlayer_onUpdate(millisec * midiPlayer_updateRate);
129 //console.log(millisec * UPDATE_RATE);
131 midiPlayer_lastMillisec = millisec;
134 function completeConversion(status) {
135 midiPlayer_drainBuffer = true;
136 console.debug('completeConversion');
137 midiPlayer_convertionJob = null;
139 if (_EM_signalStop != 2) {
140 setTimeout(stop, 1000);
144 /************************************************************************
145 * Global player variables and functions
149 var midiPlayer_width;
151 var midiPlayer_progress;
152 var midiPlayer_playingTime;
154 var midiPlayer_pause;
156 var midiPlayer_totalTime;
159 var midiPlayer_isLoaded = false;
160 var midiPlayer_isAudioInit = false;
161 var midiPlayer_input = null;
162 var midiPlayer_lastMillisec = 0;
163 var midiPlayer_midiName = ''
164 var midiPlayer_convertionJob = null;
165 var midiPlayer_currentSamples = ULONG_MAX;
166 var midiPlayer_totalSamples = 0;
167 var midiPlayer_updateRate = 50;
168 var midiPlayer_drainBuffer = false;
169 var BASE64_MARKER = ';base64,';
172 var midiPlayer_onStop = null;
173 var midiPlayer_onUpdate = null;
177 totalDependencies: 1,
178 monitorRunDependencies: function(left) {
179 //console.log(this.totalDependencies);
181 if ((left == 0) && !midiPlayer_isLoaded) {
182 console.log("MidiPlayer is loaded");
183 midiPlayer_isLoaded = true;
188 function onAudioProcess(audioProcessingEvent) {
189 var generated = circularBuffer.use();
191 if (!generated && midiPlayer_drainBuffer) {
192 // wait for remaining buffer to drain before disconnect audio
194 midiPlayer_drainBuffer = false;
198 //console.log('buffer under run!!')
199 generated = emptyBuffer;
202 var outputBuffer = audioProcessingEvent.outputBuffer;
204 if (outputBuffer.copyToChannel !== undefined) {
205 // Firefox -> about 50% faster than decoding
206 outputBuffer.copyToChannel(generated.getChannelData(0), 0, offset);
207 outputBuffer.copyToChannel(generated.getChannelData(1), 1, offset);
209 // Other browsers -> about 20 - 70% slower than decoding
210 var leftChannel = outputBuffer.getChannelData(0);
211 var rightChannel = outputBuffer.getChannelData(1);
212 var generatedLeftChannel = generated.getChannelData(0);
213 var generatedRightChannel = generated.getChannelData(1);
215 for (i = 0; i < BUFFER; i++) {
216 leftChannel[i] = generatedLeftChannel[i];
217 rightChannel[i] = generatedRightChannel[i];
222 function samplesToTime(at) {
223 var in_s = Math.floor(at / SAMPLE_RATE);
225 var min = in_s / 60 | 0;
226 return min + ':' + (s === 0 ? '00': s < 10 ? '0' + s: s);
229 function convertDataURIToBinary(dataURI) {
230 var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
231 var base64 = dataURI.substring(base64Index);
232 var raw = window.atob(base64);
233 var rawLength = raw.length;
234 var array = new Uint8Array(new ArrayBuffer(rawLength));
236 for (var i = 0; i < rawLength; i++) {
237 array[i] = raw.charCodeAt(i);
242 function convertFile(file, data) {
243 midiPlayer_midiName = file;
244 midiPlayer_input = null;
245 console.log('open ', midiPlayer_midiName);
246 MidiPlayer['FS'].writeFile(midiPlayer_midiName, data, {
254 circularBuffer.reset();
255 midiPlayer_play.style.display = 'inline-block';
256 midiPlayer_pause.style.display = 'none';
260 if (!midiPlayer_isLoaded) {
261 console.error("MidiPlayer is not loaded yet");
264 if (!midiPlayer_isAudioInit) {
266 midiPlayer_isAudioInit = true;
269 _EM_seekSamples = midiPlayer_currentSamples;
270 if (midiPlayer_convertionJob) {
275 midiPlayer_play.style.display = 'none';
276 midiPlayer_pause.style.display = 'inline-block';
277 midiPlayer_stop.style.display = 'inline-block';
278 // add small delay so UI can update.
279 setTimeout(runConversion, 100);
285 circularBuffer.reset();
287 midiPlayer_totalSamples = 0;
288 midiPlayer_currentSamples = ULONG_MAX;
289 midiPlayer_progress.style.width = '0%';
290 midiPlayer_playingTime.innerHTML = "00.00";
291 midiPlayer_totalTime.innerHTML = "00.00";
293 midiPlayer_play.style.display = 'none';
294 midiPlayer_pause.style.display = 'none';
295 midiPlayer_stop.style.display = 'none';
297 if (midiPlayer_onStop != null) midiPlayer_onStop();
300 function runConversion() {
301 midiPlayer_convertionJob = {
302 sourceMidi: midiPlayer_midiName,
303 targetWav: midiPlayer_midiName.replace(/\.midi?$/i, '.wav'),
305 conversion_start: Date.now()
309 circularBuffer.reset();
312 console.log(midiPlayer_convertionJob);
314 MidiPlayer.ccall('wildwebmidi',
315 null,[ 'string', 'string', 'number'],[midiPlayer_convertionJob.sourceMidi, midiPlayer_convertionJob.targetPath, sleep], {
320 /************************************************************************
321 * jQuery player plugin
326 $.fn.midiPlayer = function (options) {
328 var options = $.extend({
329 // These are the defaults.
331 backgroundColor: "white",
338 // width should not be less than 150
339 options.width = Math.max(options.width, 150);
340 // update rate should not be less than 10 milliseconds
341 options.updateRate = Math.max(options.updateRate, 10);
343 if(options.locateFile) MidiPlayer.locateFile = options.locateFile;
345 MidiModule(MidiPlayer);
347 $.fn.midiPlayer.play = function (song) {
348 if (midiPlayer_isLoaded == false) {
349 midiPlayer_input = song;
352 var byteArray = convertDataURIToBinary(song);
353 if (midiPlayer_totalSamples > 0) {
355 // a timeout is necessary because otherwise writing to the disk is not done
356 setTimeout(function() {convertFile("player.midi", byteArray);}, 200);
359 convertFile("player.midi", byteArray);
364 $.fn.midiPlayer.pause = function () {
368 $.fn.midiPlayer.seek = function (millisec) {
369 if (midiPlayer_totalSamples == 0) return;
370 var samples = millisec * SAMPLE_RATE / 1000;
371 midiPlayer_currentSamples = Math.min(midiPlayer_totalSamples, samples);
375 $.fn.midiPlayer.stop = function () {
380 this.append("<div id=\"midiPlayer_div\"></div>");
381 $("#midiPlayer_div").append("<div id=\"midiPlayer_playingTime\">0:00</div>")
382 .append("<div id=\"midiPlayer_bar\"><div id=\"midiPlayer_progress\"></div></div>")
383 .append("<div id=\"midiPlayer_totalTime\">0:00</div>")
384 .append("<a class=\"icon play\" id=\"midiPlayer_play\" onclick=\"play()\"></a>")
385 .append("<a class=\"icon pause\" id=\"midiPlayer_pause\" onclick=\"pause()\"></a>")
386 .append("<a class=\"icon stop\" id=\"midiPlayer_stop\" onclick=\"stop()\"></a>");
388 $("#midiPlayer_div").css("width", options.width + 200);
389 $("#midiPlayer_bar").css("width", options.width);
390 $("#midiPlayer_progress").css("background", options.color);
392 // Assign the global variables
393 midiPlayer_onStop = options.onStop;
394 midiPlayer_onUpdate = options.onUpdate;
395 midiPlayer_updateRate = options.updateRate;
396 midiPlayer_bar = document.getElementById('midiPlayer_bar');
397 midiPlayer_progress = document.getElementById('midiPlayer_progress');
398 midiPlayer_playingTime = document.getElementById('midiPlayer_playingTime');
399 midiPlayer_play = document.getElementById('midiPlayer_play');
400 midiPlayer_pause = document.getElementById('midiPlayer_pause');
401 midiPlayer_stop = document.getElementById('midiPlayer_stop');
402 midiPlayer_totalTime = document.getElementById('midiPlayer_totalTime');
405 var pageDragStart = 0;
406 var barDragStart = 0;
407 midiPlayer_bar.addEventListener('mousedown', function (e) {
408 if (midiPlayer_totalSamples == 0) return;
409 pageDragStart = e.pageX;
410 barDragStart = e.offsetX;
411 updateDragging(e.pageX);
413 window.addEventListener('mousemove', function (e) {
414 if (pageDragStart != 0) {
416 updateDragging(e.pageX);
420 window.addEventListener('mouseup', function (e) {
421 if (pageDragStart == 0) return;
422 if (midiPlayer_totalSamples == 0) return;
427 function updateDragging(pageX) {
428 var posX = barDragStart + (pageX - pageDragStart);
429 if (posX >= 0 && posX <= options.width) {
430 var percent = posX / options.width;
431 midiPlayer_currentSamples = percent * midiPlayer_totalSamples | 0;
432 updateProgress(midiPlayer_currentSamples, midiPlayer_totalSamples);