Koha/koha-tmpl/opac-tmpl/lib/verovio/midiplayer.js
David Cook a4bdd27fd9
Bug 34613: Replace onclick attributes in midiplayer.js
This patch replaces the onclick attributes in midiplayer.js
with Javascript click handlers.

To test:
0. Apply patch
1. Clear browser cache (it may be necessary to go into the
   Network tab and explicitly disable cache)
2. Add 031 subfields 2gopnu to Default framework
3. Create a catalogue record with a 031 like follows:
2: pe
g: G-2
o: 4/4
p: 4bB''C2bE/2F4GbB/'bB2''C4D/F2.bE/4GG2bB/4'''C2C4''bB/4bE2G4bB/4bE2.F/
n: xFCGD
4. Enable the following sysprefs:
   OPACShowMusicalInscripts
   OPACPlayMusicalInscripts
5. Go to OPAC record view and click "Play this sample"
6. Play with the "Pause/Start" and "Stop" buttons
7. If the buttons work, it means the patch worked

Signed-off-by: Owen Leonard <oleonard@myacpl.org>
Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
2023-09-18 15:31:47 -03:00

445 lines
14 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
var play_button = $("<a class=\"icon play\" id=\"midiPlayer_play\"></a>");
play_button.on("click",play);
var pause_button = $("<a class=\"icon pause\" id=\"midiPlayer_pause\"></a>");
pause_button.on("click",pause);
var stop_button = $("<a class=\"icon stop\" id=\"midiPlayer_stop\"></a>");
stop_button.on("click",stop);
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(play_button)
.append(pause_button)
.append(stop_button);
$("#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));