Koha/koha-tmpl/opac-tmpl/lib/verovio/midiplayer.js
Agustin Moyano 426a055a07
Bug 22581: Show and play musical inscripts
This patch adds musical inscripts to OPAC's detail page

To test:
1. run previous patch test plan
2. apply this patch
3. edit a the marc structure of a MARC bibliographic framework, and in tag 031 enable the following subfiels to be visible in editor: 2, g, n, o, p, u
4. search the catalog for a record that belongs to that framework, and edit tag 031 with the following:
   * 2:pe
   * g:G-2
   * n:xFCGD
   * o:3/8
   * p:'6B/{8B+(6B''E'B})({AFD})/{6.E3G},8B-/({6'EGF})({FAG})({GEB})/4F6-
   * u:http://nonexistent.org/url/of/a/midi
5. save and click in opac view
CHECK => even though you add a 031 tag there is no musical inscript shown in opac view
6. in admin module enable OPACShowMusicalInscripts preference
7. refresh opac view
SUCCESS => it takes a few seconds to load, but you see a link that says 'Audio file' pointing to the URL you placed in 'u' subfield, and below you see the musical inscript
8. in admin module enable OPACPlayMusicalInscripts preference
9. refresh opac view
SUCCESS => You see a play button below the musical inscript, and when you click, the song is played
10. sign off

Sponsored-by: Biblioteca Provincial Fr. Mamerto Esquiú (Provincia Franciscana de la Asunción)
Co-authored-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
2019-11-03 08:11:38 +00:00

439 lines
No EOL
13 KiB
JavaScript

/************************************************************************
* 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("<div id=\"midiPlayer_div\"></div>");
$("#midiPlayer_div").append("<div id=\"midiPlayer_playingTime\">0:00</div>")
.append("<div id=\"midiPlayer_bar\"><div id=\"midiPlayer_progress\"></div></div>")
.append("<div id=\"midiPlayer_totalTime\">0:00</div>")
.append("<a class=\"icon play\" id=\"midiPlayer_play\" onclick=\"play()\"></a>")
.append("<a class=\"icon pause\" id=\"midiPlayer_pause\" onclick=\"pause()\"></a>")
.append("<a class=\"icon stop\" id=\"midiPlayer_stop\" onclick=\"stop()\"></a>");
$("#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));