UNPKG

abcjs

Version:

Renderer for abc music notation

1,298 lines (1,250 loc) 1.1 MB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["abcjs"] = factory(); else root["ABCJS"] = factory(); })(this, function() { return /******/ (function() { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./index.js": /*!******************!*\ !*** ./index.js ***! \******************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { /**! Copyright (c) 2009-2022 Paul Rosen and Gregory Dyke Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. **This text is from: http://opensource.org/licenses/MIT** !**/ var version = __webpack_require__(/*! ./version */ "./version.js"); var animation = __webpack_require__(/*! ./src/api/abc_animation */ "./src/api/abc_animation.js"); var tuneBook = __webpack_require__(/*! ./src/api/abc_tunebook */ "./src/api/abc_tunebook.js"); var sequence = __webpack_require__(/*! ./src/synth/abc_midi_sequencer */ "./src/synth/abc_midi_sequencer.js"); var strTranspose = __webpack_require__(/*! ./src/str/output */ "./src/str/output.js"); var abcjs = {}; abcjs.signature = "abcjs-basic v" + version; Object.keys(animation).forEach(function (key) { abcjs[key] = animation[key]; }); Object.keys(tuneBook).forEach(function (key) { abcjs[key] = tuneBook[key]; }); abcjs.renderAbc = __webpack_require__(/*! ./src/api/abc_tunebook_svg */ "./src/api/abc_tunebook_svg.js"); abcjs.TimingCallbacks = __webpack_require__(/*! ./src/api/abc_timing_callbacks */ "./src/api/abc_timing_callbacks.js"); var glyphs = __webpack_require__(/*! ./src/write/creation/glyphs */ "./src/write/creation/glyphs.js"); abcjs.setGlyph = glyphs.setSymbol; abcjs.strTranspose = strTranspose; var CreateSynth = __webpack_require__(/*! ./src/synth/create-synth */ "./src/synth/create-synth.js"); var instrumentIndexToName = __webpack_require__(/*! ./src/synth/instrument-index-to-name */ "./src/synth/instrument-index-to-name.js"); var pitchToNoteName = __webpack_require__(/*! ./src/synth/pitch-to-note-name */ "./src/synth/pitch-to-note-name.js"); var SynthSequence = __webpack_require__(/*! ./src/synth/synth-sequence */ "./src/synth/synth-sequence.js"); var CreateSynthControl = __webpack_require__(/*! ./src/synth/create-synth-control */ "./src/synth/create-synth-control.js"); var registerAudioContext = __webpack_require__(/*! ./src/synth/register-audio-context */ "./src/synth/register-audio-context.js"); var activeAudioContext = __webpack_require__(/*! ./src/synth/active-audio-context */ "./src/synth/active-audio-context.js"); var supportsAudio = __webpack_require__(/*! ./src/synth/supports-audio */ "./src/synth/supports-audio.js"); var playEvent = __webpack_require__(/*! ./src/synth/play-event */ "./src/synth/play-event.js"); var SynthController = __webpack_require__(/*! ./src/synth/synth-controller */ "./src/synth/synth-controller.js"); var getMidiFile = __webpack_require__(/*! ./src/synth/get-midi-file */ "./src/synth/get-midi-file.js"); abcjs.synth = { CreateSynth: CreateSynth, instrumentIndexToName: instrumentIndexToName, pitchToNoteName: pitchToNoteName, SynthController: SynthController, SynthSequence: SynthSequence, CreateSynthControl: CreateSynthControl, registerAudioContext: registerAudioContext, activeAudioContext: activeAudioContext, supportsAudio: supportsAudio, playEvent: playEvent, getMidiFile: getMidiFile, sequence: sequence }; abcjs['Editor'] = __webpack_require__(/*! ./src/edit/abc_editor */ "./src/edit/abc_editor.js"); abcjs['EditArea'] = __webpack_require__(/*! ./src/edit/abc_editarea */ "./src/edit/abc_editarea.js"); module.exports = abcjs; /***/ }), /***/ "./src/api/abc_animation.js": /*!**********************************!*\ !*** ./src/api/abc_animation.js ***! \**********************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { // abc_animation.js: handles animating the music in real time. var TimingCallbacks = __webpack_require__(/*! ./abc_timing_callbacks */ "./src/api/abc_timing_callbacks.js"); var animation = {}; (function () { "use strict"; var timer; var cursor; animation.startAnimation = function (paper, tune, options) { //options.bpm //options.showCursor //options.hideCurrentMeasure //options.hideFinishedMeasures if (timer) { timer.stop(); timer = undefined; } if (options.showCursor) { cursor = paper.querySelector('.abcjs-cursor'); if (!cursor) { cursor = document.createElement('DIV'); cursor.className = 'abcjs-cursor cursor'; cursor.style.position = 'absolute'; paper.appendChild(cursor); paper.style.position = 'relative'; } } function hideMeasures(elements) { for (var i = 0; i < elements.length; i++) { var element = elements[i]; if (!element.classList.contains('abcjs-bar')) element.style.display = "none"; } } var lastMeasure; function disappearMeasuresAfter(selector) { if (lastMeasure) { var elements = paper.querySelectorAll(lastMeasure); hideMeasures(elements); } lastMeasure = selector; } function disappearMeasuresBefore(selector) { var elements = paper.querySelectorAll(selector); hideMeasures(elements); } function measureCallback(selector) { if (options.hideCurrentMeasure) { disappearMeasuresBefore(selector); } else if (options.hideFinishedMeasures) { disappearMeasuresAfter(selector); } } function getLineAndMeasure(element) { return '.abcjs-l' + element.line + '.abcjs-m' + element.measureNumber; } function setCursor(range) { if (range) { if (range.measureStart) { var selector = getLineAndMeasure(range); if (selector) measureCallback(selector); } if (cursor) { cursor.style.left = range.left + "px"; cursor.style.top = range.top + "px"; cursor.style.width = range.width + "px"; cursor.style.height = range.height + "px"; } } else { timer.stop(); timer = undefined; } } timer = new TimingCallbacks(tune, { qpm: options.bpm, eventCallback: setCursor }); timer.start(); }; animation.pauseAnimation = function (pause) { if (timer) { if (pause) timer.pause();else timer.start(); } }; animation.stopAnimation = function () { if (timer) { timer.stop(); timer = undefined; } }; })(); module.exports = animation; /***/ }), /***/ "./src/api/abc_tablatures.js": /*!***********************************!*\ !*** ./src/api/abc_tablatures.js ***! \***********************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { /* * Tablature Plugins * tablature are defined dynamically and registered inside abcjs * by calling abcTablatures.register(plugin) * where plugin represents a plugin instance * */ var ViolinTablature = __webpack_require__(/*! ../tablatures/instruments/violin/tab-violin */ "./src/tablatures/instruments/violin/tab-violin.js"); var GuitarTablature = __webpack_require__(/*! ../tablatures/instruments/guitar/tab-guitar */ "./src/tablatures/instruments/guitar/tab-guitar.js"); /* extend the table below when adding a new instrument plugin */ // Existing tab classes var pluginTab = { 'violin': 'ViolinTab', 'guitar': 'GuitarTab' }; var abcTablatures = { inited: false, plugins: {}, /** * to be called once per plugin for registration * @param {*} plugin */ register: function register(plugin) { var name = plugin.name; var tablature = plugin.tablature; this.plugins[name] = tablature; }, setError: function setError(tune, msg) { if (tune.warnings) { tune.warning.push(msg); } else { tune.warnings = [msg]; } }, /** * handle params for current processed score * @param {*} tune current tune * @param {*} tuneNumber number in tune list * @param {*} params params to be processed for tablature * @return prepared tablatures plugin instances for current tune */ preparePlugins: function preparePlugins(tune, tuneNumber, params) { var returned = null; var nbPlugins = 0; if (params.tablature) { // validate requested plugins var tabs = params.tablature; returned = []; for (var ii = 0; ii < tabs.length; ii++) { var args = tabs[ii]; var instrument = args['instrument']; if (instrument == null) { this.setError(tune, "tablature 'instrument' is missing"); return returned; } var tabName = pluginTab[instrument]; var plugin = null; if (tabName) { plugin = this.plugins[tabName]; } if (plugin) { if (params.visualTranspose != 0) { // populate transposition request to tabs args.visualTranspose = params.visualTranspose; } args.abcSrc = params.tablature.abcSrc; var pluginInstance = { classz: plugin, tuneNumber: tuneNumber, params: args, instance: null }; // proceed with tab plugin init // plugin.init(tune, tuneNumber, args, ii); returned.push(pluginInstance); nbPlugins++; } else { // unknown tab plugin //this.emit_error('Undefined tablature plugin: ' + tabName) this.setError(tune, 'Undefined tablature plugin: ' + instrument); return returned; } } } return returned; }, /** * Call requested plugin * @param {*} renderer * @param {*} abcTune */ layoutTablatures: function layoutTablatures(renderer, abcTune) { var tabs = abcTune.tablatures; // chack tabs request for each staffs for (var ii = 0; ii < abcTune.lines.length; ii++) { var line = abcTune.lines[ii]; var curStaff = line.staff; if (curStaff) { for (var jj = 0; jj < curStaff.length; jj++) { if (tabs[jj]) { // tablature requested for staff var tabPlugin = tabs[jj]; if (tabPlugin.instance == null) { tabPlugin.instance = new tabPlugin.classz(); // plugin.init(tune, tuneNumber, args, ii); // call initer first tabPlugin.instance.init(abcTune, tabPlugin.tuneNumber, tabPlugin.params, jj); } // render next tabPlugin.instance.render(renderer, line, jj); } } } } }, /** * called once internally to register internal plugins */ init: function init() { // just register plugin hosted by abcjs if (!this.inited) { this.register(new ViolinTablature()); this.register(new GuitarTablature()); this.inited = true; } } }; module.exports = abcTablatures; /***/ }), /***/ "./src/api/abc_timing_callbacks.js": /*!*****************************************!*\ !*** ./src/api/abc_timing_callbacks.js ***! \*****************************************/ /***/ (function(module) { var TimingCallbacks = function TimingCallbacks(target, params) { var self = this; if (!params) params = {}; self.qpm = params.qpm ? parseInt(params.qpm, 10) : null; if (!self.qpm) { var tempo = target.metaText ? target.metaText.tempo : null; self.qpm = target.getBpm(tempo); } self.extraMeasuresAtBeginning = params.extraMeasuresAtBeginning ? parseInt(params.extraMeasuresAtBeginning, 10) : 0; self.beatCallback = params.beatCallback; // This is called for each beat. self.eventCallback = params.eventCallback; // This is called for each note or rest encountered. self.lineEndCallback = params.lineEndCallback; // This is called when the end of a line is approaching. self.lineEndAnticipation = params.lineEndAnticipation ? parseInt(params.lineEndAnticipation, 10) : 0; // How many milliseconds before the end should the call happen. self.beatSubdivisions = params.beatSubdivisions ? parseInt(params.beatSubdivisions, 10) : 1; // how many callbacks per beat is desired. self.joggerTimer = null; self.replaceTarget = function (newTarget) { self.noteTimings = newTarget.setTiming(self.qpm, self.extraMeasuresAtBeginning); if (newTarget.noteTimings.length === 0) newTarget.setTiming(0, 0); if (self.lineEndCallback) { self.lineEndTimings = getLineEndTimings(newTarget.noteTimings, self.lineEndAnticipation); } self.startTime = null; self.currentBeat = 0; self.currentEvent = 0; self.currentLine = 0; self.currentTime = 0; self.isPaused = false; self.isRunning = false; self.pausedPercent = null; self.justUnpaused = false; self.newSeekPercent = 0; self.lastTimestamp = 0; if (self.noteTimings.length === 0) return; // noteTimings contains an array of events sorted by time. Events that happen at the same time are in the same element of the array. self.millisecondsPerBeat = 1000 / (self.qpm / 60) / self.beatSubdivisions; self.lastMoment = self.noteTimings[self.noteTimings.length - 1].milliseconds; self.totalBeats = Math.round(self.lastMoment / self.millisecondsPerBeat); }; self.replaceTarget(target); self.doTiming = function (timestamp) { // This is called 60 times a second, that is, every 16 msecs. //console.log("doTiming", timestamp, timestamp-self.lastTimestamp); if (self.lastTimestamp === timestamp) return; // If there are multiple seeks or other calls, then we can easily get multiple callbacks for the same instant. self.lastTimestamp = timestamp; if (!self.startTime) { self.startTime = timestamp; } if (!self.isPaused && self.isRunning) { self.currentTime = timestamp - self.startTime; self.currentTime += 16; // Add a little slop because this function isn't called exactly. while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) { if (self.eventCallback && self.noteTimings[self.currentEvent].type === 'event') { var thisStartTime = self.startTime; // the event callback can call seek and change the position from beneath us. self.eventCallback(self.noteTimings[self.currentEvent]); if (thisStartTime !== self.startTime) { self.currentTime = timestamp - self.startTime; } } self.currentEvent++; } if (self.lineEndCallback && self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds < self.currentTime && self.currentEvent < self.noteTimings.length) { var leftEvent = self.noteTimings[self.currentEvent].milliseconds === self.currentTime ? self.noteTimings[self.currentEvent] : self.noteTimings[self.currentEvent - 1]; self.lineEndCallback(self.lineEndTimings[self.currentLine], leftEvent, { line: self.currentLine, endTimings: self.lineEndTimings, currentTime: self.currentTime }); self.currentLine++; } if (self.currentTime < self.lastMoment) { requestAnimationFrame(self.doTiming); if (self.currentBeat * self.millisecondsPerBeat < self.currentTime) { var ret = self.doBeatCallback(timestamp); if (ret !== null) self.currentTime = ret; } } else if (self.currentBeat <= self.totalBeats) { // Because of timing issues (for instance, if the browser tab isn't active), the beat callbacks might not have happened when they are supposed to. To keep the client programs from having to deal with that, this will keep calling the loop until all of them have been sent. if (self.beatCallback) { var ret2 = self.doBeatCallback(timestamp); if (ret2 !== null) self.currentTime = ret2; requestAnimationFrame(self.doTiming); } } if (self.currentTime >= self.lastMoment) { if (self.eventCallback) { // At the end, the event callback can return "continue" to keep from stopping. // The event callback can either be a promise or not. var promise = self.eventCallback(null); self.shouldStop(promise).then(function (shouldStop) { if (shouldStop) self.stop(); }); } else self.stop(); } } }; self.shouldStop = function (promise) { // The return of the last event callback can be "continue" or a promise that returns "continue". // If it is then don't call stop. Any other value calls stop. return new Promise(function (resolve) { if (!promise) return resolve(true); if (promise === "continue") return resolve(false); if (promise.then) { promise.then(function (result) { resolve(result !== "continue"); }); } }); }; self.doBeatCallback = function (timestamp) { if (self.beatCallback) { var next = self.currentEvent; while (next < self.noteTimings.length && self.noteTimings[next].left === null) { next++; } var endMs; var ev; if (next < self.noteTimings.length) { endMs = self.noteTimings[next].milliseconds; next = Math.max(0, self.currentEvent - 1); while (next >= 0 && self.noteTimings[next].left === null) { next--; } ev = self.noteTimings[next]; } var position = {}; var debugInfo = {}; if (ev) { position.top = ev.top; position.height = ev.height; // timestamp = the time passed in from the animation timer // self.startTime = the time that the tune was started (if there was seeking or pausing, it is adjusted to keep the math the same) // ev = the event that is either happening now or has most recently passed. // ev.milliseconds = the time that the current event starts (relative to self.startTime) // endMs = the time that the next event starts // ev.endX = the x coordinate that the next event happens (or the end of the line or repeat measure) // ev.left = the x coordinate of the current event // // The output is the X coordinate of the current cursor location. It is calculated with the ratio of the length of the event and the width of it. var offMs = Math.max(0, timestamp - self.startTime - ev.milliseconds); // Offset in time from the last beat var gapMs = endMs - ev.milliseconds; // Length of this event in time var gapPx = ev.endX - ev.left; // The length in pixels var offPx = gapMs ? offMs * gapPx / gapMs : 0; position.left = ev.left + offPx; // See if this is before the first event - that is the case where there are "prep beats" if (self.currentEvent === 0 && ev.milliseconds > timestamp - self.startTime) position.left = undefined; debugInfo = { timestamp: timestamp, startTime: self.startTime, ev: ev, endMs: endMs, offMs: offMs, offPx: offPx, gapMs: gapMs, gapPx: gapPx }; } else { debugInfo = { timestamp: timestamp, startTime: self.startTime }; } var thisStartTime = self.startTime; // the beat callback can call seek and change the position from beneath us. self.beatCallback(self.currentBeat / self.beatSubdivisions, self.totalBeats / self.beatSubdivisions, self.lastMoment, position, debugInfo); if (thisStartTime !== self.startTime) { return timestamp - self.startTime; } else self.currentBeat++; } return null; }; // In general music doesn't need a timer at 60 fps because notes don't happen that fast. // For instance, at 120 beats per minute, a sixteenth note takes 125ms. So just as a // compromise value between performance and jank this is set about half that. var JOGGING_INTERVAL = 60; self.animationJogger = function () { // There are some cases where the animation timer doesn't work: for instance when // this isn't running in a visible tab and sometimes on mobile devices. We compensate // by having a backup timer using setTimeout. This won't be accurate so the performance // will be jerky, but without it the requestAnimationFrame might be skipped and so // not called again. if (self.isRunning) { self.doTiming(performance.now()); self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL); } }; self.start = function (offsetPercent, units) { self.isRunning = true; if (self.isPaused) { self.isPaused = false; if (offsetPercent === undefined) self.justUnpaused = true; } if (offsetPercent) { self.setProgress(offsetPercent, units); } else if (offsetPercent === 0) { self.reset(); } else if (self.pausedPercent !== null) { var now = performance.now(); self.currentTime = self.lastMoment * self.pausedPercent; self.startTime = now - self.currentTime; self.pausedPercent = null; self.reportNext = true; } requestAnimationFrame(self.doTiming); self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL); }; self.pause = function () { self.isPaused = true; var now = performance.now(); self.pausedPercent = (now - self.startTime) / self.lastMoment; self.isRunning = false; if (self.joggerTimer) { clearTimeout(self.joggerTimer); self.joggerTimer = null; } }; self.currentMillisecond = function () { return self.currentTime; }; self.reset = function () { self.currentBeat = 0; self.currentEvent = 0; self.currentLine = 0; self.startTime = null; self.pausedPercent = null; }; self.stop = function () { self.pause(); self.reset(); }; self.setProgress = function (position, units) { // the effect of this function is to move startTime so that the callbacks happen correctly for the new seek. var percent; switch (units) { case "seconds": self.currentTime = position * 1000; if (self.currentTime < 0) self.currentTime = 0; if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment; percent = self.currentTime / self.lastMoment; break; case "beats": self.currentTime = position * self.millisecondsPerBeat * self.beatSubdivisions; if (self.currentTime < 0) self.currentTime = 0; if (self.currentTime > self.lastMoment) self.currentTime = self.lastMoment; percent = self.currentTime / self.lastMoment; break; default: // this is "percent" or any illegal value // this is passed a value between 0 and 1. percent = position; if (percent < 0) percent = 0; if (percent > 1) percent = 1; self.currentTime = self.lastMoment * percent; break; } if (!self.isRunning) self.pausedPercent = percent; var now = performance.now(); self.startTime = now - self.currentTime; var oldEvent = self.currentEvent; self.currentEvent = 0; while (self.noteTimings.length > self.currentEvent && self.noteTimings[self.currentEvent].milliseconds < self.currentTime) { self.currentEvent++; } if (self.lineEndCallback) { self.currentLine = 0; while (self.lineEndTimings.length > self.currentLine && self.lineEndTimings[self.currentLine].milliseconds + self.lineEndAnticipation < self.currentTime) { self.currentLine++; } } var oldBeat = self.currentBeat; self.currentBeat = Math.floor(self.currentTime / self.millisecondsPerBeat); if (self.beatCallback && oldBeat !== self.currentBeat) // If the movement caused the beat to change, then immediately report it to the client. self.doBeatCallback(self.startTime + self.currentTime); if (self.eventCallback && self.currentEvent >= 0 && self.noteTimings[self.currentEvent].type === 'event') self.eventCallback(self.noteTimings[self.currentEvent]); if (self.lineEndCallback) self.lineEndCallback(self.lineEndTimings[self.currentLine], self.noteTimings[self.currentEvent], { line: self.currentLine, endTimings: self.lineEndTimings }); self.joggerTimer = setTimeout(self.animationJogger, JOGGING_INTERVAL); }; }; function getLineEndTimings(timings, anticipation) { // Returns an array of milliseconds to call the lineEndCallback. // This figures out the timing of the beginning of each line and subtracts the anticipation from it. var callbackTimes = []; var lastTop = null; for (var i = 0; i < timings.length; i++) { var timing = timings[i]; if (timing.type !== 'end' && timing.top !== lastTop) { callbackTimes.push({ measureNumber: timing.measureNumber, milliseconds: timing.milliseconds - anticipation, top: timing.top, bottom: timing.top + timing.height }); lastTop = timing.top; } } return callbackTimes; } module.exports = TimingCallbacks; /***/ }), /***/ "./src/api/abc_tunebook.js": /*!*********************************!*\ !*** ./src/api/abc_tunebook.js ***! \*********************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } // abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes. var Parse = __webpack_require__(/*! ../parse/abc_parse */ "./src/parse/abc_parse.js"); var bookParser = __webpack_require__(/*! ../parse/abc_parse_book */ "./src/parse/abc_parse_book.js"); var tablatures = __webpack_require__(/*! ./abc_tablatures */ "./src/api/abc_tablatures.js"); var tunebook = {}; (function () { "use strict"; tunebook.numberOfTunes = function (abc) { var tunes = abc.split("\nX:"); var num = tunes.length; if (num === 0) num = 1; return num; }; var TuneBook = tunebook.TuneBook = function (book) { var parsed = bookParser(book); this.header = parsed.header; this.tunes = parsed.tunes; }; TuneBook.prototype.getTuneById = function (id) { for (var i = 0; i < this.tunes.length; i++) { if (this.tunes[i].id === '' + id) return this.tunes[i]; } return null; }; TuneBook.prototype.getTuneByTitle = function (title) { for (var i = 0; i < this.tunes.length; i++) { if (this.tunes[i].title === title) return this.tunes[i]; } return null; }; tunebook.parseOnly = function (abc, params) { var numTunes = tunebook.numberOfTunes(abc); // this just needs to be passed in because this tells the engine how many tunes to process. var output = []; for (var i = 0; i < numTunes; i++) { output.push(1); } function callback() { // Don't need to do anything with the parsed tunes. } return tunebook.renderEngine(callback, output, abc, params); }; tunebook.renderEngine = function (callback, output, abc, params) { var ret = []; var isArray = function isArray(testObject) { return testObject && !testObject.propertyIsEnumerable('length') && _typeof(testObject) === 'object' && typeof testObject.length === 'number'; }; // check and normalize input parameters if (output === undefined || abc === undefined) return; if (!isArray(output)) output = [output]; if (params === undefined) params = {}; var currentTune = params.startingTune ? parseInt(params.startingTune, 10) : 0; // parse the abc string var book = new TuneBook(abc); var abcParser = new Parse(); // output each tune, if it exists. Otherwise clear the div. for (var i = 0; i < output.length; i++) { var div = output[i]; if (div === "*") { // This is for "headless" rendering: doing the work but not showing the svg. } else if (typeof div === "string") div = document.getElementById(div); if (div) { if (currentTune >= 0 && currentTune < book.tunes.length) { abcParser.parse(book.tunes[currentTune].abc, params, book.tunes[currentTune].startPos - book.header.length); var tune = abcParser.getTune(); // // Init tablatures plugins // if (params.tablature) { tablatures.init(); tune.tablatures = tablatures.preparePlugins(tune, currentTune, params); } var warnings = abcParser.getWarnings(); if (warnings) tune.warnings = warnings; var override = callback(div, tune, i, book.tunes[currentTune].abc); ret.push(override ? override : tune); } else { if (div['innerHTML']) div.innerHTML = ""; } } currentTune++; } return ret; }; function flattenTune(tuneObj) { // This removes the line breaks and removes the non-music lines. var staves = []; for (var j = 0; j < tuneObj.lines.length; j++) { var line = tuneObj.lines[j]; if (line.staff) { for (var k = 0; k < line.staff.length; k++) { var staff = line.staff[k]; if (!staves[k]) staves[k] = staff;else { for (var i = 0; i < staff.voices.length; i++) { if (staves[k].voices[i]) staves[k].voices[i] = staves[k].voices[i].concat(staff.voices[i]); // TODO-PER: If staves[k].voices[i] doesn't exist, that means a voice appeared in the middle of the tune. That isn't handled yet. } } } } } return staves; } function measuresParser(staff, tune) { var voices = []; var lastChord = null; var measureStartChord = null; var fragStart = null; var hasNotes = false; for (var i = 0; i < staff.voices.length; i++) { var voice = staff.voices[i]; voices.push([]); for (var j = 0; j < voice.length; j++) { var elem = voice[j]; if (fragStart === null && elem.startChar >= 0) { fragStart = elem.startChar; if (elem.chord === undefined) measureStartChord = lastChord;else measureStartChord = null; } if (elem.chord) lastChord = elem; if (elem.el_type === 'bar') { if (hasNotes) { var frag = tune.abc.substring(fragStart, elem.endChar); var measure = { abc: frag }; lastChord = measureStartChord && measureStartChord.chord && measureStartChord.chord.length > 0 ? measureStartChord.chord[0].name : null; if (lastChord) measure.lastChord = lastChord; if (elem.startEnding) measure.startEnding = elem.startEnding; if (elem.endEnding) measure.endEnding = elem.endEnding; voices[i].push(measure); fragStart = null; hasNotes = false; } } else if (elem.el_type === 'note') { hasNotes = true; } } } return voices; } tunebook.extractMeasures = function (abc) { var tunes = []; var book = new TuneBook(abc); for (var i = 0; i < book.tunes.length; i++) { var tune = book.tunes[i]; var arr = tune.abc.split("K:"); var arr2 = arr[1].split("\n"); var header = arr[0] + "K:" + arr2[0] + "\n"; var lastChord = null; var measureStartChord = null; var fragStart = null; var measures = []; var hasNotes = false; var tuneObj = tunebook.parseOnly(tune.abc)[0]; var hasPickup = tuneObj.getPickupLength() > 0; // var staves = flattenTune(tuneObj); // for (var s = 0; s < staves.length; s++) { // var voices = measuresParser(staves[s], tune); // if (s === 0) // measures = voices; // else { // for (var ss = 0; ss < voices.length; ss++) { // var voice = voices[ss]; // if (measures.length <= ss) // measures.push([]); // var measureVoice = measures[ss]; // for (var sss = 0; sss < voice.length; sss++) { // if (measureVoice.length > sss) // measureVoice[sss].abc += "\n" + voice[sss].abc; // else // measures.push(voice[sss]); // } // } // } // console.log(voices); // } // measures = measures[0]; for (var j = 0; j < tuneObj.lines.length; j++) { var line = tuneObj.lines[j]; if (line.staff) { for (var k = 0; k < 1 /*line.staff.length*/; k++) { var staff = line.staff[k]; for (var kk = 0; kk < 1 /*staff.voices.length*/; kk++) { var voice = staff.voices[kk]; for (var kkk = 0; kkk < voice.length; kkk++) { var elem = voice[kkk]; if (fragStart === null && elem.startChar >= 0) { fragStart = elem.startChar; if (elem.chord === undefined) measureStartChord = lastChord;else measureStartChord = null; } if (elem.chord) lastChord = elem; if (elem.el_type === 'bar') { if (hasNotes) { var frag = tune.abc.substring(fragStart, elem.endChar); var measure = { abc: frag }; lastChord = measureStartChord && measureStartChord.chord && measureStartChord.chord.length > 0 ? measureStartChord.chord[0].name : null; if (lastChord) measure.lastChord = lastChord; if (elem.startEnding) measure.startEnding = elem.startEnding; if (elem.endEnding) measure.endEnding = elem.endEnding; measures.push(measure); fragStart = null; hasNotes = false; } } else if (elem.el_type === 'note') { hasNotes = true; } } } } } } tunes.push({ header: header, measures: measures, hasPickup: hasPickup }); } return tunes; }; })(); module.exports = tunebook; /***/ }), /***/ "./src/api/abc_tunebook_svg.js": /*!*************************************!*\ !*** ./src/api/abc_tunebook_svg.js ***! \*************************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { var tunebook = __webpack_require__(/*! ./abc_tunebook */ "./src/api/abc_tunebook.js"); var Tune = __webpack_require__(/*! ../data/abc_tune */ "./src/data/abc_tune.js"); var EngraverController = __webpack_require__(/*! ../write/engraver-controller */ "./src/write/engraver-controller.js"); var Parse = __webpack_require__(/*! ../parse/abc_parse */ "./src/parse/abc_parse.js"); var wrap = __webpack_require__(/*! ../parse/wrap_lines */ "./src/parse/wrap_lines.js"); // var tablatures = require('./abc_tablatures'); var resizeDivs = {}; function resizeOuter() { var width = window.innerWidth; for (var id in resizeDivs) { if (resizeDivs.hasOwnProperty(id)) { var outer = resizeDivs[id]; var ofs = outer.offsetLeft; width -= ofs * 2; outer.style.width = width + "px"; } } } try { window.addEventListener("resize", resizeOuter); window.addEventListener("orientationChange", resizeOuter); } catch (e) { // if we aren't in a browser, this code will crash, but it is not needed then either. } function renderOne(div, tune, params, tuneNumber, lineOffset) { if (params.viewportHorizontal) { // Create an inner div that holds the music, so that the passed in div will be the viewport. div.innerHTML = '<div class="abcjs-inner"></div>'; if (params.scrollHorizontal) { div.style.overflowX = "auto"; div.style.overflowY = "hidden"; } else div.style.overflow = "hidden"; resizeDivs[div.id] = div; // We use a hash on the element's id so that multiple calls won't keep adding to the list. div = div.children[0]; // The music should be rendered in the inner div. } else if (params.viewportVertical) { // Create an inner div that holds the music, so that the passed in div will be the viewport. div.innerHTML = '<div class="abcjs-inner scroll-amount"></div>'; div.style.overflowX = "hidden"; div.style.overflowY = "auto"; div = div.children[0]; // The music should be rendered in the inner div. } else div.innerHTML = ""; var engraver_controller = new EngraverController(div, params); engraver_controller.engraveABC(tune, tuneNumber, lineOffset); tune.engraver = engraver_controller; if (params.viewportVertical || params.viewportHorizontal) { // If we added a wrapper around the div, then we need to size the wrapper, too. var parent = div.parentNode; parent.style.width = div.style.width; } } // A quick way to render a tune from javascript when interactivity is not required. // This is used when a javascript routine has some abc text that it wants to render // in a div or collection of divs. One tune or many can be rendered. // // parameters: // output: an array of divs that the individual tunes are rendered to. // If the number of tunes exceeds the number of divs in the array, then // only the first tunes are rendered. If the number of divs exceeds the number // of tunes, then the unused divs are cleared. The divs can be passed as either // elements or strings of ids. If ids are passed, then the div MUST exist already. // (if a single element is passed, then it is an implied array of length one.) // (if a null is passed for an element, or the element doesn't exist, then that tune is skipped.) // abc: text representing a tune or an entire tune book in ABC notation. // renderParams: hash of: // startingTune: an index, starting at zero, representing which tune to start rendering at. // (If this element is not present, then rendering starts at zero.) // width: 800 by default. The width in pixels of the output paper var renderAbc = function renderAbc(output, abc, parserParams, engraverParams, renderParams) { // Note: all parameters have been condensed into the first ones. It doesn't hurt anything to allow the old format, so just copy them here. var params = {}; var key; if (parserParams) { for (key in parserParams) { if (parserParams.hasOwnProperty(key)) { params[key] = parserParams[key]; } } if (params.warnings_id && params.tablature) { params.tablature.warning_id = params.warnings_id; } } if (engraverParams) { for (key in engraverParams) { if (engraverParams.hasOwnProperty(key)) { // There is a conflict with the name of the parameter "listener". If it is in the second parameter, then it is for click. if (key === "listener") { if (engraverParams[key].highlight) params.clickListener = engraverParams[key].highlight; } else params[key] = engraverParams[key]; } } } if (renderParams) { for (key in renderParams) { if (renderParams.hasOwnProperty(key)) { params[key] = renderParams[key]; } } } function callback(div, tune, tuneNumber, abcString) { var removeDiv = false; if (div === "*") { removeDiv = true; div = document.createElement("div"); div.setAttribute("style", "visibility: hidden;"); document.body.appendChild(div); } if (params.afterParsing) params.afterParsing(tune, tuneNumber, abcString); if (!removeDiv && params.wrap && params.staffwidth) { tune = doLineWrapping(div, tune, tuneNumber, abcString, params); return tune; } renderOne(div, tune, params, tuneNumber, 0); if (removeDiv) div.parentNode.removeChild(div); return null; } return tunebook.renderEngine(callback, output, abc, params); }; function doLineWrapping(div, tune, tuneNumber, abcString, params) { var engraver_controller = new EngraverController(div, params); var widths = engraver_controller.getMeasureWidths(tune); var ret = wrap.calcLineWraps(tune, widths, params); if (ret.reParse) { var abcParser = new Parse(); abcParser.parse(abcString, ret.revisedParams); tune = abcParser.getTune(); var warnings = abcParser.getWarnings(); if (warnings) tune.warnings = warnings; } renderOne(div, tune, ret.revisedParams, tuneNumber, 0); tune.explanation = ret.explanation; return tune; } module.exports = renderAbc; /***/ }), /***/ "./src/const/key-accidentals.js": /*!**************************************!*\ !*** ./src/const/key-accidentals.js ***! \**************************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { var _require = __webpack_require__(/*! ./relative-major */ "./src/const/relative-major.js"), relativeMajor = _require.relativeMajor; var key1sharp = { acc: 'sharp', note: 'f' }; var key2sharp = { acc: 'sharp', note: 'c' }; var key3sharp = { acc: 'sharp', note: 'g' }; var key4sharp = { acc: 'sharp', note: 'd' }; var key5sharp = { acc: 'sharp', note: 'A' }; var key6sharp = { acc: 'sharp', note: 'e' }; var key7sharp = { acc: 'sharp', note: 'B' }; var key1flat = { acc: 'flat', note: 'B' }; var key2flat = { acc: 'flat', note: 'e' }; var key3flat = { acc: 'flat', note: 'A' }; var key4flat = { acc: 'flat', note: 'd' }; var key5flat = { acc: 'flat', note: 'G' }; var key6flat = { acc: 'flat', note: 'c' }; var key7flat = { acc: 'flat', note: 'F' }; var keys = { 'C#': [key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp], 'F#': [key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp], 'B': [key1sharp, key2sharp, key3sharp, key4sharp, key5sharp], 'E': [key1sharp, key2sharp, key3sharp, key4sharp], 'A': [key1sharp, key2sharp, key3sharp], 'D': [key1sharp, key2sharp], 'G': [key1sharp], 'C': [], 'F': [key1flat], 'Bb': [key1flat, key2flat], 'Eb': [key1flat, key2flat, key3flat], 'Cm': [key1flat, key2flat, key3flat], 'Ab': [key1flat, key2flat, key3flat, key4flat], 'Db': [key1flat, key2flat, key3flat, key4flat, key5flat], 'Gb': [key1flat, key2flat, key3flat, key4flat, key5flat, key6flat], 'Cb': [key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat], // The following are not in the 2.0 spec, but seem normal enough. // TODO-PER: These SOUND the same as what's written, but they aren't right 'A#': [key1flat, key2flat], 'B#': [], 'D#': [key1flat, key2flat, key3flat], 'E#': [key1flat], 'G#': [key1flat, key2flat, key3flat, key4flat], 'none': [] }; function keyAccidentals(key) { var newKey = keys[relativeMajor(key)]; if (!newKey) // If we don't recognize the key then there is no change return null; return JSON.parse(JSON.stringify(newKey)); } ; module.exports = keyAccidentals; /***/ }), /***/ "./src/const/relative-major.js": /*!*************************************!*\ !*** ./src/const/relative-major.js ***! \*************************************/ /***/ (function(module) { // All these keys have the same number of accidentals var keys = { 'C': { modes: ['CMaj', 'Amin', 'Am', 'GMix', 'DDor', 'EPhr', 'FLyd', 'BLoc'], stepsFromC: 0 }, 'Db': { modes: ['DbMaj', 'Bbmin', 'Bbm', 'AbMix', 'EbDor', 'FPhr', 'GbLyd', 'CLoc'], stepsFromC: 1 }, 'D': { modes: ['DMaj', 'Bmin', 'Bm', 'AMix', 'EDor', 'F#Phr', 'GLyd', 'C#Loc'], stepsFromC: 2 }, 'Eb': { modes: ['EbMaj', 'Cmin', 'Cm', 'BbMix', 'FDor', 'GPhr', 'AbLyd', 'DLoc'], stepsFromC: 3 }, 'E': { modes: ['EMaj', 'C#min', 'C#m', 'BMix', 'F#Dor', 'G#Phr', 'ALyd', 'D#Loc'], stepsFromC: 4 }, 'F': { modes: ['FMaj', 'Dmin', 'Dm', 'CMix', 'GDor', 'APhr', 'BbLyd', 'ELoc'], stepsFromC: 5 }, 'Gb': { modes: ['GbMaj', 'Ebmin', 'Ebm', 'DbMix', 'AbDor', 'BbPhr', 'CbLyd', 'FLoc'], stepsFromC: 6 }, 'G': { modes: ['GMaj', 'Emin', 'Em', 'DMix', 'ADor', 'BPhr', 'CLyd', 'F#Loc'], stepsFromC: 7 }, 'Ab': { modes: ['AbMaj', 'Fmin', 'Fm', 'EbMix', 'BbDor', 'CPhr', 'DbLyd', 'GLoc'], stepsFromC: 8 }, 'A': { modes: ['AMaj', 'F#min', 'F#m', 'EMix', 'BDor', 'C#Phr', 'DLyd', 'G#Loc'], stepsFromC: 9 }, 'Bb': { modes: ['BbMaj', 'Gmin', 'Gm', 'FMix', 'CDor', 'DPhr', 'EbLyd', 'ALoc'], stepsFromC: 10 }, 'B': { modes: ['BMaj', 'G#min', 'G#m', 'F#Mix', 'C#Dor', 'D#Phr', 'ELyd', 'A#Loc'], stepsFromC: 11 }, // Enharmonic keys 'C#': { modes: ['C#Maj', 'A#min', 'A#m', 'G#Mix', 'D#Dor', 'E#Phr', 'F#Lyd', 'B#Loc'], stepsFromC: 1 }, 'F#': { modes: ['F#Maj', 'D#min', 'D#m', 'C#Mix', 'G#Dor', 'A#Phr', 'BLyd', 'E#Loc'], stepsFromC: 6 }, 'Cb': { modes: ['CbMaj', 'Abmin', 'Abm', 'GbMix', 'DbDor', 'EbPhr', 'FbLyd', 'BbLoc'], stepsFromC: 11 } }; var keyReverse = null; function createKeyReverse() { keyReverse = {}; var allKeys = Object.keys(keys); for (var i = 0; i < allKeys.length; i++) { var keyObj = keys[allKeys[i]]; keyReverse[allKeys[i].toLowerCase()] = allKeys[i]; for (var j = 0; j < keyObj.modes.length; j++) { var mode = keyObj.modes[j].toLowerCase(); keyReverse[mode] = allKeys[i]; } } } function relativeMajor(key) { // Translate a key to its relative major. If it doesn't exist, do the best we can // by just returning the original key. // There are alternate spellings of these - so the search needs to be case insensitive. // To make this efficient, the first time this is called the "keys" object is reversed so this search is fast in the future if (!keyReverse) { createKeyReverse(); } // get the key portion itself - there might be other stuff, like extra sharps and flats, or the mode written out. var mode = key.toLowerCase().match(/([a-g][b#]?)(maj|min|mix|dor|phr|lyd|loc|m)?/); if (!mode || !mode[2]) return key; mode = mode[1] + mode[2]; var maj = keyReverse[mode]; if (maj) return maj; return key; } function relativeMode(majorKey, mode) { // The reverse of the relativeMajor. Translate it back to the original mode. // If it isn't a recognized mode or it is already major, then just return the major key. var group = keys[majorKey]; if (!group) return majorKey; if (mode === '') return majorKey; var match = mode.toLowerCase().match(/^(maj|min|mix|dor|phr|lyd|loc|m)/); if (!match) return majorKey; var regMode = match[1]; for (var i = 0; i < group.modes.length; i++) { var thisMode = group.modes[i]; var ind = thisMode.toLowerCase().indexOf(regMode); if (ind !== -1 && ind === thisMode.length - regMode.length) return thisMode.substring(0, thisMode.length - regMode.length); } return majorKey; } function transposeKey(key, steps) { // This takes a major key and adds the desired steps. // It assigns each key a number that is the number of steps from C so that there can just be arithmetic. var match = keys[key]; if (!match) return key; while (steps < 0) { steps += 12; } var fromC = (match.stepsFromC + steps) % 12; for (var i = 0; i < Object.keys(keys).length; i++) { var k = Object.keys(keys)[i]; if (keys[k].stepsFromC === fromC) return k; } return key; } module.exports = { relativeMajor: relativeMajor, relativeMode: relativeMode, transposeKey: transposeKey }; /***/ }), /***/ "./src/data/abc_tune.js": /*!******************************!*\ !*** ./src/data/abc_tune.js ***! \******************************/ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { // abc_tune.js: a computer usable internal structure representing one tune. var parseCommon = __webpack_require__(/*! ../parse/abc_common */ "./src/parse/abc_common.js"); var spacing = __webpack_require__(/*! ../write/helpers/spacing */ "./src/write/helpers/spacing.js"); var sequence = __webpack_require__(/*! ../synth/abc_midi_sequencer */ "./src/synth/abc_midi_sequencer.js"); var flatten = __webpack_require__(/*! ../synth/abc_midi_flattener */ "./src/synth/abc_midi_flattener.js"); var delineTune = __webpack_require__(/*! ./deline-tune */ "./src/data/deline-tune.js"); /** * This is the data for a single ABC tune. It is created and populated by the window.ABCJS.parse.Parse class. * Also known as the ABCJS Abstract Syntax Tree * @alternateClassName ABCJS.Tune */ var Tune = function Tune() { this.reset = function () { this.version = "1.1.0"; this.media = "screen"; this.metaText = {}; this.metaTextInfo = {}; this.formatting = {}; this.lines = []; this.staffNum = 0; this.voiceNum = 0; this.lineNum = 0; thi