UNPKG

abcjs

Version:

Renderer for abc music notation

309 lines (277 loc) 8.9 kB
var CreateSynthControl = require('./create-synth-control'); var CreateSynth = require('./create-synth'); var TimingCallbacks = require('../api/abc_timing_callbacks'); var activeAudioContext = require('./active-audio-context'); function SynthController() { var self = this; self.warp = 100; self.cursorControl = null; self.visualObj = null; self.timer = null; self.midiBuffer = null; self.options = null; self.currentTempo = null; self.control = null; self.isLooping = false; self.isStarted = false; self.isLoaded = false; self.isLoading = false; self.load = function (selector, cursorControl, visualOptions) { if (!visualOptions) visualOptions = {}; self.control = new CreateSynthControl(selector, { loopHandler: visualOptions.displayLoop ? self.toggleLoop : undefined, restartHandler: visualOptions.displayRestart ? self.restart : undefined, playPromiseHandler: visualOptions.displayPlay ? self.play : undefined, progressHandler: visualOptions.displayProgress ? self.randomAccess : undefined, warpHandler: visualOptions.displayWarp ? self.onWarp : undefined, afterResume: self.init }); self.cursorControl = cursorControl; self.disable(true); }; self.disable = function(isDisabled) { if (self.control) self.control.disable(isDisabled); }; self.setTune = function(visualObj, userAction, audioParams) { self.visualObj = visualObj; self.disable(false); self.options = audioParams; if (self.control) { self.pause(); self.setProgress(0, 1); self.control.resetAll(); self.restart(); self.isStarted = false; } self.isLooping = false; if (userAction) return self.go(); else { return Promise.resolve({status: "no-audio-context"}); } }; self.go = function () { self.isLoading = true; var millisecondsPerMeasure = self.visualObj.millisecondsPerMeasure() * 100 / self.warp; self.currentTempo = Math.round(self.visualObj.getBeatsPerMeasure() / millisecondsPerMeasure * 60000); if (self.control) self.control.setTempo(self.currentTempo); self.percent = 0; var loadingResponse; if (!self.midiBuffer) self.midiBuffer = new CreateSynth(); return activeAudioContext().resume().then(function (response) { return self.midiBuffer.init({ visualObj: self.visualObj, options: self.options, millisecondsPerMeasure: millisecondsPerMeasure }); }).then(function (response) { loadingResponse = response; return self.midiBuffer.prime(); }).then(function () { var subdivisions = 16; if (self.cursorControl && self.cursorControl.beatSubdivisions !== undefined && parseInt(self.cursorControl.beatSubdivisions, 10) >= 1 && parseInt(self.cursorControl.beatSubdivisions, 10) <= 64) subdivisions = parseInt(self.cursorControl.beatSubdivisions, 10); // Need to create the TimingCallbacks after priming the midi so that the midi data is available for the callbacks. self.timer = new TimingCallbacks(self.visualObj, { beatCallback: self.beatCallback, eventCallback: self.eventCallback, lineEndCallback: self.lineEndCallback, qpm: self.currentTempo, extraMeasuresAtBeginning: self.cursorControl ? self.cursorControl.extraMeasuresAtBeginning : undefined, lineEndAnticipation: self.cursorControl ? self.cursorControl.lineEndAnticipation : 0, beatSubdivisions: subdivisions, }); if (self.cursorControl && self.cursorControl.onReady && typeof self.cursorControl.onReady === 'function') self.cursorControl.onReady(self); self.isLoaded = true; self.isLoading = false; return Promise.resolve({ status: "created", notesStatus: loadingResponse }); }); }; self.destroy = function () { if (self.timer) { self.timer.reset(); self.timer.stop(); self.timer = null; } if (self.midiBuffer) { self.midiBuffer.stop(); self.midiBuffer = null; } self.setProgress(0, 1); if (self.control) self.control.resetAll(); }; self.play = function () { return self.runWhenReady(self._play, undefined); }; function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms) }); } self.runWhenReady = function(fn, arg1) { if (!self.visualObj) return Promise.resolve({status: "loading"}); if (self.isLoading) { // Some other promise is waiting for the tune to be loaded, so just wait. return sleep(500).then(function() { if (self.isLoading) return self.runWhenReady(fn, arg1); return fn(arg1); }) } else if (!self.isLoaded) { return self.go().then(function () { return fn(arg1); }); } else { return fn(arg1); } }; self._play = function () { return activeAudioContext().resume().then(function () { self.isStarted = !self.isStarted; if (self.isStarted) { if (self.cursorControl && self.cursorControl.onStart && typeof self.cursorControl.onStart === 'function') self.cursorControl.onStart(); self.midiBuffer.start(); self.timer.start(self.percent); if (self.control) self.control.pushPlay(true); } else { self.pause(); } return Promise.resolve({status: "ok"}); }) }; self.pause = function() { if (self.timer) { self.timer.pause(); self.midiBuffer.pause(); if (self.control) self.control.pushPlay(false); } }; self.toggleLoop = function () { self.isLooping = !self.isLooping; if (self.control) self.control.pushLoop(self.isLooping); }; self.restart = function () { if (self.timer) { self.timer.setProgress(0); self.midiBuffer.seek(0); } }; self.randomAccess = function (ev) { return self.runWhenReady(self._randomAccess, ev); }; self._randomAccess = function (ev) { var background = (ev.target.classList.contains('abcjs-midi-progress-indicator')) ? ev.target.parentNode : ev.target; var percent = (ev.x - background.offsetLeft) / background.offsetWidth; if (percent < 0) percent = 0; if (percent > 1) percent = 1; self.seek(percent); return Promise.resolve({status: "ok"}); }; self.seek = function (percent, units) { if (self.timer && self.midiBuffer) { self.timer.setProgress(percent, units); self.midiBuffer.seek(percent, units); } }; self.setWarp = function (newWarp) { if (parseInt(newWarp, 10) > 0) { self.warp = parseInt(newWarp, 10); var wasPlaying = self.isStarted; var startPercent = self.percent; self.destroy(); self.isStarted = false; return self.go().then(function () { self.setProgress(startPercent, self.midiBuffer.duration * 1000); if (self.control) self.control.setWarp(self.currentTempo, self.warp); if (wasPlaying) { return self.play().then(function () { self.seek(startPercent); return Promise.resolve(); }); } self.seek(startPercent); return Promise.resolve(); }); } return Promise.resolve(); }; self.onWarp = function (ev) { var newWarp = ev.target.value; return self.setWarp(newWarp); }; self.setProgress = function (percent, totalTime) { self.percent = percent; if (self.control) self.control.setProgress(percent, totalTime); }; self.finished = function () { self.timer.reset(); if (self.isLooping) { self.timer.start(0); self.midiBuffer.finished(); self.midiBuffer.start(); return "continue"; } else { self.timer.stop(); if (self.isStarted) { if (self.control) self.control.pushPlay(false); self.isStarted = false; self.midiBuffer.finished(); if (self.cursorControl && self.cursorControl.onFinished && typeof self.cursorControl.onFinished === 'function') self.cursorControl.onFinished(); self.setProgress(0, 1); } } }; self.beatCallback = function (beatNumber, totalBeats, totalTime, position) { var percent = beatNumber / totalBeats; self.setProgress(percent, totalTime); if (self.cursorControl && self.cursorControl.onBeat && typeof self.cursorControl.onBeat === 'function') self.cursorControl.onBeat(beatNumber, totalBeats, totalTime, position); }; self.eventCallback = function (event) { if (event) { if (self.cursorControl && self.cursorControl.onEvent && typeof self.cursorControl.onEvent === 'function') self.cursorControl.onEvent(event); } else { return self.finished(); } }; self.lineEndCallback = function (lineEvent, leftEvent) { if (self.cursorControl && self.cursorControl.onLineEnd && typeof self.cursorControl.onLineEnd === 'function') self.cursorControl.onLineEnd(lineEvent, leftEvent); }; self.getUrl = function () { return self.midiBuffer.download(); }; self.download = function(fileName) { var url = self.getUrl(); var link = document.createElement('a'); document.body.appendChild(link); link.setAttribute("style","display: none;"); link.href = url; link.download = fileName ? fileName : 'output.wav'; link.click(); window.URL.revokeObjectURL(url); document.body.removeChild(link); }; } module.exports = SynthController;