diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..6b07a1c2fe70a348a59c7abd935190b98ff2f836 --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "@babel/preset-env" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..33cfa6c4ecdfd3cc741d657c4e3213d439e49303 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.idea/* +tmp/* +/node_modules +/coverage +to-*.sh +docs/.vuepress/.cache/* +docs/.vuepress/.temp/* +docs/.vuepress/.dist/* +dist/report.html +.eslintrc.json +.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..1465b9d9feff6f76e598fd625ce27f39a0374bc7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/.vuepress/dist"] + path = docs/.vuepress/dist + url = https://github.com/paulrosen/abcjs.git diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000000000000000000000000000000000..324f0a544b61d60a0f462c172690b4476eb05165 --- /dev/null +++ b/.npmignore @@ -0,0 +1,20 @@ +.idea/ +dist/*.html +build-utils/ +docs/ +examples/ +font_generator/ +tests/ +tmp/ + +.babelrc +.eslintrc +.gitmodules +deploy-docs.sh +docker-start.sh +docker-build.sh +docker-compose.yml +Dockerfile +start-version.sh +webpack.config.js +to-*.sh diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..ba38352b773ef9928394a81473383f5378529925 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at blog@paulrosen.net. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..2b6ad1244bcd246469b531b648ed0baa8d6ca8dd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +Information about contributions to abcjs is available here: [Developers](https://paulrosen.github.io/abcjs/developers/pull-requests.html) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a7754c5e5542aebbedb62d7d23846297da5d8b50 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:20.14.0 + +RUN npm install -g npm@8.19.2 + +RUN mkdir /srv/app && chown node:node /srv/app + +# USER node + +WORKDIR /srv/app diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..ad3b93938cfa345f4464caa5606495b5a5693a96 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +Copyright (c) 2009-2024 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** diff --git a/README0.md b/README0.md new file mode 100644 index 0000000000000000000000000000000000000000..4014f09eca880a8d15489d150d530448f6fa8091 --- /dev/null +++ b/README0.md @@ -0,0 +1,127 @@ +![abcjs](https://paulrosen.github.io/abcjs/img/abcjs_comp_extended_08.svg) + +# Javascript library for rendering standard music notation in a browser. + +This library makes it easy to incorporate **sheet music** into your **websites**. You can also turn visible **ABC** text into sheet music on websites that you don't own using a greasemonkey script, or change your own website that contains ABC text with no other changes than the addition of one javascript file. You can also generate **MIDI files** or play them directly in your browser. + +[List of Examples](https://cdn.rawgit.com/paulrosen/abcjs/main/examples/toc.html) + +Full documentation is here: [abcjs documentation](https://paulrosen.github.io/abcjs/) + +There is an organization that has a collection of useful projects related to abcjs called [abcjs-music](https://github.com/abcjs-music). See some examples there. If you have a project that you think would be of general interest and would like to add it to that organization, contact me. + +## Announcement: version 6.4.0 + +The method for creating chord accompaniment has changed. Hopefully there aren't any regressions but if you have an example that doesn't match what it used to be, please open an issue. + +## Announcement: version 6.3.0 + +Most users won't see any difference, but there was some changes to the underlying SVG structure for the top (the title, etc.) and bottom (the notes, etc.) text, so if your code depends on particular classes then this might be a breaking change. Hopefully the new structure will be clearer and will allow css to target particular parts easier. + +## Announcement: version 6.2.2 + +There was a major bug in Firefox 112 that causes some lines to disappear. That is supposed to be fixed in Firefox 113 but in the meantime this update will take care of the problem. +## Announcement: version 6.1.2 + +There is a little difference in the generated SVG: Now each line is surrounded with a `` element. This probably won't affect your program unless you are doing very specific manipulation of the SVG. + +## Announcement: version 6.1.0 + +There is a brand new transposing feature. This allows you to input an ABC string and get a new ABC back in a different key. See the documentation for details. I'm sure there are ways to expand this - let me know if your use case is missing. + +## Announcement: version 6.0.0 + +After way too long, abcjs 6.0.0 is now out of beta. + +There are only a few bug fixes since the last beta. + +The current plan is to continue doing bug fixes and minor features to the 6.x.x branch, but version 7 is already in planning. + +There are some minor features that will still be added to the version 6 branch, along with fixing transposition. + +## Informal roadmap for version 7.0.0 + +There will be some architecture changes which will go in a 7.0.0 release: + +* Improve the API for controlling the synth. That is, make the parameters less confusing. +* Create a plugin architecture so that large features that are not widely used can be added without bloating this library. +* Change the build to use typescript while still maintaining legacy browser capability. +* Reduce the size of the library by optimizing code. +* Control the spacing of the elements on the line better: support equal size measures, and support allowing control of the spacing between notes. +* Improved documentation, particularly for synth. +* Bug fixes. + +I anticipate that for a user that wants the most common functionality, the size of the library might be cut in half. + +Thanks, Paul + + +## Major New Feature! 6.0.0-beta.36 + +String tablature is now available by adding an option to the `renderAbc` parameters. See [tablature documentation](https://paulrosen.github.io/abcjs/visual/tablature.html) + +## Fix to audio in octave clefs 6.0.0-beta.31 + +If you are using an octave clef (for instance `K:C clef=treble-8`) it will now sound an octave different. The octave calculation was happening twice. + +## Last version supporting midi.js is 6.0.0-beta.28 + +The file [abcjs version supporting midi.js](https://github.com/paulrosen/historical-abcjs-versions/blob/main/version-6/abcjs_midi-min.js) is the last version of the old style of sound production that will receive updates. + +## Rename the default branch to `main` + +The default branch is now named `main`. If you have cloned this repo previously, you can change it with: +```shell +git branch -m master main +git fetch origin +git branch -u origin/main main +``` + +## New css for audio control 6.0.0-beta.27 + +Be sure to download the latest abcjs-audio.css file if you are using it. If you are styling the audio control with your own css, add the rule: +```css +.abcjs-css-warning { + display: none; +} +``` + +## Historical abcjs Packages 6.0.0-beta.27 + +Old versions of abcjs have been removed from this repo. If you are looking for them, you can find them here: [Historical Versions](https://github.com/paulrosen/historical-abcjs-versions) + +## Breaking change when using midi.js for 6.0.0-beta.26 + +The midi.js package is no longer a direct dependency, it is now a peerDependency so it is not included by default. That way, users who aren't using the old style of sound generation won't need to load the package. If you are using the old style that uses midi.js, include this line in your `package.json` file: + +``` +"midi": "https://github.com/paulrosen/MIDI.js.git#abcjs" +``` +Note that if you are using the minified version of the library with a ` + + diff --git a/docs/.vuepress/components/CheckBox.vue b/docs/.vuepress/components/CheckBox.vue new file mode 100644 index 0000000000000000000000000000000000000000..d271704083c001d281310fedf285372345cd4823 --- /dev/null +++ b/docs/.vuepress/components/CheckBox.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/docs/.vuepress/components/ExampleTuneBook.vue b/docs/.vuepress/components/ExampleTuneBook.vue new file mode 100644 index 0000000000000000000000000000000000000000..3ae30e277914bf0be3e8974e91988f4f0b09ebed --- /dev/null +++ b/docs/.vuepress/components/ExampleTuneBook.vue @@ -0,0 +1,199 @@ + + + diff --git a/docs/.vuepress/components/FieldSet.vue b/docs/.vuepress/components/FieldSet.vue new file mode 100644 index 0000000000000000000000000000000000000000..679a33c274636b5f8693882c309fb32389019653 --- /dev/null +++ b/docs/.vuepress/components/FieldSet.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/docs/.vuepress/components/FoundClasses.vue b/docs/.vuepress/components/FoundClasses.vue new file mode 100644 index 0000000000000000000000000000000000000000..09bd461f3086216e7d6e96d30190df1bb13f0872 --- /dev/null +++ b/docs/.vuepress/components/FoundClasses.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/docs/.vuepress/components/NumTunes.vue b/docs/.vuepress/components/NumTunes.vue new file mode 100644 index 0000000000000000000000000000000000000000..447e1a55a569148d5e4a726a35d260df16f1ec1a --- /dev/null +++ b/docs/.vuepress/components/NumTunes.vue @@ -0,0 +1,27 @@ + + + diff --git a/docs/.vuepress/components/RadioGroup.vue b/docs/.vuepress/components/RadioGroup.vue new file mode 100644 index 0000000000000000000000000000000000000000..b0649642ae21155fd902813ee55917d089cee68b --- /dev/null +++ b/docs/.vuepress/components/RadioGroup.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/docs/.vuepress/components/RenderAbc.vue b/docs/.vuepress/components/RenderAbc.vue new file mode 100644 index 0000000000000000000000000000000000000000..ed004c123edaaaee06a99cac8950df959260722d --- /dev/null +++ b/docs/.vuepress/components/RenderAbc.vue @@ -0,0 +1,39 @@ + + + diff --git a/docs/.vuepress/components/RenderAudio.vue b/docs/.vuepress/components/RenderAudio.vue new file mode 100644 index 0000000000000000000000000000000000000000..d9656bd12ecf738d5efeb251bb6271914c52737f --- /dev/null +++ b/docs/.vuepress/components/RenderAudio.vue @@ -0,0 +1,40 @@ + + + diff --git a/docs/.vuepress/components/SandboxCode.vue b/docs/.vuepress/components/SandboxCode.vue new file mode 100644 index 0000000000000000000000000000000000000000..e4fc0586cbd229a58e25636ce379b91b18c7e4f9 --- /dev/null +++ b/docs/.vuepress/components/SandboxCode.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/docs/.vuepress/components/SandboxContainer.vue b/docs/.vuepress/components/SandboxContainer.vue new file mode 100644 index 0000000000000000000000000000000000000000..f65c92d53971d22d93ec9036d60ab982c2d2caf3 --- /dev/null +++ b/docs/.vuepress/components/SandboxContainer.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/docs/.vuepress/components/SandboxInput.vue b/docs/.vuepress/components/SandboxInput.vue new file mode 100644 index 0000000000000000000000000000000000000000..229170f1879a8b31be9048b321b5e7be1196cf86 --- /dev/null +++ b/docs/.vuepress/components/SandboxInput.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/docs/.vuepress/components/SandboxOutput.vue b/docs/.vuepress/components/SandboxOutput.vue new file mode 100644 index 0000000000000000000000000000000000000000..41f443900a4459a48c463ec6b9869b2f7365cda5 --- /dev/null +++ b/docs/.vuepress/components/SandboxOutput.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/docs/.vuepress/components/ShowAndRenderAbc.vue b/docs/.vuepress/components/ShowAndRenderAbc.vue new file mode 100644 index 0000000000000000000000000000000000000000..42a5b645408b540687069c2b958bbf91f6485a5d --- /dev/null +++ b/docs/.vuepress/components/ShowAndRenderAbc.vue @@ -0,0 +1,42 @@ + + + diff --git a/docs/.vuepress/components/TimingCallbacks.vue b/docs/.vuepress/components/TimingCallbacks.vue new file mode 100644 index 0000000000000000000000000000000000000000..5b7a461263719ef05a3fbaa4231ec7baa22ce67a --- /dev/null +++ b/docs/.vuepress/components/TimingCallbacks.vue @@ -0,0 +1,189 @@ + + + + + + + + diff --git a/docs/.vuepress/components/TuneBookInfo.vue b/docs/.vuepress/components/TuneBookInfo.vue new file mode 100644 index 0000000000000000000000000000000000000000..917864776202ebc8038ad4d4364f9d87aec6d479 --- /dev/null +++ b/docs/.vuepress/components/TuneBookInfo.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/docs/.vuepress/components/example-strings-css.js b/docs/.vuepress/components/example-strings-css.js new file mode 100644 index 0000000000000000000000000000000000000000..0d8f8c3dda19d63eb268d2764d5cffbd4ea7338b --- /dev/null +++ b/docs/.vuepress/components/example-strings-css.js @@ -0,0 +1,55 @@ + +export const cssString = (hasAudio, hasCursor, hideMeasures, allowDragging, usingCallbacks) => { + let css = []; + if (hasAudio) + css.push(``); + if (hasCursor) + css.push(` +`); + if (hideMeasures) { + css.push(` +`) + } + if (allowDragging) { + css.push(` +`) + } + if (usingCallbacks) { + css.push(` +`) +} + return css.join("\n"); +}; diff --git a/docs/.vuepress/components/example-strings-js-audio.js b/docs/.vuepress/components/example-strings-js-audio.js new file mode 100644 index 0000000000000000000000000000000000000000..67d3ceef8d1e4faa1afd87d20d98bbe8b838ab9e --- /dev/null +++ b/docs/.vuepress/components/example-strings-js-audio.js @@ -0,0 +1,208 @@ +export const soundJsString = ((usingNode, getters) => { + if (!getters.hasSound) + return ""; + const abcjs = usingNode ? "abcjs" : "ABCJS"; + + let output = `document.querySelector(".activate-audio").addEventListener("click", activate); +function activate() { + if (${abcjs}.synth.supportsAudio()) { +`; + + if (getters.playbackWidget) { + let controlOptions = [] + if (getters.loop) controlOptions.push("\t\t\tdisplayLoop: true"); + if (getters.restart) controlOptions.push("\t\t\tdisplayRestart: true"); + if (getters.play) controlOptions.push("\t\t\tdisplayPlay: true"); + if (getters.progress) controlOptions.push("\t\t\tdisplayProgress: true"); + if (getters.warp) controlOptions.push("\t\t\tdisplayWarp: true"); + if (getters.clock) controlOptions.push("\t\t\tdisplayClock: true"); + output += `\t\tvar controlOptions = {\n${controlOptions.join(",\n")}\n\t\t};\n` + } + let options = []; + let optionsOptions = []; + if (getters.tempo) options.push("millisecondsPerMeasure: 800"); + if (getters.stereo) optionsOptions.push("pan: [-.5,.5]"); +// if (getters.instrument) options.push("instrument"); +// if (getters.transpose) options.push("transpose"); + if (getters.noChords) options.push("chordsOff: true"); + if (getters.noVoice) options.push("voicesOff: true"); +// if (getters.tweak) options.push("tweak"); + if (getters.midi) options.push("midi"); +// if (getters.playImmediate) options.push("immediate"); +// if (getters.switchTunes) options.push("switchtunes"); +// if (getters.hideVoice) options.push("hideVoice"); +// if (getters.preload) options.push("preload"); +// if (getters.loopMeasures) options.push("loopmeasures"); + if (getters.swingFeel) options.push("swing"); + if (getters.soundfont) optionsOptions.push("soundfont: '/path/to/soundfont/'"); + options.push("options: {\n\t\t\t\t"+optionsOptions.join(",\n\t\t\t\t") + "\n\t\t\t}\n") + + output += `\t\tvar synthControl = new ${abcjs}.synth.SynthController();\n`; + var cursorControl = getters.cursor ? "cursorControl" : "null"; + output += `\t\tsynthControl.load("#audio", ${cursorControl}, controlOptions);\n`; + + output += ` synthControl.disable(true); + var midiBuffer = new ${abcjs}.synth.CreateSynth(); + midiBuffer.init({ + visualObj: visualObj[0], + ${options.join(",\n\t\t\t")} + }).then(function () { + synthControl.setTune(visualObj[0], true).then(function (response) { + document.querySelector(".abcjs-inline-audio").classList.remove("disabled"); +\t\t\t}) + }); +`; + + output += `\t} else { + console.log("audio is not supported on this browser"); + }; +} +` + return output +}) + +const str = ` +if (ABCJS.synth.supportsAudio()) { +\tvar synthControl = new ABCJS.synth.SynthController(); +\tsynthControl.load("#audio", + cursorControl, + { + displayLoop: true, + displayRestart: true, + displayPlay: true, + displayProgress: true, + displayWarp: true + } + ); + +var audioParams = { chordsOff: true }; + +\tvar createSynth = new ABCJS.synth.CreateSynth(); +\tcreateSynth.init({ visualObj: visualObj[0] }).then(function () { +\t\tsynthControl.setTune(visualObj[0], false, audioParams).then(function () { +\t\t\tconsole.log("Audio successfully loaded.") +\t\t}).catch(function (error) { +\t\t\tconsole.warn("Audio problem:", error); +\t\t}); +\t}).catch(function (error) { +\t\tconsole.warn("Audio problem:", error); +\t}); +} else { +\tdocument.querySelector("#audio").innerHTML = + "Audio is not supported in this browser."; +\t} +}` +//////////////////////////////////////////// + +// TODO-PER: from sheetMusicJs. To be integrated: + +// const synthOptions = {}; // will be passed to mySynth.init +// const audioParams = {}; // will be passed to synthControl.setTune() +// const animationOptions = {}; // will be passed to startAnimation() +// +// if (getters.soundfont) { +// synthOptions.soundFontURL = 'URL'; +// audioParams.soundFontURL = 'URL'; +// } +// +// // VISUAL +// const animation = animationCode(getters, animationOptions); +// +// // AUDIO CONTROL +// const widget = audioControlCode(getters); +// +// // SOUND +// soundCode(getters, animationOptions, visualOptions, audioParams); +// +// // OTHER +// const other = otherCode(getters, synthOptions, audioParams); +// +// // RETURNED CODE +// return ` +// var visualOptions = ${replaceFunctionPlaceholders(JSON.stringify(visualOptions))}; +// var audioParams = ${JSON.stringify(audioParams)}; +// var cursorControl = null; +// var synthOptions = { visualObj: visualObj[0], ...${JSON.stringify(synthOptions)} }; +// +// ${changes} +// +// // trigger these on a user gesture: +// ${widget} +// +// ${animation} ${timing} ${other}`; + +//////////////////////////////////////////// +// TODO-PER: to be integrated + +// const audioControlCode = (sandbox) => { +// if (!sandbox.playbackWidget) { +// return `var mySynth = new ABCJS.synth.CreateSynth(); +// mySynth.init(synthOptions).then(() => { +// synth.prime().then(() => { +// start(); +// }); +// });`; +// } +// +// return `var mySynth = new ABCJS.synth.CreateSynth(); +// var synthControl = new synth.SynthController(); +// var mySynth = new ABCJS.synth.CreateSynth(); +// +// mySynth.init(synthOptions).then(function() { +// synthControl.setTune(visualObj[0], true, audioParams) +// .then(function(){ +// console.log('Audio successfully loaded.') +// }).catch(function(error) { +// console.warn('Audio problem: ', error); +// }) +// }); +// +// synthControl.load('#audio', cursorControl, { +// displayLoop: ${sandbox.loop}, +// displayRestart: ${sandbox.restart}, +// displayPlay: ${sandbox.play}, +// displayProgress: ${sandbox.progress}, +// displayWarp: ${sandbox.warp}, +// displayClock: ${sandbox.clock} +// });` +// } +// +// const soundCode = (sandbox, animationOptions, visualOptions, audioParams) => { +// // TODO: How should a dev use these parameters if they do not have a playback widget to pass audioParams to? +// if(sandbox.metronome) console.log('metronome') // TODO +// if(sandbox.tempo) { +// animationOptions.bpm = 120; +// audioParams.qpm = 120; +// } +// if(sandbox.stereo) console.log('stereo') // TODO +// if(sandbox.instrument) audioParams.program = 0; +// if(sandbox.transpose) { +// visualOptions.visualTranspose = 0; +// audioParams.midiTranspose = 0; +// } +// if(sandbox.noChords) audioParams.chordsOff = true; +// if(sandbox.noVoice) audioParams.voicesOff = true; +// } +// +// const timingCode = (sandbox) => { +// if (!sandbox.usingCallbacks) return ''; +// +// // TODO: Currently, both objects will be instantiated if user selects "Listen for callbacks". Suggestion to present TimingCallbacks and CursorControl as separate options in the sandbox. +// return ` +// var timingCallbacks = new abcjs.TimingCallbacks(visualObj, {}); +// cursorControl = new synth.CursorControl();` +// } +// +// const otherCode = (sandbox, synthOptions, audioParams) => { +// if(sandbox.tweak) { +// synthOptions.sequenceCallback = 'callback to change audio before it is buffered'; +// audioParams.sequenceCallback = 'callback to change audio before it is buffered'; +// } +// // TODO +// if(sandbox.playImmediate) { +// console.log('playImmediate') +// } +// +// // TODO: Midi is deprecated. +// return sandbox.midi ? `ABCJS.renderMidi("midi-download", abc, { generateDownload: true, generateInline: false });` : ''; +// } diff --git a/docs/.vuepress/components/example-strings-js-cursor.js b/docs/.vuepress/components/example-strings-js-cursor.js new file mode 100644 index 0000000000000000000000000000000000000000..7786ced452ea2951aeeb1ca3a35afcdb1c2bf133 --- /dev/null +++ b/docs/.vuepress/components/example-strings-js-cursor.js @@ -0,0 +1,126 @@ +import {entryPoint} from "./example-strings-js"; + +const cursor1 = ` +// This demonstrates two methods of indicating where the music is. + // 1) An element is created that is moved along for each note. + // 2) The currently being played note is given a class so that it can be transformed. + self.cursor = null; // This is the svg element that will move with the music. + self.rootSelector = rootSelector; // This is the same selector as the renderAbc call uses. + + self.onStart = function() { + // This is called when the timer starts so we know the svg has been drawn by now. + // Create the cursor and add it to the sheet music's svg. + var svg = document.querySelector(self.rootSelector + " svg"); + self.cursor = document.createElementNS("http://www.w3.org/2000/svg", "line"); + self.cursor.setAttribute("class", "abcjs-cursor"); + self.cursor.setAttributeNS(null, 'x1', 0); + self.cursor.setAttributeNS(null, 'y1', 0); + self.cursor.setAttributeNS(null, 'x2', 0); + self.cursor.setAttributeNS(null, 'y2', 0); + svg.appendChild(self.cursor); + }; + + self.removeSelection = function() { + // Unselect any previously selected notes. + var lastSelection = document.querySelectorAll(self.rootSelector + " .abcjs-highlight"); + for (var k = 0; k < lastSelection.length; k++) + lastSelection[k].classList.remove("abcjs-highlight"); + }; + +`; + +const cursor2 = ` + // This is called every time a note or a rest is reached and contains the coordinates of it. + if (ev.measureStart && ev.left === null) + return; // this was the second part of a tie across a measure line. Just ignore it. + + self.removeSelection(); + + // Select the currently selected notes. + for (var i = 0; i < ev.elements.length; i++ ) { + var note = ev.elements[i]; + for (var j = 0; j < note.length; j++) { + note[j].classList.add("abcjs-highlight"); + } + } + + // Move the cursor to the location of the current note. + if (self.cursor) { + self.cursor.setAttribute("x1", ev.left - 2); + self.cursor.setAttribute("x2", ev.left - 2); + self.cursor.setAttribute("y1", ev.top); + self.cursor.setAttribute("y2", ev.top + ev.height); + } +`; + +const cursor3 = ` self.removeSelection(); + + if (self.cursor) { + self.cursor.setAttribute("x1", 0); + self.cursor.setAttribute("x2", 0); + self.cursor.setAttribute("y1", 0); + self.cursor.setAttribute("y2", 0); + } +`; + +const hide1 = ` if (ev.measureStart) { + var elements = document.querySelectorAll('.abcjs-mm' + ev.measureNumber); + for (var j = 0; j < elements.length; j++) { + const element = elements[j]; + if (!element.classList.contains('abcjs-bar')) + element.classList.add('hide-note'); + } + } +`; + +const hide2 = ` + var elements = document.querySelectorAll('.hide-note'); + for (var j = 0; j < elements.length; j++) { + const element = elements[j]; + element.classList.remove('hide-note'); + } +`; + +const cursorString = (usingNode, hasCursor, hideMeasures) => { + return `function CursorControl(rootSelector) { + var self = this; +${hasCursor ? cursor1 : '' } + self.onEvent = function(ev) { +${hasCursor ? cursor2 : '' } + ${hideMeasures ? hide1 : '' } + + }; + self.onFinished = function() { + ${hasCursor ? cursor3 : '' } + ${hideMeasures ? hide2 : '' } + }; +} + +var cursorControl = new CursorControl("#paper"); + +document.querySelector(".start").addEventListener("click", startTimer); + +function onEvent(ev) { +if (ev) +cursorControl.onEvent(ev); +else +cursorControl.onFinished(); +} + +function startTimer() { + ${hasCursor ? 'cursorControl.onStart();' : '' } + +var timingCallbacks = new ${usingNode ? 'abcjs' : 'ABCJS'}.TimingCallbacks(visualObj[0], { + eventCallback: onEvent +}); +timingCallbacks.start(); +} +` +} + +export const cursorJsString = (usingNode, hasCursor, hideMeasures) => { + if (!hasCursor && !hideMeasures) + return ''; + return cursorString(usingNode, hasCursor, hideMeasures); +}; + diff --git a/docs/.vuepress/components/example-strings-js-drag.js b/docs/.vuepress/components/example-strings-js-drag.js new file mode 100644 index 0000000000000000000000000000000000000000..d592ccd4fc1a03ca344da60105ace340d9a5b8d3 --- /dev/null +++ b/docs/.vuepress/components/example-strings-js-drag.js @@ -0,0 +1,100 @@ +export const dragJsString = (usingNode, allowDragging) => { + if (!allowDragging) + return ''; + + return `function sanitize(str) { +return str.replace(/&/g, '&').replace(//g, '>'); +} +function formatAbc(start, end) { +var abc; +if (start < 0 || end < 0) +abc = sanitize(abcString); +else { +abc = sanitize(abcString.substring(0, start)) + +'' + +sanitize(abcString.substring(start, end)) + +'' + +sanitize(abcString.substring(end)); +} +var el = document.getElementById("source"); +el.innerHTML = abc.replace(/\\n/g,"
"); +} +function draw() { +var options = { +add_classes: true, +selectionColor: "green", +dragColor: "blue", +clickListener: clickListener, +dragging: true, +selectTypes: [ 'note' ] +}; + +${usingNode ? 'abcjs' : 'ABCJS'}.renderAbc("paper", abcString, options); +} + +var allPitches = [ +'C,,,,', 'D,,,,', 'E,,,,', 'F,,,,', 'G,,,,', 'A,,,,', 'B,,,,', +'C,,,', 'D,,,', 'E,,,', 'F,,,', 'G,,,', 'A,,,', 'B,,,', +'C,,', 'D,,', 'E,,', 'F,,', 'G,,', 'A,,', 'B,,', +'C,', 'D,', 'E,', 'F,', 'G,', 'A,', 'B,', +'C', 'D', 'E', 'F', 'G', 'A', 'B', +'c', 'd', 'e', 'f', 'g', 'a', 'b', +"c'", "d'", "e'", "f'", "g'", "a'", "b'", +"c''", "d''", "e''", "f''", "g''", "a''", "b''", +"c'''", "d'''", "e'''", "f'''", "g'''", "a'''", "b'''", +"c''''", "d''''", "e''''", "f''''", "g''''", "a''''", "b''''" +]; + +function moveNote(note, step) { +var x =allPitches.indexOf(note); +if (x >= 0) +return allPitches[x-step]; +return note; +} + +function tokenize(str) { +var arr = str.split(/(!.+?!|".+?")/); +var output = []; +for (var i = 0; i < arr.length; i++) { +var token = arr[i]; +if (token.length > 0) { +if (token[0] !== '"' && token[0] !== '!') { +var arr2 = arr[i].split(/([A-Ga-g][,']*)/); +output = output.concat(arr2); +} else +output.push(token); +} +} +return output; +} + +var selectionCallback; +var currentIndex = -1; +var maxIndex = -1; + +function clickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) { +if (drag) { +selectionCallback = drag.setSelection; +currentIndex = drag.index; +maxIndex = drag.max; +} +if (abcelem.pitches && drag && drag.step && abcelem.startChar >= 0 && abcelem.endChar >= 0) { +var originalText = abcString.substring(abcelem.startChar, abcelem.endChar); +var arr = tokenize(originalText); +// arr now contains elements that are either a chord, a decoration, a note name, or anything else. It can be put back to its original string with .join(""). +for (var i = 0; i < arr.length; i++) { +arr[i] = moveNote(arr[i], drag.step); +} +var newText = arr.join(""); + +abcString = abcString.substring(0, abcelem.startChar) + newText + abcString.substring(abcelem.endChar); +formatAbc(abcelem.startChar, abcelem.endChar); +draw(); +} else if (abcelem.startChar >= 0 && abcelem.endChar >= 0) +formatAbc(abcelem.startChar, abcelem.endChar); +} + +formatAbc(-1,-1); +draw(); +` +}; diff --git a/docs/.vuepress/components/example-strings-js.js b/docs/.vuepress/components/example-strings-js.js new file mode 100644 index 0000000000000000000000000000000000000000..efaf6290146538630d6509e65297c62c542b7203 --- /dev/null +++ b/docs/.vuepress/components/example-strings-js.js @@ -0,0 +1,79 @@ +export function entryPoint(usingNode) { + return usingNode ? 'abcjs' : 'ABCJS'; +} + +function target(sheetMusic) { + return sheetMusic ? 'paper' : '*'; +} + +export const renderAbcString = (usingNode, hasRender, showMusic, visualOptions) => { + if (!hasRender) + return ''; + + return `var visualOptions = ${visualOptions}; +var visualObj = ${entryPoint(usingNode)}.renderAbc("${target(showMusic)}", abcString, visualOptions);` +}; + +export const editorJsString = (usingNode, hasEditor, showMusic) => { + if (!hasEditor) + return ''; + + return `var options = {}; +var editor = new ${entryPoint(usingNode)}.Editor("abc", { + canvas_id: "${target(showMusic)}", + warnings_id: "warnings", + abcjsParams: options +});` +}; + +export const visualOptionsString = (responsive, callbacks, metronome, hideMeasures, jazzChords) => { + let options = []; + if (responsive) + options.push("responsive: 'resize'"); + if (callbacks) { + options.push("clickListener: clickListener"); + } + if (callbacks || hideMeasures) { + options.push("add_classes: true"); + } + if (jazzChords) { + options.push("jazzchords: true"); + } + if (metronome) + options.push("drum: 'dddd 76 77 77 77 60 30 30 30'"); + return "{ " + options.join(", ") + " }"; +}; + +export const clickListenerJsString = (callbacks) => { + if (!callbacks) + return ''; + return `function clickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) { +var output = "abcelem: [Object]
tuneNumber: " + tuneNumber + "
classes: " + classes + "
analysis: " + JSON.stringify(analysis) + "
drag: " + JSON.stringify(drag) + "
mouseEvent: { clientX: " + mouseEvent.clientX + ", clientY: " + mouseEvent.clientY + " }"; +document.querySelector(".clicked-info").innerHTML = "
Clicked info:
" + output; +} +`; +}; + + +/////////////////////////// + +const replaceFunctionPlaceholders = (stringifiedObject) => { + return stringifiedObject + .replace("\"__FUNCTION_PLACEHOLDER_CLICK_LISTENER__\"", CLICK_LISTENER_FUNCTION); +} + +const animationCode = (sandbox, animationOptions) => { + if (sandbox.cursor || sandbox.hideMeasures) { + if (sandbox.cursor) { + animationOptions.showCursor = true; + } + if (sandbox.hideMeasures) { + animationOptions.hideFinishedMeasures = true; + } + // TODO: startAnimation() is deprecated. + return ` +ABCJS.startAnimation(paper, abc_editor.tunes[0], ${JSON.stringify(animationOptions)});` + } + + return ''; +} diff --git a/docs/.vuepress/components/example-strings.js b/docs/.vuepress/components/example-strings.js new file mode 100644 index 0000000000000000000000000000000000000000..8fc6b2a4b41f7a16186b8ff83c7b04a84659082e --- /dev/null +++ b/docs/.vuepress/components/example-strings.js @@ -0,0 +1,107 @@ +export const setupString = (isNode) => { + if (isNode) + return 'import abcjs from "abcjs"'; + else + return ''; +}; + +export const paperString = (showMusic) => { + return showMusic ? '
' : ''; +}; + +export const editorString = (showEditor) => { + return showEditor ? '\n' + + '\n' + + '
No errors
' : ''; +}; + +export const audioString = (showAudio, isLarge) => { + let audio = ''; + if (showAudio) { + if (isLarge) { + audio = `
`; + } + else audio = '
'; + audio += `` + } + return audio; +}; + +export const midiString = (showMidi) => { + return showMidi ? '
' : ''; +}; + +export const clickListenerHtmlString = (clickListener) => { + return clickListener ? '
Click on a note to see information about that note.
' : ''; +}; + +export const dragExplanationHtmlString = (allowDragging) => { + return allowDragging ? '

Drag a note up and down and watch the source code change to match it.

' : ''; +}; + +export const dragHtmlString = (allowDragging) => { + return allowDragging ? '

Source

\n' + + '
' : ''; +}; + +export const startTimerHtmlString = (hasTimer) => { + if (!hasTimer) + return ''; + return ''; +}; + +export const preambleString = (title) => { + return ` + + + + + + +${title} +` +}; + +export const preamble2String = (hasEditor) => { + const abcString = hasEditor ? '' : `var abcString = "T: Cooley's\\n" + +"M: 4/4\\n" + +"L: 1/8\\n" + +"R: reel\\n" + +"K: Emin\\n" + +"|:D2|EB{c}BA B2 EB|~B2 AB dBAG|FDAD BDAD|FDAD dAFD|\\n" + +"EBBA B2 EB|B2 AB defg|afe^c dBAF|DEFD E2:|\\n" + +"|:gf|eB B2 efge|eB B2 gedB|A2 FA DAFA|A2 FA defg|\\n" + +"eB B2 eBgB|eB B2 defg|afe^c dBAF|DEFD E2:|";`; + + return ` + + + +
+

${title}

+
+
+`; + +export const postAmbleString = () => `
+ + +`; + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js new file mode 100644 index 0000000000000000000000000000000000000000..14a81cce339031804377fb6737754e754b5b5e27 --- /dev/null +++ b/docs/.vuepress/config.js @@ -0,0 +1,16 @@ +import { defaultTheme } from '@vuepress/theme-default' +export default { + base: "/abcjs/", + title: "abcjs", + description: "JavaScript library for displaying sheet music in a browser.", + theme: defaultTheme({ + logo: '/img/abcjs_comp_extended_08.svg', + displayAllHeaders: true, + sidebar: require("./sidebar") + }), + head: [ + ['link', { rel: 'icon', href: '/abcjs/favicon.ico' }], + ['link', { rel:"stylesheet", type: "text/css", href: 'https://paulrosen.github.io/abcjs/abcjs-audio.css'}], + ['script', { src: "https://paulrosen.github.io/abcjs/dist/abcjs-basic.js", type:"text/javascript" }] + ], +}; diff --git a/docs/.vuepress/public/favicon.ico b/docs/.vuepress/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ac52440052f8c6ac3f5f33d4a1763f72ab332874 Binary files /dev/null and b/docs/.vuepress/public/favicon.ico differ diff --git a/docs/.vuepress/public/fonts/itim-music.svg b/docs/.vuepress/public/fonts/itim-music.svg new file mode 100644 index 0000000000000000000000000000000000000000..b1546cc29d1d2f3bee293a783237a3dc9f781487 --- /dev/null +++ b/docs/.vuepress/public/fonts/itim-music.svg @@ -0,0 +1,16 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/.vuepress/public/fonts/itim-music.ttf b/docs/.vuepress/public/fonts/itim-music.ttf new file mode 100644 index 0000000000000000000000000000000000000000..892fa95352a79879488661951f9c974e2f41c4af Binary files /dev/null and b/docs/.vuepress/public/fonts/itim-music.ttf differ diff --git a/docs/.vuepress/public/fonts/itim-music.woff b/docs/.vuepress/public/fonts/itim-music.woff new file mode 100644 index 0000000000000000000000000000000000000000..97adfa7f9aa2f656a629e4cf59caabfff8456f43 Binary files /dev/null and b/docs/.vuepress/public/fonts/itim-music.woff differ diff --git a/docs/.vuepress/public/img/abcjs_comp_extended_08.svg b/docs/.vuepress/public/img/abcjs_comp_extended_08.svg new file mode 100644 index 0000000000000000000000000000000000000000..3ae7590620583897127bd175566f1c0115320607 --- /dev/null +++ b/docs/.vuepress/public/img/abcjs_comp_extended_08.svg @@ -0,0 +1,9 @@ + + + + abcjs logo + + diff --git a/docs/.vuepress/public/img/browserstack-logo-600x315.png b/docs/.vuepress/public/img/browserstack-logo-600x315.png new file mode 100644 index 0000000000000000000000000000000000000000..e9358251e9d32004c4b46d015f76bc4cdbc55534 Binary files /dev/null and b/docs/.vuepress/public/img/browserstack-logo-600x315.png differ diff --git a/docs/.vuepress/sidebar.json b/docs/.vuepress/sidebar.json new file mode 100644 index 0000000000000000000000000000000000000000..213fa197086baf7c21b9cf67a25d2aab8a183623 --- /dev/null +++ b/docs/.vuepress/sidebar.json @@ -0,0 +1,86 @@ +[ + { + "isGroup": true, + "text": "Overview", + "children": [ + "/overview/purpose.md", + "/overview/examples.md", + "/overview/getting-started.md", + "/overview/example-generator.md", + "/overview/abc-notation.md", + "/overview/faq.md" + ] + }, + { + "isGroup": true, + "text": "Visual", + "children": [ + "/visual/overview.md", + "/visual/render-abc-options.md", + "/visual/render-abc-result.md", + "/visual/classes.md", + "/visual/click-listener.md", + "/visual/dragging.md", + "/visual/tablature.md" + ] + }, + { + "isGroup": true, + "text": "Audio", + "children": [ + "/audio/synthesized-sound.md" + ] + }, + { + "isGroup": true, + "text": "Animation", + "children": [ + "/animation/timing-callbacks.md" + ] + }, + { + "isGroup": true, + "text": "Interactive", + "children": [ + "/interactive/interactive-editor.md" + ] + }, + { + "isGroup": true, + "text": "Transposing", + "children": [ + "/transposing/transposing.md" + ] + }, + { + "isGroup": true, + "text": "Analysis", + "children": [ + "/analysis/tune-book.md" + ] + }, + { + "isGroup": true, + "text": "Deprecated", + "children": [ + "/deprecated/deprecated-api.md" + ] + }, + { + "isGroup": true, + "text": "Developers", + "children": [ + "/developers/pull-requests.md", + "/developers/basic-architecture.md", + "/developers/releasing.md" + ] + }, + { + "isGroup": true, + "text": "Upgrading", + "children": [ + "/upgrading/release-notes.md", + "/upgrading/midijs.md" + ] + } +] diff --git a/docs/.vuepress/store.js b/docs/.vuepress/store.js new file mode 100644 index 0000000000000000000000000000000000000000..e391116655bb4480c76fd7b5f8e34930872fcae6 --- /dev/null +++ b/docs/.vuepress/store.js @@ -0,0 +1,233 @@ +import {audioString, clickListenerHtmlString, dragExplanationHtmlString, dragHtmlString, editorString, middleString, midiString, paperString, postAmbleString, preamble2String, preambleString, setupString, startTimerHtmlString} from "./components/example-strings"; +import {cssString} from "./components/example-strings-css"; +import {clickListenerJsString, editorJsString, renderAbcString, visualOptionsString} from "./components/example-strings-js"; +import {cursorJsString} from "./components/example-strings-js-cursor"; +import {dragJsString} from "./components/example-strings-js-drag"; +import {soundJsString} from "./components/example-strings-js-audio"; + +const state = { + examples: { + usingNode: true, + soundfont: false, + + sheetMusic: true, + cursor: false, + hideMeasures: false, + jazzChords: false, + responsive: false, + changes: 'programmatic', + hideVoice: false, + preload: false, + loopMeasures: false, + swingFeel: false, + + hasSound: false, + playbackWidget: true, + large: false, + loop: false, + restart: true, + play: true, + progress: true, + warp: false, + clock: true, + + usingCallbacks: false, + metronome: false, + tempo: false, + stereo: false, + instrument: false, + transpose: false, + noChords: false, + noVoice: false, + + tweak: false, + midi: false, + playImmediate: false, + switchTunes: false, + + isDownloading: false, + } +} + +const getters = { + usingNode(state) { return state.examples.usingNode }, + soundfont(state) { return state.examples.soundfont }, + sheetMusic(state) { return state.examples.sheetMusic }, + cursor(state) { return state.examples.cursor }, + hideMeasures(state) { return state.examples.hideMeasures }, + jazzChords(state) { return state.examples.jazzChords }, + hideVoice(state) { return state.examples.hideVoice }, + preload(state) { return state.examples.preload }, + loopMeasures(state) { return state.examples.loopMeasures }, + swingFeel(state) { return state.examples.swingFeel }, + responsive(state) { return state.examples.responsive }, + changes(state) { return state.examples.changes }, + hasSound(state) { return state.examples.hasSound }, + playbackWidget(state) { return state.examples.playbackWidget }, + large(state) { return state.examples.large }, + loop(state) { return state.examples.loop }, + restart(state) { return state.examples.restart }, + play(state) { return state.examples.play }, + progress(state) { return state.examples.progress }, + warp(state) { return state.examples.warp }, + clock(state) { return state.examples.clock }, + usingCallbacks(state) { return state.examples.usingCallbacks }, + metronome(state) { return state.examples.metronome }, + tempo(state) { return state.examples.tempo }, + stereo(state) { return state.examples.stereo }, + instrument(state) { return state.examples.instrument }, + transpose(state) { return state.examples.transpose }, + noChords(state) { return state.examples.noChords }, + noVoice(state) { return state.examples.noVoice }, + tweak(state) { return state.examples.tweak }, + midi(state) { return state.examples.midi }, + playImmediate(state) { return state.examples.playImmediate }, + switchTunes(state) { return state.examples.switchTunes }, + isDownloading(state) { return state.examples.isDownloading }, + + declaration(state, getters) { + return setupString(getters.usingNode) + }, + sheetMusicHtml(state, getters) { + return `${dragExplanationHtmlString(getters.changes === 'drag')} +${editorString(getters.hasEditor)} +${paperString(getters.sheetMusic)} +${audioString(getters.playbackWidget && getters.hasSound, getters.large)} +${midiString(getters.midi)} +${clickListenerHtmlString(getters.sheetMusic && getters.usingCallbacks)} +${dragHtmlString(getters.changes === 'drag')} +${startTimerHtmlString(getters.sheetMusic && (getters.cursor || getters.hideMeasures))} +`; + }, + + sheetMusicCss(state, getters) { + return cssString( + getters.playbackWidget && getters.hasSound, + getters.sheetMusic && getters.cursor, + getters.sheetMusic && getters.hideMeasures, + getters.sheetMusic && getters.changes === 'drag', + getters.sheetMusic && getters.usingCallbacks + ); + }, + + sheetMusicJs(state, getters) { + var usingNode = getters.isDownloading ? false : getters.usingNode; + return sheetMusicJsBuilder(getters, usingNode); + }, + sheetMusicJsNode(state, getters) { + return sheetMusicJsBuilder(getters, true); + }, + hasEditor(state, getters) { + return getters.changes === 'editor'; + }, + title(state, getters) { + let options = []; + if (getters.sheetMusic) options.push("visual"); + if (getters.sheetMusic && getters.responsive) options.push("responsive"); + if (getters.sheetMusic && getters.cursor) options.push("cursor"); + if (getters.sheetMusic && getters.hideMeasures) options.push("hide"); + if (getters.sheetMusic && getters.jazzChords) options.push("jazz"); + options.push(getters.changes); + if (getters.sheetMusic && getters.usingCallbacks) options.push("callback"); + if (getters.hasSound) options.push("sound"); + if (getters.hasSound && getters.playbackWidget) options.push("playback"); + if (getters.hasSound && getters.playbackWidget && getters.large) options.push("large"); + if (getters.hasSound && getters.playbackWidget && getters.loop) options.push("loop"); + if (getters.hasSound && getters.playbackWidget && getters.restart) options.push("restart"); + if (getters.hasSound && getters.playbackWidget && getters.play) options.push("play"); + if (getters.hasSound && getters.playbackWidget && getters.progress) options.push("progress"); + if (getters.hasSound && getters.playbackWidget && getters.warp) options.push("warp"); + if (getters.hasSound && getters.playbackWidget && getters.clock) options.push("clock"); + if (getters.hasSound && getters.metronome) options.push("metronome"); + if (getters.hasSound && getters.tempo) options.push("tempo"); + if (getters.hasSound && getters.stereo) options.push("stereo"); + if (getters.hasSound && getters.instrument) options.push("instrument"); + if (getters.hasSound && getters.transpose) options.push("transpose"); + if (getters.hasSound && getters.noChords) options.push("nochords"); + if (getters.hasSound && getters.noVoice) options.push("novoice"); + if (getters.hasSound && getters.tweak) options.push("tweak"); + if (getters.hasSound && getters.midi) options.push("midi"); + if (getters.hasSound && getters.playImmediate) options.push("immediate"); + if (getters.hasSound && getters.switchTunes) options.push("switchtunes"); + if (getters.hasSound && getters.hideVoice) options.push("hideVoice"); + if (getters.hasSound && getters.preload) options.push("preload"); + if (getters.hasSound && getters.loopMeasures) options.push("loopmeasures"); + if (getters.hasSound && getters.swingFeel) options.push("swing"); + if (getters.hasSound && getters.soundfont) options.push("soundfont"); + + return options.join(' '); + }, + filename(state, getters) { + return getters.title.replace(/ /g,'-') + ".html"; + }, + fullDemo(state, getters) { + return `${preambleString(getters.title)}${getters.sheetMusicCss}${preamble2String(getters.hasEditor)}${getters.sheetMusicJs}${middleString(getters.title)}${getters.sheetMusicHtml}${postAmbleString()}`; + } + +} + +const mutations = { + usingNode(state, payload) { state.examples.usingNode = payload }, + soundfont(state, payload) { state.examples.soundfont = payload }, + sheetMusic(state, payload) { state.examples.sheetMusic = payload }, + cursor(state, payload) { state.examples.cursor = payload }, + hideMeasures(state, payload) { state.examples.hideMeasures = payload }, + jazzChords(state, payload) { state.examples.jazzChords = payload }, + hideVoice(state, payload) { state.examples.hideVoice = payload }, + preload(state, payload) { state.examples.preload = payload }, + loopMeasures(state, payload) { state.examples.loopMeasures = payload }, + swingFeel(state, payload) { state.examples.swingFeel = payload }, + responsive(state, payload) { state.examples.responsive = payload }, + changes(state, payload) { state.examples.changes = payload }, + hasSound(state, payload) { state.examples.hasSound = payload }, + playbackWidget(state, payload) { state.examples.playbackWidget = payload }, + large(state, payload) { state.examples.large = payload }, + loop(state, payload) { state.examples.loop = payload }, + restart(state, payload) { state.examples.restart = payload }, + play(state, payload) { state.examples.play = payload }, + progress(state, payload) { state.examples.progress = payload }, + warp(state, payload) { state.examples.warp = payload }, + clock(state, payload) { state.examples.clock = payload }, + usingCallbacks(state, payload) { state.examples.usingCallbacks = payload }, + metronome(state, payload) { state.examples.metronome = payload }, + tempo(state, payload) { state.examples.tempo = payload }, + stereo(state, payload) { state.examples.stereo = payload }, + instrument(state, payload) { state.examples.instrument = payload }, + transpose(state, payload) { state.examples.transpose = payload }, + noChords(state, payload) { state.examples.noChords = payload }, + noVoice(state, payload) { state.examples.noVoice = payload }, + tweak(state, payload) { state.examples.tweak = payload }, + midi(state, payload) { state.examples.midi = payload }, + playImmediate(state, payload) { state.examples.playImmediate = payload }, + switchTunes(state, payload) { state.examples.switchTunes = payload }, + isDownloading(state, payload) { state.examples.isDownloading = payload }, +} + +const actions = { +} + +export const store = { + state, + getters, + mutations, + actions, +} + +function sheetMusicJsBuilder(getters, usingNode) { + + const visualOptions = visualOptionsString( + getters.responsive, + getters.sheetMusic && getters.usingCallbacks, + getters.hasSound && getters.metronome, + getters.hideMeasures, + getters.jazzChords, + ); + const str =`${renderAbcString(usingNode, !getters.hasEditor, getters.sheetMusic, visualOptions)} +${editorJsString(usingNode, getters.hasEditor, getters.sheetMusic)} +${soundJsString(usingNode, getters)} +${clickListenerJsString(getters.sheetMusic && getters.usingCallbacks)} +${cursorJsString(usingNode, getters.sheetMusic && getters.cursor, getters.sheetMusic && getters.hideMeasures)} +${dragJsString(usingNode, getters.changes === 'drag')} +`; + return str.replace(/\t/g," "); +} diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..71d4947327f781db3166c198f13e8db54d3faa4b --- /dev/null +++ b/docs/.vuepress/styles/index.scss @@ -0,0 +1,39 @@ +header.navbar .site-name { + display: none; +} + +textarea { + width: 100%; + height: 200px; +} + +.my-code { + color: #fff; + padding: 1.25rem 1.5rem; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + + #paper { + max-width: 740px; + } + +.code-results { + display: inline-block; + border: 2px outset #fff; + padding: 3px 10px; + border-radius: 4px; + color: #000; + background: #a8f5e7; + font-weight: bold; +} + +@import url(https://fonts.googleapis.com/css?family=Itim); + +@font-face { + font-family: 'itim-music'; + src: url('https://paulrosen.github.io/abcjs/fonts/itim-music.ttf') format('truetype'), + url('https://paulrosen.github.io/abcjs/fonts/itim-music.woff') format('woff'), + url('https://paulrosen.github.io/abcjs/fonts/itim-music.svg#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/docs/.vuepress/wait-for-abcjs.js b/docs/.vuepress/wait-for-abcjs.js new file mode 100644 index 0000000000000000000000000000000000000000..6d4433fdc4a6192fe621e26167fdfb951cbb3ad8 --- /dev/null +++ b/docs/.vuepress/wait-for-abcjs.js @@ -0,0 +1,10 @@ +export const waitForAbcjs = async function() { + while (!window.ABCJS) { + await sleep(100) + } + window.abcjs = ABCJS +} + +export const sleep = (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)); +}; diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9ff5e0df55c8075e55383b9edaee1554eda32433 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,33 @@ +# Javascript library for inserting music in the browser. + +![abcjs](https://paulrosen.github.io/abcjs/img/abcjs_comp_extended_08.svg) + +**ABC Music Notation** is a format for specifying sheet music using only a string of characters. +For instance, the following string encodes the music that is shown below it. That music was +drawn by including this library on this webpage. + +``` +X: 1 +T: Cooley's +M: 4/4 +L: 1/8 +K: Emin +|:D2|EB{c}BA B2 EB|~B2 AB dBAG|FDAD BDAD|FDAD dAFD| +EBBA B2 EB|B2 AB defg|afe^c dBAF|DEFD E2:| +|:gf|eB B2 efge|eB B2 gedB|A2 FA DAFA|A2 FA defg| +eB B2 eBgB|eB B2 defg|afe^c dBAF|DEFD E2:| +``` + + + +In addition, the music can also be played by a synthesizer: + + diff --git a/docs/analysis/tune-book.md b/docs/analysis/tune-book.md new file mode 100644 index 0000000000000000000000000000000000000000..77fbd5f56e7099cbeb76d38cfc0693c1cd9e4d1b --- /dev/null +++ b/docs/analysis/tune-book.md @@ -0,0 +1,70 @@ +# Tune Book + +The following analysis is based on the abc string that is in the textarea at the bottom of this page. + +## Number of Tunes + +It might be useful to know in advance how many tunes are in a tunebook before rendering them: + + + +## Get Tune Info + +The following assumes you've created a `TuneBook` object like this: + +``` +var tuneBook = new ABCJS.TuneBook(tunebookString) +``` + +### Extract Tune by ID + +If you know the ID, then you can use the following to get a particular tune in a tunebook. The id is the value in the `X:` field. + + + +### Extract Tune by Title + +If you know the title, then you can use the following to get a particular tune in a tunebook. The title is the value in the *first* `T:` field. + + + +### Get all Tune Info + +You can directly access the array of tunes in a tune book with: + +```javascript +var arrayOfTunes = tunebook.tunes; +``` + +### Get all measures separately + +To parse the string and return each measure: + +```javascript +var measureArray = abcjs.extractMeasures(tunebookString); +``` + +## Test Data + +Paste in any ABC you want here and see how that affects the analysis above: + + + + diff --git a/docs/animation/timing-callbacks.md b/docs/animation/timing-callbacks.md new file mode 100644 index 0000000000000000000000000000000000000000..148f77dae4c673bdc4b2b4de0d416856ee6520c8 --- /dev/null +++ b/docs/animation/timing-callbacks.md @@ -0,0 +1,188 @@ +# Timing Callbacks + +This runs an animation timer and does callbacks at various intervals. This allows you to do various effects that are timed with beats or playing notes. + +## Creation + +To use this create an instance of it: +```javascript +var timingCallbacks = new abcjs.TimingCallbacks(visualObj, params); +``` + +| Parameters | Description | +| ------------- | ----------- | +| visualObj | This is the output of the `renderAbc()` call. It is the music that will be timed. | +| params | This is a object. See below for the possible properties. | + +## Parameters + +| Name | Default | Description | +| ------------- | ------- | ----------- | +| `qpm` | whatever is in the Q: field | Number of beats per minute. | +| `extraMeasuresAtBeginning` | 0 | Don't start the callbacks right away, but insert this number of measures first. | +| `beatCallback` | null | Called for each beat passing the beat number (starting at 0). | +| `eventCallback` | null | Called for each event (either a note, a rest, or a chord, and notes in separate voices are grouped together.) | +| `lineEndCallback` | null | Called at the end of each line. (This is useful if you want to be sure the music is scrolled into view at the right time.) See `lineEndAnticipation` for more details. | +| `lineEndAnticipation` | 0 | The number of milliseconds for the `lineEndCallback` to anticipate end of the line. That is, if you want to get the callback half a second before the end of the line, use 500. | +| `beatSubdivisions` | 1 | How many callbacks should happen for each beat. This allows finer control in the client, for instance, to handle a progress bar. | + +## Callbacks + +### beatCallback + +This is called once for every beat in the tune. It is called one additional time when the tune is finished. + +```javascript +function beatCallback(beatNumber, totalBeats, totalTime, position, debugInfo) {} +``` + +|Name|Description| +|---|---| +| beatNumber | Zero-based beat number. Usually this will increment sequentially and regularly, but if javascript is paused long enough (for instance, if the browser tab is changed), then there may be a number of these calls at once when it catches up. | +| totalBeats | The total number of beats (including all repeats) that will be played. | +| totalTime | The total number of milliseconds of the tune. | +| position | The interpolated position of the cursor if the beat occurs between notes. This is an object with the attributes { left: , top: , height: } This can be used to smooth out the cursor by moving it on the beat callbacks. The higher the number of `beatSubdivisions` the smoother the cursor will be. | +| debugInfo | A hash of some extra info that might be useful in figuring out why the callback was triggered. | + +### eventCallback + +This is called once for every "event" in time - either a note or a rest. If there are multiple notes at the same time, then it is only called once +for that group of notes. + +```javascript +function eventCallback(ev) {} +``` + +The parameter `ev` is an object that looks like this: + +```javascript +ev = { + "type": "event", // This is always "event" + + "milliseconds": number, // The number of milliseconds from the beginning of the piece + "millisecondsPerMeasure": number, // The number of milliseconds per measure + + "line": number, // The current "line", that is, the staff system. + "measureNumber": number, // The measure number. Resets per line, so the first measure number on a line is zero. + + "top": number, // The number of pixels from the top of the svg that the note appears. + "height": number, // The height of the note, in pixels. + "left": number, // The number of pixels from the left edge of the svg. + "width": number, // The width of the note + + "elements": [ ], // Array of the actual elements on the page that are represented by the note or notes. + "startCharArray": [ number ], // the character position in the original abc string + "endCharArray": [ number ], // the character position in the original abc string + "midiPitches": [ // Array of the currently playing pitches + { + "pitch": number, // The pitch number (based on the midi standard, i.e. middle C is 60) + "durationInMeasures": number, // the note value as a fraction. (that is, a quarter note is 0.025) + "volume": number, // The volume expressed as a number between 0 and 127 + "instrument": number // The instrument number (based on the midi standard, i.e. acoustic_grand_piano is 0) + } + ] +} +``` + +#### Notes: + +* The `startCharArray` and `endCharArray` are arrays because there is more than one location in the abc string if there is more than one voice. + +* The format of the `elements` array is subject to change in future versions. + +* This is called one last time with passing in `null` at the end of the tune. On that call `eventCallback` can return the string "continue" to keep the timer from stopping. This is useful if you want to play on repeat - in theory you would probably have another call to `seek()`. + +* This function can be a Promise or not. + +### lineEndCallback + +This will be called as the cursor is approaching the end of a line of music. This is useful if there is more than a screen's worth of music; it can be used to scroll the page at the right time. + +```javascript +function lineEndCallback(info, event, details) {} +``` + +The parameter `info` looks like this: + +```javascript +info = { + "milliseconds": number, // current milliseconds from beginning of piece + "top": number, // The number of pixels from the top of the svg to the top of the cursor + "bottom": number // The number of pixels from the top of the svg to the bottom of the cursor +} +``` +The parameter `event` is the standard note event. + +The parameter `details` looks like this: +```javascript +details = { + "line": number, // the current line number (zero-based) + "endTimings": array // the array of the timings for each line +} +``` +The `endTimings` array elements are of the same type as the `info` parameter. + +## Functions + +These are the entry points that can be called on the `timingCallbacks` object. + +### start(position, units) + +This starts the timer that triggers the callbacks. This is called to both start and resume after calling pause. See the `setProgress` method below for explanation of the parameters with one special case: + +If `position` is undefined then if the previous call was to `pause()`, then the animation continues from where it left off. If there was no pause, then the animation starts from the beginning. + +### pause() + +Pauses the animation. Calling `start()` afterwards will resume from where it left off. + +### stop() + +Stop the animation. After calling this, the next call to `start()` will start at the beginning. + +### reset() + +Move the timer back to the beginning, so the animation starts over. This can be called either when the animation is currently running or when it is paused. + +### setProgress(position, units) + +Change the position of the animation. This allows random access to any place in the tune. + +If the second parameter is not present, then `units` equals "percent". The possible values are: + +* `"percent"`: The percent passed in is a number between 0 and 1. This can be called either when the animation is currently running or when it is paused. + +* `"seconds"`: The seconds from the beginning of the tune. If this is passed the end of the tune it is changed to the end. + +* `"beats"`: The beats from the beginning of the tune. If this is passed the end of the tune it is changed to the end. + +### replaceTarget(visualObj) + +If the underlying music changes on the fly, this replaces the current object without having to destroy the object and start over. `visualObj` is the return value from `renderAbc`. + +## Example + +Paste in any ABC you want here then click "start" to see what is returned by the timing callbacks: + + + + + + diff --git a/docs/audio/synthesized-sound.md b/docs/audio/synthesized-sound.md new file mode 100644 index 0000000000000000000000000000000000000000..d6cd29fd704d1fd4ea56d8eca58b1c1494afee14 --- /dev/null +++ b/docs/audio/synthesized-sound.md @@ -0,0 +1,671 @@ +# Synthesized Sound + +## Browser Compatibility and Requirements + +* This works in any browser that supports `AudioContext`, `AudioContext.resume`, and `Promises`. That does NOT include IE, but this will work on any other "modern" browser that is at least the following version: Firefox 40, Safari 9.1, Edge 13, and Chrome 43. + +* This requires an internet connection for the "sound fonts". You can supply your own sound fonts, so if you want to deliver them locally you can get by without the network. The default sound fonts come from [this github repo](https://paulrosen.github.io/midi-js-soundfonts/abcjs). + +* It is theoretically possible to create complex enough pieces to bog down your browser or eat up memory. The two things that take resources are each unique note in each instrument and the overall length of the music. Music of a few minutes long with a variety of notes in a few different instruments work with no problem on all devices that have been tested. + +## Theory + +* This creates an audio buffer that is similar to a WAV file and contains the entire piece to play. + +* The sounds themselves come from a "sound font": that is, each individual note on each instrument is a separate sound file that is combined into the audio buffer. + +* The instrument numbers and the pitch numbers come from the MIDI spec. MIDI is not normally produced, but it is MIDI-like. (There is a function to create a MIDI file for download.) + +## Examples + +See [Basic Synth](https://raw.githubusercontent.com/paulrosen/abcjs/main/examples/basic-synth.html) for the simplest possible way of making sound. + +See [Full Synth](https://raw.githubusercontent.com/paulrosen/abcjs/main/examples/full-synth.html) for an example that incorporates a bouncing-ball type animation and an audio control. + +## API + +Since there are a number of ways to use the synthesized sound: with or without a visual depiction of the tune, with or without a user-facing audio control, and with or without various timing callbacks, there are a number of different entry points. + +## CreateSynth + +Creates the object that caches and buffers the audio to be played. All implementations of audio playback will need a CreateSynth. + +```javascript +var synth = new ABCJS.synth.CreateSynth(); +``` + +### init(synthOptions) + +The first call that must be made on the CreateSynth object. This will load all of the needed notes and will return a promise when they are loaded. There might be a considerable delay for this to finish. Because the notes are cached, though, the second time CreateSynth is created with a piece of music with similar notes, it will take much less time. See below for the synthOptions. + +This must not be called until the user has made a gesture on the page because this references an `AudioContext`. + +This returns a promise after all the notes have been loaded. The promise contains: +``` +{ + cached: [], // an array of the notes that were previously loaded. + error: [], // an array of the notes that haven't been loaded and the error message that was received + loaded: [] // an array of the notes that have been loaded. +} +``` + +#### synthOptions + +| Attribute | Default | Description | +| ------------- | ----------- |----------- | +| audioContext | undefined | This the value of `new AudioContext()`. It should be passed in instead of created because the calling program might be managing and reusing this. It also MUST be creating in the handler of a user action. It can't be created at any other time. But if you don't pass one in then abcjs will create it. This value is cached for the length of the browser session. | +| visualObj | undefined | This is the result of `renderAbc()`. Important: `renderAbc()` returns an array, since an ABC string can contain more than one tune. This variable is just one element in that array. Either this must be supplied, or `sequence` must be supplied. | +| sequence | undefined | This is a manually-created set of instructions for creating the audio. It is built using the `SynthSequence` object. | +| millisecondsPerMeasure | calculated | This allows control over the tempo. If this is present, then the tempo specified in the ABC string is ignored. | +| debugCallback | undefined | This will be called with various extra info at different times in the process. | +| options | undefined | Some options for the sound creation (see list below). | + +#### synthOptions.options + +In addition to the following option, you can also set the options described in audioParams below. + +| Attribute | Default | Description | +| ------------- | ----------- |----------- | +| soundFontUrl | "https://paulrosen.github.io/midi-js-soundfonts/abcjs/" | This is the public URL for the sound font. If it isn't present, then the sound fonts come from the github repo. This can be replaced if the new sound font follows the same format. | +| soundFontVolumeMultiplier | 1.0 | This is the amount to multiply all the volumes to compensate for different volume soundfonts. If you find that either the volume is too low or the output is clipped, you can experiment with this number. | +| programOffsets | {} | The offset of each voice to the beat. Some voices have a ramp up time so that the beginning of the sound isn't the beat. This is the number of milliseconds that the program should be offset. This is expressed as `{ 'program_name': 100 }` where the program name is one of the standard midi names, like "acoustic_grand_piano". If you use the default soundfont then these values are set automatically. You can still provide this parameter to override the settings if you like. | +| fadeLength | 200 | The number of milliseconds to fade out each note after its has played for the correct length. The gain will go from 100% to 0% in this number of milliseconds. | +| sequenceCallback | undefined | This is called after the array of notes is created, and just before it is used to create the audio buffer. The array of tracks is passed in, and this gives a chance to tweak the audio before it is created: you can give it some swing, you can change volumes, or anything else. | +| callbackContext | undefined | This is passed back when the sequenceCallback function is called. | +| onEnded | undefined | This function is called after the playback stops. | +| pan | [0...] | An array of numbers between -1 and 1 for how far to pan each track. -1 is all the way to the left and 1 is all the way to the right. If there are not enough items in the array for all the tracks, then the remaining tracks will be in the middle. | + +#### Example +```javascript +var myContext = new AudioContext(); +var visualObj = ABCJS.renderAbc(...); +synth.init({ + audioContext: myContext, + visualObj: visualObj[0], + millisecondsPerMeasure: 500, + options: { + soundFontUrl: "https:/path/to/soundfont/folder", + pan: [ -0.3, 0.3 ] + } +}).then(function (results) { + // Ready to play. The results are details about what was loaded. +}).catch(function (reason) { + console.log(reason) +}); +``` + +The above will: +1. create audio for the first tune found in the tunebook that is passed to `renderAbc`. +2. It will play at 1/2 second per measure (180 bpm for 2/4 time). +3. It will use the URL provided for the soundfont. +4. It will put the first track a little bit to the left side and the second track a little bit to the right. (Note that if the music is a single line of music with chord diagrams, the melody will be track 0 and the chords will be track 1.) + +### prime() + +This creates the actual buffer - it doesn't require a network connection since all the notes have now been preloaded. It returns a promise because there might be a little bit of time delay doing the calculations. + +After calling this, everything is setup so you can freely call the rest of the functions to control how the audio works. + +This returns a promise that is `{ status: audioContextStatus, duration: lengthInSecondsOfAudio }` + +Note that normally the status will be "running". On iOS, though, it can sometimes be either "suspended" or "interrupted". It might require user intervention to resolve this. + +#### Example +```javascript +synth.init(...).then(() => { + synth.prime().then((response) => { + console.log(response.status) + ... + }); +}); +``` + +### start() + +This starts the audio. + +Call this after `prime()` has returned its promise. This will happen fast and doesn't have any latency, so you can call this right as you are timing other things to be in sync with the audio. + +### pause() and resume() + +After `start()` has been called, pause and resume can be called to control the playback. + +### seek(percent, units) + +This changes the playback position. It can be called whether the sound is currently playing or not. + +If the second parameter is not present, then `units` equals "percent". The possible values are: + +* `"percent"`: The percent passed in is a number between 0 and 1. This can be called either when the animation is currently running or when it is paused. + +* `"seconds"`: The seconds from the beginning of the tune. If this is passed the end of the tune it is changed to the end. + +* `"beats"`: The beats from the beginning of the tune. If this is passed the end of the tune it is changed to the end. + +### stop() + +Stops playing the sound and resets the progress to the beginning of the sound file. + +### download() + +This returns the audio buffer created. (It is in WAV format.) + +### getAudioBuffer() + +This returns the AudioBuffer that was created in the the `prime()` call. + +## SynthController + +Creates a visual widget that allows the user to control playback, including play and stop buttons, a progress bar, etc. This is the quickest way to set up a playback widget. See the section below for options. + +The constructor can be called at any time, including before much is initialized: + +```javascript +var synthControl = new ABCJS.synth.SynthController(); +``` + +### load(selector, cursorControl, visualOptions) + +After the DOM is loaded, this should be called to initialize the visual widget that contains the "play", etc. buttons. + +While this can be called multiple times, it is generally just called once during initialization. + +#### selector + +This is a CSS-style selector of the element that should be turned into the audio control. + +#### cursorControl + +This is an optional object that can be passed in that will receive callbacks when events happen that should move the cursor. See the section on "CursorControl" for more info. + +#### visualOptions + +This is a hash with the following possible properties: + +| Option | Default | Description | +|---|---|---| +| displayLoop | false | Whether to display a button that the user can press to make the tune loop instead of stopping when it gets to the end. | +| displayRestart | false | Whether to display a button that the user can press to make the tune go back to the beginning. | +| displayPlay | true | Whether to display a button that the user can press to make the tune start playing. (Note: this turns into the "pause" button when the tune is playing.) | +| displayProgress | true | Whether to display the progress slider. The user can click anywhere on this to get the music to jump to that location. | +| displayWarp | false | Whether to display the tempo and allow the user to change it on the fly. | + +### disable(isDisabled) + +This is called internally when waiting for the audio to finish loading. It can also be called directly by the client. The most common use for that is to disable the visual control when you are about to load in a new tune. + +If you load in a new tune without disabling the control first and the user clicks play while the notes are still being loaded over the network, the old tune will play until the new one is ready. + +### setTune(visualObj, userAction, audioParams) + +This is called whenever there is a new tune ready to be loaded into the player. + +#### visualObj + +This is one of the tunes that is returned from the `renderAbc()` call. That is, `renderAbc` will return an array of tunes. Often it is an array of length 1 if there is only one tune in the abc string, but it could be multiple tunes. + +#### userAction + +True if this is being called inside an event handler from a user gesture. The audio buffer can't be created until then. If this is `true`, then the audio buffer is created immediately. If this is `false` then the audio buffer is not created until the user clicks the `play` button. + +### audioParams + +Here are the possible properties that can be passed in: + +| Property | Default | Description | +|---|---|---| +| audioContext | create it. | An AudioContext object so that they can be reused. | +| debugCallback; | null | A function that is called at various times in the creation of the audio. | +| soundFontUrl | use the default | The publicly available URL of the soundfont to use. | +| soundFontVolumeMultiplier | 1.0 | This is the amount to multiply all the volumes to compensate for different volume soundfonts. If you find that either the volume is too low or the output is clipped, you can experiment with this number. | +| millisecondsPerMeasure | calculated | An override of the tempo in the tune. | +| visualObj | null | The object returned from `renderAbc`. | +| options | {} | Options to pass to the low-level buffer creation routines. | +| sequence | null | An alternate audio specification, if visualObj is not present. | +| onEnded | null | A callback function when the AudioBuffer finishes playing. | + +The `options` element above can have the following properties: + +| Property | Default | Description | +|---|---|---| +| sequenceCallback | null | A hook to get the instructions that will be passed to the Audio Buffer. This can be used either to debug what audio was generated or to modify the sequence before the audio is created. This can be useful to add "swing" to the beats, or do any other processing that isn't possible in ABC notation. | +| callbackContext | null | This is passed back with the sequenceCallback. It can be anything you want. | +| program | 0 | The midi program (aka "instrument") to use, if not specified in ABC string. | +| midiTranspose | 0 | The number of half-steps to transpose everything, if not specified in ABC string. | +| channel | 0 | The "midi channel" to use. This isn't particularly useful except that specifying channel 10 means to use the percussion sounds. | +| drum | null | Whether to add a drum (or metronome) track. A string formatted like the `%%MIDI drum` specification. Using this parameter also implies `%%MIDI drumon` See the section for "Drum Parameter" for an explanation. | +| drumBars | 1 | How many bars to spread the drum pattern over. See the section for "Drum Parameter" for an explanation. | +| drumIntro | 0 | The number of measures of count in beats before the music starts. | +| drumOff | false | If you want a metronome only for the intro measures but not when the tune starts, use this along with the `drumIntro` and `drum` params. This has no effect if either one of those is missing. | +| qpm | null | The tempo to use. This overrides a tempo that is in the tune. | +| defaultQpm | null | The tempo to use, only if there is no tempo in the tune. | +| chordsOff | false | If true, then don't turn the guitar chord symbols into sound. (But do play the metronome if there is one.) | +| voicesOff | false | If true, play the metronome and accompaniment; do the animation callbacks, but don't play any melody lines. This can also be an array of voices to turn off. The voices are numbered starting at zero. | +| detuneOctave | 0 | The number of cents to raise the pitch of the top note of an octave that is played at the same time. That is, in multipart music, if the tenor and soprano parts are an octave apart the soprano note gets lost in the overtones. Making the top note slightly sharp brings it out without making it sound out of tune. | + +### play(), pause(), toggleLoop(), restart(), setProgress(ev) + +These do the same thing as the user pressing these buttons, but can be called programmatically. + +### setWarp(percent) + +This changes the tempo to the percent passed in. That should be a positive integer. It will change the tempo immediately if the music is already playing. + +### download(fileName) + +This will download the current audio buffer as a WAV file to the fileName passed in. + +### Example + +The following creates an audio control that the user can manipulate. + +```javascript +// given that there are two elements in the DOM with the IDs "paper" and "audio" +var cursorControl = { ... }; // see section on CursorControl +var abc = "X:1\n etc..."; +var abcOptions = { add_classes: true }; +var audioParams = { chordsOff: true }; + +if (ABCJS.synth.supportsAudio()) { + var synthControl = new ABCJS.synth.SynthController(); + synthControl.load("#audio", + cursorControl, + { + displayLoop: true, + displayRestart: true, + displayPlay: true, + displayProgress: true, + displayWarp: true + } + ); + + var visualObj = ABCJS.renderAbc("paper", + abc, abcOptions); + var createSynth = new ABCJS.synth.CreateSynth(); + createSynth.init({ visualObj: visualObj[0] }).then(function () { + synthControl.setTune(visualObj[0], false, audioParams).then(function () { + console.log("Audio successfully loaded.") + }).catch(function (error) { + console.warn("Audio problem:", error); + }); + }).catch(function (error) { + console.warn("Audio problem:", error); + }); +} else { + document.querySelector("#audio").innerHTML = + "Audio is not supported in this browser."; + } +} +``` + +## getMidiFile(source, options) + +This is called to get the audio in MIDI format, instead of as a playable audio buffer. + +```javascript +ABCJS.synth.getMidiFile(source, { midiOutputType: 'binary', bpm: 100 }) +``` + +### source + +Either the ABC string to create the MIDI from or the object that is returned from `renderAbc`. +Note that `renderAbc` returns an array, so to get the first tune, for instance, you need to use `[0]`. +When using the object many of the options passed in are ignored because the tune is already created. + +### options + +The same options as are used elsewhere, with the addition of: + +```javascript +options = { + midiOutputType: "encoded" | "binary" | "link", + // The following OPTIONAL parameters are only used when the type is "link": + downloadClass: "class-name-to-add", + preTextDownload: "text that appears before the link", + downloadLabel: function() | "the text that appears as the body of the anchor tag that is clickable", + postTextDownload: "text that appears after the link", + fileName: "the name of the file that the midi will be saved as" +} +``` +Note that `downloadLabel` can be either a string or a function that is passed the tune object and the tune index. +The return of that function must be a string. If downloadLabel is text and contains `%T` then that is replaced by the tune's title. + +#### midiOutputType + +If this is not present or set to `link`, then the return value is a link that can be placed directly on the page for the user to download. + +If this is set to `binary`, then the actual contents of the midi file are returned, as a blob. + +If this is set to "encoded", then the contents of the midi file are returned, as an encoded ASCII string. That is, bytes are represented by `%xx` where xx is a hexidecimal value. + +#### downloadLabel + +If this isn't present, then the title is retrieved from the tune and the label is "Download MIDI for $TITLE". + +If you'd like to use the title in the label but modify it, use `%T`. That is: `downloadLabel: "the tune %T is ready for download"` + +### Examples + +#### Complete download link + +```html + +``` + +```javascript +var midi = ABCJS.synth.getMidiFile("X:1\nT:Cooley's\netc...", { chordsOff: true, midiOutputType: "link" }); +document.getElementById("midi-link").innerHTML = midi; +``` +That results in the following html: + +```html + +``` + +#### The midi data for your own download + +```html +MIDI +``` + +```javascript +var midi = ABCJS.synth.getMidiFile("X:1\nT:Cooley's\netc...", { chordsOff: true, midiOutputType: "encoded" }); +document.getElementById("midi-link").setAttribute("html", midi); +``` + +#### Actual midi file to pass to another library + +```javascript +var midi = ABCJS.synth.getMidiFile("X:1\nT:Cooley's\netc...", { chordsOff: true, midiOutputType: "binary" }); +otherLibraryMidiPlayer.loadTune(midi); +``` + +#### Passing an object + +If you already have parsed the tune you can just pass it in: +```javascript +var visualObj = ABCJS.renderAbc("paper", "X:1\nT:Cooley's\netc..."); +var midi = ABCJS.synth.getMidiFile(visualObj[0], { midiOutputType: "encoded" }); +``` + +## CreateSynthControl + +Lower level object than `SynthController` if you want the functionality without the visible control. + +```javascript +var control = new ABCJS.synth.CreateSynthControl(element, options); +``` + +`element` is either a string representing a selector of an existing element on the page or a DOM element. The contents of that element are replaced with an audio control. + +### options parameter + +| Option | Description | +| ------------- | ------------- | +| loopHandler | Callback function when the loop button is clicked. If this is not present, then the loop button is not displayed. | +| restartHandler | Callback function when the restart button is clicked. if this is not present, then the restart button is not displayed. | +| playHandler or playPromiseHandler | Callback function when the play button is clicked. if this is not present, then the play button is not displayed. If the `handler` version is present, then it must return a promise. | +| progressHandler | Callback function when the progress bar is clicked. if this is not present, then the progress bar is not displayed.| +| warpHandler | Callback function when the warp percent is changed. if this is not present, then the warp percent is not displayed. | +| afterResume | Callback function after the AudioContext is set up correctly. | +| ------------- | ------------- | +| hasClock | Whether to display a clock on the control. | +| ac | The AudioContext to use for this control. (Optional - if this is not present, then a button will appear asking the user to click to get an AudioContext.) | +| ------------- | ------------- | +| repeatTitle | To override the text of the tooltip for toggling loop mode. | +| repeatAria | To override the text of the aria for loop mode. (By default, the repeatTitle is used.) | +| restartTitle | To override the text of the tooltip for the restart button. | +| restartAria | To override the text of the aria for restart mode. (By default, the restartTitle is used.) | +| playTitle | To override the text of the tooltip for the play/pause button. | +| playAria | To override the text of the aria for the play/pause. (By default, the playTitle is used.)| +| randomTitle | To override the text of the tooltip for the progress slider. | +| randomAria | To override the text of the aria for progress slider. (By default, the randomTitle is used.)| +| warpTitle | To override the text of the tooltip for the warp input. | +| warpAria | To override the text of the aria for warp input. (By default, the warpTitle is used.)| +| bpm | To override the text "BPM" for beats per minute. | + +## SynthSequence + +Creates an object that builds data for `CreateSynth`. This is normally done internally if `CreateSynth` is passed a visual object, but this is a way to custom build any sequence. + +```javascript +var sequencer = ABCJS.synth.SynthSequence() +sequencer.addTrack(); +sequencer.setInstrument(0, 25); +sequencer.appendNote({ trackNumber: 0, pitch: 60, durationInMeasures: 1, volume: 80 }) + +var buffer = new ABCJS.synth.CreateSynth(); +return buffer.init({ + sequence: sequence, +}).then(function () { + return buffer.prime(); +}).then(function () { + return buffer.start(); +}); +``` +This is a helper object that will create an object that is consumed by `CreateSynth`. There is nothing special about this that you couldn't create the object by hand, but this provides some convenience functions. + +| Method | Parameters |Description | +| ------------- | ----------- | ----------- | +| `addTrack` | (none) | Returns the number of the track that was created. This must be called before anything can be added to the track. | +| `setInstrument` | `trackNumber, instrumentNumber` | `trackNumber` is the value that is returned by `addTrack`. instrumentNumber is the stand MIDI number for the instrument. (See `ABCJS.synth.instrumentIndexToName` for the list of instruments.) This should be called right after `addTrack` and may be called at any time after that to change the instrument midstream. | +| `appendNote` | `trackNumber, pitch, durationInMeasures, volume` | This adds a note. `trackNumber` is the value returned from `addTrack`. `pitch` is the standard midi pitch number. `durationInMeasures` is a floating point number where "1" is one measure. `volume` is a value from 0 to 255 that is the volume of the note. | + +## CursorControl object + +If you want notification when events happen, then you can pass in an object that you create yourself. Note that even though this object is called `CursorControl` it can be used for anything that requires knowledge of various events that happen during playback: when a note is played, when a beat is reached, when the end of a music line is near, when a measure starts, and when the music stops. + +The following properties are used: + +- beatSubdivisions + +How often to call the beat callback. If this is not set, then the beat callback is called once per beat. If you want a finer grained control, you can set this to a larger number. Notice that a large number will affect performance. + +- extraMeasuresAtBeginning + +How many extra measures to have at the beginning before the tune actually starts. This can be used for count in beats. + +- lineEndAnticipation + +When to call the onLineEnd event. If you want to scroll the music when the end of the line is reached, then you probably want to scroll it a little in advance so the user can read ahead. This is the number of milliseconds, so the value of 500 means to scroll the music one half second before the end of the line. + +- onReady(synthController) + +Called when the tune has actually been loaded. Because the audio buffer can only be initialized after a user gesture, the tune might not have been loaded when the visual control is created. This might be called when `setTune` is called, or when the user clicks PLAY. + +The parameter is the instance of the synthController that called it. + +- onStart() + +Called when the tune has actually started: that is, after all the set up has been completed. + +- onFinished() + +Called when the tune has finished. + +- onBeat(beatNumber, totalBeats, totalTime) + +Called each beat, or each subdivision of a beat. + + - beatNumber + +This is the current beat - in a perfect case, this is called regularly. There are various things that can cause JavaScript to stop running, though, so it might get called a bunch of times in a row to catch up. This can be a fraction, if `beatSubdivisions` is present. + +- onEvent(event) + +This is called every time a note, rest, or bar is encountered. + +The `event` parameter has these properties: + +| Property | Description | +|---|---| +| measureStart | `true` if this is the beginning of a measure. (Note, beware of the case where the only event at the beginning of a measure is a note tied from a previous note. There might not be anything to do.) | +| elements | The actual SVG elements that represent the note(s) being played. | +| left | The leftmost point of the current elements. | +| top | The topmost point of the current elements. | +| height | The height of the current elements. | +| width | the width of the current elements. | + +- onLineEnd(data) + +This is called when the end of the line is approaching. The data is the following properties: + +| Property | Description | +|---|---| +| milliseconds | The current time. | +| top | The top of the current line. | +| bottom | The bottom of the current line. | + +Use this to determine if the SVG should be scrolled. + +Example: +```javascript +var CursorControl = function() { + this.beatSubdivisions = 2; + this.onStart = function() { + console.log("The tune has started playing."); + } + this.onFinished = function() { + console.log("The tune has stopped playing."); + } + this.onBeat = function(beatNumber) { + console.log("Beat " + beatNumber + " is happening."); + } + this.onEvent = function(event) { + console.log("An event is happening", event); + } +} +var cursorControl = new CursorControl(); +synthControl = new ABCJS.synth.SynthController(); +synthControl.load("#audio", cursorControl, {displayPlay: true, displayProgress: true}); +``` + +## playEvent(pitches, gracenotes, millisecondsPerMeasure) + +This will play a single event that is passed. The event must have the same format as the events that are passed back by the click listener. + +### pitches + +An array of pitches. If there is more than one item in the array, they are played at the same time. Pitches contain: + +| Attribute | Description | +|---|---| +| pitch | An integer value of the pitch, where middle C is 60 | +| durationInMeasures | The length of the note. For instance, a quarter note in 4/4 would be .25 | +| volume | A number from 0 to 127 for the volume of the note. | +| instrument | The number of the instrument in the MIDI spec. | + +### gracenotes + +These are the same format as above, except that these notes are played before the main note for a short time. Also, if there is more than one note in the array, the notes are played sequentially. + +### millisecondsPerMeasure + +This is used to translate the `durationInMeasures` value into an actual time. + +### Example: + +```javascript +ABCJS.synth.playEvent( + [ // a C chord + {"cmd":"note","pitch":60,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + ], + [ // start with a D as a grace note + {"pitch":62,"durationInMeasures":0.125,"volume":70,"instrument":0} + ], + 1000 // a measure takes one second. +).then(function (response) { + console.log("note played"); +}).catch(function (error) { + console.log("error playing note", error); +}); + +``` + +## activeAudioContext() + +If there is an AudioContext that is being used then this retrieves it. It allows freely sharing the same one in different parts of your app. + +```javascript +var ac = ABCJS.synth.activeAudioContext(); +``` + +## instrumentIndexToName[index] + +This is an array that converts the standard MIDI instrument indexes to a name. For instance: +```javascript +console.log(ABCJS.synth.instrumentIndexToName[9]); +// "glockenspiel" +``` + +## pitchToNoteName[pitchNumber] + +This is an array that converts the standard MIDI pitch indexes to a name. For instance: +```javascript +console.log(ABCJS.synth.pitchToNoteName[60]); +// "C4" +``` + +## Tempos + +If the `qpm` parameter is not supplied, abcjs makes its best guess about what tempo should be used. If there is no tempo indicated at all in the ABC string, then 180 BPM is arbitrarily used. If `defaultQpm` is supplied, then that default will be used only if there is no explicit tempo. + +If an exact tempo line is supplied with the `Q:` line, then that tempo is used. If the `Q:` contains a standard tempo string, that string is used to make a guess at an appropriate tempo. Here is a list of the known tempo strings and their associated tempos. If you would like to make suggestions about other strings to support or changes to these tempos, please get in touch: + +|Tempo|BPM| +|---|---| +|larghissimo|20| +|adagissimo|24| +|sostenuto|28| +|grave|32| +|largo|40| +|lento|50| +|larghetto|60| +|adagio|68| +|adagietto|74| +|andante|80| +|andantino|88| +|marcia moderato|84| +|andante moderato|100| +|moderato|112| +|allegretto|116| +|allegro moderato|120| +|allegro|126| +|animato|132| +|agitato|140| +|veloce|148| +|mosso vivo|156| +|vivace|164| +|vivacissimo|172| +|allegrissimo|176| +|presto|184| +|prestissimo|210| + +## Drum parameter + +See the ABC documentation for the correct way to format the string that is passed as the drum parameter. Here is a table that provides a fairly reasonable default for drum, drumIntro, and drumBars when used as a metronome: +``` + const drumBeats = { + // the array is [0]=drum [1]=drumIntro + "2/4": ["dd 76 77 60 30", 2], + "3/4": ["ddd 76 77 77 60 30 30", 1], + "4/4": ["dddd 76 77 77 77 60 30 30 30", 1], + "5/4": ["ddddd 76 77 77 76 77 60 30 30 60 30", 1], + "Cut Time": ["dd 76 77 60 30", 2], + "6/8": ["dd 76 77 60 30", 2], + "9/8": ["ddd 76 77 77 60 30 30", 1], + "12/8": ["dddd 76 77 77 77 60 30 30 30", 1] + }; +``` +A more complicated example that has the drum pattern fall over two measures of 2/4 time (This is a typical Bulgar pattern): +``` +{ drum: "d2dd2ddz 76 77 76 77 77 60 30 60 30 30", drumBars: 2, drumIntro: 2 } +``` + +Note that the default soundfont that is used by abcjs contains sounds for pitches **27** through **87**. You can experiment with any of them for different effects. + + diff --git a/docs/deprecated/deprecated-api.md b/docs/deprecated/deprecated-api.md new file mode 100644 index 0000000000000000000000000000000000000000..3872f62df2aa69803462ae50930735804bcae971 --- /dev/null +++ b/docs/deprecated/deprecated-api.md @@ -0,0 +1,99 @@ +# Deprecated API Calls + +::: warning Deprecation +The following calls still work, but they have been superseded by other methods. If you are writing a new app, avoid these because they might go away sometime in the future. +::: + +## parseOnly + +```javascript +tuneObjectArray = ABCJS.parseOnly(tunebookString, params) +``` + +Parses all the tunes in the tunebookString and returns an array of them parsed structure. + +This has turned out to not be that useful since you can do the same effect by passing "*" in as the element and the returned value will have a lot more information. + +## Animation + +This animation has been replaced by `TimingCallbacks`, which is much more flexible. + +### startAnimation +```javascript +ABCJS.startAnimation(outputElement, tuneObject, animationParams) +``` + +Puts an animated cursor on the rendered music. Note: this is deprecated in favor of `TimingCallbacks`. + +### stopAnimation +```javascript +ABCJS.stopAnimation() +``` + +Stops the animation that was started with `startAnimation`. + +### pauseAnimation +```javascript +ABCJS.pauseAnimation(pause) +``` + +Pauses/resumes the animation that was started with `startAnimation`. Pass `true` or `false` to pause or resume. + +## Midi.js + +This has been replaced by the new audio interface. Hopefully the new interface will work on more systems, be faster, and require fewer resources. + +### deviceSupportsMidi +```javascript + ABCJS.midi.deviceSupportsMidi() +``` +Returns true if the device and browser is capable of playing MIDI. + +### setSoundFont +```javascript +ABCJS.midi.setSoundFont(url) +``` +Sets an alternate location for the soundfont. + +### renderMidi +```javascript +tuneObjectArray = ABCJS.renderMidi(output, tunebookString, params) +``` + +Completely creates midi for the tunebook. Note: this is deprecated in favor of [Synth Documentation](../docs/audio/synthesized-sound.md). + + +### startPlaying +```javascript +ABCJS.midi.startPlaying(targetEl) +``` + +Starts playing the MIDI for the element passed in. If the element is already playing, this pauses it. + +### stopPlaying +```javascript +ABCJS.midi.stopPlaying() +``` + +Stops playing whatever is currently playing. + +### restartPlaying +```javascript +ABCJS.midi.restartPlaying() +``` + +Moves the progress back to the beginning for whatever is currently playing. + +### setRandomProgress +```javascript +ABCJS.midi.setRandomProgress(percent) +``` + +Moves the progress to whatever percent is passed in for whatever is currently playing. + +### setLoop +```javascript +ABCJS.midi.setLoop(targetEl, state) +``` + +Sets the "loop" mode for the element passed in. State should be true or false. diff --git a/docs/developers/basic-architecture.md b/docs/developers/basic-architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..ab38e175dbcebddb2dfbae5018006f83aa3ad2f9 --- /dev/null +++ b/docs/developers/basic-architecture.md @@ -0,0 +1,144 @@ +# Basic Architecture + +There are two forms of this library: the npm form and a minimized form for non-npm users. + +When testing abcjs changes locally, there is no build step required. A handy way to test is to run: + +``` +npm link +``` + +On this folder, and +``` +npm link abcjs +``` + +in an npm-based test application (like React or Vue). Then any changes made to the abcjs code will automatically be picked up by that test application's webpack. + +## Docker + +There are docker files included so that npm can be run without installing it. This only applies to someone who wants to keep all their development tools separate on their computer. If you have nodejs installed then you can ignore this. + +To run, type: +```bash +./docker-start.sh +``` +That will create a linux virtual machine and give you a command line to run npm. + +## Building locally + +To build the library so that it can be included with a ` +``` + +## How do I use the sound fonts I have saved locally in my ABCJS project? + +See the [sandbox](../audio/synthesizer-sandbox.md) to see the syntax. + +## I’m trying to get the names of notes on click and it doesn’t work. I’ve got a visual rendering of my tune, but no playback widget. + +There is some optional extra information that can be added to the music structure. This is added when creating the synth but it isn't added by default so that the code isn't unnecessarily executed for users who don't need it. + +To add that information: +```javascript +var visualObj = abcjs.renderAbc( ... ) +visualObj[0].setUpAudio() +``` + +## Can I use ABCJS in React Native/other mobile development frameworks? + +ABJS needs a DOM to 1) calculate how much space the notes will take up once rendered, and 2) render the notes as an SVG. This makes integrating ABCJS with frameworks like React Native that don’t use a DOM less than straightforward. + +An approach by Matthew Dorner: His app, react-native-songbook, relies on react-native-svg (via standalone-vexflow-context) to draw to the SVG. You might also try experimenting with rendering the ABCJS elements in a React Native webview component, which supplies the required DOM, as a browser to an external page with ABCJS functionality, rather than as a native rendering engine. Kudos to @rpattcorner for this tip. + +## How do I use ABCJS in a server-side rendering app? + +If you are using Nuxt or Next or any other framework that builds the pages on the server before sending them to the client, you'll have to delay calling abcjs until you are in the client. + +Here's an example: +```javascript +const abcjs = process.browser ? require('abcjs') : null; + +if (abcjs) { + abcjs.renderAbc( ... ) +} +``` +## Why do I have to wait until the user interacts with the page to create the audio? + +Because browsers have placed constraints on AudioContexts to prevent developers from building websites that autoplay sound on page load, you can’t begin to buffer audio until the user makes some gesture on the page. + +## How do I get the audio to start right away? + +There is some network traffic and construction of the sound buffer that takes some time. This can be done a little bit ahead of time, so, as soon as the user interacts with the page and as soon as you know what music you want to play, you can begin the process. You'll notice that there are `init()` and `prime()` calls that return a promise. After those promises resolve clicking `play()` will be fast. + +## How do I prevent this error?: “The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on the page.” + +Don't call any audio-related functionality until the user has interacted with the page. + +## I want to replace all the notes with custom glyphs/symbols. How can I do that? + +After your call to renderAbc(), search for all items with the class "abcjs-note" and replace the path in them with your picture. +For this to work, call renderAbc with the parameter { add_classes: true }. Notice there are other things you can search for, like the staff line of the note so you can have different pictures for different notes. + +## How can I manipulate the horizontal positioning of the notes — e.g., if I want the notes to be horizontally centered? + +You can use `%%staffwidth 400` to pick the number of pixels that the staff will take. You can experiment with that to get it to look right. You can also use `%%stretchlast` to stretch the last line of music out. Also, `staffwidth` can be passed in to the `renderAbc` function. Also, see the wrap.html demo. + +## I’m having difficulty transposing my tune. +Visual and audio transposition are handled separately to suit situations where a musician might want the sheet music transposed but not the audio playback, and vice-versa. For examples of how to handle both forms of transposition, see basic-transpose.html and editor-transpose.html. diff --git a/docs/overview/getting-started.md b/docs/overview/getting-started.md new file mode 100644 index 0000000000000000000000000000000000000000..20576af1530e7fc065ba324abbed56e471616c4d --- /dev/null +++ b/docs/overview/getting-started.md @@ -0,0 +1,55 @@ +# Getting Started + +## Which flavor should you use? + +### node.js + +If you are in the node ecosystem, simply install the packaged version with: + + ``` + npm install --save abcjs +``` + +To import, use: +``` +import abcjs from "abcjs"; +``` + +To get the styles for the audio control: + +``` +import 'abcjs/abcjs-audio.css'; +``` + +### Old-style minimized download + +If you are writing significant JavaScript on your site, and you are generating the music yourself, or you are allowing the user to enter music using ABC notation, whether a whole tune or a fragment, then you probably want to use `abcjs-basic`. This gives you control over the generation in a smaller package. + +If you already have ABC notation on your page and don't want to modify the page more than you have to, then you can use `abcjs-plugin`, which will render all ABC that it finds on the page on page load, simply by including one line: the line to include the script. Another use of this is if you have a comment section on a blog, then you can allow users to post ABC tunes and they will appear as sheet music automatically. + +If you are looking at someone else's website and see ABC on the page and want to see what it looks like in standard notation, you can install the greasemonkey script in FireFox or Chrome and it will render the ABC for you. + +Here are the latest versions. You can just download one of these: + +- [Basic](https://raw.githubusercontent.com/paulrosen/abcjs/main/dist/abcjs-basic-min.js) + +- [Plugin](https://raw.githubusercontent.com/paulrosen/abcjs/main/dist/abcjs-plugin-min.js) + +- [Styles for the Audio control](https://raw.githubusercontent.com/paulrosen/abcjs/main/abcjs-audio.css) + +**NOTE: Do NOT link to these files directly! Upload them to your own server! [Here's why.](https://github.com/blog/1482-heads-up-nosniff-header-support-coming-to-chrome-and-firefox)** + +You can also use this CDN: `https://cdn.jsdelivr.net/npm/abcjs@VERSION/dist/abcjs-basic-min.js` where `VERSION` is the version of the library you want to use. Note that I don't maintain those files, so I can't guarantee that will still work in the future. + +When loading the library directly, you will find the library at `window.ABCJS`. + +## Simplest Usage + +```html +
+``` + +```javascript +abcjs.renderAbc("paper", "X:1\nK:D\nDD AA|BBA2|\n"); +``` + \ No newline at end of file diff --git a/docs/overview/purpose.md b/docs/overview/purpose.md new file mode 100644 index 0000000000000000000000000000000000000000..0a3c7fda5e3715e9bf6c30a7bdfdd2365754b474 --- /dev/null +++ b/docs/overview/purpose.md @@ -0,0 +1,49 @@ +# Purpose + +This library makes it easy to incorporate **sheet music** into your **websites**. It is primarily aimed at **javascript developers**. The amount of javascript required for simple uses is very small, though, so one doesn't need to be an expert. + +## Uses + +* Draw arbitrary sheet music. + +* Instantly modify the music. + +* Do animation effects with the drawn music. + +* Style the music using CSS. + +* Create synthesized audio for the music. + +* Search for **ABC** formatted strings on a webpage (for instance in post or comments on a blog) and turn them into sheet music. + +* Allow the user to modify the music instantly by typing an **ABC** string. + +## NOT + +* It does not provide a visual editor (although it could be used as a basis if you want to write one.) + +## Open Source Apps that require no programming + +* If you just want to render your abc but aren't trying to make an entire website, there are a number of openly accessible apps that you can use. + +* For working with tunes in the browser, see [the Editor](https://editor.drawthedots.com). + +* Also see Michael Eskin's [ABC Tools](https://michaeleskin.com/abctools/abctools.html) for a much more fully-featured editor. + +* If you are using VSCode, there is an extension that you install. Get to the extension panel with shift-cmd-X, then search for "abcjs". Once you install that you can see the notation rendered as you type in a file. To use it, open your tune file (ending in `.abc`) and type shift-cmd-P to get the command pallet. Search for "abcjs" to open the preview pane. + +## Browser/device support + +* The visual part of this library is supported from IE9 and newer, Safari 5.1 and newer, and all modern browsers. + +* This synth audio part of this library does not work on IE, but works on any system that supports `AudioContext.resume` and `Promises`. That is, any browser newer than Firefox 40, Safari 9.1, Edge 13, and Chrome 43. + +## Supported by BrowserStack + +If you aren't using the same browser and machine that I use, you can thank [BrowserStack](https://browserstack.com/) for their support of this open-source project. + +![BrowserStack](https://paulrosen.github.io/abcjs/img/browserstack-logo-600x315.png) + +## License + +[The MIT License (MIT)](http://opensource.org/licenses/MIT) diff --git a/docs/overview/resources.md b/docs/overview/resources.md new file mode 100644 index 0000000000000000000000000000000000000000..0c1f1d5b49b8007f5e223802432597f44df75fd3 --- /dev/null +++ b/docs/overview/resources.md @@ -0,0 +1,20 @@ +# Other Resources + +[abcjs Home page](https://abcjs.net) (Overview of what this library does) + +[Configurator](https://configurator.abcjs.net) (Experiment with all configuration options) + +[API Documentation](api.md) (All the details about using abcjs) + +[Special Notes](special-notes.md) (Notes from previous versions) + +[Old Versions of abcjs](https://github.com/paulrosen/historical-abcjs-versions) (All previous versions of abcjs) + +[Info for abcjs contributors](contributing.md) (Info about how abcjs is built and managed) + +[Support of the ABC standard](abc-notation.md) (How abcjs varies from the ABC standard) + +[Release Notes](RELEASE.md) + +License: [The MIT License (MIT)](http://opensource.org/licenses/MIT) + diff --git a/docs/plugin/plugin.md b/docs/plugin/plugin.md new file mode 100644 index 0000000000000000000000000000000000000000..44390f22f8d08a9826fb6b6ff32efea2f8c869e3 --- /dev/null +++ b/docs/plugin/plugin.md @@ -0,0 +1,42 @@ +# Plugin + +The abcjs plugin renders all the abc in a page (determined as a new line beginning with X:). + +::: tip TODO +This page is currently being enhanced. Check back soon! +::: + +To use, simply include the plugin version in the page: + +```javascript + +``` + +Certain options for the plugin can be changed like this, if executed on page load, just after including the plugin file: + +```html + +``` + +The options available in abc_plugin are: + +| Option | Description | +| ------------- | ----------- | +| `show_midi` | NO LONGER SUPPORTED: This option has been removed. | +| `hide_abc` | Whether the abc text should be hidden or not. (false by default) since 1.0.2 | +| `render_before` | Whether the rendered score should appear before the abc text. (false by default) since 1.0.2 | +| `midi_options` | NO LONGER SUPPORTED: This option has been removed. | +| `auto_render_threshold` | Number of tunes beyond which auto rendering is disabled; instead, each tune is accompanied by a "show" button. (default value is 20) since 1.0.2 | +| `show_text` | Text to be included on the "show" button before the tune title. (default value is "show score for: ") since 1.0.2 | +| `render_options` | The options to be used for the `engraverParams` | +| `render_classname` | The class name to use for the resulting SVG (default value is "abcrendered") | +| `text_classname` | The class name to use for wrapping the found ABC text (default value is "abctext") | + +When abcjs plugin finds an abc tune, it wraps a `div.abctext` around it and renders it into a `div.abcrendered`. The show button is an `a.abcshow`. These hooks can be used for styling. since 1.0.2 + + +## abcjs greasemonkey script + +Just include the greasemonkey script in either FireFox or Chrome. You will then get a button that will begin the scan of the website. diff --git a/docs/transposing/transposing.md b/docs/transposing/transposing.md new file mode 100644 index 0000000000000000000000000000000000000000..b88c4298ae4ae4ec64dba627876bd22ed8571014 --- /dev/null +++ b/docs/transposing/transposing.md @@ -0,0 +1,52 @@ +# Transposition + +The ABC string can be transposed to another key and output as a string. + +Note that there are other forms of transposition using parameters to the `abcRender` call, but they do not output a string that can be used elsewhere. If you want to visually transpose you can use [visualTranspose](/abcjs/visual/render-abc-options.html#visualtranspose). If you want to transpose just the audio, use [midiTranspose](/abcjs/audio/synthesized-sound.html#audioparams) + +## Entry Point + +``` +var newAbc = ABCJS.strTranspose(originalAbc, visualObj, steps) +``` + +## Parameters + +All parameters are required + +### originalAbc +Default: none + +This is the ABC string that you want to transpose. + +### visualObj +Default: none + +This is the result of rendering that string. You must call `renderAbc()` before calling `strTranspose()`. + +### steps +Default: none + +This is the number of half steps to transpose. It can be positive to transpose higher in pitch or negative to transpose lower. It can be `12` or `-12` to transpose an octave and can be larger than that to transpose more than an octave. + +## Example +``` +var abc = "K:D\nDEFG|" +var steps = 2 +var visualObj = abcjs.renderAbc("paper", abc); +var output = abcjs.strTranspose(abc, visualObj, steps) +``` + +For a full example, see the [Transposition Example](https://paulrosen.github.io/abcjs/examples/output-transpose.html). + +## Notes + +There are some types of ABC that cannot be transposed and because of the wide range of ways to write ABC there are some ABC strings that won't be transposed completely. + +For instance, lines that have a clef of "perc" will not be transposed. And some ABC strings contain notes and chords in the lyric line or using the "annotation" syntax. Those will not be transposed. + +That said, most input strings use a subset of the full ABC spec and are transposed completely. + +::: tip Bugs +This is a new feature and because of the complexity of the ABC specification there are bound to be bugs! Please let me know if you see an example of something that *should* have been transposed but wasn't. +::: diff --git a/docs/upgrading/midijs.md b/docs/upgrading/midijs.md new file mode 100644 index 0000000000000000000000000000000000000000..5191231549b8b5426bcb6b198c961d77191424e5 --- /dev/null +++ b/docs/upgrading/midijs.md @@ -0,0 +1,57 @@ +# MIDI.JS Usage + +## Midi.js removed + +The last version supporting this interface is 6.0.0-beta.28. + +## Notes for Version 5.8.0 + +This way of creating midi is being deprecated by a new method that is much smaller, less buggy and integrates with the TimingCallbacks object. This will continue to work for the forseeable future, but probably won't change much. + +See [Synth Documentation](../audio/synthesized-sound.md) for details. + +## Notes for Version 3.0 Beta + +There are a number of features described below that are not yet activated. This release is primarily to get the main MIDI functionality working. Here is a list of features you can look forward to in upcoming versions: + +* Changing the instrument and channel in the midi file: right now, one channel is used, and the instrument is Grand Piano. + +* Changing the tempo is not available. + +* The listener doesn't return much information: look for much more to come. + +* The "play selection" functionality is not implemented. + +* The "bouncing ball" functionality is not implemented. + +## MIDI generation in ABCJS + +There are two ways to generate MIDI: as a download link, and as an inline control. The download link method is built into ABCJS. The inline control, though, depends on the external library [MIDI.js](https://github.com/mudcube/MIDI.js) + +That, in turn, is dependent on a set of sound fonts. A good place to get them is [MIDI.js Soundfonts](https://github.com/paulrosen/midi-js-soundfonts) + +## Site Setup + +* Use the version of the library that contains midi.js. + +* The soundfonts, by default, are served from github. If you would like host them yourself, put them on your server in a publicly accessible place and call: + +`window.ABCJS.midi.setSoundFont("/url/to/soundfont/");` + +The trailing slash is required. + +There is also some CSS required to make the MIDI control look right. You can use the example CSS in this repository and modify it to match your site. The example CSS uses Font Awesome. Include these two lines: + +`` +`` + +## Creating the MIDI + +After doing the above steps to load the CSS and the sound fonts, the simplest way to produce the MIDI is: + +`window.ABCJS.renderMidi("id-of-div-to-place-midi-controls", abcString, {}, { generateInline: true }, {});` + + +## Example + +See the examples in this repository. They contain the prerequisite files. [Editor](/examples/editor-midi.html), [Printable](/examples/printable.html). diff --git a/docs/upgrading/release-notes.md b/docs/upgrading/release-notes.md new file mode 100644 index 0000000000000000000000000000000000000000..56abe82a2b44ace2a0736a01a4229ac0d2f86dbe --- /dev/null +++ b/docs/upgrading/release-notes.md @@ -0,0 +1,163 @@ +# Release Notes + +Full release notes can be found in the [RELEASE.md](https://github.com/paulrosen/abcjs/blob/main/RELEASE.md) file. + +## Notes for Version 6.0.0 + +There has been a large change in the underlying SVG output. It should look exactly the same, but it will take up much less space. Also a number of inconsistencies in the way classes are applied has been fixed. If you are just using the library to output standard notation, you probably won't notice any difference. However, if you are querying the SVG directly, or setting CSS that targets elements, then you will need to retest. + +There has been a change to the data that comes back from the click listener. This includes information if the user has dragged a note. + +There are some minor improvements to the spacing of elements. That may slightly change how the music is laid out. + +There have been many improvements to the audio quality. More improvements are coming, too! + +## Notes for Version 5.9.0 + +This is a beta version of the new synth method. It is likely there will be some changes to the API in the short run but hopefully not too much. + +Please try this out and report any issues that you have. + +## **Special note for Version 5.8.0:** + +A new method for creating synth sound is included as a **beta** release in this release. See [Synth Documentation](../docs/audio/synthesized-sound.md) for details in how to use it. That means that the current method of creating sound using midi.js will be deprecated at some point in the future. It will be supported in its current form as long as possible. The new synth is much smaller and faster and appears less buggy. It does not work on IE 11 or older browsers, however, so it might not yet be appropriate for your site. + +## Special notes for Version 5.0.0: + +* The dependency on the Raphael library has been removed! This has made the minimized package 90K smaller, and has increased the speed of generating the SVG image by about 6 times! + +For the most common use of creating either the sheet music or the audio, there isn't any change. + +However, if you use the animation callback in the audio to manipulate the notes, then be aware that, instead of receiving elements that are wrapped in a Raphael object, you now receive the actual +SVG element. For the most common example of the animation functionality, the following was recommended to change the color of notes: +``` +element.attr({ fill: color }); +``` +That should be changed to: +``` +element.setAttribute("fill", color); +``` + +* If you do specific manipulation of the SVG, you will need to retest your code. The generated SVG, while it looks the same on the page, has changed somewhat. The selectors you use may return different results. + +## Special notes for Version 4.0.0: +* **BREAKING CHANGE**: The names of all the classes that are generated are now prefixed with `abcjs-`. Any code that searched for particular class names before will have to be adjusted. + +* The parameters have been combined into one set of parameters, instead of three sets like previous versions. The old way of calling the parameters will still work, but you are encouraged to use the new, simplified approach going forward. + +## Special notes for Version 3.3.0: + +* The build process has switched over to webpack. The minimization is now done with UglifyJS. This shouldn't cause any side effects. + +* The "editor" version of the library has been rolled into the "basic" version. There is only the "basic" and "midi" versions now, since the editor code doesn't add much to the size. + +* The npm version has a new export called "signature" that gives your javascript code some version information. + +* The documentation has all been moved to the `/docs` folder. + +* The examples have all been moved to the `/examples` folder. + +## Special note for Version 3.2.0: + +abcjs is proud to announce that it can now be installed with `npm`. Instead of including the minimized files on your page, you can use the library by doing the following in your project: +```bash +npm install --save abcjs +``` + +Note that the minimized versions will still be maintained, so you can still copy the minimized file to your project. + +## Special notes for Version 3.0: + +In-browser [MIDI](/docs/upgrading/midi.md) is now supported. There are some extra dependencies when using that feature. Downloadable MIDI is still supported with no extra dependencies. + +## API Changes for Version 3.0 + +* Added viewPortHorizontal and scrollHorizontal to the renderParams. +* Add class "slur" to slurs and ties. +* Add "hint measure" +* Allow scrolling in the animation. +* Handle %%titlecaps directive. +* Add curly brace to indicate piano part (with inspiration from Anthony P. Pancerella). +* Add invisible marker to the top of each system so that it can be found easily. +* Add an option to put each line in a separate svg so that browsers will paginate correctly. + +## API Changes for Version 3.0 Beta + +* The default MIDI program has been changed to "0". + +* There are a number of new MIDI parameters. + +## API Changes for Version 1.11 + +"Bouncing Ball" cursor: + + ABCJS.startAnimation(paper, tune, options) + paper: the output div that the music is in. + tune: the tune object returned by renderAbc. + options: a hash containing the following: + hideFinishedMeasures: true or false [ false is the default ] + showCursor: true or false [ false is the default ] + bpm: number of beats per minute [ the default is whatever is in the Q: field ] + +`renderABC()` now returns the object that was created by the process. This allows further processing. + +`highlight()` and `unhighlight()` now can be passed an optional class name and color. + +Descriptive classes to all SVG elements: If you include `{ add_classes: true }` in the rendering params, +then a set of classes are applied to each SVG element so they can be manipulated with css. + +## API Changes for Version 1.3 + +There is a new public entry point that is designed for those who want some information about what is in a tunebook before processing it. + +```JavaScript +// Tunebook is the contents of the text file containing one or more +// ABC-formatted tunes, plus global header info, and inter-tune text. +var book = new ABCJS.TuneBook(tunebook); + +var fileHeader = book.header; +var numberOfTunes = book.tunes.length; + +for (var i = 0; i < numberOfTunes; i++) { + var title = book.tunes[i].title; + var tuneAndHeader = book.tunes[i].abc; + var justTheTune = book.tunes[i].pure; + var id = book.tunes[i].id; +} + +var tune = book.getTuneById(id); +tune = book.getTuneByTitle(title); +``` + +The variable `book` contains: + +| Member | Description | +| ------------- | ----------- | +| book.header | This is all of the text that appears before the first tune starts in the file. | +| book.tunes.length | This is how many tunes are in that file. | +| book.tunes[i].title | This is the first title found for the particular tune. White space is trimmed from both the beginning and end. | +| book.tunes[i].abc | This is the particular tune with the global header information added to it. This is what should be passed to the parser in most cases. | +| book.tunes[i].pure | This is the particular tune without the header. | +| book.tunes[i].id | This is the id (that is, the text on the X: line). White space is trimmed from both the beginning and end. | +| book.getTuneById | This will find the FIRST tune in the tune book with the id. | +| book.getTuneByTitle | This will find the FIRST tune in the tune book with the title. | + + +## API Changes for Version 1.1 + +IMPORTANT: Version 1.1 has removed all globals and any side effects of ABCJS except for this single global: + +```JavaScript +window.ABCJS +``` + +This means that you will have to modify your pages to use the new syntax. All of the old entry points are still available with a slightly different name. Here is a list of all recommended entry points: + +|New name|Old name| +| ------------- | ----------- | +|ABCJS.numberOfTunes|numberOfTunes| +|ABCJS.renderAbc|renderABC| +|ABCJS.renderMidi|renderMidi| +|ABCJS.Editor|ABCEditor| +|ABCJS.plugin|abc_plugin| + diff --git a/docs/visual/classes.md b/docs/visual/classes.md new file mode 100644 index 0000000000000000000000000000000000000000..d5e57a30d44af67e440f0fb207838ecffa07fe93 --- /dev/null +++ b/docs/visual/classes.md @@ -0,0 +1,112 @@ +# Classes + +## Class Names + +If you use, `{ add_classes: true }`, then the following classes are attached to various elements: + +| class | description | +| ------------- | ----------- | +| abcjs-annotation | Text added with the `"^..."` format. | +| abcjs-author | The author text | +| abcjs-bar | The bar lines. | +| abcjs-bar-number | The bar numbers. | +| abcjs-beam-elem | The beams connecting eighth notes together. | +| abcjs-brace | The brace on the left side of the staff (like for piano music.) | +| abcjs-bracket | The bracket on the left side of the staff. | +| abcjs-chord | The chord symbols, specified in quotes. | +| abcjs-clef | All clefs | +| abcjs-composer | The composer text | +| abcjs-d0-25, etc. | The duration of the note. (Replace the dash with a decimal point. That is, the example is a duration of 0.25, or a quarter note.) | +| abcjs-decoration | Everything to do with the extra symbols, like crescendo. | +| abcjs-defined-text | Text that appears between the lines of music, created with `%%text`. | +| abcjs-dynamics | The dynamics markings: `p` for instance. Also the crescendo mark. | +| abcjs-end-m0-n0 | Added to slurs to indicate the ending note. | +| abcjs-ending | The line and decoration for the 1st and 2nd ending. | +| abcjs-key-signature | All key signatures | +| abcjs-l0, abcjs-l1, etc. | (lower case L, followed by a number) The staff line number, starting at zero. | +| abcjs-ledger | ledger line. | +| abcjs-lyric | The lyric line. | +| abcjs-m0, abcjs-m1, etc. | The measure count from the START OF THE LINE. | +| abcjs-mm0, abcjs-mm1, etc. | The measure count from the START OF THE TUNE. | +| abcjs-meta-bottom | Everything that is printed after all the music. | +| abcjs-meta-top | Everything that is printed before the first staff line. | +| abcjs-n0, abcjs-n1, etc. | The note count from the START OF THE MEASURE. | +| abcjs-note | Everything to do with a note. | +| abcjs-note_selected | This is the element that the user has clicked on. | +| abcjs-p-1, abcjs-p1, etc. | The y-position of the note (where middle-C is zero). | +| abcjs-part | Each part marking in the music itself. | +| abcjs-part-order | The part order indicator at the top. | +| abcjs-rest | Everything to do with a rest. | +| abcjs-rhythm | The rhythm text. | +| abcjs-slur | Slurs and ties. (backwards compatible) | +| abcjs-start-m0-n0 | Added to slurs to indicate the beginning note. | +| abcjs-tie | Tie. | +| abcjs-legato | Slur. Because "abcjs-slur" was historically used to indicate either a slur or a tie this indicates only a slur. | +| abcjs-staff | The horizontal lines that make up the staff. | +| abcjs-staff-extra | Clefs, key signatures, time signatures. | +| abcjs-stem | | +| abcjs-subtitle | The subtitle, both on the top and inserted in the middle | +| abcjs-symbol | Any special symbol, like a trill. | +| abcjs-tempo | The tempo marking. | +| abcjs-text | Extra text that is not part of the music. | +| abcjs-time-signature | All time signatures | +| abcjs-title | The line specified by T: | +| abcjs-top-line | This marks the top line of each staff. This is useful if you are trying to find where on the page the music has been drawn. | +| abcjs-top-of-system | This marks the top of each set of staves. This is useful if you are trying to find where on the page the music has been drawn. | +| abcjs-triplet | The extra markings that indicate a triplet. (But not the notes themselves.) | +| abcjs-unaligned-words | Lyrics at the bottom that aren't lined up with notes. | +| abcjs-v0, abcjs-v1, etc. | the voice number, starting at zero. | + +## Test Tune + +Paste in any ABC you want here and see how that affects the classes below: + + + + + +## Found Classes + +Select the following classes to see what they point to. (They are ANDed together.) + + + +## CSS Possibilities + +### changing colors + +If you want to just change everything to one other color, you can do something like: +``` + + + + + + + +
+ abcjs logo +

Accompaniment

+
+
+

This shows different options for tweaking the sound of the accompaniment.

+
+

Pattern:

+ + + + +
+
+

Stress:

+ + + +
+
+

Duration:

+ + + + +
+
+

Melody:

+ + +
+
+
+

Add this to your code:

+

+		
+
+ + + \ No newline at end of file diff --git a/examples/add-note-names.html b/examples/add-note-names.html new file mode 100644 index 0000000000000000000000000000000000000000..4a0df76531b0c09199d39a43c7b5bf20592fd2c8 --- /dev/null +++ b/examples/add-note-names.html @@ -0,0 +1,71 @@ + + + + + + + + + + abcjs: Add Note Names + + + + +
+ abcjs logo +

Add note names

+
+
+

This will add note names underneath all the pitches. The trick is to create a dummy lyric line with "w: Z Z Z ..." then after the tune is rendered to replace the text with the note name. You can make the "w:" longer than the number of notes - any lyrics that are extra are just ignored. You can use two characters for each of the lyrics if you want to allow more space.

+
+
+ + diff --git a/examples/analysis.html b/examples/analysis.html new file mode 100644 index 0000000000000000000000000000000000000000..e7707be1fb88414975c73bf58fe2c53545987c6f --- /dev/null +++ b/examples/analysis.html @@ -0,0 +1,116 @@ + + + + + + + + + + abcjs: Analysis Demo + + + + + +
+ abcjs logo +

Analyze tune book string

+
+
+

Given the following ABC string, the following information can be gleaned from it.

+

ABC String

+

+	  

Creation

+

var tuneBook = new ABCJS.TuneBook(abc)

+

Header

+

tuneBook.header

+

+	  

Number Of Tunes

+

tuneBook.tunes.length

+

+	  

Tune Names

+

tuneBook.tunes[i].title

+

+	  

Tune Ids

+

tuneBook.tunes[i].id

+

+	  

Tune "101"

+

tuneBook.getTuneById(101)

+

+	  

Tune "Cooley's"

+

tuneBook.getTuneByTitle("Cooley's")

+

+	  

Parse Only - warnings

+

var visualObj = ABCJS.parseOnly(abc)

+

+  
+ + diff --git a/examples/animation.html b/examples/animation.html new file mode 100644 index 0000000000000000000000000000000000000000..7a49569c0a740b6a8c7e7d47b7794efa92519a68 --- /dev/null +++ b/examples/animation.html @@ -0,0 +1,184 @@ + + + + + + + + + + abcjs animation + + + + + + +
+ abcjs logo +

Animation

+
+
+
+
+ + + + +
+
+
+
+
+ + + + diff --git a/examples/annotating.html b/examples/annotating.html new file mode 100644 index 0000000000000000000000000000000000000000..1afa38326cdd1fde3f0628a8985751bde80c7c26 --- /dev/null +++ b/examples/annotating.html @@ -0,0 +1,204 @@ + + + + + + + + + + abcjs: Annotating + + + + + +
+ abcjs logo +

Annotation

+
+
+

+ This is a simple example of how elements can be found on the page and how + annotations can be drawn. +

+

1) Note that annotations above the staves are written into the ABC notation using the syntax "^text".

+

2) Note that room is created for annotations below the staves by creating a w: line and then setting its fill to none on the stylesheet.

+

3) Note that the notes are retrieved by their unique class and their position is retrieved. When the music is changed, + this will automatically move the drawn lines to the right place.

+
+ + + +

Note that \ is a character which must be escaped in JavaScript.

+
+ + diff --git a/examples/basic-synth.html b/examples/basic-synth.html new file mode 100644 index 0000000000000000000000000000000000000000..41d57e21fa645bf4854354aadca41382bb7306cf --- /dev/null +++ b/examples/basic-synth.html @@ -0,0 +1,148 @@ + + + + + + + + + + abcjs: Basic Synth Demo + + + + + + + +
+
+ abcjs logo +

Basic Synth

+
+
+
+

Browsers won't allow audio to work unless the audio is started in response to a + user action. This prevents auto-playing web sites. Therefore, the + following button is needed to do the initialization:

+
+
+ + + +
+
+
+
+
+ + diff --git a/examples/basic-transpose.html b/examples/basic-transpose.html new file mode 100644 index 0000000000000000000000000000000000000000..fc1a800fb13315a40ea38972fe8ba35aec45bb29 --- /dev/null +++ b/examples/basic-transpose.html @@ -0,0 +1,116 @@ + + + + + + + + + + abcjs: Basic Demo + + + + + + + +
+ abcjs logo +

Basic Transposition

+
+
+

This transposes either/both the visual music and the synth.

+ + + + +
+
+
+
+ + diff --git a/examples/basic.html b/examples/basic.html new file mode 100644 index 0000000000000000000000000000000000000000..77a488339b9109942a30c8103e22d9841e1bc6e3 --- /dev/null +++ b/examples/basic.html @@ -0,0 +1,39 @@ + + + + + + + + + + abcjs: Basic Demo + + + + +
+ abcjs logo +

Basic abcjs

+
+
+

On page load, a hard-coded tune is displayed on the page.

+
+
+ + diff --git a/examples/change_glyphs.html b/examples/change_glyphs.html new file mode 100644 index 0000000000000000000000000000000000000000..079a97ffe2f6ec6b7c124b0c63ace98a42a3cf15 --- /dev/null +++ b/examples/change_glyphs.html @@ -0,0 +1,116 @@ + + + + + + + + + + abcjs: Glyph Demo + + + + +
+ abcjs logo +

Changing Glyphs

+
+
+

+ This is an example of modifying the font used to create the music. In this case, the quarter sharp + and quarter flat symbols are changed to the Koron and Sori symbols. +

+

+ The path supplied must use relative positioning. +

+

+ NOTE: Use this feature at your own risk because the format of the font may change in the future! +

+
+
+
+ + diff --git a/examples/dragging.html b/examples/dragging.html new file mode 100644 index 0000000000000000000000000000000000000000..8da2f2c5b44441f0d0e80adc1cbdfe8dafef790f --- /dev/null +++ b/examples/dragging.html @@ -0,0 +1,282 @@ + + + + + + + + + + abcjs: Drag And Drop Demo + + + + + +
+ abcjs logo +

Drag and drop demo

+
+
+
+
+
Render Time: ms
+
SVG size: K
+
    +
  • +
    When checked, then text is not selectable on the music and notes are draggable. You can + also navigate through the music with the tab key, and change notes with the up and down arrows. When not checked, then items are selectable, but not draggable. +
    +
  • +
  • +
    When this is checked, nearly every visible thing is selectable. When it is not checked, then only + the notes, rests, and measures are selectable. +
    +
  • +
  • +
+
+
+

Last Click

+
+ +
+
+
+
+
+

Output

+
+
+
+

Source

+
+
+
+
+ + diff --git a/examples/editor-swing.html b/examples/editor-swing.html new file mode 100644 index 0000000000000000000000000000000000000000..e3ce1dabfd9e45b306c2567442695dcc6ba40ae8 --- /dev/null +++ b/examples/editor-swing.html @@ -0,0 +1,131 @@ + + + + + + + + + + + + Swing with Editor + + + + + + +
+ abcjs logo +

abcjs swing with editor

+
+
+
+

This demonstrates the addition of swing in a tune. + The swing is added to the MIDI player, so it is not reflected in the sheet music. + The swing is represented as a percentage of the duration of the first note to the + duration of the pair of eighth notes. + The default is 50%, which is no swing; both eights have the same duration and the ratio is 1:1. + 66% is a common value for triplet-swing, with a 2:1 ratio of the first note to the second. + Other common swing values are 60% which corresponds to a 3:2 ratio of the first note to the second and 57% + which corresponds to a 4:3 ratio. + The maximum value of swing is 75; in this case the first half of the pair is 3 times longer than the second + half. + resulting to a 3:1 ratio or a dotted-eighth followed by a sixteenth note. +

+

In the example below, change the value of the swing to see how it matches of differs from the explicit + triplets of dotted eighth and sixteenth notes. +

+

+ Swing is supported in all X/4 and X/8 meters. In a X/8 meter the sixteenths swing. + You can try a X/8 meter by changing the meter in the ABC string to "M: 4/8 + L: 1/16" + However, meter changes in the middle of a tune do not affect the main swing duration. +

+
+

ABC String

+ +
+ Options +

Swing Parameters

+ +
+
+ +
+

Output

+
+
+
+
+ +
+

Code Sample

+
+        
+
+
+
+ + + \ No newline at end of file diff --git a/examples/editor-synth.html b/examples/editor-synth.html new file mode 100644 index 0000000000000000000000000000000000000000..a935ad0a61309f70b6407a3347514a7a6384b445 --- /dev/null +++ b/examples/editor-synth.html @@ -0,0 +1,90 @@ + + + + + + + + + + abcjs: Editor and Synth Demo + + + + + +
+ abcjs logo +

Synth with Editor

+
+
+ + +
+
+
+
+
+ + +
+ + diff --git a/examples/editor-transpose.html b/examples/editor-transpose.html new file mode 100644 index 0000000000000000000000000000000000000000..8e186185ac32cee74e17c7b5c90fda80296f052e --- /dev/null +++ b/examples/editor-transpose.html @@ -0,0 +1,174 @@ + + + + + + + + + + + Transposition with Editor + + + + + +
+ abcjs logo +

abcjs transposition with editor

+
+
+
+

This demonstrates the different methods of transposition. It is possible to transpose by inserting commands + into the ABC string or by using parameters when calling the rendering function. When using the parameters the transposition always adds to whatever the ABC string says. When inserting strings into ABC that always uses the last transposition encountered.

+
+

ABC String

+ +
+ Options +

Transpose Parameters

+

These are added to whatever is in the tune (in half steps)

+ + +

ABC Insertions

+

These all do the same thing.

+ + + +
+
+ +
+

Output

+
+
+
+
+ +
+

Code Sample

+
+
+        
+
+
+
+ + diff --git a/examples/editor.html b/examples/editor.html new file mode 100644 index 0000000000000000000000000000000000000000..9cc641e90333533639889a204703ca1e33696c64 --- /dev/null +++ b/examples/editor.html @@ -0,0 +1,46 @@ + + + + + + + + + + Basic Editor + + + + +
+ abcjs logo +

abcjs editor

+
+
+ + +
+
+
+
+ + diff --git a/examples/examples-styles.css b/examples/examples-styles.css new file mode 100644 index 0000000000000000000000000000000000000000..dcaba0f2661aa0c1a6a225ac73f02961396f3d7d --- /dev/null +++ b/examples/examples-styles.css @@ -0,0 +1,174 @@ +* { + box-sizing: border-box; +} + +@font-face { + font-family: Roboto; + font-style: normal; + font-weight: 300; + src: url(https://fonts.gstatic.com/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fCRc4AMP6lbBP.woff2); +} + +body { + padding: 20px; + font-family: Roboto, Arial, sans-serif; + font-size: 16px; + line-height: 22px; + color: currentColor; +} + +header { + margin-bottom: 30px; +} + +.container { + max-width: 700px; +} + +main { + padding: 0; +} + +fieldset { + margin: 15px 0; +} + +fieldset p { + padding: 0; + margin: 0; + color: #6d6d6d; + font-style: italic; +} + +textarea { + width: 100%; + border: 2px solid currentColor; + height: 200px; + padding: 10px; +} + +#warnings { + color: #dd0000; + padding: 5px 0; +} + +#warnings:empty { + display: none; +} + +.audio-error { + color: red; + border: 2px solid red; + padding: 10px; +} + +pre, span.code { + font-size: 14px; + padding: 5px; + font-family: Monaco, monospace; + border: 1px solid #ffffff; +} + +a { + color: currentColor; +} + +a:visited { + color: currentColor; +} + +h1 { + margin: 20px 0; +} + +h2 { + margin-bottom: 5px; +} + +p { + text-align: start; +} + +input { + font-size: 1em; +} + +button { + margin: 0 4px; + padding: 5px 10px; + font-size: inherit; + background: #e2fdf1; + border: 1px solid #5c5c5c; + border-radius: 4px; +} + +button:hover { + background-color: #3FB07C; + color: white; + border-color: #3FB07C; +} + +.visible-background svg { + background: #f5f5f5; +} + +table { + border-spacing: 0; +} + +th { + text-align: start; +} + +th, td { + border: 1px solid #dfe2e5; + padding: 16px 20px; +} + +tr:nth-child(even) { + background-color: #F6F8FA; +} + +.table-header td { + background: #adf3f3; + font-size: 22px; +} + +@media only screen and (min-width:700px) { + + header { + max-width: 1000px; + margin: 0 auto 30px auto; + padding: 0 30px; + } + + /* center content on wider screens */ + .container { + margin: 0 auto; + max-width: 770px; + } + + h1 { + text-align: center; + } + + table { + margin: 0 auto; + } +} + +@media (prefers-color-scheme: dark) { + body { + color: #ffffff; + background: #121212; + } + .table-header td { + color: #000000; + } + tr:nth-child(even) { + background-color: #666666; + } + .visible-background svg { + background: #404040; + } +} diff --git a/examples/favicon.ico b/examples/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ac52440052f8c6ac3f5f33d4a1763f72ab332874 Binary files /dev/null and b/examples/favicon.ico differ diff --git a/examples/full-synth.html b/examples/full-synth.html new file mode 100644 index 0000000000000000000000000000000000000000..daa6c15d6acb93ee54ee1b8e4fcfc9edc95485c6 --- /dev/null +++ b/examples/full-synth.html @@ -0,0 +1,344 @@ + + + + + + + + + + abcjs: Synth Demo + + + + + + + + +
+ abcjs logo +

Full Synth

+
+
+
+

This is a complete demo of what you can do with matching audio with the visual music.

+

Different pieces of music are provided to demonstrate changing tunes. To toggle between the pieces, click "Next Tune".

+

You are in control of the look of the cursor. Two different techniques are demonstrated: highlighting the note being played and putting a cursor on the page. The class CursorControl must be supplied by your program.

+

As the piece is playing, there are callbacks when the note changes. The info returned in the callback is printed to the page as it is received.

+

The visual control for playing music can look different. In this example, the abcjs-audio.css file has been loaded. You can supply your own css.

+ + + + + +
+ + + + First start playing to load audio before seeking. +
+
MIDI
+
+
+

+ +

+      
+    
+
+ + diff --git a/examples/karaoke-synth.html b/examples/karaoke-synth.html new file mode 100644 index 0000000000000000000000000000000000000000..f0a6812d0c835c9120d2ec8ff7f0baab1ad8693f --- /dev/null +++ b/examples/karaoke-synth.html @@ -0,0 +1,168 @@ + + + + + + + + + + Karaoke Synth + + + + + + + +
+ abcjs logo +

abcjs karaoke synth

+
+
+
+
+
+ + + + + + +
+ +
+
+ + diff --git a/examples/modify-synth-input.html b/examples/modify-synth-input.html new file mode 100644 index 0000000000000000000000000000000000000000..bcc1cf87d078dcb137f32d556b1a1dbf7b458b1d --- /dev/null +++ b/examples/modify-synth-input.html @@ -0,0 +1,133 @@ + + + + + + + + + + abcjs: Modify Synth Input Demo + + + + + + + +
+ abcjs logo +

Modify Synth Input

+
+
+
+

Demonstration of changing the synth on the fly if you want to make adjustments.

+

This example adds some "swing" to the tune.

+

+

??

+
+

Browsers won't allow audio to work unless the audio is started in response to a user action. This prevents auto-playing web sites. Therefore, the + following button is needed to do the initialization:

+ + + +
+
+ + diff --git a/examples/output-transpose.html b/examples/output-transpose.html new file mode 100644 index 0000000000000000000000000000000000000000..937f16e2a2e18e8c8c98475900b4bd172d955bb5 --- /dev/null +++ b/examples/output-transpose.html @@ -0,0 +1,161 @@ + + + + + + + + + + + + Output Transposition + + + + + + +
+ abcjs logo +

Output Transposition

+
+
+
+

This demonstrates transposing an ABC string and retrieving that transposed string. Type any valid ABC string in the "input string" area and choose your transposition in half-steps below the input string. The resultant ABC string, transposed audio, and transposed notation are shown on the right side of the page.

+
+
+

Input String

+ + +
+
+

Output String

+

+					
+
+
+ +
+
+
+

Original Output

+
+
+
+

Transposed Output

+
+
+
+
+
+ + + \ No newline at end of file diff --git a/examples/parsing.html b/examples/parsing.html new file mode 100644 index 0000000000000000000000000000000000000000..71cb98dca772e8dc194b16bf5764798a51528b48 --- /dev/null +++ b/examples/parsing.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + abcjs: Basic Demo + + + + + +
+ abcjs logo +

Basic abcjs

+
+
+

This extracts lyrics from a tune

+
+

The lyrics found:

+
+
+ + + \ No newline at end of file diff --git a/examples/persian.html b/examples/persian.html new file mode 100644 index 0000000000000000000000000000000000000000..01a4eef9afd500e04796bf99d70f37f23f8028b6 --- /dev/null +++ b/examples/persian.html @@ -0,0 +1,270 @@ + + + + + + + + + + abcjs: Microtone Demo + + + + + + + + +
+ abcjs logo +

Microtone Demo

+
+
+
+

This is a tune with a non-traditional note (the A half sharp).

+

You can play the tune with the audio control, click on individual notes to hear them, and download it as a MIDI file.

+
MIDI
+
+
+ +
+
+ + diff --git a/examples/play-on-repeat.html b/examples/play-on-repeat.html new file mode 100644 index 0000000000000000000000000000000000000000..f374d2a777c54275f8c2344d3bb2dd16d76ae6bd --- /dev/null +++ b/examples/play-on-repeat.html @@ -0,0 +1,191 @@ + + + + + + + + + + abcjs: Play On Repeat Demo + + + + + + + +
+ abcjs logo +

Play on Repeat

+
+
+
+

Demonstration of playing a selection of a tune in a loop. Set the start and end measures on the fly using the inputs.

+
+ + +
+
+

Browsers won't allow audio to work unless the audio is started in response to a user action. This prevents auto-playing web sites. Therefore, the + following button is needed to do the initialization:

+ + + +
+
+ + diff --git a/examples/plugin/abc_plugin-midi.html b/examples/plugin/abc_plugin-midi.html new file mode 100644 index 0000000000000000000000000000000000000000..2390014dfdc53286123cd7894efa0f5c1a283152 --- /dev/null +++ b/examples/plugin/abc_plugin-midi.html @@ -0,0 +1,43 @@ + + + + + abcjs: Plugin Demo + + + + + + + + + + + +
+X: 1
+T: Me Mother Won't Let Me Marry
+M: 6/8
+L: 1/8
+R: jig
+K: D
+DFA B2A|FGF F2E|DFA B2A|G3-G2 F|
+EGE EFG|c2c c2B|A^GA B2A|F3-F2 E|
+DFA B2A|F=GF F2E|DFA B2A|G3-G2 F|
+E2E EFG|c2c c2B|A^GA ABc|d3 e2 z|]
+f2f f2e|d2d d2e|ded c2d|e3-e2 f|
+g2g g2e|c2c c2B|A^GA B2A|F3-F2 d|
+f2f f2e|d2d d2e|ded c2d|e3-e2 f|
+g2g g2e|c2c c2B|A^GA ABc|d3-d2 z|]
+
+ + + diff --git a/examples/plugin/abc_plugin.html b/examples/plugin/abc_plugin.html new file mode 100644 index 0000000000000000000000000000000000000000..f493aa55fc4f4948a92afcb1b283e6b8263966ec --- /dev/null +++ b/examples/plugin/abc_plugin.html @@ -0,0 +1,140 @@ + + + + + abcjs: Plugin Demo + + + + + + + + + +This one shows no newline before the music starts: +
X:0
+T: No Score
+C: The Traditional Tune Archive
+M:
+K:
+x
+
+ +


X:1
T: 1 Speed the Plough
M:4/4
L:1/8
C:Trad
K:G
|:GABc dedB|dedB dedB|c2ec B2dB|c2A2A2BA|
|GABc dedB|dedB dedB|c2ec B2dB|A2F2G4:|
|:g2gf g2Bd|g2fe dBGB|c2ec B2dB|c2A2A2df|
|g2gfg2Bd|g2fe dBGB|c2ec B2dB|A2F2G4:|

Chris
+ +from thesession: +

+X: 1
+T: Me Mother Won't Let Me Marry
+M: 6/8
+L: 1/8
+R: jig
+K: Dmaj
+DFA B2A|FGF F2E|DFA B2A|G3-G2 F|
+ +EGE EFG|c2c c2B|A^GA B2A|F3-F2 E|
+ +DFA B2A|F=GF F2E|DFA B2A|G3-G2 F|
+ +E2E EFG|c2c c2B|A^GA ABc|d3 e2 z|]
+ +f2f f2e|d2d d2e|ded c2d|e3-e2 f|
+ +g2g g2e|c2c c2B|A^GA B2A|F3-F2 d|
+ +f2f f2e|d2d d2e|ded c2d|e3-e2 f|
+ +g2g g2e|c2c c2B|A^GA ABc|d3-d2 z|]
+ +

+ +In this document, small bits of abc are interpreted +
+X: 1
+T: 2 body child
+A

+ +
+X: 1
T: 3 div child
A

+
+ X: 1
T: 4 div div child
A

+
+
+

This also works when in a paragraph
+X: 1
+T: 5 Here is a title
+K: Emin
+CDEFGABcdefgab
+

+

On a new line inside a pre +

+X: 1
+T: 6 Cooley's
+M: 4/4
+L: 1/8
+K: Emin
+|:D2|EB
+
+
X:1
T: 7 begin
G

This is mid-line.
X:1
T:8 middle
G

X:1
T:9 blank lines on either side
G

in the middle it should work
X:1
T:10 end
G
+X:1 +
+T:11 begin +
+K:G +
+G +
+ +
+Here is some text between tunes. +
+X:1 +
+T:12 middle +
+G +
+ +
+ +X:1 +
+T:13 blank lines on either side +
+G +
+ +
+in the middle it should work +
+X:1 +
+T:14 end +
+G +
+ + + + diff --git a/examples/plugin/test-plugin-title-bug.html b/examples/plugin/test-plugin-title-bug.html new file mode 100644 index 0000000000000000000000000000000000000000..7dc8211f8f4c5921073aba736a0b369841f3ad42 --- /dev/null +++ b/examples/plugin/test-plugin-title-bug.html @@ -0,0 +1,53 @@ + + + + + + + Test abcjs plugin + + + + + + + +
+X:1
+T:College Hornpipe
+M:C
+L:1/8
+R:Hornpipe
+S:William Clark of Lincoln music manuscript collection (1770)
+Z:AK/Fiddler’s Companion
+N:Filed at The Traditional Tune Archive
+K:Bb
+BA|B2 B,4 FE|DF B4 (3dcB|c2C4 cB|Ac f4 ga|
+bagf gfed|ecde BAGF|GBAc Bdce|d2(B2B2):|
+|:FE|DFBF DFBF|G2E2 E2 GF|EGcG EGcG|A2F2F2 ed|
+ef (g2 g)fed|ecde BAGF|GBAc Bece|d2 (B2B2):|]
+
+ +
X:1
+T:College Hornpipe 2
+M:C
+L:1/8
+R:Hornpipe
+S:William Clark of Lincoln music manuscript collection (1770)
+Z:AK/Fiddler’s Companion
+N:Filed at The Traditional Tune Archive
+K:Bb
+BA|B2 B,4 FE|DF B4 (3dcB|c2C4 cB|Ac f4 ga|
+bagf gfed|ecde BAGF|GBAc Bdce|d2(B2B2):|
+|:FE|DFBF DFBF|G2E2 E2 GF|EGcG EGcG|A2F2F2 ed|
+ef (g2 g)fed|ecde BAGF|GBAc Bece|d2 (B2B2):|]
+
+ + + diff --git a/examples/plugin/textarea_test.html b/examples/plugin/textarea_test.html new file mode 100644 index 0000000000000000000000000000000000000000..2af1087d6fd861d151fc8f6f7801cd3edf707632 --- /dev/null +++ b/examples/plugin/textarea_test.html @@ -0,0 +1,304 @@ + + + + + + abcjs plugin textarea + + + + + + + + + + + + + + +
+ + + + + +
+ + + + +
+ +

tune page - abc tune search

+ +
+
+
+ + + + + + + + + + + + + +
+
+
+ + + + + +
+find abcnotation on facebook +
+donate button +
+

+ + + + +
+
+
+ + +   +music +text + +   +a - z +file order + + +  search help + +
+
+
+ + + + + + + + + +
+

The Labours of Marlow

+ +
+
+ browse similar - search: +file +| collection +
 download: +abc + +| midi +| png +
+
+ + + + +
+The Labours of Marlow - staff notation
+ +
+
Visitors who viewed this tune also viewed:
+ + + + +
+[no title given] +
+ +
+
+ +
+
+ + + + +
+ +la Bell Catherine +
+ +
+
+ + +
+
+ + + + + +
+Rigodon de Gap +
Lou Coucou +
+ +
+
+ +
+
+
+ +
+ +
+ +

+Note for newcomers: +If you've stumbled across this page by accident - perhaps via a web search - welcome, and thanks for visiting the abc web site! +Here is just a taste of what you can find at this site, either here or via a link: +

+ +

Back to the top

+ +
+ + + + + +
+ +... celtic, CDs, bands, gigs, sheet music, concerts, accessories, instruments, folk clubs, festivals ...
+... fiddle, bouzouki, autoharp, whistle, percussion, piano, tuba, voila, drums, fretted ...

+scores of music lovers visit abcnotation.com every day - they could be reading your advert right now +
+
+ +
+ + + + + +
+ +© Chris Walshaw, 1995-2012. +Site maintained by + + + + +advertise + +| site map +| printable page +| mobile version + +
+
+ + + + + + + + + + + + + + + + + +
+instruments +
+equipment +
+gigs +
+advertise
+scores of music lovers visit abcnotation.com every day

they could be reading your advert right now
+
+
+ + + + + +
+Locations of visitors to this page + +
+ +
+ + + diff --git a/examples/printable.html b/examples/printable.html new file mode 100644 index 0000000000000000000000000000000000000000..3c217fa55d6d33174e67c91e2ab1439295fada90 --- /dev/null +++ b/examples/printable.html @@ -0,0 +1,105 @@ + + + + + + + + + + abcjs: PDF Maker Demo + + + + + + + +
+ abcjs logo +

PDF Maker

+
+
+
+
+

+ This page demonstrates how to take the output of abcjs and make a nicely formatted PDF out of it. It does that by using the browser's built in print capability. The + browser's "Print Preview" dialog contains an option to "print to PDF". + (It is useful to use the "oneSvgPerLine" option when printing. This will keep the browser from breaking the music in the middle of a staff when there is a page break.) +

+
    +
  • + Use the input dialog below to create the music you wish to print.
  • +
  • + Use the music player to test your input for typos.
  • +
  • + Click the "print" button to save as PDF.
  • +
+
+
+ + +
+
+
+
+ +
+
+
+
+ + diff --git a/examples/responsive.html b/examples/responsive.html new file mode 100644 index 0000000000000000000000000000000000000000..7c4f9379fd5873006696e424e6038d51ed853df0 --- /dev/null +++ b/examples/responsive.html @@ -0,0 +1,40 @@ + + + + + + + + + + abcjs: Responsive Demo + + + + +
+ abcjs logo +

Responsive To Browser Width

+
+
+

Change the browser width to see how the music fills up the available space.

+
+
+ + diff --git a/examples/synth-only.html b/examples/synth-only.html new file mode 100644 index 0000000000000000000000000000000000000000..0b5c96d890dbc8907d18192ad44ff42cb84a4c7e --- /dev/null +++ b/examples/synth-only.html @@ -0,0 +1,81 @@ + + + + + + + + + + abcjs: Synth Only Demo + + + + + + + + +
+ abcjs logo +

Synth (audio only)

+
+
+
+

This shows how to play notes without a visual representation.

+

Type valid ABC code in the textarea and click "Play" to hear it.

+ + +
+
+
+ + diff --git a/examples/synth-player.html b/examples/synth-player.html new file mode 100644 index 0000000000000000000000000000000000000000..bd7c979afb83ad322742cd212859331f29a973d0 --- /dev/null +++ b/examples/synth-player.html @@ -0,0 +1,151 @@ + + + + + + + + + + abcjs: Synth Player Demo + + + + + + + + +
+ abcjs logo +

Synth Player

+
+
+
+

Demo of using the built in synth player control.

+
+
+
+
+ + diff --git a/examples/synth-reuse.html b/examples/synth-reuse.html new file mode 100644 index 0000000000000000000000000000000000000000..54214eb3411a7916f808d7160840d0ef2fe10b17 --- /dev/null +++ b/examples/synth-reuse.html @@ -0,0 +1,158 @@ + + + + + + + + + + abcjs: Synth Demo + + + + + + + +
+ abcjs logo +

Reusing Synth

+
+
+
+

This shows how you can create and save snippets of music so that it is easy to replay them.

+

When you click "Prime" all of the buffers are created from the same music - playing different sets of voices + at different tempos.

+

Then the user can play them repeatedly with very little additional processing.

+ + + +
+
+
+ + diff --git a/examples/tablatures.html b/examples/tablatures.html new file mode 100644 index 0000000000000000000000000000000000000000..5c9624eb40ab3b90c7c493aa4a9bee7c8e061416 --- /dev/null +++ b/examples/tablatures.html @@ -0,0 +1,175 @@ + + + + + + + + + + + Tablatures + + + + +
+ abcjs logo +

abcjs with tablatures

+
+
+

Guitar and Editor

+ + +
+
+
+

Guitar with Capo

+
+

Violin

+
+

Viola

+
+

Multi Voice

+
+
+ + + + \ No newline at end of file diff --git a/examples/tempo.html b/examples/tempo.html new file mode 100644 index 0000000000000000000000000000000000000000..d3a820a64261e60cf3cb8bccf6d085378de957be --- /dev/null +++ b/examples/tempo.html @@ -0,0 +1,178 @@ + + + + + + + + + + abcjs: Tempo Demo + + + + + +
+
+ abcjs logo +

Tempo

+
+
+

Tempo can be set in various ways: it can be specified in the ABC string, it can be specified in the + parameters passed in, or a default can be set that is only used if the tempo is not in the string. +

+ +

Specified in the ABC String

+

The tempo is set using the Q: 1/4=150 line.

+
+
+
+ + +
+
+ +

Passed in as a Parameter

+

The tempo is set using the { qpm: 100 } attribute. Notice that this overrides the tempo in the Q: line.

+
+
+
+ + +
+
+ +

Using a default

+

The tempo is set using the { defaultQpm: 60 } attribute. Notice there is no Q: line.

+
+
+
+ + +
+
+ +

The tempo is set using the { defaultQpm: 60 } attribute. However, there is a Q: line, so that takes precedence.

+
+
+
+ + +
+
+ +
+
+ + diff --git a/examples/toc.html b/examples/toc.html new file mode 100644 index 0000000000000000000000000000000000000000..4912a3512e74c768b54e3bafd25137b9015c49f5 --- /dev/null +++ b/examples/toc.html @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + abcjs: Table of Contents + + + + + + +
abcjs logo
+ +
+

Table of Contents

+

+ Click on any of the demos below to get an idea of the types of things you can do with abcjs. + If this is your first time using abcjs, the demos in the first section are suggested as they are + the easiest to set up. After loading each demo, right-click the webpage and choose "view page source" + to see how the demo works. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Start Here
BasicA demo of the most bare-boned way to display sheet music.
EditorA demo of the simplest way to present the user with an editor so they can change music on the + fly.
Basic SynthA demo of the most basic way to incorporate audio playback using abcjs.
AnalysisA demo of the information that is available about an ABC tune book.
Visual Demos
AnimationA demo of animated cursor effects that can be added using abcjs.
AnnotatingA demo showing how to annotate your sheet music using abcjs.
BasicA demo of the most bare-boned way to display sheet music.
Change GlyphsA demo showing how to substitute the music symbols with your own.
Line WrappingA demo of the line wrapping capabilities.
PluginA demo showing how to use the plugin version of abcjs. (Useful for sites with a CMS where a user + might enter ABC code)
PrintableA demo showing how to format the music for printing.
ResponsiveA demo showing the music filling up whatever available horizontal space is on the page.
Zoom To FitA demo showing the music filling up whatever available space is on the page.
TransposeA demo showing how to transpose.
Output Transposed ABCA demo showing how to transpose and output the result of the transposition.
TablatureA demo showing how to create tablature.
Tune BookA demo showing how to pick tunes from a tune book.
Automatically Add Note NamesA demo of modifying the SVG output after drawing to add note names.
Interactive Demos
DraggingA demo showing how to implement a visual interface that supports dragging.
EditorA demo of the simplest way to present the user with an editor so they can change music on the + fly.
Editor With SynthA demo of the editor with synth capabilities
Editor With TransposeA demo of the editor and transposing
Audio Demos
Basic SynthA demo of the most basic way to incorporate audio playback using abcjs.
KaraokeA demo of allowing the user to turn off voices.
MicrotonesA demo of creating non-western music that relies on a different scale.
Modify Synth InputA demo showing how to tweak the synth after the music has been processed.
Play On RepeatA demo showing how to loop a section of the music.
Synth OnlyA demo showing how to create sound without any visual representation.
Synth OptionsA full-featured demo with many synth options.
Synth PlayerA demo showing how to use abcjs' synth player.
Tempo ChangingShowing how to control the tempo.
Tune/Instrument SwitcherShowing how to render and switch multiple tunes and change instruments.
Swing FeelShowing how to add a swing feel to the synth.
AccompanimentShowing how to vary the sound of the generated accompaniment.
Analysis
AnalysisA demo of the information that is available about an ABC tune book.
ParsingA demo of getting lyrics out of an abc string.
+
+ + + \ No newline at end of file diff --git a/examples/tune-instrument-switcher.html b/examples/tune-instrument-switcher.html new file mode 100644 index 0000000000000000000000000000000000000000..be8daec8ef0e6b460609b4d6ee0ed5c5202b3c41 --- /dev/null +++ b/examples/tune-instrument-switcher.html @@ -0,0 +1,238 @@ + + + + + + + + + + abcjs: Tune/Instrument Switcher Demo + + + + + + + +
+
+ abcjs logo +

Tune/Instrument Switcher Demo

+
+
+
+

+ +

+

+ +

+

+ + +

+
+ + +
+

Tune1 status:

+

Tune2 status:

+
+
+
+ + diff --git a/examples/tune-selector.html b/examples/tune-selector.html new file mode 100644 index 0000000000000000000000000000000000000000..2abdb22b80f4d2b78a4b3c989de80b1618a6ac2d --- /dev/null +++ b/examples/tune-selector.html @@ -0,0 +1,72 @@ + + + + + + + + + + abcjs: Basic Demo + + + + +
+ abcjs logo +

Tune Selector

+
+
+

Select the tune you want to display. The tunes are in the same input string, so this is one way to display a tune collection.

+ +
+
+ + diff --git a/examples/wrap.html b/examples/wrap.html new file mode 100644 index 0000000000000000000000000000000000000000..e960fe4de6b106e95f5e95597ee27610cee036ec --- /dev/null +++ b/examples/wrap.html @@ -0,0 +1,91 @@ + + + + + + + + + + abcjs: Basic Demo + + + + + +
+ abcjs logo +

Wrap

+
+
+ + + + + +
+
+ + diff --git a/examples/zoom-to-fit.html b/examples/zoom-to-fit.html new file mode 100644 index 0000000000000000000000000000000000000000..205483d4ad26d836e498f7bb96f192666db8f9d9 --- /dev/null +++ b/examples/zoom-to-fit.html @@ -0,0 +1,69 @@ + + + + + + + + + + + abcjs: Responsive Demo + + + + +
+ abcjs logo +

Zoom To Fit

+
+
+

Change the browser height and width to see how the music fills up the available space.

+
+
+
+
+ + diff --git a/font_generator/Bravura.svg b/font_generator/Bravura.svg new file mode 100644 index 0000000000000000000000000000000000000000..0ae2b0642ae8e2b4c60d2ae58bfd3c519d6989b5 --- /dev/null +++ b/font_generator/Bravura.svg @@ -0,0 +1,4841 @@ + + + + +Created by FontForge 20190801 at Fri Jan 29 23:57:12 2021 + By Unknown +Copyright \(c\) 2021, Steinberg Media Technologies GmbH \(http://www.steinberg.net/\), with Reserved Font Name "Bravura". This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License \(OFL\) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder\(s\) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement\(s\). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder\(s\). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1\) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2\) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3\) No Modified Version of the Font Software may use the Reserved Font Name\(s\) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4\) The name\(s\) of the Copyright Holder\(s\) or the Author\(s\) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution\(s\) of the Copyright Holder\(s\) and the Author\(s\) or with their explicit written permission. 5\) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/font_generator/BravuraText.svg b/font_generator/BravuraText.svg new file mode 100644 index 0000000000000000000000000000000000000000..1bebf9e0c5ee5affae5aea9f3c1c4568d0956521 --- /dev/null +++ b/font_generator/BravuraText.svg @@ -0,0 +1,20373 @@ + + + + +Created by FontForge 20190801 at Tue Feb 9 14:19:06 2021 + By Unknown +Copyright \(c\) 2021, Steinberg Media Technologies GmbH \(http://www.steinberg.net/\), with Reserved Font Name "Bravura". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License \(OFL\) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder\(s\) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement\(s\). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder\(s\). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1\) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2\) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3\) No Modified Version of the Font Software may use the Reserved Font Name\(s\) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4\) The name\(s\) of the Copyright Holder\(s\) or the Author\(s\) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution\(s\) of the Copyright Holder\(s\) and the Author\(s\) or with their explicit written permission. + +5\) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/font_generator/Emmentaler_400.font.js b/font_generator/Emmentaler_400.font.js new file mode 100644 index 0000000000000000000000000000000000000000..25945ac8f66435e3d6c6fac5aaa63ce84a475ad3 --- /dev/null +++ b/font_generator/Emmentaler_400.font.js @@ -0,0 +1,10 @@ +/*! + * The following copyright notice may not be removed under any circumstances. + * + * Copyright: + * This font is distributed under the GNU General Public License. As a special + * exception, if you create a document which uses this font, and embed this font or + * unaltered portions of this font into the document, this font does not by itself + * cause the resulting document to be covered by the GNU General Public License. + */ +Raphael.registerFont({"w":250,"face":{"font-family":"Emmentaler","font-weight":400,"font-stretch":"normal","units-per-em":"1000","panose-1":"2 0 5 3 0 0 0 0 0 0","ascent":"800","descent":"-200","x-height":"25","bbox":"-513.5 -1248.89 800.126 1323.13","underline-thickness":"50","underline-position":"-75","unicode-range":"U+0020-U+E269"},"glyphs":{" ":{"w":193},"+":{"d":"116,-373v6,-3,12,-3,18,0v6,3,8,5,11,10r2,5r0,43r0,43r43,0v41,0,43,0,47,2v2,1,5,3,6,4v4,3,7,11,7,16v0,5,-3,13,-7,16v-1,1,-4,3,-6,4v-4,2,-6,2,-47,2r-43,0r0,43v0,41,0,43,-2,47v-3,6,-5,8,-10,11v-7,3,-13,3,-19,0v-6,-3,-8,-5,-11,-11v-2,-4,-2,-6,-2,-47r0,-43r-43,0v-41,0,-43,0,-47,-2v-6,-3,-8,-5,-11,-11v-3,-6,-3,-12,0,-18v3,-6,5,-8,11,-11r5,-2r42,0r43,0r0,-43v0,-41,0,-43,2,-47v3,-6,5,-8,11,-11"},",":{"d":"44,-112v19,-5,39,1,53,15v15,15,20,32,17,63v-3,41,-14,82,-33,131v-10,24,-24,54,-26,56v-6,7,-17,6,-22,-2v-1,-2,-2,-5,-2,-6v0,-2,4,-11,8,-21v28,-60,34,-87,23,-108v-4,-8,-9,-12,-25,-20v-12,-5,-14,-7,-20,-13v-23,-23,-23,-57,0,-80v7,-7,17,-13,27,-15","w":114},"-":{"d":"6,-178v3,-2,5,-2,77,-2v82,0,79,0,82,7v4,7,1,14,-5,18v-3,2,-5,2,-76,2v-72,0,-74,0,-77,-2v-9,-5,-9,-18,-1,-23","w":167},".":{"d":"44,-112v35,-9,70,19,70,55v0,36,-35,64,-70,55v-30,-7,-50,-38,-42,-68v4,-21,21,-37,42,-42","w":114},"0":{"d":"161,-499v11,-1,37,0,49,2v56,12,99,53,126,120v40,99,27,232,-30,309v-26,36,-57,57,-97,65v-15,3,-44,3,-59,0v-27,-6,-49,-17,-69,-34v-78,-69,-105,-224,-58,-340v29,-72,76,-114,138,-122xm198,-470v-7,-2,-23,-3,-29,-2v-18,4,-29,14,-39,33v-12,22,-17,52,-20,100v-1,25,-1,153,0,177v3,50,9,80,20,102v8,16,19,26,32,30v9,3,26,3,35,0v13,-4,24,-14,32,-30v11,-22,17,-52,20,-102v1,-24,1,-152,0,-177v-3,-49,-9,-79,-20,-101v-8,-16,-18,-26,-31,-30","w":359},"1":{"d":"110,-502v2,-2,7,-1,22,5v27,13,36,13,61,1v7,-3,13,-5,14,-5v4,0,7,3,9,7v2,4,2,11,2,198v0,131,0,195,1,201v2,12,5,23,9,32v12,25,31,39,56,42v10,1,13,3,13,10v0,5,-1,6,-3,8v-2,2,-3,2,-16,2v-14,0,-23,-1,-70,-8v-30,-5,-59,-5,-89,0v-47,7,-56,8,-70,8v-13,0,-14,0,-16,-2v-2,-2,-2,-3,-2,-8v0,-7,2,-9,12,-10v25,-3,44,-17,56,-42v4,-9,7,-20,9,-32v1,-6,1,-53,1,-143v0,-129,0,-135,-2,-138v-3,-5,-6,-8,-13,-8v-4,0,-5,1,-7,2v-1,2,-15,33,-32,71v-16,38,-30,70,-31,72v-2,5,-7,8,-11,8v-8,0,-14,-6,-14,-13v0,-2,109,-254,111,-258","w":297},"2":{"d":"141,-499v19,-2,56,0,78,6v23,6,50,18,67,30v45,32,65,75,59,127v-5,45,-22,78,-56,105v-20,16,-48,31,-104,55v-44,19,-60,27,-79,38v-19,11,-19,11,-8,9v13,-3,42,-3,56,0v24,5,47,15,70,30v33,21,62,29,85,25v8,-2,14,-5,19,-10v4,-3,10,-14,10,-17v0,-3,4,-7,8,-8v6,-1,13,4,13,10v0,4,-5,19,-10,29v-18,34,-52,58,-93,67v-14,3,-41,3,-54,1v-27,-6,-44,-15,-67,-37v-15,-15,-21,-19,-32,-23v-28,-9,-63,4,-75,30v-4,7,-7,18,-7,24v0,4,-4,7,-9,8v-5,0,-9,-1,-11,-5v-3,-7,3,-36,11,-57v8,-22,22,-42,43,-63v15,-15,30,-27,64,-52v43,-31,63,-48,78,-66v29,-35,42,-73,40,-121v-2,-43,-13,-77,-32,-97v-12,-11,-24,-17,-39,-18v-28,-1,-51,14,-53,35v-1,11,4,20,19,38v15,18,18,29,14,47v-5,21,-18,37,-36,46v-21,11,-40,11,-61,0v-8,-4,-11,-6,-18,-13v-6,-6,-9,-10,-12,-17v-8,-15,-9,-28,-7,-46v4,-25,15,-47,34,-66v24,-24,58,-39,95,-44","w":359},"3":{"d":"126,-499v10,-1,47,0,61,2v74,10,117,44,124,97v1,11,1,42,-1,55v-4,28,-16,49,-35,59v-9,5,-12,8,-15,13v-3,7,-3,12,0,19v3,5,6,8,17,13v25,14,41,38,47,71v2,14,2,45,0,57v-6,27,-16,46,-34,65v-25,24,-60,40,-106,46v-14,2,-52,2,-65,0v-63,-11,-106,-43,-117,-88v-1,-4,-1,-11,-1,-20v0,-12,0,-14,2,-21v4,-10,9,-17,17,-25v8,-8,15,-13,25,-17v7,-2,9,-2,20,-2v11,0,13,0,20,2v10,4,17,9,25,17v12,11,19,25,20,40v0,7,0,9,-2,14v-3,6,-4,8,-18,18v-17,12,-21,18,-20,29v2,18,18,30,46,33v12,2,24,1,32,-2v27,-9,43,-41,48,-93v1,-15,1,-65,-1,-79v-3,-25,-11,-41,-25,-48v-11,-6,-15,-6,-66,-6v-45,0,-47,0,-50,-2v-6,-4,-8,-13,-4,-20v4,-5,5,-5,56,-5v50,0,54,0,63,-5v6,-3,14,-12,18,-19v6,-14,9,-30,10,-65v1,-40,-2,-60,-12,-79v-8,-16,-21,-27,-38,-32v-10,-2,-36,-2,-46,1v-20,5,-30,14,-32,28v-1,10,2,15,21,28v11,8,14,13,15,21v1,24,-19,50,-44,55v-35,9,-70,-19,-70,-55v0,-15,5,-32,13,-46v4,-7,18,-21,27,-27v19,-14,46,-23,75,-27","w":326},"4":{"d":"288,-498v9,-3,14,-4,18,-1v3,2,5,7,5,10v-1,2,-64,77,-141,168v-77,91,-141,166,-142,167v-1,2,4,2,85,2r87,0r0,-79v0,-73,1,-79,2,-82v1,-2,7,-6,19,-14v36,-24,46,-36,62,-72v4,-10,8,-18,9,-19v4,-4,13,-2,15,4v2,3,2,19,2,132r0,130r36,0v35,0,37,0,40,2v8,5,8,18,0,23v-3,2,-5,2,-40,2r-36,0r0,11v0,19,3,37,10,51v12,25,31,39,56,42v10,1,13,3,13,10v0,5,-1,6,-3,8v-2,2,-3,2,-16,2v-14,0,-23,-1,-70,-8v-30,-5,-59,-5,-89,0v-47,7,-56,8,-70,8v-13,0,-14,0,-16,-2v-2,-2,-2,-3,-2,-8v0,-7,2,-9,12,-10v25,-3,44,-17,56,-42v7,-14,10,-32,10,-51r0,-11r-90,0v-97,0,-95,0,-103,-5v-6,-4,-10,-13,-9,-18v1,-2,6,-8,11,-15v25,-30,53,-69,71,-101v11,-18,28,-54,35,-72v19,-47,28,-88,30,-135v1,-21,2,-24,8,-27r4,-2r15,4v22,6,34,8,49,9v20,1,41,-3,67,-11","w":391},"5":{"d":"34,-498v4,-3,1,-3,36,2v83,12,145,12,232,-2v19,-3,22,-2,27,2v5,6,4,8,-5,17v-43,42,-108,68,-186,77v-20,3,-40,4,-57,4v-13,0,-15,0,-19,2v-3,2,-5,4,-7,7r-2,4r0,55r0,55r7,-7v22,-19,47,-32,73,-38v11,-2,47,-2,65,0v87,12,134,58,142,138v1,15,1,36,-1,48v-6,34,-26,67,-53,90v-24,19,-54,34,-83,40v-46,9,-101,2,-140,-18v-36,-18,-57,-44,-62,-76v-3,-23,3,-43,19,-58v8,-8,15,-13,25,-17v7,-2,9,-2,20,-2v11,0,13,0,20,2v10,4,17,9,25,17v12,11,19,25,20,40v0,7,0,9,-2,14v-3,6,-4,8,-18,18v-6,4,-12,10,-14,11v-12,14,-6,33,12,42v17,9,49,12,67,9v31,-7,49,-39,55,-97v2,-15,2,-63,0,-77v-5,-40,-17,-70,-35,-85v-7,-6,-18,-12,-27,-13v-10,-2,-28,-1,-42,2v-31,6,-55,20,-72,40v-5,7,-9,10,-13,10v-5,0,-10,-3,-12,-6v-2,-3,-2,-5,-2,-122v0,-113,0,-119,2,-122v1,-2,3,-5,5,-6","w":330},"6":{"d":"166,-499v12,-1,40,0,53,2v30,5,56,17,75,35v19,17,29,41,28,66v-1,17,-7,30,-20,42v-8,8,-15,13,-25,17v-7,2,-9,2,-20,2v-11,0,-13,0,-20,-2v-10,-4,-17,-9,-25,-17v-13,-12,-19,-26,-19,-42v0,-9,0,-10,3,-14v1,-3,6,-7,10,-10v4,-3,10,-7,13,-9v3,-2,7,-6,9,-8v2,-4,3,-5,3,-11v0,-6,-1,-8,-3,-12v-8,-13,-25,-20,-46,-19v-18,1,-30,6,-41,16v-27,24,-36,72,-32,179r0,21r10,-4v26,-9,43,-11,70,-9v49,4,83,18,109,43v16,17,27,37,32,63v2,9,2,14,2,31v0,18,0,23,-2,32v-5,26,-16,46,-32,63v-18,17,-39,29,-66,36v-38,10,-80,11,-108,1v-50,-16,-88,-63,-109,-134v-12,-41,-17,-94,-14,-136v10,-122,76,-210,165,-222xm188,-252v-9,-3,-27,-4,-36,-2v-24,6,-36,23,-41,57v-2,18,-2,100,0,118v6,42,24,59,60,58v13,-1,21,-3,30,-9v22,-14,30,-44,30,-108v0,-74,-12,-104,-43,-114","w":333},"7":{"d":"7,-499v7,-2,15,0,18,5v2,3,2,5,2,13v0,8,0,11,2,14v2,4,7,8,9,8v1,0,4,-4,8,-7v32,-40,86,-45,133,-14v5,4,14,10,18,15v16,13,27,19,43,20v23,1,50,-10,71,-29v3,-3,9,-10,13,-15v4,-5,8,-9,10,-10v6,-2,13,1,17,7v2,6,2,8,-9,24v-6,8,-18,26,-26,39v-79,118,-118,209,-129,300v-1,11,-1,22,-1,42v0,30,0,36,5,63v2,15,2,16,1,20v-2,3,-7,7,-10,7v-1,0,-9,-2,-18,-5v-28,-9,-37,-10,-55,-10v-19,0,-28,1,-52,9v-20,6,-23,7,-27,5v-4,-2,-7,-6,-7,-10v0,-5,20,-48,40,-87v38,-74,91,-156,170,-267v7,-9,12,-16,11,-16v0,0,-4,2,-9,4v-18,10,-33,13,-52,13v-25,1,-40,-6,-61,-25v-33,-30,-61,-39,-77,-24v-6,5,-12,17,-15,28v-2,8,-2,11,-3,66v0,54,-1,58,-2,60v-5,8,-18,8,-23,0v-2,-3,-2,-5,-2,-119v0,-114,0,-116,2,-119v1,-2,3,-4,5,-5","w":332},"8":{"d":"166,-499v11,-1,34,-1,44,0v44,4,83,20,107,44v13,13,22,27,26,43v3,12,3,36,0,48v-7,28,-22,53,-53,85r-10,10r9,6v49,31,77,77,75,125v-1,25,-8,45,-21,65v-15,22,-34,38,-61,51v-60,29,-140,29,-200,1v-54,-26,-84,-72,-82,-122v2,-33,18,-59,60,-99v18,-17,18,-18,16,-19v-13,-9,-32,-26,-40,-38v-25,-37,-29,-80,-10,-120v23,-45,75,-75,140,-80xm217,-476v-14,-3,-37,-4,-46,-2v-10,2,-20,6,-27,10v-7,4,-20,17,-24,24v-17,29,-14,63,7,84v7,7,12,10,65,41v32,18,58,33,59,34v3,0,21,-20,33,-37v7,-12,16,-29,19,-41v2,-8,2,-12,2,-24v0,-15,-1,-22,-5,-33v-13,-27,-43,-48,-83,-56xm169,-207r-63,-36v-1,0,-6,5,-13,11v-40,36,-55,65,-53,100v3,53,45,95,107,108v11,2,15,2,31,2v21,0,27,-1,43,-9v30,-14,49,-47,47,-80v-2,-22,-13,-43,-30,-55v-4,-3,-35,-21,-69,-41","w":359},"9":{"d":"141,-499v14,-1,43,0,54,2v17,4,31,10,46,19v51,34,84,108,91,198v6,85,-16,166,-61,219v-35,42,-80,63,-131,61v-41,-2,-77,-15,-101,-38v-19,-17,-29,-41,-28,-66v1,-17,7,-30,20,-42v8,-8,15,-13,25,-17v7,-2,9,-2,20,-2v11,0,13,0,20,2v10,4,17,9,25,17v13,12,19,26,19,42v0,9,0,10,-3,14v-1,3,-6,7,-10,10v-4,3,-10,7,-13,9v-3,2,-7,6,-9,8v-2,4,-2,5,-2,11v0,6,0,8,2,12v8,13,25,20,46,19v18,-1,30,-6,41,-16v27,-24,36,-72,32,-179r0,-21r-10,4v-26,9,-43,11,-70,9v-49,-4,-83,-18,-109,-43v-16,-17,-27,-37,-32,-63v-2,-9,-2,-14,-2,-32v0,-17,0,-22,2,-31v5,-26,16,-46,32,-63v5,-4,11,-9,14,-12v23,-17,54,-27,92,-31xm180,-477v-7,-2,-19,-2,-27,-1v-26,4,-42,23,-47,58v-4,21,-5,65,-3,93v4,57,21,80,59,82v36,1,54,-16,60,-58v2,-18,2,-100,0,-118v-5,-35,-17,-51,-42,-56","w":333},"f":{"d":"331,-476v51,-6,96,15,104,50v4,17,0,44,-9,62v-5,10,-14,19,-21,23v-23,12,-52,1,-61,-23v-3,-8,-3,-23,0,-29v2,-4,7,-8,15,-14v14,-8,19,-15,20,-24v1,-11,-3,-13,-21,-14v-10,0,-15,0,-20,1v-27,7,-45,31,-58,82v-2,9,-16,75,-16,77v0,1,13,1,30,1v24,0,30,0,33,2v14,5,15,24,1,30v-4,2,-8,2,-39,2r-35,0r-26,85v-15,47,-29,93,-32,102v-29,79,-79,158,-126,197v-35,30,-68,41,-103,36v-37,-6,-63,-26,-68,-53v-4,-22,5,-57,18,-73v23,-25,62,-18,74,13v2,5,3,9,3,16v0,8,-1,9,-4,14v-1,3,-5,6,-9,9v-3,2,-9,7,-12,9v-8,6,-12,12,-13,20v-1,11,3,13,21,14v14,0,21,-1,30,-5v20,-10,32,-32,46,-88v3,-14,21,-85,39,-159r34,-136v0,-1,-12,-1,-27,-1v-24,0,-27,0,-31,-2v-14,-6,-13,-25,1,-30v3,-2,9,-2,35,-2r32,0r0,-3v2,-6,10,-24,17,-39v40,-82,110,-141,178,-150","w":321},"m":{"d":"93,-297v3,0,10,-1,15,-1v8,1,10,1,15,4v12,5,21,18,25,34r1,7r11,-10v23,-23,46,-34,69,-34v9,0,11,0,16,2v7,3,16,12,21,20v1,3,4,9,6,14v1,5,3,9,4,9v0,0,3,-3,6,-7v11,-13,29,-27,43,-33v26,-11,49,-7,67,11v10,11,16,23,20,38v3,14,2,18,-18,102v-11,43,-19,80,-19,81v0,4,3,7,7,7v8,0,25,-10,40,-24v15,-13,20,-15,26,-9v6,6,3,12,-15,29v-35,32,-61,49,-86,57v-31,11,-51,7,-60,-11v-2,-5,-2,-7,-2,-15v0,-8,1,-16,20,-94v14,-57,20,-88,21,-93v1,-19,-10,-25,-28,-16v-8,4,-18,13,-22,21v-1,3,-14,46,-30,100v-30,105,-28,100,-38,105r-5,3r-26,0v-20,0,-26,0,-28,-2v-3,-1,-6,-6,-6,-9v0,-1,12,-46,28,-99v19,-68,27,-99,28,-104v1,-18,-10,-24,-28,-15v-8,4,-19,14,-22,21v-2,3,-17,48,-35,99v-17,52,-33,95,-33,97v-2,4,-7,8,-12,10v-4,2,-7,2,-30,2v-20,0,-26,0,-28,-2v-3,-1,-6,-6,-6,-9v0,-1,15,-46,33,-99v35,-106,35,-106,31,-115v-4,-9,-13,-10,-24,-5v-18,9,-38,39,-52,80v-2,5,-5,10,-6,12v-7,7,-19,9,-24,3v-3,-3,-2,-7,2,-21v16,-42,42,-82,67,-107v19,-18,40,-29,61,-34","w":438},"p":{"d":"64,-290v9,-3,27,-2,37,1v18,6,31,17,39,33v3,5,5,11,6,12r0,4r10,-9v22,-20,45,-34,71,-40v7,-2,11,-2,26,-2v15,0,17,0,28,3v43,11,69,44,75,93v3,27,-3,67,-15,93v-18,42,-62,85,-106,101v-15,6,-27,8,-43,8v-23,-1,-45,-6,-62,-15v-10,-5,-17,-6,-23,-3v-3,1,-6,3,-6,4v-3,4,-35,98,-35,102v0,8,6,16,17,21v6,2,18,5,25,5v7,0,12,2,14,6v4,6,2,14,-4,18v-3,1,-5,1,-26,0v-66,-5,-127,-5,-193,0v-21,1,-23,1,-26,0v-8,-5,-8,-19,1,-22v2,-1,16,-3,33,-4v29,-2,37,-3,45,-7v6,-2,11,-6,13,-10v2,-4,108,-314,109,-320v2,-11,1,-19,-5,-23v-3,-2,-4,-2,-10,-2v-23,2,-51,34,-76,87v-3,7,-7,15,-9,17v-3,4,-11,8,-16,8v-6,0,-12,-5,-12,-10v0,-8,26,-61,42,-85v24,-37,49,-58,76,-64xm243,-241v-9,-4,-25,-1,-38,7v-25,16,-49,56,-63,105v-15,49,-14,78,0,90v15,13,42,7,61,-12v17,-17,33,-56,46,-109v10,-39,11,-58,5,-71v-3,-5,-5,-7,-11,-10","w":365},"r":{"d":"211,-304v9,-1,31,0,40,2v28,7,41,27,34,51v-8,25,-30,39,-52,32v-11,-3,-17,-10,-22,-25v-1,-4,-3,-8,-4,-10v-3,-5,-10,-8,-16,-8v-19,0,-46,18,-55,36v-2,5,-11,39,-30,109v-19,77,-27,104,-29,107v-1,2,-4,5,-6,7r-4,2r-27,1v-23,0,-27,0,-30,-1v-3,-2,-6,-7,-6,-10v0,-2,13,-54,30,-117v28,-108,29,-115,29,-124v0,-7,0,-9,-1,-12v-4,-5,-7,-8,-14,-8v-8,0,-15,5,-26,14v-11,12,-15,18,-24,38v-1,4,-7,8,-12,9v-4,0,-5,0,-8,-2v-6,-4,-6,-7,-2,-18v7,-19,14,-31,26,-44v18,-17,40,-27,65,-29v27,-1,51,10,64,29r4,6r3,-3v19,-15,47,-28,73,-32","w":219},"s":{"d":"149,-291v3,0,12,-1,19,-1v25,1,43,8,57,21v17,18,22,42,12,61v-8,14,-21,19,-37,14v-11,-3,-20,-12,-20,-19v0,-1,2,-7,5,-13v4,-7,5,-11,6,-16v0,-8,-2,-16,-5,-20v-5,-7,-14,-8,-25,-5v-9,2,-16,6,-23,12v-13,13,-17,32,-11,46v3,7,14,17,26,24v37,23,53,37,63,56v7,13,8,26,5,43v-6,40,-39,72,-84,84v-34,8,-65,4,-90,-14v-24,-17,-33,-49,-20,-73v8,-16,24,-21,39,-14v11,6,18,15,19,27v0,7,-1,10,-11,17v-11,8,-13,14,-9,23v2,5,7,9,15,11v10,3,29,3,40,0v25,-7,41,-24,43,-45v1,-14,-5,-27,-18,-40v-8,-8,-16,-14,-47,-34v-23,-14,-35,-31,-35,-49v0,-13,4,-29,10,-41v9,-19,26,-35,46,-45v8,-4,21,-9,30,-10","w":207},"z":{"d":"88,-265v12,-3,27,-1,57,9v26,7,32,9,58,10v29,2,34,1,46,-7v7,-5,11,-5,16,-2v5,3,7,10,5,15v-1,2,-42,42,-92,89r-91,85r18,1v18,1,24,1,67,5v12,1,30,2,40,3v22,0,27,-1,34,-8v10,-10,13,-24,9,-41v-2,-9,-2,-9,-1,-13v5,-10,18,-9,23,1v5,11,9,34,9,50v0,49,-37,90,-84,93v-19,1,-34,-3,-67,-17v-34,-14,-41,-16,-71,-18v-27,-2,-32,-1,-42,6v-4,2,-8,4,-9,4v-9,0,-15,-10,-12,-17v1,-2,44,-44,97,-93r96,-91v-1,0,-7,1,-14,2v-7,1,-26,3,-41,4v-37,4,-41,5,-65,9v-24,5,-39,6,-43,3v-9,-6,-7,-25,4,-42v13,-20,31,-34,53,-40","w":286},"\ue100":{"d":"2,1r3,-2r182,0r183,0r3,2r2,3r0,73r0,73r-2,3r-3,2r-183,0r-182,0r-3,-2r-2,-3r0,-73r0,-73","w":375},"\ue101":{"d":"2,-154r3,-2r182,0r183,0r3,2r2,3r0,73r0,73r-2,3r-3,2r-183,0r-182,0r-3,-2r-2,-3r0,-73r0,-73","w":375},"\ue102":{"d":"-143,-20r5,-2r326,0v326,0,327,0,331,2v6,3,8,5,11,10v3,7,3,13,0,19v-3,6,-5,8,-11,11v-4,2,-6,2,-74,2r-70,0r0,65r0,64r-2,3r-3,2r-182,0r-183,0r-3,-2r-2,-3r0,-64r0,-65r-69,0r-69,0r-5,-2v-6,-3,-8,-5,-11,-11v-3,-6,-3,-12,0,-18v3,-6,5,-8,11,-11","w":375},"\ue103":{"d":"2,-154r3,-2r182,0r183,0r3,2r2,3r0,64r0,65r70,0v68,0,70,0,74,2v2,1,5,3,6,4v4,3,7,11,7,16v0,5,-3,13,-7,16v-1,1,-4,3,-6,4v-4,2,-5,2,-331,2v-326,0,-327,0,-331,-2v-6,-3,-8,-5,-11,-11v-3,-6,-3,-12,0,-18v3,-6,5,-8,11,-11r5,-2r69,0r69,0r0,-65r0,-64","w":375},"\ue104":{"d":"2,-248r3,-2r70,0r70,0r3,2r2,3r0,245r0,244r-2,3r-3,2r-70,0r-70,0r-3,-2r-2,-3r0,-244r0,-245xm302,-248r3,-2r70,0r70,0r3,2r2,3r0,245r0,244r-2,3r-3,2r-70,0r-70,0r-3,-2r-2,-3r0,-244r0,-245","w":450},"\ue105":{"d":"2,-248r3,-2r70,0r70,0r3,2r2,3r0,245r0,244r-2,3r-3,2r-70,0r-70,0r-3,-2r-2,-3r0,-244r0,-245","w":150},"\ue106":{"d":"2,-248r3,-2r70,0r70,0r3,2r2,3r0,120r0,120r-2,3r-3,2r-70,0r-70,0r-3,-2r-2,-3r0,-120r0,-120","w":150},"\ue107":{"d":"63,-394v4,-2,8,-2,12,-1v3,2,158,186,162,194v7,13,5,26,-5,42v-8,11,-24,27,-54,52v-15,12,-29,25,-32,28v-31,33,-38,83,-20,121v6,13,9,16,44,56v64,75,61,72,61,78v0,6,-6,12,-12,13v-5,0,-9,-2,-16,-9v-25,-25,-82,-43,-113,-36v-15,3,-23,9,-30,23v-4,10,-7,22,-8,38v-1,22,3,45,10,67v5,14,8,22,15,32v6,8,6,11,1,14v-4,2,-6,1,-15,-10v-36,-46,-69,-112,-80,-161v-9,-35,-5,-59,9,-69v7,-4,14,-5,29,-5v29,2,70,13,110,30r13,6r-55,-65v-84,-99,-87,-103,-90,-109v-3,-8,-4,-16,-1,-25v5,-16,19,-32,61,-67v15,-12,28,-24,31,-26v23,-25,34,-60,30,-93v-2,-11,-7,-28,-13,-37v-3,-5,-15,-20,-27,-35v-12,-14,-23,-27,-24,-29v-3,-6,0,-14,7,-17","w":237},"\ue108":{"d":"166,-203v10,-3,25,-2,37,1v15,5,28,15,37,28v25,38,10,89,-33,110v-17,8,-23,9,-45,9v-27,0,-49,-4,-85,-16r-17,-5v-1,0,25,74,57,163v51,145,58,163,56,164v0,1,-4,3,-7,5v-6,2,-8,2,-15,2v-9,0,-10,0,-16,-3v-4,-1,-8,-4,-8,-5v-1,-1,-30,-98,-64,-214v-53,-177,-63,-212,-62,-214v2,-3,7,-6,11,-6v5,0,10,3,13,10v8,15,25,38,40,53v16,16,24,19,36,13v9,-4,11,-10,18,-37v6,-24,10,-32,19,-42v9,-8,18,-13,28,-16"},"\ue109":{"d":"56,-204v22,-3,41,3,56,17v9,10,13,18,19,42v3,11,6,22,7,24v4,9,11,15,20,16v4,0,6,0,11,-3v13,-6,44,-43,56,-66v3,-7,8,-10,13,-10v4,0,9,3,11,6v1,2,-9,37,-62,214v-34,116,-63,213,-64,214v0,1,-4,4,-8,5v-6,3,-7,3,-15,3v-8,0,-10,0,-16,-2v-3,-2,-7,-4,-7,-5v-2,-1,5,-19,56,-164v32,-89,58,-163,57,-163r-17,5v-36,12,-58,16,-85,16v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v11,-15,28,-27,46,-30"},"\ue10a":{"d":"111,-204v22,-3,41,3,56,17v9,10,13,18,19,42v3,11,6,22,7,24v5,13,19,19,29,14v13,-6,40,-41,54,-69v2,-5,8,-8,12,-8v4,0,9,3,11,6v1,2,-15,62,-89,339v-50,185,-91,338,-92,339v-1,1,-4,4,-8,5v-6,3,-7,3,-15,3v-8,0,-10,0,-16,-2v-3,-2,-7,-4,-7,-5v-2,-1,4,-19,48,-164v27,-89,49,-162,49,-163v-1,0,-9,2,-18,5v-36,12,-59,16,-86,16v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v24,-35,74,-41,102,-14v10,11,14,20,20,46v3,15,7,26,11,30v3,3,9,6,15,7v4,0,6,0,11,-3v11,-5,34,-31,47,-53v4,-7,6,-13,13,-36v22,-70,39,-128,39,-129v0,0,-7,2,-14,5v-17,5,-40,11,-56,14v-11,2,-17,2,-32,2v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v11,-15,28,-27,46,-30","w":300},"\ue10b":{"d":"141,-454v22,-3,41,3,56,17v9,10,13,18,19,42v3,11,6,22,7,24v4,9,11,15,20,16v4,0,6,0,9,-2v11,-7,33,-37,48,-66v3,-8,7,-11,13,-11v4,0,9,3,11,6v1,2,-19,89,-107,463v-60,254,-110,463,-110,464v-1,2,-4,4,-8,6v-7,3,-8,3,-16,3v-8,0,-10,0,-16,-2v-3,-2,-7,-4,-7,-5v-2,-1,3,-19,41,-164v23,-89,42,-162,43,-163v0,-1,-4,-1,-16,4v-39,13,-74,19,-100,18v-14,-1,-25,-4,-37,-10v-44,-21,-59,-72,-34,-110v24,-35,74,-41,102,-14v10,11,14,20,20,46v3,15,7,26,11,30v4,3,10,6,16,7v4,0,6,0,10,-3v14,-7,43,-43,52,-63v1,-4,41,-153,41,-155v0,-1,-6,1,-13,4v-21,6,-40,12,-58,15v-13,2,-18,2,-34,2v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v24,-35,74,-41,102,-14v10,11,14,20,20,46v3,15,7,26,11,30v6,6,17,9,24,5v10,-4,23,-19,36,-39v14,-20,13,-17,35,-101v11,-42,20,-77,20,-78v0,0,-7,1,-15,4v-19,6,-38,11,-54,14v-11,2,-17,2,-32,2v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v11,-15,28,-27,46,-30","w":325},"\ue10c":{"d":"171,-454v22,-3,41,3,56,17v9,10,13,18,19,42v5,21,7,27,11,32v6,7,18,10,25,6v8,-4,21,-22,36,-52v11,-22,13,-24,20,-24v4,0,9,3,11,6v1,2,-23,122,-118,588v-65,322,-119,587,-119,588v-1,2,-4,4,-8,6v-7,3,-8,3,-16,3v-8,0,-10,0,-16,-2v-3,-2,-7,-4,-7,-5v-2,-1,2,-19,35,-165v20,-90,36,-163,36,-164v0,0,-8,2,-17,5v-22,8,-40,12,-59,16v-14,2,-19,2,-35,2v-23,0,-29,-1,-45,-9v-44,-21,-59,-72,-34,-110v24,-35,74,-41,102,-14v10,11,14,20,20,46v3,15,7,26,11,30v3,3,9,6,15,7v7,1,13,-3,24,-14v15,-15,34,-42,39,-55v1,-3,9,-38,18,-78v9,-40,16,-73,17,-74v0,-1,-3,-1,-16,4v-39,13,-74,19,-100,18v-14,-1,-25,-4,-37,-10v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v3,11,6,22,7,24v5,13,19,19,30,14v12,-6,40,-42,49,-63v1,-3,10,-40,19,-81r17,-76r-18,6v-37,12,-60,16,-87,16v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v5,21,7,27,11,32v7,7,18,10,25,6v12,-6,31,-31,43,-56v4,-8,6,-16,21,-85r17,-77v0,-1,-6,1,-13,4v-38,12,-70,18,-94,17v-14,-1,-25,-4,-37,-10v-44,-21,-59,-72,-34,-110v11,-15,28,-27,46,-30","w":350},"\ue10d":{"d":"201,-704v22,-3,41,3,56,17v9,10,13,18,19,42v3,11,6,22,7,24v4,9,11,15,20,16v7,0,11,-2,18,-12v5,-7,18,-31,26,-49v5,-11,6,-13,10,-16v6,-3,15,0,17,5v1,3,-237,1425,-239,1428v-1,1,-5,3,-8,5v-6,2,-8,2,-15,2v-8,0,-10,0,-16,-2v-3,-2,-7,-4,-7,-5v-2,-1,1,-19,28,-166v17,-90,31,-164,30,-164v0,0,-5,2,-12,4v-26,9,-54,16,-77,19v-5,1,-18,1,-27,1v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v3,11,6,22,7,24v4,9,11,15,21,16v4,0,6,0,10,-3v14,-7,38,-37,50,-61v4,-9,4,-9,18,-84v8,-41,14,-75,13,-75v0,0,-8,2,-17,6v-42,13,-75,19,-102,18v-14,-1,-25,-4,-37,-10v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v5,21,7,27,11,32v6,7,17,10,25,6v12,-5,35,-33,47,-59r5,-10r14,-75v7,-42,14,-76,13,-76r-17,5v-37,13,-63,17,-90,17v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v5,21,7,27,11,32v6,6,16,9,24,7v11,-4,38,-42,47,-65v0,-3,7,-37,15,-78v7,-40,13,-74,13,-76v1,-1,0,-1,-15,4v-19,6,-40,11,-57,14v-10,2,-17,2,-31,2v-22,0,-28,-1,-44,-9v-44,-21,-59,-72,-34,-110v12,-18,32,-29,55,-31v18,-1,34,5,47,18v9,10,13,18,19,42v3,11,6,22,7,24v4,9,11,15,20,16v6,0,12,-3,19,-11v11,-12,26,-38,31,-52v1,-4,8,-40,15,-80v8,-40,14,-74,14,-76v1,-1,0,-1,-13,3v-35,12,-60,16,-86,16v-21,0,-28,-1,-43,-9v-44,-21,-59,-72,-34,-110v11,-15,28,-27,46,-30","w":375},"\ue10e":{"d":"191,-373v7,-4,18,-1,22,8v2,4,2,7,2,77v0,41,0,74,1,74v0,0,9,-4,20,-8v23,-9,26,-10,32,-5v7,5,7,6,7,46v0,34,0,37,-2,40v-1,2,-3,4,-4,5v-2,1,-14,7,-28,12r-25,11r-1,81v0,44,0,81,1,81v0,0,9,-4,20,-8v23,-9,26,-10,32,-5v7,5,7,6,7,46v0,34,0,37,-2,40v-1,2,-3,4,-4,5v-2,1,-14,7,-28,12r-25,11r-1,84v0,76,-1,85,-2,88v-7,12,-24,12,-31,0v-1,-3,-2,-11,-2,-81r0,-77r-43,17r-42,17r0,81v0,86,0,84,-5,89v-2,3,-9,6,-12,6v-4,0,-11,-3,-13,-6v-5,-5,-5,-3,-5,-81v0,-41,0,-74,-1,-74v0,0,-9,4,-20,8v-23,9,-26,10,-32,5v-7,-5,-7,-6,-7,-46v0,-34,0,-37,2,-40v1,-2,3,-4,4,-5v2,-1,14,-7,28,-12r26,-11r0,-81v0,-44,0,-81,-1,-81v0,0,-9,4,-20,8v-23,9,-26,10,-32,5v-7,-5,-7,-6,-7,-46v0,-34,0,-37,2,-40v1,-2,3,-4,4,-5v2,-1,14,-7,28,-12r26,-11r0,-84v0,-76,1,-85,2,-88v7,-12,24,-12,31,0v1,3,2,11,2,81r1,77r42,-17r42,-17r0,-81v0,-76,0,-81,2,-85v2,-4,4,-6,9,-8xm180,-18r0,-81r-43,17r-42,17r0,82r0,81r3,-1v2,-1,21,-9,43,-17r39,-16r0,-82","w":275},"\ue10f":{"d":"81,-335v7,-4,18,-1,22,8v2,4,2,7,2,67v0,35,0,63,1,63r24,-16v23,-16,23,-17,29,-17v5,0,6,1,9,3v7,5,7,6,7,47v0,37,-1,38,-3,41v-1,1,-16,13,-34,25r-33,22r0,79v0,44,0,79,1,79r24,-16v23,-16,23,-17,29,-17v5,0,6,1,9,3v7,5,7,6,7,47v0,37,-1,38,-3,41v-1,1,-16,13,-34,25r-33,22r0,75v0,65,0,76,-2,79v-2,4,-4,7,-8,9v-9,4,-18,1,-23,-8v-2,-4,-2,-7,-2,-67v0,-35,0,-63,-1,-63r-24,16v-23,16,-23,16,-29,16v-5,0,-6,0,-9,-2v-7,-5,-7,-6,-7,-47v0,-37,1,-38,3,-41v1,-1,16,-13,34,-25r33,-22r0,-79v0,-44,0,-79,-1,-79r-24,16v-23,16,-23,16,-29,16v-5,0,-6,0,-9,-2v-7,-5,-7,-6,-7,-47v0,-37,1,-38,3,-41v1,-1,16,-13,34,-25r33,-22r0,-75v0,-71,0,-76,2,-80v2,-4,4,-6,9,-8","w":175},"\ue110":{"d":"307,-373v7,-4,17,-1,22,7v1,3,2,11,2,77r1,73r25,-7v28,-8,32,-8,37,-4v7,5,7,6,7,47r0,38r-3,3v-4,5,-6,5,-39,14r-27,8r-1,82v0,65,0,82,1,82v1,-1,13,-4,27,-8v27,-7,30,-7,35,-3v7,5,7,6,7,47r0,38r-3,3v-4,5,-6,5,-39,14r-27,8r-1,80v0,72,-1,81,-2,84v-7,12,-24,12,-31,-1v-2,-4,-2,-7,-2,-79r0,-75r-2,1v-1,0,-19,5,-39,10r-36,10r-1,79v0,70,-1,79,-2,82v-7,12,-24,12,-31,0v-1,-3,-2,-11,-2,-78r0,-74r-39,10r-38,11r-1,78v0,69,-1,78,-2,81v-7,12,-24,12,-31,0v-1,-3,-2,-11,-2,-77r0,-73r-26,7v-28,8,-32,8,-37,4v-7,-5,-7,-6,-7,-47r1,-38r2,-3v4,-5,6,-5,39,-14r28,-8r0,-82v0,-65,0,-82,-1,-82v-1,1,-13,4,-26,8v-28,7,-31,7,-36,3v-7,-5,-7,-6,-7,-47r1,-38r2,-3v4,-5,6,-5,39,-14r28,-8r0,-80v0,-72,1,-81,2,-84v7,-12,24,-12,31,1v2,4,2,7,2,79r0,75r2,-1v1,0,19,-5,39,-10r37,-10r0,-79v0,-71,1,-80,2,-83v7,-12,24,-12,31,0v1,3,2,11,2,78r1,75r38,-10r39,-11r0,-78v0,-69,1,-78,2,-81v2,-3,4,-5,9,-7xm296,-26v0,-45,0,-82,-1,-82r-38,10r-38,11r-1,82r0,82r15,-4v8,-2,25,-7,39,-10r24,-7r0,-82xm182,86v1,0,1,-37,1,-82r0,-82r-39,10r-38,11r-1,82v0,67,0,82,1,82v1,-1,18,-5,39,-11v20,-5,37,-10,37,-10","w":400},"\ue111":{"d":"7,-380v8,-2,26,0,33,5v1,1,1,16,0,87v-1,48,-1,87,-1,87v0,1,25,-3,56,-8v32,-6,57,-9,58,-9v5,1,9,5,12,10r2,4r3,289v3,232,4,289,3,289v-1,1,-4,2,-7,3v-8,3,-24,3,-32,0v-3,-1,-6,-2,-7,-3v-1,-1,-1,-16,0,-87v1,-48,1,-87,1,-87v0,-1,-25,3,-56,8v-32,6,-57,9,-58,9v-5,-1,-9,-5,-12,-10r-2,-5r-3,-251v-2,-138,-3,-268,-4,-289r0,-37r5,-2v3,-1,7,-2,9,-3xm132,-100v0,-11,0,-14,-1,-14v-4,0,-93,15,-93,16v-1,0,-3,210,-3,211v1,0,93,-15,94,-16v0,0,3,-151,3,-197","w":167},"\ue112":{"d":"-12,-469v11,-2,29,0,36,5v2,1,2,12,-1,175v-2,95,-3,173,-3,173v0,1,4,-1,8,-4v21,-14,47,-22,73,-24v27,-1,49,7,68,26v19,18,29,42,31,68v1,19,-3,36,-12,54v-14,27,-34,46,-94,87v-38,26,-48,34,-60,48v-6,6,-13,13,-15,14v-9,6,-19,5,-27,-2v-2,-3,-4,-6,-5,-9v-1,-2,-3,-109,-6,-278v-3,-151,-5,-286,-6,-301r0,-26r4,-2v2,-1,6,-3,9,-4xm94,-102v-7,-4,-18,-5,-27,-2v-18,5,-33,21,-39,42v-2,10,-4,96,-2,129v1,14,1,27,2,30r1,4r15,-13v21,-18,42,-39,52,-53v10,-14,20,-33,24,-47v6,-23,3,-49,-6,-69v-5,-10,-11,-17,-20,-21","w":200},"\ue113":{"d":"161,-469v11,-2,29,0,36,5v2,1,2,20,-4,302v-3,185,-5,302,-6,304v-1,3,-3,6,-5,9v-8,7,-18,8,-27,2v-2,-1,-9,-8,-15,-14v-12,-14,-22,-22,-60,-48v-41,-28,-61,-44,-75,-59v-22,-26,-32,-52,-31,-82v3,-47,37,-86,80,-93v10,-2,28,-1,41,2v18,4,36,11,51,21v4,3,8,5,8,4v0,0,-4,-279,-6,-325r0,-22r4,-2v2,-1,6,-3,9,-4xm106,-104v-20,-6,-36,3,-46,23v-9,20,-12,46,-6,69v4,14,14,33,24,47v10,14,31,35,52,53r16,13r0,-4v1,-3,1,-16,2,-30v1,-19,1,-36,0,-74v-1,-54,-1,-54,-8,-69v-7,-14,-20,-25,-34,-28","w":200},"\ue114":{"d":"-12,-469v11,-2,29,0,36,5v2,1,2,11,-1,163v-2,89,-3,167,-3,174r0,12r5,-5v12,-10,25,-17,40,-21v11,-3,32,-3,42,-1v9,3,21,9,29,15r7,5r0,-9v0,-5,-1,-81,-3,-170v-3,-152,-3,-162,-1,-163v5,-4,13,-5,24,-5v10,0,18,1,23,5v2,1,2,11,-1,165v-2,90,-3,168,-3,174r1,10r7,-5v23,-16,48,-23,76,-23v17,0,26,1,40,7v44,21,67,76,51,123v-7,19,-17,34,-35,52v-14,14,-27,24,-64,50v-42,29,-50,36,-62,50v-13,15,-18,18,-27,17v-6,0,-7,0,-11,-2r-7,-7r-2,-4r-1,-33v-1,-18,-1,-43,-2,-56r0,-23r-7,8v-12,14,-25,25,-60,54v-34,28,-40,33,-48,46v-12,17,-18,20,-30,17v-5,-1,-13,-9,-14,-14v-1,-2,-3,-109,-6,-278v-3,-151,-5,-286,-6,-301r0,-26r4,-2v2,-1,6,-3,9,-4xm72,-103v-6,-3,-16,-4,-22,-2v-13,5,-23,18,-28,38v-2,8,-2,13,-3,58v-1,48,0,91,2,106r1,5r9,-9v31,-32,50,-65,58,-102v2,-9,2,-13,2,-32v0,-18,0,-23,-2,-31v-3,-17,-9,-27,-17,-31xm253,-103v-6,-3,-17,-4,-24,-2v-18,4,-32,21,-39,42v-2,10,-4,96,-2,130v1,14,1,27,2,30r1,4r12,-10v14,-12,34,-32,43,-43v12,-15,22,-33,27,-47v14,-41,5,-92,-20,-104","w":362},"\ue115":{"d":"-12,-469v11,-2,29,0,36,5v2,1,2,8,0,111v-1,60,-2,109,-2,110v1,0,116,-66,117,-68v1,0,0,-34,-1,-76v-1,-71,-1,-76,1,-77v1,0,4,-2,7,-3v8,-3,25,-3,33,0v10,4,9,-4,8,66v-1,34,-1,62,-1,63v1,0,18,-10,40,-23v44,-25,45,-25,55,-21v2,1,5,3,6,4v3,2,7,11,7,15v0,6,-4,14,-8,17v-2,2,-25,15,-50,30v-26,15,-48,28,-49,28v-2,2,-2,6,-4,76v0,40,-1,79,-1,85r1,12r7,-5v23,-16,48,-23,76,-23v17,0,26,1,40,7v44,21,67,76,51,123v-7,19,-17,34,-35,52v-14,14,-27,24,-64,50v-42,29,-50,36,-62,50v-13,15,-18,18,-27,17v-6,0,-7,0,-11,-2r-7,-7r-2,-4r-1,-33v-1,-18,-1,-43,-2,-56r0,-23r-7,8v-12,14,-25,25,-60,54v-34,28,-40,33,-48,46v-11,15,-16,19,-26,18v-6,-1,-12,-4,-16,-10v-2,-3,-2,-6,-3,-12v0,-4,-1,-74,-3,-155v-1,-81,-3,-147,-3,-148v-1,0,-21,11,-45,25v-48,28,-49,28,-59,24v-2,-1,-5,-3,-6,-4v-3,-2,-7,-11,-7,-16v0,-4,4,-13,7,-15v1,-1,25,-16,55,-33v29,-16,53,-31,53,-31v1,-1,-1,-163,-3,-223r0,-22r4,-2v2,-1,6,-3,9,-4xm141,-226v0,-19,-1,-35,-1,-35v0,0,-118,68,-119,68v0,1,0,18,-1,40r0,38r5,-4v12,-11,25,-18,40,-22v11,-3,32,-3,42,-1v9,3,21,9,29,15r7,5r0,-34v-1,-19,-1,-50,-2,-70xm72,-103v-6,-3,-16,-4,-22,-2v-13,5,-23,18,-28,38v-2,8,-2,13,-3,58v-1,48,0,91,2,106r1,5r9,-9v31,-32,50,-65,58,-102v2,-9,2,-13,2,-32v0,-18,0,-23,-2,-31v-3,-17,-9,-27,-17,-31xm253,-103v-6,-3,-17,-4,-24,-2v-18,4,-32,21,-39,42v-2,10,-4,96,-2,130v1,14,1,27,2,30r1,4r12,-10v14,-12,34,-32,43,-43v12,-15,22,-33,27,-47v14,-41,5,-92,-20,-104","w":362},"\ue116":{"d":"-6,-132v2,-1,4,-2,5,-2v3,0,92,9,93,10v4,1,5,4,5,17v2,32,8,53,19,70v2,3,5,7,6,8r3,2r3,-2v1,-1,4,-5,6,-8v11,-17,17,-38,19,-70v0,-13,1,-15,4,-17v1,0,22,-3,48,-5v49,-5,50,-5,52,-1v1,2,0,14,-3,48v-3,24,-5,45,-5,46v0,1,-1,3,-2,4v-2,2,-4,3,-17,3v-36,2,-60,10,-76,25r-4,3r3,3v4,5,13,11,21,15v14,6,32,9,56,11v13,0,15,1,17,3v1,1,2,3,2,4v0,1,2,22,5,46v3,34,4,46,3,48v-2,4,-3,4,-52,-1v-26,-2,-47,-5,-48,-5v-3,-2,-4,-4,-4,-18v-2,-31,-8,-52,-19,-69v-2,-3,-5,-7,-6,-8r-3,-2r-3,2v-1,1,-4,5,-6,8v-11,17,-17,38,-19,69v0,14,-1,16,-4,18v-1,0,-22,3,-48,5v-49,5,-50,5,-52,1v-1,-2,0,-14,3,-48v3,-24,5,-45,5,-46v0,-1,1,-3,2,-4v2,-2,4,-3,17,-3v24,-2,42,-5,56,-11v8,-4,17,-10,21,-15r3,-3r-4,-3v-16,-15,-40,-23,-76,-25v-13,0,-15,-1,-17,-3v-1,-1,-2,-3,-2,-4v0,-1,-2,-21,-4,-46v-3,-24,-5,-45,-5,-46"},"\ue117":{"d":"45,-259v3,-2,9,-2,12,0v2,2,16,23,26,39v35,59,56,118,64,178v2,20,2,63,0,82v-8,61,-29,120,-64,178v-10,16,-19,31,-24,37v-5,7,-14,6,-18,0v-3,-5,-2,-8,4,-18v39,-59,57,-135,57,-238v0,-102,-18,-178,-57,-237v-3,-5,-6,-10,-6,-12v0,-3,3,-8,6,-9","w":147},"\ue118":{"d":"-55,-260v7,-3,16,2,16,10v0,2,-3,7,-6,12v-39,59,-57,135,-57,237v0,103,18,179,57,238v6,10,7,13,4,18v-4,6,-13,7,-18,0v-5,-6,-14,-21,-24,-37v-35,-58,-56,-117,-64,-178v-2,-19,-2,-62,0,-82v8,-61,29,-119,65,-178v12,-21,24,-38,27,-40","w":0},"\ue119":{"d":"-239,-123v3,-2,8,-2,13,-1v2,1,10,9,19,18v9,9,21,20,27,25v49,39,103,59,166,63v21,1,24,2,28,8v4,6,4,14,0,20v-4,6,-7,7,-28,8v-63,4,-117,24,-166,63v-6,5,-18,16,-27,25v-16,16,-20,19,-26,19v-4,0,-10,-3,-12,-6v-5,-6,-6,-12,-3,-19v3,-6,31,-34,46,-45v27,-22,57,-39,87,-51r10,-4r-10,-4v-14,-5,-34,-15,-47,-23v-25,-15,-45,-30,-68,-53v-20,-20,-23,-25,-18,-35v2,-4,4,-6,9,-8","w":17},"\ue11a":{"d":"226,-123v9,-4,18,0,22,8v5,10,2,15,-18,35v-23,23,-43,38,-68,53v-13,8,-33,18,-47,23r-10,4r10,4v30,12,60,29,88,51v14,11,42,39,45,45v3,7,2,13,-3,19v-2,3,-8,6,-12,6v-6,0,-10,-3,-26,-19v-9,-9,-21,-20,-27,-25v-49,-39,-103,-59,-166,-63v-21,-1,-24,-2,-28,-8v-4,-6,-4,-14,0,-20v4,-6,7,-7,28,-8v63,-4,117,-24,166,-63v6,-5,18,-16,27,-25v9,-9,17,-17,19,-17"},"\ue11b":{"d":"-8,-15v3,-1,6,-2,8,-2v7,0,15,6,16,11v1,2,1,11,2,21v4,62,24,116,63,165v5,6,16,18,25,27v16,16,19,20,19,26v0,4,-3,10,-6,12v-6,5,-12,6,-19,3v-6,-3,-34,-31,-45,-46v-22,-27,-39,-57,-51,-87r-4,-10r-4,10v-12,30,-29,60,-51,87v-11,15,-39,43,-45,46v-7,3,-13,2,-19,-3v-3,-2,-6,-8,-6,-12v0,-6,3,-10,19,-26v9,-9,20,-21,25,-27v39,-49,59,-103,63,-165v1,-10,1,-19,2,-21v0,-3,4,-7,8,-9","w":125},"\ue11c":{"d":"-114,-248v4,-3,9,-2,14,0v6,3,34,31,45,45v22,28,39,58,51,88r4,10r4,-10v12,-30,29,-60,51,-88v11,-14,39,-42,45,-45v7,-3,13,-2,19,3v3,2,6,8,6,12v0,6,-3,10,-19,26v-9,9,-20,21,-25,27v-39,49,-59,103,-63,166v-1,21,-2,24,-8,28v-6,4,-14,4,-20,0v-6,-4,-7,-7,-8,-28v-4,-63,-24,-117,-63,-166v-5,-6,-16,-18,-25,-27v-20,-19,-22,-24,-17,-33v2,-4,4,-6,9,-8","w":125},"\ue11d":{"d":"-248,-122v5,-5,8,-4,20,8v31,31,92,66,154,89v26,10,57,17,71,17v5,0,6,0,9,2v3,4,3,8,0,12v-3,2,-4,2,-9,2v-14,0,-45,7,-71,17v-62,23,-123,58,-154,89v-12,12,-15,13,-20,8v-3,-2,-3,0,1,-27v7,-39,9,-82,7,-120v-1,-29,-3,-47,-7,-71v-4,-26,-4,-24,-1,-26","w":0},"\ue11e":{"d":"228,-114v12,-12,15,-13,20,-8v3,2,3,0,-1,26v-10,63,-10,129,0,191v4,27,4,25,1,27v-5,5,-8,4,-20,-8v-31,-31,-92,-66,-154,-89v-26,-10,-57,-17,-71,-17v-5,0,-6,0,-9,-2v-3,-4,-3,-8,0,-12v3,-2,4,-2,9,-2v14,0,45,-7,71,-17v62,-23,123,-58,154,-89"},"\ue11f":{"d":"-6,-6v2,-1,4,-2,6,-2v2,0,4,1,6,2v2,3,2,4,2,9v0,14,7,45,17,71v23,62,58,123,89,154v12,12,13,15,8,20v-2,3,0,3,-26,-1v-40,-7,-83,-9,-121,-7v-29,1,-47,3,-70,7v-27,4,-25,4,-27,1v-5,-5,-4,-8,8,-20v31,-31,66,-92,89,-154v10,-26,17,-57,17,-71v0,-5,0,-6,2,-9","w":125},"\ue120":{"d":"-122,-248v2,-3,0,-3,27,1v62,10,128,10,191,0v26,-4,24,-4,26,-1v5,5,4,8,-8,20v-31,31,-66,92,-89,154v-10,26,-17,57,-17,71v0,5,0,6,-2,9v-4,3,-8,3,-12,0v-2,-3,-2,-4,-2,-9v0,-14,-7,-45,-17,-71v-23,-62,-58,-123,-89,-154v-12,-12,-13,-15,-8,-20","w":125},"\ue121":{"d":"44,-56v3,-1,9,-2,13,-2v32,0,58,26,58,57v0,32,-26,58,-57,58v-32,0,-58,-26,-58,-57v0,-26,18,-50,44,-56","w":114},"\ue122":{"d":"494,-884v6,-3,12,-3,18,0v6,3,8,5,11,10r2,5r0,493r0,493r-2,5v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11v-2,-4,-2,-5,-2,-46r0,-41r-4,6v-16,28,-49,53,-94,70v-28,11,-57,18,-95,22v-15,2,-72,2,-87,0v-38,-4,-67,-11,-95,-22v-45,-17,-78,-42,-94,-70r-4,-6r0,41v0,41,0,42,-2,46v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11r-2,-5r0,-129r0,-129r2,-5v2,-2,4,-5,5,-6v3,-3,10,-6,15,-6v5,0,12,3,15,6v1,1,3,4,5,6r2,5r0,53r0,52r4,-6v16,-28,49,-53,94,-70v28,-11,57,-18,95,-22v15,-2,72,-2,87,0v38,4,67,11,95,22v45,17,78,42,94,70r4,6r0,-416r0,-417r2,-4v3,-6,5,-8,11,-11xm234,-113v-12,-2,-30,-2,-38,0v-10,1,-22,8,-29,14v-20,18,-30,54,-25,94v4,31,17,56,37,77v25,24,61,40,95,42v35,2,61,-18,70,-55v7,-30,4,-65,-8,-94v-12,-27,-36,-51,-65,-65v-10,-5,-26,-10,-37,-13","w":489},"\ue123":{"d":"-23,-134v6,-3,12,-3,18,0v6,3,8,5,11,10v2,5,2,6,2,47r0,41r4,-6v16,-28,49,-53,94,-70v28,-11,57,-18,95,-22v15,-2,72,-2,87,0v38,4,67,11,95,22v45,17,78,42,94,70r4,6r0,-41v0,-40,0,-42,2,-46v3,-6,5,-8,11,-11v6,-3,12,-3,18,0v6,3,8,5,11,10r2,5r0,118r0,118r-2,5v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11v-2,-4,-2,-6,-2,-46r0,-41r-4,6v-24,42,-88,77,-162,88v-27,5,-37,5,-70,5v-34,0,-44,0,-71,-5v-25,-4,-46,-9,-68,-18v-45,-17,-78,-42,-94,-70r-4,-6r0,416r0,416r-2,5v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11r-2,-5r0,-493r0,-493r2,-4v3,-6,5,-8,11,-11xm234,-113v-12,-2,-30,-2,-38,0v-10,1,-22,8,-29,14v-20,18,-30,54,-25,94v4,31,17,56,37,77v25,24,61,40,95,42v35,2,61,-18,70,-55v7,-30,4,-65,-8,-94v-12,-27,-36,-51,-65,-65v-10,-5,-26,-10,-37,-13","w":489},"\ue124":{"d":"-23,-134v6,-3,12,-3,18,0v6,3,8,5,11,10v2,5,2,6,2,47r0,41r4,-6v24,-42,88,-77,162,-88v27,-5,37,-5,71,-5v33,0,43,0,70,5v25,4,46,9,68,18v45,17,78,42,94,70r4,6r0,-41v0,-40,0,-42,2,-46v3,-6,5,-8,11,-11v6,-3,12,-3,18,0v6,3,8,5,11,10r2,5r0,118r0,118r-2,5v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11v-2,-4,-2,-6,-2,-46r0,-41r-4,6v-16,28,-49,53,-94,70v-28,11,-57,18,-95,22v-15,2,-72,2,-87,0v-38,-4,-67,-11,-95,-22v-45,-17,-78,-42,-94,-70r-4,-6r0,41v0,41,0,42,-2,46v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11r-2,-5r0,-118v0,-116,0,-118,2,-122v3,-6,5,-8,11,-11xm234,-113v-12,-2,-30,-2,-38,0v-10,1,-22,8,-29,14v-20,18,-30,54,-25,94v4,31,17,56,37,77v25,24,61,40,95,42v35,2,61,-18,70,-55v7,-30,4,-65,-8,-94v-12,-27,-36,-51,-65,-65v-10,-5,-26,-10,-37,-13","w":489},"\ue125":{"d":"217,-135v17,-1,67,0,84,1v47,6,88,17,124,36v40,21,65,47,73,77v3,11,3,30,0,41v-8,30,-33,56,-73,77v-36,19,-76,30,-125,36v-22,2,-77,2,-99,0v-49,-6,-89,-17,-125,-36v-40,-21,-65,-47,-73,-77v-3,-11,-3,-30,0,-41v8,-30,33,-56,73,-77v40,-21,87,-33,141,-37xm236,-113v-29,-5,-51,0,-68,17v-5,5,-8,9,-11,16v-8,17,-12,36,-11,59v1,23,6,42,14,59v20,39,58,66,106,74v37,7,65,-5,78,-33v8,-17,12,-36,11,-60v-2,-37,-15,-68,-39,-92v-21,-21,-49,-35,-80,-40","w":501},"\ue126":{"d":"248,-135v2,-1,9,-1,16,-1v35,0,57,8,70,27v14,20,15,45,6,80v-14,53,-38,91,-72,113v-47,31,-106,48,-180,51v-39,1,-63,-7,-76,-27v-14,-20,-15,-45,-6,-80v14,-53,38,-91,72,-113v21,-14,41,-24,66,-32v30,-10,55,-14,104,-18xm291,-106v-9,-3,-21,-4,-30,-1v-24,8,-51,23,-109,60v-78,50,-110,75,-119,93v-12,24,-2,50,22,59v8,4,23,3,33,0v28,-10,64,-31,138,-79v54,-36,79,-57,87,-73v12,-24,2,-50,-22,-59","w":346},"\ue127":{"d":"203,-135v12,-1,40,0,51,2v39,8,63,28,72,61v2,6,2,10,2,22v0,15,0,21,-5,36v-22,68,-102,131,-184,146v-18,3,-48,3,-61,1v-41,-9,-66,-29,-75,-62v-2,-6,-2,-10,-2,-22v0,-15,0,-21,5,-36v8,-26,25,-51,48,-74v40,-40,95,-67,149,-74","w":329},"\ue128":{"d":"232,-135v2,-1,9,-1,16,-1v24,1,41,10,81,42v35,29,45,33,88,42v35,7,49,12,59,20v17,16,17,47,0,63v-10,8,-24,13,-59,20v-43,9,-53,13,-88,42v-30,24,-44,33,-62,39v-8,2,-10,2,-22,2v-13,0,-15,0,-23,-2v-18,-6,-32,-15,-62,-39v-35,-29,-45,-33,-88,-42v-35,-7,-49,-12,-59,-20v-17,-16,-17,-47,0,-63v10,-8,24,-13,59,-20v43,-9,53,-13,88,-42v35,-27,52,-37,72,-41xm231,-72v-14,-4,-27,-5,-32,-3v-11,4,-15,16,-17,49v-1,19,-1,39,1,48v3,13,9,21,20,28v9,6,16,9,34,14v35,12,50,14,59,6v4,-3,7,-10,9,-20v2,-11,3,-40,3,-55v-1,-26,-6,-37,-22,-46v-11,-7,-30,-14,-55,-21","w":489},"\ue129":{"d":"308,-135v7,-1,20,-1,26,2v8,3,17,12,22,21v8,17,4,31,-16,61v-22,34,-25,41,-27,81v-3,28,-4,37,-10,48v-7,13,-21,23,-39,28v-12,3,-39,3,-65,0v-10,-1,-25,-2,-33,-3v-25,-1,-34,2,-74,18v-29,11,-37,13,-50,13v-14,0,-20,-2,-28,-10v-8,-8,-13,-17,-14,-26v-1,-12,4,-24,20,-48v22,-34,25,-41,27,-81v3,-28,4,-37,10,-48v7,-13,21,-23,40,-28v11,-3,38,-3,64,0v49,6,61,4,107,-15v21,-8,30,-11,40,-13xm276,-80v-7,-3,-18,-4,-24,-2v-19,6,-45,20,-96,54v-52,34,-76,54,-83,68v-8,17,-3,33,11,39v8,4,21,3,31,0v19,-8,43,-22,89,-52v52,-34,76,-54,83,-68v8,-17,3,-33,-11,-39","w":360},"\ue12a":{"d":"173,-134v11,-3,22,-2,49,4v22,5,30,6,41,6v8,-1,13,-2,42,-9v14,-3,30,-4,37,0v6,3,11,7,15,14v8,12,9,24,3,36v-6,13,-13,21,-42,46v-34,29,-39,37,-61,87v-11,27,-16,36,-24,48v-7,10,-20,24,-27,29v-16,10,-29,10,-64,2v-22,-5,-30,-6,-40,-6v-9,1,-14,2,-42,9v-15,3,-31,4,-38,0v-6,-3,-11,-7,-15,-14v-8,-12,-9,-24,-3,-36v6,-13,13,-21,43,-46v33,-29,38,-37,60,-87v11,-26,16,-36,24,-48v13,-19,27,-31,42,-35","w":364},"\ue12b":{"d":"25,-178v3,-1,9,-2,13,-2v5,0,7,0,21,8v105,54,220,89,337,104v38,5,70,8,116,9v30,1,33,1,38,3v14,5,20,17,15,28v-4,8,-12,12,-41,20v-138,35,-258,103,-360,203v-11,10,-20,19,-22,19v-5,2,-11,2,-23,0v-11,-1,-20,-6,-24,-11v-4,-5,-4,-10,-1,-25v9,-42,9,-96,0,-143v-7,-36,-20,-74,-36,-105v-15,-28,-35,-57,-50,-72v-7,-7,-9,-11,-9,-16v0,-4,2,-9,5,-12v4,-3,13,-7,21,-8xm155,-74v-15,-5,-27,-9,-28,-10v-1,0,0,4,5,15v26,50,38,102,38,157v0,20,1,23,2,22v7,-6,25,-20,36,-29v51,-38,109,-70,169,-94v8,-3,14,-6,14,-6v-2,0,-37,-5,-47,-7v-64,-11,-126,-27,-189,-48","w":567},"\ue12c":{"d":"20,-162v4,-1,12,-1,17,0v2,0,9,4,15,9v74,48,161,80,248,91v23,4,41,5,69,6v24,1,27,1,34,8v10,9,10,22,0,31v-5,5,-9,7,-27,13v-53,18,-104,45,-148,78v-39,29,-74,63,-105,103v-12,16,-15,17,-27,17v-8,0,-10,0,-16,-2v-7,-4,-12,-9,-14,-15v-1,-4,-1,-6,2,-27v4,-30,5,-71,2,-101v-8,-60,-27,-115,-60,-165v-9,-14,-11,-18,-11,-23v0,-10,7,-18,21,-23xm131,-56v-10,-4,-21,-9,-26,-11v-4,-2,-8,-4,-8,-3v0,0,2,4,4,10v16,41,26,87,28,133r0,16r9,-8v38,-36,85,-70,128,-92v5,-2,9,-4,8,-5v0,0,-6,-2,-13,-3v-41,-7,-90,-21,-130,-37","w":410},"\ue12d":{"d":"20,-162v4,-1,12,-1,17,0v2,0,9,4,15,9v74,48,161,80,248,91v23,4,41,5,69,6v24,1,27,1,34,8v10,9,10,22,0,31v-5,5,-9,7,-27,13v-53,18,-104,45,-148,78v-39,29,-74,63,-105,103v-12,16,-15,17,-27,17v-8,0,-10,0,-16,-2v-7,-4,-12,-9,-14,-15v-1,-4,-1,-6,2,-27v4,-30,5,-71,2,-101v-8,-60,-27,-115,-60,-165v-9,-14,-11,-18,-11,-23v0,-10,7,-18,21,-23xm131,-56v-10,-4,-21,-9,-26,-11v-4,-2,-8,-4,-8,-3v0,0,2,4,4,10v16,41,26,87,28,133r0,16r9,-8v38,-36,85,-70,128,-92v5,-2,9,-4,8,-5v0,0,-6,-2,-13,-3v-41,-7,-90,-21,-130,-37","w":410},"\ue12e":{"d":"14,-161v7,-3,13,-3,19,0v2,1,10,6,16,12v13,10,41,28,55,36v40,23,87,41,131,49v24,5,59,9,80,9v14,0,21,5,26,15v3,9,0,20,-7,27v-4,3,-4,3,-24,11v-85,35,-160,99,-207,177v-7,11,-10,14,-17,17v-7,4,-18,2,-25,-4v-6,-6,-8,-15,-6,-29v7,-36,8,-83,2,-123v-3,-29,-13,-69,-23,-96v-8,-22,-14,-36,-31,-68v-6,-12,-1,-27,11,-33","w":341},"\ue12f":{"d":"14,-161v7,-3,13,-3,19,0v2,1,10,6,16,12v13,10,41,28,55,36v40,23,87,41,131,49v24,5,59,9,80,9v14,0,21,5,26,15v3,9,0,20,-7,27v-4,3,-4,3,-24,11v-85,35,-160,99,-207,177v-7,11,-10,14,-17,17v-7,4,-18,2,-25,-4v-6,-6,-8,-15,-6,-29v7,-36,8,-83,2,-123v-3,-29,-13,-69,-23,-96v-8,-22,-14,-36,-31,-68v-6,-12,-1,-27,11,-33","w":341},"\ue130":{"d":"310,-259v2,-2,14,-2,220,-2r217,0r3,3v2,2,2,3,2,5v-1,4,-307,508,-310,511v-2,2,-14,2,-220,2r-217,0r-3,-3v-2,-2,-2,-3,-2,-5v1,-4,307,-508,310,-511xm617,-168r13,-21r-139,0r-140,0r-4,7v-4,6,-113,186,-211,347r-14,23r140,0r139,-1r102,-167","w":752},"\ue131":{"d":"310,-259v2,-2,10,-2,136,-2r133,0r3,3v2,2,2,3,2,5v-1,4,-307,508,-310,511v-2,2,-10,2,-136,2r-133,0r-3,-3v-2,-2,-2,-3,-2,-5v1,-4,307,-508,310,-511xm449,-168r13,-21r-55,0r-56,0r-4,7v-4,6,-113,186,-211,347r-14,23r56,0r55,-1r102,-167","w":584},"\ue132":{"d":"310,-259v2,-2,6,-2,57,-2r55,0r3,3v2,2,2,3,2,5v-1,4,-307,508,-310,511v-2,2,-6,2,-57,2r-55,0r-3,-3v-2,-2,-2,-3,-2,-5v1,-4,307,-508,310,-511","w":427},"\ue133":{"d":"80,-145v6,-4,6,-4,69,39r59,40r59,-40v62,-43,62,-43,69,-39v7,3,76,51,78,53v2,4,2,12,0,15v-2,2,-27,19,-56,39r-54,37v0,1,24,18,54,38v29,20,54,37,56,39v2,3,2,11,0,15v-2,2,-71,50,-78,53v-7,4,-7,4,-69,-39r-59,-40r-59,40v-62,43,-62,43,-69,39v-7,-3,-76,-51,-78,-53v-2,-4,-2,-12,1,-15v1,-2,26,-19,56,-39v29,-20,53,-37,53,-38r-54,-37v-29,-20,-54,-37,-55,-39v-3,-3,-3,-11,-1,-15v1,-2,69,-49,78,-53xm141,-79r-56,-38r-43,29r-5,4r54,36v36,25,55,38,56,40v3,4,3,11,0,15v-1,2,-19,15,-55,39r-54,37v-1,0,44,31,47,33r56,-38v66,-45,63,-43,67,-43v4,0,1,-2,67,43r56,38v3,-2,48,-33,47,-33r-54,-37v-36,-24,-54,-37,-55,-39v-3,-4,-3,-11,0,-15v1,-2,19,-15,55,-39r54,-37v1,0,-44,-31,-47,-33r-56,38v-66,45,-63,43,-67,43v-4,0,-1,2,-67,-43","w":416},"\ue134":{"d":"62,-139v5,-3,9,-2,14,1v2,1,28,20,57,42r53,39r53,-39v29,-22,55,-41,57,-42v5,-3,9,-4,13,-1v7,3,59,43,61,45v3,5,3,10,0,14v-2,2,-26,20,-54,41r-52,38v0,1,23,18,52,39v28,21,52,39,54,41v3,4,3,9,0,14v-2,2,-54,42,-61,45v-4,3,-8,2,-13,-1v-2,-1,-28,-20,-57,-42r-53,-39r-53,39v-29,22,-55,41,-57,42v-5,3,-9,4,-13,1v-7,-3,-59,-43,-61,-45v-3,-5,-3,-10,0,-14v2,-2,26,-20,54,-41v29,-21,52,-38,52,-39r-52,-38v-28,-21,-52,-39,-54,-41v-3,-4,-3,-9,0,-14v2,-2,53,-41,60,-45xm135,-61v-24,-18,-49,-37,-55,-41r-12,-9r-8,6v-5,4,-12,9,-16,12r-8,6r52,38v31,23,53,40,54,41v3,4,3,11,0,14v-1,2,-23,19,-54,42r-52,38r8,6v4,3,11,8,16,12r8,6r12,-9v6,-4,31,-23,55,-41v24,-17,44,-32,46,-33v2,-1,8,-1,10,0v2,1,22,16,46,33v24,18,49,37,55,41r12,9r8,-6v5,-4,12,-9,16,-12r8,-6r-52,-38v-31,-23,-53,-40,-54,-42v-1,-1,-2,-4,-2,-7v0,-2,1,-5,2,-7v1,-1,23,-18,54,-41r52,-38r-8,-6v-4,-3,-11,-8,-16,-12r-8,-6r-12,9v-6,4,-31,23,-55,41v-24,17,-44,32,-46,33v-2,1,-8,1,-10,0v-2,-1,-22,-16,-46,-33","w":372},"\ue135":{"d":"26,-135v4,-1,8,-1,12,1v1,1,31,24,65,52r62,50r62,-50v34,-28,64,-51,65,-52v7,-4,11,-3,25,8v10,9,12,12,12,18v0,1,-1,4,-2,6v-1,2,-30,25,-63,52r-60,49v0,1,27,23,60,50v33,27,62,50,63,52v1,2,2,5,2,6v0,6,-2,9,-12,18v-14,11,-18,12,-25,8v-1,-1,-31,-24,-65,-52r-62,-50r-62,50v-34,28,-64,51,-65,52v-7,4,-11,3,-25,-8v-10,-9,-12,-12,-12,-18v0,-1,1,-4,2,-6v1,-2,30,-25,63,-52r60,-49v0,-1,-27,-23,-60,-50v-33,-27,-62,-50,-63,-52v-2,-4,-3,-7,-1,-12v1,-3,19,-19,24,-21","w":329},"\ue136":{"d":"173,-169v45,-4,93,6,130,27v45,27,75,66,85,113v2,9,2,14,2,28v0,15,0,20,-2,29v-7,32,-21,58,-45,82v-65,65,-174,78,-255,31v-45,-27,-75,-66,-85,-113v-2,-9,-2,-14,-2,-29v0,-14,0,-19,2,-28v7,-32,21,-58,45,-83v33,-31,77,-52,125,-57xm215,-137v-26,-3,-54,0,-79,9v-11,3,-29,12,-37,17r-5,3r50,42v28,24,51,43,52,43r51,-43r50,-42r-5,-3v-8,-5,-26,-14,-37,-17v-12,-4,-27,-8,-40,-9xm120,-43v-27,-24,-50,-44,-51,-44v-1,0,-11,11,-17,20v-7,11,-13,25,-16,38v-3,11,-3,13,-3,28v0,16,0,18,3,29v3,13,9,27,16,38v5,7,16,20,17,20r51,-43r51,-44xm328,-80v-3,-4,-5,-7,-6,-7r-51,43r-51,44r51,42v27,24,50,44,51,44v1,0,11,-11,17,-20v7,-11,13,-25,16,-38v3,-11,3,-13,3,-29v0,-15,0,-17,-3,-28v-5,-19,-13,-36,-27,-51xm248,65v-27,-23,-50,-42,-51,-43v-2,-1,-7,4,-52,42r-51,43r5,3v8,5,26,14,37,17v46,16,97,12,141,-8v6,-3,13,-7,15,-9r5,-3","w":391},"\ue137":{"d":"243,-113v4,-1,10,-1,14,0v2,1,57,45,122,98v101,83,118,98,120,101v5,10,0,22,-9,26v-4,2,-13,2,-239,2v-256,0,-239,0,-245,-5v-3,-3,-6,-10,-6,-15v0,-9,-6,-4,122,-109v65,-53,120,-97,121,-98xm339,3v-48,-40,-88,-72,-89,-72r-88,72r-87,71r88,1r176,0r87,-1","w":501},"\ue138":{"d":"165,-112v2,-1,6,-2,8,-2v9,0,7,-3,92,102v43,53,79,99,80,100v2,7,0,16,-5,21v-6,5,4,5,-167,5v-171,0,-161,0,-167,-5v-5,-5,-7,-14,-5,-21v1,-1,37,-47,80,-100v68,-83,80,-98,84,-100xm230,6v-31,-38,-56,-69,-57,-69r-56,69r-56,68r56,1r112,0r56,-1","w":346},"\ue139":{"d":"165,-112v2,-1,6,-2,8,-2v9,0,7,-3,92,102v43,53,79,99,80,100v2,7,0,16,-5,21v-6,5,4,5,-167,5v-171,0,-161,0,-167,-5v-5,-5,-7,-14,-5,-21v1,-1,37,-47,80,-100v68,-83,80,-98,84,-100xm230,6v-31,-38,-56,-69,-57,-69r-56,69r-56,68r56,1r112,0r56,-1","w":346},"\ue13a":{"d":"156,-112v2,-1,6,-2,9,-2v9,0,6,-4,87,102v41,53,75,99,76,100v2,7,0,16,-5,21v-6,5,3,5,-159,5v-161,0,-152,0,-158,-5v-5,-5,-7,-14,-5,-21v1,-1,35,-47,76,-100v66,-86,75,-98,79,-100","w":329},"\ue13b":{"d":"156,-112v2,-1,6,-2,9,-2v9,0,6,-4,87,102v41,53,75,99,76,100v2,7,0,16,-5,21v-6,5,3,5,-159,5v-161,0,-152,0,-158,-5v-5,-5,-7,-14,-5,-21v1,-1,35,-47,76,-100v66,-86,75,-98,79,-100","w":329},"\ue13c":{"d":"11,-112v4,-2,14,-2,240,-2v225,0,235,0,239,2v4,2,9,7,10,12v1,2,1,15,1,42v-1,37,-1,39,-3,48v-4,17,-10,29,-20,43v-6,8,-22,24,-32,30v-36,26,-88,43,-151,49v-17,2,-72,2,-88,0v-77,-7,-135,-29,-170,-64v-18,-18,-28,-34,-34,-58v-2,-9,-2,-10,-3,-48v0,-26,0,-40,1,-42v1,-5,6,-10,10,-12xm462,-52r0,-23r-211,0r-212,0r0,23v0,24,1,31,4,42v13,39,51,64,115,77v56,11,129,11,185,0v64,-13,102,-38,115,-77v3,-11,4,-18,4,-42","w":501},"\ue13d":{"d":"11,-112v4,-2,11,-2,162,-2v140,0,157,0,161,2v5,2,8,5,10,9v2,4,2,8,2,39v0,27,0,38,-2,46v-5,34,-23,66,-51,89v-68,57,-172,57,-240,0v-25,-20,-42,-48,-50,-79v-2,-10,-2,-12,-3,-49v0,-28,0,-41,1,-43v1,-5,6,-10,10,-12xm307,-49r0,-26r-134,0r-134,0r0,26v1,31,2,37,10,53v16,34,50,59,94,68v15,3,45,3,60,0v44,-9,78,-34,94,-68v8,-16,9,-22,10,-53","w":346},"\ue13e":{"d":"11,-112v4,-2,11,-2,162,-2v140,0,157,0,161,2v5,2,8,5,10,9v2,4,2,8,2,39v0,27,0,38,-2,46v-5,34,-23,66,-51,89v-68,57,-172,57,-240,0v-25,-20,-42,-48,-50,-79v-2,-10,-2,-12,-3,-49v0,-28,0,-41,1,-43v1,-5,6,-10,10,-12xm307,-49r0,-26r-134,0r-134,0r0,26v1,31,2,37,10,53v16,34,50,59,94,68v15,3,45,3,60,0v44,-9,78,-34,94,-68v8,-16,9,-22,10,-53","w":346},"\ue13f":{"d":"12,-112v3,-2,10,-2,153,-2v142,0,149,0,153,2v4,2,9,7,10,12v1,2,1,15,1,43v-1,34,-1,40,-3,47v-6,30,-20,54,-40,74v-14,14,-27,23,-43,31v-16,8,-27,12,-45,16v-11,2,-15,2,-33,2v-19,0,-23,0,-34,-2v-18,-4,-29,-8,-44,-16v-17,-8,-30,-17,-43,-30v-20,-19,-33,-42,-39,-67v-4,-15,-5,-25,-5,-62v0,-28,0,-35,2,-38v2,-5,5,-8,10,-10","w":329},"\ue140":{"d":"12,-112v3,-2,10,-2,153,-2v142,0,149,0,153,2v4,2,9,7,10,12v1,2,1,15,1,43v-1,34,-1,40,-3,47v-6,30,-20,54,-40,74v-14,14,-27,23,-43,31v-16,8,-27,12,-45,16v-11,2,-15,2,-33,2v-19,0,-23,0,-34,-2v-18,-4,-29,-8,-44,-16v-17,-8,-30,-17,-43,-30v-20,-19,-33,-42,-39,-67v-4,-15,-5,-25,-5,-62v0,-28,0,-35,2,-38v2,-5,5,-8,10,-10","w":329},"\ue141":{"d":"138,-69v75,-31,110,-45,112,-45v3,0,36,13,122,49v65,26,120,49,121,50v10,7,10,23,0,30v-5,4,-239,99,-243,99v-2,0,-41,-15,-120,-48v-65,-27,-119,-49,-120,-50v-9,-4,-12,-15,-8,-25v3,-7,-11,-1,136,-60xm327,-32v-49,-20,-89,-36,-90,-36v-2,0,-153,63,-152,63r89,37r89,36r5,-2v25,-10,149,-61,148,-61","w":501},"\ue142":{"d":"158,-113v3,-1,9,-1,12,0v4,1,149,96,153,99v3,3,6,10,6,14v0,4,-3,11,-6,14v-4,3,-149,98,-153,99v-3,1,-8,1,-11,0v-4,-1,-149,-96,-153,-99v-3,-3,-6,-10,-6,-14v0,-4,3,-11,6,-14v3,-2,149,-98,152,-99xm203,-35v-25,-16,-46,-29,-47,-30v-1,0,-22,13,-47,30r-45,29r9,6v5,3,29,19,53,35v25,16,46,29,47,30r47,-30r45,-29r-9,-6v-5,-3,-29,-19,-53,-35","w":329},"\ue143":{"d":"158,-113v3,-1,9,-1,12,0v4,1,149,96,153,99v3,3,6,10,6,14v0,4,-3,11,-6,14v-4,3,-149,98,-153,99v-3,1,-8,1,-11,0v-4,-1,-149,-96,-153,-99v-3,-3,-6,-10,-6,-14v0,-4,3,-11,6,-14v3,-2,149,-98,152,-99","w":329},"\ue144":{"d":"12,-112v3,-2,12,-2,238,-2v210,0,235,0,239,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-3,7,-10,12,-18,12v-4,0,-471,-191,-475,-194v-1,-1,-3,-4,-4,-7v-5,-9,0,-21,10,-25xm462,-5r0,-70r-171,0r-171,0v1,1,338,139,340,140v2,0,2,-4,2,-70","w":501},"\ue145":{"d":"12,-112v2,-1,5,-2,7,-2v5,0,472,191,476,194v3,3,6,10,6,14v0,5,-3,12,-6,15v-6,5,11,5,-244,5v-210,0,-235,0,-239,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10xm211,5v-92,-38,-169,-69,-170,-70v-2,0,-2,4,-2,70r0,70r171,0r171,0v0,-1,-77,-32,-170,-70","w":501},"\ue146":{"d":"12,-112v3,-2,10,-2,161,-2v140,0,157,0,161,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-4,10,-15,14,-24,11v-3,-1,-311,-191,-314,-193v-1,-2,-3,-4,-4,-7v-5,-9,0,-21,10,-25xm307,-8r0,-67r-109,0v-92,0,-109,0,-107,1v4,4,215,133,215,133v1,0,1,-30,1,-67","w":346},"\ue147":{"d":"12,-112v4,-2,10,-3,14,-1v3,1,311,191,314,193v3,3,6,10,6,14v0,5,-3,12,-6,15v-6,5,4,5,-167,5v-140,0,-157,0,-161,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10xm147,7r-107,-66v-1,0,-1,30,-1,67r0,67r109,0v92,0,109,0,107,-1v-1,-1,-50,-31,-108,-67","w":346},"\ue148":{"d":"12,-112v3,-2,10,-2,152,-2v132,0,149,0,153,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-4,10,-15,14,-24,11v-3,-1,-293,-190,-297,-193v-1,-2,-3,-4,-4,-7v-5,-9,0,-21,10,-25","w":329},"\ue149":{"d":"12,-112v4,-2,10,-3,14,-1v3,1,293,190,297,193v3,3,6,10,6,14v0,5,-3,12,-6,15v-6,5,3,5,-159,5v-131,0,-148,0,-152,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10","w":329},"\ue14a":{"d":"12,-112v3,-2,12,-2,238,-2v210,0,235,0,239,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-2,5,-5,8,-10,10v-4,2,-29,2,-238,2v-210,0,-235,0,-239,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10xm462,0r0,-75r-211,0r-212,0r0,75r0,75r212,0r211,0r0,-75","w":501},"\ue14b":{"d":"12,-112v3,-2,10,-2,161,-2v140,0,157,0,161,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-2,5,-5,8,-10,10v-4,2,-21,2,-161,2v-140,0,-157,0,-161,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10xm307,0r0,-75r-134,0r-134,0r0,75r0,75r134,0r134,0r0,-75","w":346},"\ue14c":{"d":"12,-112v3,-2,10,-2,152,-2v132,0,149,0,153,2v5,2,8,5,10,9v2,4,2,9,2,103v0,94,0,99,-2,102v-2,5,-5,8,-10,10v-4,2,-21,2,-153,2v-131,0,-148,0,-152,-2v-5,-2,-8,-5,-10,-10v-2,-3,-2,-8,-2,-102v0,-86,0,-99,2,-102v2,-5,5,-8,10,-10","w":329},"\ue14d":{"d":"225,-113v12,-1,59,0,72,1v42,5,82,15,119,30v26,11,70,34,78,42v4,3,7,10,7,14v0,5,-3,11,-7,14v-2,2,-56,30,-120,64v-117,61,-118,61,-123,61v-6,0,-7,0,-123,-61v-65,-34,-119,-62,-121,-64v-4,-3,-7,-9,-7,-14v0,-4,3,-11,7,-14v8,-8,52,-31,78,-42v44,-17,91,-28,140,-31xm279,-74v-16,-1,-43,-1,-59,0v-38,3,-77,12,-113,25v-13,5,-44,19,-46,21v-1,0,1,2,4,3v2,2,45,24,95,50v49,26,90,47,91,47r91,-47v49,-26,92,-48,94,-50v3,-1,5,-3,4,-3v-2,-2,-33,-16,-46,-21v-37,-14,-75,-22,-115,-25","w":501},"\ue14e":{"d":"150,-113v12,-1,32,-1,45,0v52,5,105,30,140,67v8,9,11,14,11,20v0,9,1,8,-82,74v-43,34,-80,62,-83,64v-6,3,-10,3,-16,0v-3,-2,-40,-30,-83,-64v-83,-66,-82,-65,-82,-74v0,-6,3,-11,11,-20v35,-36,88,-62,139,-67xm193,-74v-49,-5,-99,10,-138,40r-6,5r2,3v5,4,121,95,122,95v1,0,117,-91,122,-95r2,-3r-6,-5v-28,-22,-63,-36,-98,-40","w":346},"\ue14f":{"d":"150,-113v12,-1,32,-1,45,0v52,5,105,30,140,67v8,9,11,14,11,20v0,9,1,8,-82,74v-43,34,-80,62,-83,64v-6,3,-10,3,-16,0v-3,-2,-40,-30,-83,-64v-83,-66,-82,-65,-82,-74v0,-6,3,-11,11,-20v35,-36,88,-62,139,-67xm193,-74v-49,-5,-99,10,-138,40r-6,5r2,3v5,4,121,95,122,95v1,0,117,-91,122,-95r2,-3r-6,-5v-28,-22,-63,-36,-98,-40","w":346},"\ue150":{"d":"139,-112v41,-5,84,2,121,21v20,10,35,22,52,38v16,17,20,25,15,35v-1,2,-19,18,-76,65v-41,35,-76,64,-78,65v-3,1,-6,2,-9,2v-2,0,-5,-1,-8,-2v-2,-1,-37,-30,-78,-65v-57,-47,-75,-63,-76,-65v-5,-10,-1,-18,15,-35v33,-33,76,-54,122,-59","w":329},"\ue151":{"d":"139,-112v41,-5,84,2,121,21v20,10,35,22,52,38v16,17,20,25,15,35v-1,2,-19,18,-76,65v-41,35,-76,64,-78,65v-3,1,-6,2,-9,2v-2,0,-5,-1,-8,-2v-2,-1,-37,-30,-78,-65v-57,-47,-75,-63,-76,-65v-5,-10,-1,-18,15,-35v33,-33,76,-54,122,-59","w":329},"\ue152":{"d":"-25,-359v4,0,15,-1,23,-1v97,-1,185,51,247,145v39,57,65,124,81,201v4,17,4,19,1,23v-4,7,-16,9,-23,4v-4,-3,-6,-8,-9,-23v-26,-121,-114,-218,-226,-246v-26,-7,-40,-8,-69,-8v-21,0,-28,0,-40,2v-61,9,-114,36,-160,79v-47,45,-80,107,-95,173v-3,15,-5,20,-9,23v-7,5,-19,3,-23,-4v-3,-4,-3,-6,1,-23v11,-54,26,-100,49,-146v59,-118,148,-189,252,-199xm-14,-110v46,-10,86,30,77,75v-5,24,-26,45,-49,50v-46,9,-86,-31,-77,-77v5,-23,26,-43,49,-48","w":329},"\ue153":{"d":"-321,-14v5,-3,12,-2,17,1v4,3,6,8,9,22v26,122,114,219,226,247v26,7,40,8,69,8v21,0,28,0,40,-2v61,-9,114,-36,160,-79v47,-45,80,-107,95,-174v3,-14,5,-19,9,-22v7,-5,19,-3,23,4v3,4,3,6,-1,23v-11,54,-26,100,-49,146v-64,128,-163,200,-277,200v-114,0,-213,-72,-277,-200v-16,-32,-28,-64,-38,-99v-6,-23,-14,-58,-14,-64v0,-4,3,-9,8,-11xm-14,-14v40,-9,78,21,78,62v0,30,-22,56,-50,63v-46,9,-86,-31,-77,-77v5,-23,26,-43,49,-48","w":329},"\ue154":{"d":"-5,-549v3,-2,11,0,14,2v2,1,4,4,6,7v1,3,55,121,118,262v109,239,116,257,116,262v0,5,0,6,-2,9v-5,7,-6,7,-38,7v-31,0,-34,0,-38,-6v-1,-1,-45,-98,-99,-216v-54,-118,-98,-215,-98,-214r-96,212v-52,116,-96,213,-97,215v-6,11,-20,12,-28,2v-2,-3,-2,-4,-2,-9v0,-5,7,-23,116,-262v63,-141,117,-259,118,-262v3,-4,6,-8,10,-9xm-39,-123v34,-8,67,14,75,47v9,45,-32,85,-76,74v-34,-9,-54,-42,-46,-75v6,-23,25,-42,47,-46"},"\ue155":{"d":"-241,2v3,-2,8,-2,32,-2v31,0,34,0,38,6v1,1,45,98,99,216v54,118,98,215,98,214r96,-212v52,-116,96,-213,97,-215v6,-11,20,-12,28,-2v2,3,2,4,2,9v0,5,-7,23,-116,262v-63,141,-117,259,-118,262v-4,7,-8,9,-15,9v-7,0,-11,-2,-15,-9v-1,-3,-55,-121,-118,-262v-109,-239,-116,-257,-116,-262v0,-5,0,-6,2,-9v2,-2,4,-4,6,-5xm10,2v33,-9,67,12,76,46v11,44,-31,86,-75,75v-34,-9,-54,-42,-46,-75v6,-23,23,-40,45,-46"},"\ue156":{"d":"-311,-373r3,-2r308,0r308,0r3,2r2,3r0,179v0,198,1,182,-7,188v-5,4,-13,4,-18,0v-8,-6,-7,5,-7,-142r0,-133r-281,0r-281,0r0,133v0,147,1,136,-7,142v-5,4,-13,4,-18,0v-8,-6,-7,10,-7,-188r0,-179xm-14,-123v34,-8,67,12,75,46v10,38,-20,77,-61,77v-41,0,-71,-39,-61,-77v6,-23,24,-42,47,-46","w":313},"\ue157":{"d":"-304,2v5,-3,12,-2,16,1v8,6,7,-5,7,142r0,133r281,0r281,0r0,-133v0,-147,-1,-136,7,-142v5,-4,13,-4,18,0v8,6,7,-10,7,188r0,179r-2,3r-3,2r-308,0r-308,0r-3,-2r-2,-3r0,-180r1,-180r2,-3v2,-2,4,-4,6,-5xm-14,2v34,-8,67,12,75,46v10,38,-20,77,-61,77v-41,0,-71,-39,-61,-77v6,-23,24,-42,47,-46","w":313},"\ue158":{"d":"-373,-398r3,-2r370,0r370,0r3,2r2,3r0,191v0,213,1,195,-7,201v-5,4,-13,4,-18,0v-8,-6,-7,5,-7,-141r0,-131r-343,0r-343,0r0,131v0,146,1,135,-7,141v-3,2,-4,2,-9,2v-5,0,-6,0,-9,-2v-8,-6,-7,12,-7,-201r0,-191xm-248,-223r3,-2r245,0r245,0r3,2r2,3r0,104v0,115,0,107,-7,113v-5,4,-13,4,-18,0v-7,-6,-7,-2,-7,-78r0,-69r-218,0r-218,0r0,69v0,76,0,72,-7,78v-5,4,-13,4,-18,0v-7,-6,-7,2,-7,-113r0,-104xm-11,-120v7,-2,23,0,31,2v24,9,41,32,41,57v0,26,-16,49,-41,58v-7,2,-9,2,-20,2v-11,0,-13,0,-20,-2v-29,-11,-46,-41,-39,-71v5,-23,23,-41,48,-46","w":375},"\ue159":{"d":"-366,2v5,-3,12,-2,16,1v8,6,7,-5,7,141r0,131r343,0r343,0r0,-131v0,-146,-1,-135,7,-141v5,-4,13,-4,18,0v8,6,7,-12,7,201r0,191r-2,3r-3,2r-370,0r-370,0r-3,-2r-2,-3r0,-192r1,-193r2,-3v2,-2,4,-4,6,-5xm-241,2v5,-3,12,-2,16,1v7,6,7,2,7,78r0,69r218,0r218,0r0,-69v0,-76,0,-72,7,-78v5,-4,13,-4,18,0v7,6,7,-2,7,113r0,104r-2,3r-3,2r-245,0r-245,0r-3,-2r-2,-3r0,-105r1,-105r2,-3v2,-2,4,-4,6,-5xm-11,1v7,-2,23,0,31,2v24,9,41,32,41,57v0,26,-16,49,-41,58v-7,2,-9,2,-20,2v-11,0,-13,0,-20,-2v-29,-11,-46,-41,-39,-71v5,-23,23,-41,48,-46","w":375},"\ue15a":{"d":"-18,-123v5,-1,12,-2,17,-2v48,0,86,37,98,95v3,16,3,44,0,60v-9,47,-36,81,-72,91r-6,2r0,4v1,2,2,15,3,29v1,19,1,26,0,28v-3,9,-13,16,-22,16v-9,0,-19,-7,-22,-16v-1,-2,-1,-9,0,-28v1,-14,2,-27,3,-29r0,-4r-6,-2v-36,-10,-63,-44,-72,-91v-3,-16,-3,-44,0,-60v5,-28,17,-51,34,-68v13,-13,28,-21,45,-25xm17,-93v-5,-3,-7,-3,-15,-4v-5,0,-10,1,-13,1v-19,6,-30,24,-36,58v-2,16,-2,60,0,76v5,30,14,48,30,55v6,3,7,3,17,3v10,0,11,0,17,-3v16,-7,25,-25,30,-55v1,-9,1,-18,1,-38v0,-20,0,-29,-1,-38v-5,-30,-15,-48,-30,-55","w":100},"\ue15b":{"d":"-215,-123v2,-1,5,-2,6,-2v2,0,95,24,208,53r211,55v11,2,15,7,15,17v0,10,-4,15,-15,17r-211,55v-113,29,-206,53,-207,53v-7,0,-16,-8,-17,-15v0,-5,2,-12,6,-15v3,-2,29,-9,128,-35v68,-18,128,-33,134,-34v5,-2,38,-8,74,-14v35,-6,64,-12,64,-12v0,0,-29,-6,-64,-12v-36,-6,-69,-12,-74,-14v-6,-1,-66,-16,-134,-34v-99,-26,-125,-33,-128,-35v-4,-3,-6,-10,-6,-15v1,-5,5,-10,10,-13","w":225},"\ue15c":{"d":"-250,-72v110,-29,201,-53,203,-53v4,0,10,3,14,8v5,7,3,19,-5,24v-7,3,-254,67,-276,71r-70,13r-51,9r51,9r70,13v22,4,269,68,276,71v8,5,10,17,5,24v-4,5,-10,8,-14,8v-1,0,-93,-24,-204,-53r-209,-55v-11,-2,-15,-7,-15,-17v0,-11,4,-15,17,-17v5,-1,98,-26,208,-55xm40,-123v2,-1,5,-2,6,-2v2,0,94,24,205,53r209,55v11,2,15,7,15,17v0,10,-4,15,-15,17r-209,55v-111,29,-203,53,-204,53v-4,0,-10,-3,-14,-8v-5,-7,-3,-19,6,-24v6,-3,253,-67,276,-71v10,-2,41,-8,69,-13r51,-9r-51,-9v-28,-5,-59,-11,-69,-13v-23,-4,-270,-68,-276,-71v-12,-7,-11,-24,1,-30","w":475},"\ue15d":{"d":"-12,-49v31,-7,62,17,62,49v0,31,-29,55,-60,49v-18,-4,-34,-19,-38,-36v-7,-27,9,-55,36,-62","w":50},"\ue15e":{"d":"-11,-247v12,-2,24,-1,35,5v9,4,17,12,21,20v6,11,6,8,-15,119v-10,56,-19,103,-20,105v-1,5,-6,9,-10,9v-4,0,-9,-4,-10,-9v-1,-2,-10,-49,-20,-105v-21,-111,-21,-108,-15,-119v5,-11,20,-22,34,-25","w":47},"\ue15f":{"d":"-6,-8v2,-2,4,-3,6,-3v4,0,9,4,10,9v1,2,10,49,20,104v21,112,21,109,15,120v-6,13,-21,23,-37,25v-28,4,-55,-13,-57,-37v0,-6,2,-20,19,-108v10,-55,19,-102,20,-104v1,-2,2,-5,4,-6","w":47},"\ue160":{"d":"-140,-16r4,-2r136,0r136,0r4,2v13,7,13,25,0,32r-4,2r-136,0r-136,0r-4,-2v-13,-7,-13,-25,0,-32","w":150},"\ue161":{"d":"-10,-197v3,0,8,-1,12,-1v14,1,25,8,31,21v3,6,4,7,4,16v0,9,-1,10,-4,16v-4,8,-10,14,-18,18v-5,2,-7,2,-15,2v-8,0,-10,0,-15,-2v-8,-4,-14,-10,-18,-18v-3,-6,-3,-7,-3,-16v0,-8,0,-10,2,-15v5,-10,14,-18,24,-21xm-139,-15v1,-1,63,-1,139,-1r138,0r3,2v9,5,11,15,6,22v-6,8,5,7,-147,7v-152,0,-141,1,-147,-7v-6,-8,-2,-20,8,-23","w":150},"\ue162":{"d":"-141,-13v3,-2,16,-2,141,-2v152,0,141,-1,147,7v5,7,3,17,-6,22r-3,2r-139,0r-138,-1r-4,-2v-4,-3,-7,-9,-7,-14v1,-5,5,-10,9,-12xm-10,125v3,0,8,-1,12,-1v14,1,25,8,31,21v3,6,4,7,4,16v0,9,-1,10,-4,16v-4,8,-10,14,-18,18v-5,2,-7,2,-15,2v-8,0,-10,0,-15,-2v-8,-4,-14,-10,-18,-18v-3,-6,-3,-7,-3,-16v0,-8,0,-10,2,-15v5,-10,14,-18,24,-21","w":150},"\ue163":{"d":"-5,-273v5,-4,12,-1,15,5v7,14,115,255,115,257v0,4,-4,9,-7,10v-1,1,-17,1,-38,1v-35,0,-36,0,-39,-2v-3,-2,-8,-12,-39,-80v-19,-43,-35,-78,-36,-78v0,-1,-17,34,-36,78v-31,69,-36,78,-38,80v-2,1,-5,2,-6,2v-5,0,-11,-6,-11,-11v0,-2,108,-244,115,-257v1,-2,3,-5,5,-5","w":125},"\ue164":{"d":"-119,1v1,0,19,-1,39,-1v35,0,36,0,39,2v3,2,8,12,39,80v19,43,35,78,36,78v0,1,17,-34,36,-78v31,-69,36,-78,38,-80v2,-1,5,-2,6,-2v5,0,11,6,11,11v0,3,-115,258,-118,261v-4,4,-10,4,-14,0v-3,-3,-118,-258,-118,-261v0,-3,4,-9,6,-10","w":125},"\ue165":{"d":"-18,-123v5,-1,12,-2,17,-2v48,0,86,37,98,95v3,16,3,44,0,60v-11,58,-49,95,-97,95v-48,0,-86,-37,-97,-95v-3,-16,-3,-44,0,-60v5,-28,17,-51,34,-68v13,-13,28,-21,45,-25xm19,-93v-7,-3,-9,-3,-17,-4v-10,0,-14,1,-22,5v-8,4,-17,13,-22,21v-18,31,-21,88,-7,127v7,18,17,30,31,37v7,3,8,3,18,3v10,0,11,0,18,-3v14,-7,24,-19,31,-37v12,-33,12,-79,0,-112v-7,-18,-17,-30,-30,-37","w":100},"\ue166":{"d":"-9,-136v6,-3,12,-3,18,0v6,3,8,5,11,10r2,5r0,50r0,49r49,0r50,0r5,2v5,3,7,5,10,11v3,6,3,12,0,18v-3,6,-5,8,-11,11v-4,2,-6,2,-54,2r-49,0r0,49r0,49r-2,5v-3,6,-5,8,-11,11v-6,3,-12,3,-18,0v-6,-3,-8,-5,-11,-11r-2,-5r0,-49r0,-49r-49,0v-48,0,-50,0,-54,-2v-6,-3,-8,-5,-11,-11v-3,-6,-3,-12,0,-18v3,-6,5,-8,11,-11r5,-2r49,0r49,0r0,-49v0,-48,0,-50,2,-54v3,-6,5,-8,11,-11","w":138},"\ue167":{"d":"-155,-518v4,-3,12,-2,16,1v1,1,3,3,4,5v1,2,22,71,47,154v45,147,46,152,67,232r21,82r21,-82v21,-80,22,-85,67,-232v25,-83,46,-152,47,-154v2,-5,6,-7,12,-8v5,0,10,2,13,6v5,7,8,-6,-70,252v-40,132,-74,244,-75,247v0,4,-2,9,-3,10v-4,7,-20,7,-24,0v-1,-1,-3,-6,-3,-10v-1,-3,-35,-115,-75,-247v-78,-258,-75,-245,-70,-252v1,-1,3,-3,5,-4","w":163},"\ue168":{"d":"-185,-331r3,-2r182,0r182,0r3,2r2,3r0,159v0,176,0,163,-6,167v-6,4,-14,2,-18,-4v-2,-3,-2,-6,-2,-99r0,-95r-161,0r-161,0r0,95v0,93,0,96,-2,99v-5,8,-17,8,-22,0v-2,-3,-2,-7,-2,-163r0,-159","w":188},"\ue169":{"d":"-174,-131v10,-1,29,0,38,2v32,9,49,34,43,60v-6,23,-24,38,-48,40v-20,1,-40,-10,-48,-26r-3,-5r-5,2v-32,7,-53,39,-45,70v6,26,33,45,63,45v20,0,44,-7,64,-19v20,-12,37,-26,74,-62v48,-48,70,-66,95,-82v48,-29,99,-34,144,-13v15,8,25,15,37,26v26,27,39,61,38,98v-1,23,-6,43,-16,63v-17,32,-42,52,-75,61v-11,3,-35,3,-45,0v-33,-9,-50,-33,-44,-60v6,-23,24,-38,48,-40v20,-1,40,10,48,26r3,5r5,-2v32,-7,53,-39,45,-70v-6,-26,-33,-45,-63,-45v-20,0,-44,7,-64,19v-20,12,-37,26,-74,62v-48,48,-70,66,-95,82v-48,29,-99,34,-144,13v-15,-8,-25,-15,-37,-27v-26,-26,-39,-60,-38,-97v2,-50,27,-93,65,-114v8,-5,25,-11,34,-12","w":273},"\ue16a":{"d":"-159,-130v12,-2,35,-2,48,1v26,5,50,17,78,38v20,15,35,29,74,67v37,36,54,50,74,62v20,12,44,19,64,19v30,0,57,-19,63,-45v8,-31,-13,-63,-45,-70r-5,-2r-3,5v-1,3,-5,8,-8,11v-24,24,-68,18,-83,-12v-16,-31,1,-62,39,-73v10,-3,34,-3,45,0v33,9,58,29,75,61v23,47,21,100,-7,142v-7,10,-23,27,-33,34v-10,7,-28,15,-39,18v-41,12,-83,5,-124,-20v-25,-16,-47,-34,-95,-82v-37,-36,-54,-50,-74,-62v-20,-12,-44,-19,-64,-19v-30,0,-57,19,-63,45v-8,31,13,63,45,70r5,2r3,-5v1,-3,5,-8,8,-11v24,-24,68,-18,83,12v16,31,-1,62,-39,73v-10,3,-34,3,-45,0v-33,-9,-58,-29,-75,-61v-23,-47,-21,-100,7,-142v7,-10,23,-27,33,-34v16,-11,37,-19,58,-22","w":273},"\ue16b":{"d":"-17,-534v4,-3,7,-6,7,-6r-27,134r-27,134v1,0,17,-9,36,-20v20,-10,38,-21,42,-22v38,-18,77,-20,103,-6v9,5,18,12,20,17r2,4r7,-7v30,-27,74,-33,104,-14v20,14,30,38,26,69v-5,43,-35,77,-65,75v-16,-1,-26,-10,-32,-27v-3,-9,-3,-30,-1,-40v7,-25,27,-41,53,-44r8,-1r-3,-4v-17,-22,-54,-21,-77,1v-13,14,-10,3,-41,159r-27,138v-1,0,-4,-1,-7,-3v-11,-5,-18,-6,-33,-6v-14,0,-22,1,-35,6v-4,2,-7,3,-7,3v0,-1,12,-62,27,-137v30,-149,29,-142,23,-151v-7,-12,-22,-17,-39,-12v-5,2,-74,38,-86,46v-4,3,-4,3,-7,20r-3,17r7,8v21,25,34,49,40,73v2,9,2,12,2,27v0,14,0,18,-2,26v-5,18,-11,31,-21,45v-6,8,-19,21,-27,26v-8,5,-21,12,-28,14v-9,2,-22,2,-29,1v-27,-6,-44,-35,-46,-82v-1,-20,1,-33,11,-82v7,-36,8,-44,7,-43v-40,16,-80,25,-107,24v-23,-2,-39,-10,-47,-24v-13,-25,-4,-60,22,-82v8,-6,23,-14,34,-17v23,-6,51,-5,77,3v10,3,25,10,33,15v4,3,5,3,5,1v1,-1,11,-53,24,-115v12,-62,22,-114,23,-115v0,-1,1,-1,7,1v7,2,9,2,16,2v14,-1,26,-6,42,-16v5,-4,12,-9,16,-13xm-208,-278v-9,-1,-32,-2,-40,-1v-27,4,-45,19,-50,40v-6,22,4,38,25,43v22,4,64,-4,106,-22r11,-5r3,-13v2,-7,3,-14,3,-15v0,-1,-15,-10,-25,-15v-9,-5,-22,-9,-33,-12xm-65,-157v-8,-13,-17,-25,-17,-23v-2,4,-13,64,-15,76v-3,18,-4,38,-2,46v2,14,7,20,17,19v13,-2,25,-16,31,-38v3,-11,3,-35,0,-46v-3,-13,-8,-23,-14,-34","w":213},"\ue16c":{"d":"-116,-165v7,-4,15,-2,20,5r3,3r0,86v1,86,1,87,3,94v9,34,34,58,67,67v12,3,34,3,46,0v33,-9,58,-33,67,-67v2,-7,2,-8,3,-94r1,-86r2,-3v6,-7,15,-9,22,-4v7,6,7,1,7,91v0,90,0,92,-6,111v-11,33,-34,59,-65,75v-28,13,-58,15,-89,7v-11,-3,-30,-13,-40,-20v-27,-21,-45,-52,-49,-85v-1,-7,-1,-35,-1,-91r1,-81r2,-3v2,-2,4,-4,6,-5","w":125},"\ue16d":{"d":"-18,-124v16,-2,36,-1,53,4v10,3,28,11,37,18v22,16,39,39,47,64v6,19,6,21,6,111v0,90,0,85,-7,91v-7,5,-16,3,-22,-4r-2,-3r-1,-86v-1,-86,-1,-87,-3,-94v-9,-34,-34,-58,-67,-67v-12,-3,-34,-3,-46,0v-33,9,-58,33,-67,66v-2,8,-2,9,-3,95r0,86r-3,3v-6,7,-15,9,-22,4v-7,-6,-7,-1,-7,-91v0,-87,0,-91,5,-108v3,-10,11,-28,18,-37v12,-17,28,-30,48,-40v12,-6,22,-9,36,-12","w":125},"\ue16e":{"d":"-115,-374v6,-3,17,1,20,7v0,1,16,50,35,110v35,108,35,109,47,159r13,51r13,-51v12,-50,12,-51,47,-159v19,-60,35,-109,35,-110r5,-5v4,-3,13,-4,18,0v3,2,7,9,7,13v0,2,-24,79,-54,172v-30,93,-54,171,-55,174v-1,3,-2,6,-3,8v-4,7,-22,7,-26,0v-1,-2,-2,-5,-3,-8v-1,-3,-25,-81,-55,-174v-30,-93,-54,-170,-54,-171v0,-4,2,-10,5,-12v1,-1,3,-3,5,-4","w":125},"\ue16f":{"d":"-9,1v4,-1,13,-1,17,0v5,2,7,5,8,12v1,3,25,81,55,174v30,93,54,170,54,172v0,4,-4,11,-7,13v-5,4,-14,3,-18,0r-5,-5v0,-1,-16,-50,-35,-110v-35,-108,-35,-109,-47,-159r-13,-51r-13,51v-12,50,-12,51,-47,159v-19,60,-35,109,-35,110r-5,5v-4,3,-13,4,-18,0v-3,-2,-7,-9,-7,-13v0,-2,24,-79,54,-172v30,-93,54,-171,55,-174v1,-7,3,-10,7,-12","w":125},"\ue170":{"d":"-18,-132v4,0,13,-1,22,0v16,0,26,2,39,6v39,13,71,45,83,84v5,15,6,23,6,42v0,19,-1,27,-6,42v-12,39,-45,72,-84,84v-15,5,-23,6,-42,6v-19,0,-27,-1,-42,-6v-39,-12,-72,-45,-84,-84v-5,-15,-6,-23,-6,-42v0,-19,1,-27,6,-42v15,-48,58,-83,108,-90xm28,-113v-12,-2,-33,-4,-43,-2v-49,7,-86,41,-98,88v-2,9,-2,12,-2,27v0,15,0,18,2,27v11,43,43,75,86,86v9,2,12,2,27,2v15,0,18,0,27,-2v43,-11,75,-43,86,-86v2,-9,2,-12,2,-27v0,-15,0,-18,-2,-27v-11,-43,-43,-75,-85,-86","w":133},"\ue171":{"d":"-124,-374v26,-3,53,1,77,14v40,20,67,57,77,103v3,14,3,40,1,50v-5,15,-13,27,-22,31v-11,6,-28,7,-41,5v-27,-6,-44,-31,-42,-63v1,-12,3,-19,8,-30v5,-11,15,-20,24,-25v4,-2,6,-3,6,-4v0,-1,-1,-5,-3,-8v-6,-15,-18,-29,-32,-36v-37,-19,-78,-6,-96,30v-8,17,-11,37,-8,61v9,64,50,118,131,171v16,11,42,26,43,26v1,0,45,-73,98,-163r96,-163r28,0r29,0r-1,2v-5,7,-205,347,-205,348v0,0,7,5,16,9v87,49,145,101,171,155v38,78,17,169,-48,213v-22,14,-44,21,-71,23v-67,3,-127,-47,-142,-118v-3,-14,-3,-40,-1,-50v5,-15,13,-27,22,-31v11,-6,28,-7,41,-5v27,6,44,31,42,63v-1,12,-3,19,-8,30v-5,11,-15,20,-24,25v-4,2,-6,3,-6,4v0,1,1,5,3,8v6,15,18,29,32,36v37,19,78,6,96,-30v8,-17,11,-37,8,-61v-9,-64,-50,-118,-131,-171v-16,-11,-42,-26,-43,-26v-1,0,-45,73,-97,163r-96,163r-29,0r-29,0r1,-2v5,-7,205,-347,205,-348v0,0,-7,-5,-16,-10v-87,-48,-145,-100,-171,-154v-30,-63,-24,-134,16,-184v23,-28,56,-47,91,-51xm168,-71v1,-1,5,-1,9,-1v11,1,19,6,24,16v3,6,3,19,0,25v-3,6,-7,10,-12,13v-5,2,-7,2,-13,2v-7,0,-9,0,-13,-2v-10,-5,-16,-15,-16,-25v0,-13,8,-24,21,-28xm-183,16v1,-1,5,-1,9,-1v11,1,19,6,24,16v3,6,3,19,0,25v-3,6,-7,10,-12,13v-5,2,-7,2,-13,2v-7,0,-9,0,-13,-2v-10,-5,-16,-15,-16,-25v0,-13,8,-24,21,-28"},"\ue172":{"d":"-7,-349v6,-4,14,-2,18,4v2,3,2,6,2,50r0,46r6,0v13,2,32,8,46,16v56,31,94,108,101,204v1,8,1,15,1,15v0,1,20,1,45,1v50,0,49,0,53,6v3,4,3,10,0,14v-4,6,-3,6,-53,6v-25,0,-45,0,-45,1v0,0,0,7,-1,14v-8,105,-51,186,-115,212v-9,4,-24,8,-32,9r-6,0r0,46v0,44,0,47,-2,50v-5,8,-17,8,-22,0v-2,-3,-2,-6,-2,-50r0,-46r-6,0v-13,-2,-32,-8,-46,-16v-56,-31,-94,-108,-101,-205v-1,-7,-1,-14,-1,-14v0,-1,-20,-1,-45,-1v-50,0,-49,0,-53,-6v-3,-4,-3,-10,0,-14v4,-6,3,-6,53,-6v25,0,45,0,45,-1v0,0,0,-7,1,-15v8,-104,51,-185,115,-211v9,-4,24,-8,32,-9r6,0r0,-46v0,-51,0,-50,6,-54xm-13,-118v0,-99,0,-105,-2,-105v-3,0,-17,5,-22,7v-29,17,-46,54,-52,117v-2,18,-4,53,-4,72r0,14r40,0r40,0r0,-105xm26,-220v-3,-1,-7,-2,-9,-3r-4,0r0,105r0,105r40,0r40,0r0,-27v-2,-80,-11,-123,-31,-153v-9,-13,-22,-23,-36,-27xm-13,118r0,-105r-40,0r-40,0r0,27v1,32,2,49,5,71v8,68,32,104,71,112r4,0r0,-105xm93,40r0,-27r-40,0r-40,0r0,105r0,105r4,0v39,-8,63,-44,71,-112v3,-22,4,-39,5,-71","w":254},"\ue173":{"d":"-7,-345v6,-4,14,-2,18,4v2,3,2,6,2,47r0,44r75,0r74,0r3,2r2,3r0,116r0,116r44,0v48,0,47,0,51,6v4,6,2,14,-4,18v-3,2,-6,2,-47,2r-44,0r0,116r0,116r-2,3r-3,2r-74,0r-75,0r0,44v0,41,0,44,-2,47v-5,8,-17,8,-22,0v-2,-3,-2,-6,-2,-47r0,-44r-75,0r-74,0r-3,-2r-2,-3r0,-116r0,-116r-44,0v-48,0,-47,0,-51,-6v-3,-4,-3,-10,0,-14v4,-6,3,-6,51,-6r44,0r0,-116r0,-116r2,-3r3,-2r74,0r75,0r0,-44v0,-48,0,-47,6,-51xm-13,-119r0,-105r-35,0r-35,0r0,105r0,106r35,0r35,0r0,-106xm83,-119r0,-105r-35,0r-35,0r0,105r0,106r35,0r35,0r0,-106xm-13,118r0,-105r-35,0r-35,0r0,105r0,106r35,0r35,0r0,-106xm83,118r0,-105r-35,0r-35,0r0,105r0,106r35,0r35,0r0,-106","w":251},"\ue174":{"d":"38,-154v10,-4,23,-1,31,5v4,4,12,15,17,26v30,59,18,135,-36,225v-12,21,-29,46,-32,48v-6,4,-14,2,-18,-4v-3,-6,-3,-10,4,-20v32,-48,48,-99,46,-145v-2,-31,-10,-56,-26,-82v-9,-13,-11,-21,-8,-32v3,-9,12,-18,22,-21","w":125},"\ue175":{"d":"-18,-150v6,-4,14,-2,18,4v3,6,3,10,-4,20v-32,48,-48,99,-46,145v2,31,10,56,27,83v3,5,6,11,7,14v4,19,-10,38,-29,40v-8,0,-18,-3,-24,-7v-4,-4,-12,-15,-17,-26v-30,-59,-18,-135,36,-225v12,-21,29,-46,32,-48","w":0},"\ue176":{"d":"51,-181v11,-4,25,-1,34,7v8,8,12,22,9,31v-2,5,-145,299,-147,301v-2,3,-6,5,-10,5v-6,0,-12,-6,-12,-13v0,-5,108,-317,111,-320v4,-5,9,-9,15,-11","w":63},"\ue177":{"d":"57,-161v1,-1,4,-2,5,-2v7,0,13,6,13,13v0,5,-108,316,-112,321v-11,15,-33,16,-48,3v-8,-8,-12,-22,-9,-31v1,-4,145,-299,146,-301v1,-1,3,-2,5,-3","w":63},"\ue178":{"d":"47,-305v4,-2,9,-1,12,1v4,3,129,106,134,111v5,5,9,12,9,17v0,5,-4,14,-8,20v-5,8,-16,18,-39,39v-25,22,-29,26,-35,36v-15,25,-14,55,2,80v3,4,9,9,20,18v18,14,21,17,21,22v0,4,-4,11,-7,13v-4,3,-10,4,-14,1v-4,-2,-130,-105,-136,-110v-2,-2,-4,-6,-6,-9v-4,-9,-2,-20,9,-34v7,-8,13,-13,38,-35v24,-21,30,-27,36,-40v6,-11,7,-18,7,-33v0,-12,-1,-20,-6,-30v-5,-12,-9,-17,-27,-30v-17,-14,-20,-17,-20,-22v0,-6,5,-13,10,-15","w":200},"\ue179":{"d":"65,-200v7,-3,12,-3,19,0v13,5,21,13,49,45v22,25,26,29,36,35v25,15,55,14,80,-2v4,-3,9,-9,18,-20v14,-18,17,-21,23,-21v3,0,10,4,12,7v3,4,4,10,1,14v-2,4,-105,130,-110,136v-2,2,-6,4,-9,6v-9,4,-20,2,-33,-9v-9,-7,-14,-13,-36,-38v-21,-24,-27,-30,-39,-36v-12,-6,-19,-7,-33,-7v-13,0,-21,1,-31,6v-12,5,-17,9,-30,27v-8,9,-15,17,-16,18v-4,3,-9,2,-13,0v-8,-5,-11,-13,-7,-20v3,-4,106,-129,111,-134v2,-2,6,-5,8,-7"},"\ue17a":{"d":"45,-305v4,-1,8,-1,11,0v3,1,90,93,97,102v5,7,10,17,13,28r3,8r35,-17v20,-10,39,-19,42,-21v6,-3,7,-3,10,-2v3,1,4,5,4,8v-1,4,-22,27,-52,58v-39,39,-51,53,-66,73v-14,19,-27,42,-34,59v-3,8,-4,9,-9,9v-3,0,-3,-1,-7,-10v-16,-39,-38,-68,-93,-125v-43,-43,-59,-61,-59,-66v0,-2,4,-7,7,-7v1,0,14,6,30,14v16,8,38,18,49,24r19,9r14,-17v8,-9,17,-20,20,-25v8,-12,12,-23,9,-32v-1,-5,-20,-23,-35,-36v-15,-11,-18,-14,-17,-21v1,-5,5,-11,9,-13","w":200},"\ue17b":{"d":"96,-249v1,-1,3,-1,5,-1v3,0,3,1,7,9v16,40,38,69,93,125v43,44,59,62,59,67v0,2,-4,7,-7,7v-1,0,-14,-6,-30,-14v-16,-8,-38,-18,-49,-24r-19,-9r-14,17v-8,9,-17,20,-20,25v-8,12,-12,23,-9,32v1,5,20,23,35,36v11,8,15,11,16,14v5,11,-8,25,-19,20v-3,-1,-90,-93,-97,-102v-5,-7,-10,-17,-13,-28r-3,-8r-35,17v-20,10,-39,19,-42,21v-6,3,-7,3,-10,2v-3,-1,-4,-5,-4,-8v1,-4,22,-27,52,-58v39,-39,51,-53,66,-73v14,-19,27,-42,34,-60v1,-3,3,-6,4,-7","w":200},"\ue17c":{"d":"-42,-123v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v0,0,2,-2,4,-5v2,-4,4,-6,5,-6v0,0,7,5,15,10r16,11r-37,55v-34,51,-37,55,-41,57v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v0,0,-2,2,-4,5v-2,4,-4,6,-5,6v0,0,-7,-5,-15,-10r-16,-11r37,-55v34,-51,37,-55,41,-57","w":104},"\ue17d":{"d":"-146,-123v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61r-16,23v-17,26,-18,28,-23,30v-14,6,-29,-5,-27,-20v1,-4,10,-17,50,-79v46,-69,50,-74,54,-76","w":208},"\ue17e":{"d":"-7,-165v9,-5,21,0,25,9v2,4,2,8,2,48r0,43r19,-28v17,-25,19,-28,23,-30v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-61,-63v-27,-33,-50,-60,-51,-62v-2,-1,-2,-1,-4,1v-2,2,-2,5,-2,76v0,65,0,75,-2,78v-6,15,-27,16,-35,1v-1,-2,-2,-8,-2,-47r0,-45r-19,28v-18,26,-20,29,-24,31v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61r-16,23v-17,26,-18,28,-23,30v-14,6,-29,-5,-27,-20v1,-4,10,-17,50,-79v46,-69,50,-74,54,-76v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,2,-2,3,-3v2,-4,2,-5,2,-76v0,-64,0,-74,2,-77v2,-5,5,-8,10,-10","w":208},"\ue17f":{"d":"-250,-123v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61r-16,23v-17,26,-18,28,-23,30v-14,6,-29,-5,-27,-20v1,-4,10,-17,50,-79v46,-69,50,-74,54,-76","w":313},"\ue180":{"d":"97,-165v9,-5,21,0,25,9v2,4,2,8,2,48r0,43r19,-28v17,-25,19,-28,23,-30v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-61,-63v-27,-33,-50,-60,-51,-62v-2,-1,-2,-1,-4,1v-2,2,-2,5,-2,76v0,65,0,75,-2,78v-6,15,-27,16,-35,1v-1,-2,-2,-8,-2,-47r0,-45r-19,28v-18,26,-20,29,-24,31v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61r-16,23v-17,26,-18,28,-23,30v-14,6,-29,-5,-27,-20v1,-4,10,-17,50,-79v46,-69,50,-74,54,-76v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,2,-2,3,-3v2,-4,2,-5,2,-76v0,-64,0,-74,2,-77v2,-5,5,-8,10,-10","w":313},"\ue181":{"d":"-250,-123v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-2,1,-40,60,-45,71v-15,30,-22,63,-21,96v2,39,12,72,35,108v2,2,3,7,4,9v2,14,-14,25,-27,19v-6,-2,-8,-4,-14,-16v-17,-26,-27,-54,-33,-87v-2,-13,-2,-17,-2,-38v0,-32,3,-53,13,-83v8,-23,15,-35,54,-95v67,-100,69,-103,74,-106","w":313},"\ue182":{"d":"97,-165v9,-5,21,0,25,9v2,4,2,8,2,48r0,43r19,-28v17,-25,19,-28,23,-30v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-61,-63v-27,-33,-50,-60,-51,-62v-2,-1,-2,-1,-4,1v-2,2,-2,5,-2,76v0,65,0,75,-2,78v-6,15,-27,16,-35,1v-1,-2,-2,-8,-2,-47r0,-45r-19,28v-18,26,-20,29,-24,31v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-2,1,-40,60,-45,71v-15,30,-22,63,-21,96v2,39,12,72,35,108v2,2,3,7,4,9v2,14,-14,25,-27,19v-6,-2,-8,-4,-14,-16v-17,-26,-27,-54,-33,-87v-2,-13,-2,-17,-2,-38v0,-32,3,-53,13,-83v8,-23,15,-35,54,-95v67,-100,69,-103,74,-106v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,2,-2,3,-3v2,-4,2,-5,2,-76v0,-64,0,-74,2,-77v2,-5,5,-8,10,-10","w":313},"\ue183":{"d":"-183,-123v6,-3,12,-2,17,0v4,2,7,7,45,63v22,33,40,61,41,61r51,-61v54,-66,53,-65,63,-65v2,0,6,1,8,2v4,2,7,7,45,63v22,33,40,61,41,61r51,-61v54,-66,53,-65,63,-65v2,0,6,1,8,2v5,3,7,6,74,106v39,60,46,72,54,95v10,30,13,51,13,83v0,21,0,25,-2,38v-6,33,-16,61,-33,87v-6,12,-8,14,-13,16v-14,6,-30,-5,-28,-19v1,-2,2,-7,4,-9v23,-36,33,-69,35,-108v1,-33,-6,-66,-21,-96v-5,-11,-43,-70,-45,-71r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-7,-7,-45,-63v-22,-33,-40,-61,-41,-61r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-7,-7,-45,-63v-22,-33,-40,-61,-41,-61r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-8,-7,-54,-76v-40,-62,-49,-75,-50,-79v-2,-15,13,-26,27,-20v5,2,6,4,23,30r16,23r51,-61v42,-51,51,-62,54,-63","w":313},"\ue184":{"d":"-342,-233v9,-5,21,0,25,9v4,8,3,12,-5,24v-29,45,-38,100,-27,152v2,12,8,30,13,41v2,5,2,5,3,4v1,-1,19,-28,40,-60v36,-54,39,-58,43,-60v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-8,12,-17,25v-17,25,-20,28,-27,30v-11,2,-18,-5,-32,-32v-12,-23,-20,-46,-25,-73v-2,-13,-2,-17,-2,-38v0,-27,1,-39,7,-63v6,-23,15,-45,28,-66v6,-11,8,-13,14,-15","w":313},"\ue185":{"d":"-342,-233v9,-5,21,0,25,9v4,8,3,12,-5,24v-29,45,-38,100,-27,152v2,12,8,30,13,41v2,5,2,5,3,4v1,-1,19,-28,40,-60v36,-54,39,-58,43,-60v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,2,-2,3,-3v2,-4,2,-5,2,-77v0,-68,0,-73,2,-77v2,-4,5,-7,10,-9v10,-4,21,0,25,9v2,4,2,8,2,48r0,43r19,-28v17,-25,19,-28,23,-30v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-61,-63v-27,-33,-50,-60,-51,-62v-2,-1,-2,-1,-4,1v-2,2,-2,5,-2,76v0,65,0,75,-2,78v-6,15,-27,16,-35,1v-1,-2,-2,-8,-2,-47r0,-45r-19,28v-18,26,-20,29,-24,31v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-8,12,-17,25v-17,25,-20,28,-27,30v-11,2,-18,-5,-32,-32v-12,-23,-20,-46,-25,-73v-2,-13,-2,-17,-2,-38v0,-27,1,-39,7,-63v6,-23,15,-45,28,-66v6,-11,8,-13,14,-15","w":313},"\ue186":{"d":"327,-233v2,-1,5,-2,8,-2v4,0,11,3,14,6v3,3,12,19,17,29v11,22,20,50,24,76v1,9,1,17,1,35v0,21,0,25,-2,38v-5,27,-13,50,-25,73v-14,27,-21,34,-32,32v-7,-2,-10,-5,-27,-30v-9,-13,-16,-25,-17,-25r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-7,-7,-45,-63v-22,-33,-40,-61,-41,-61r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-7,-7,-45,-63v-22,-33,-40,-61,-41,-61r-51,61v-54,66,-53,65,-63,65v-2,0,-6,-1,-8,-2v-4,-2,-8,-7,-54,-76v-40,-62,-49,-75,-50,-79v-2,-15,13,-26,27,-20v5,2,6,4,23,30r16,23r51,-61v54,-66,53,-65,63,-65v2,0,6,1,8,2v4,2,7,7,45,63v22,33,40,61,41,61r51,-61v54,-66,53,-65,63,-65v2,0,6,1,8,2v4,2,7,7,45,63v22,33,40,61,41,61r51,-61v54,-66,53,-65,63,-65v2,0,6,1,8,2v4,2,7,6,43,60v21,32,39,59,40,60v1,1,1,1,3,-4v5,-11,11,-29,13,-41v11,-52,2,-107,-27,-152v-8,-12,-8,-16,-5,-23v2,-5,5,-8,10,-10","w":313},"\ue187":{"d":"-342,-483v9,-4,20,-1,25,8v1,3,2,21,2,224r1,220r30,-45v27,-41,30,-45,34,-47v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61v1,0,19,-28,41,-61v38,-56,41,-61,45,-63v2,-1,6,-2,8,-2v10,0,9,-1,63,65r51,61r16,-23v17,-26,18,-28,23,-30v14,-6,29,5,27,20v-1,4,-10,17,-50,79v-46,69,-50,74,-54,76v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61v-1,0,-19,28,-41,61v-38,56,-41,61,-45,63v-2,1,-6,2,-8,2v-10,0,-9,1,-63,-65r-51,-61r-16,23v-17,26,-18,28,-23,30v-10,4,-21,0,-25,-10v-2,-3,-2,-13,-2,-257v0,-227,0,-254,2,-258v2,-5,5,-8,10,-10","w":313},"\ue188":{"d":"125,-310v15,-4,30,4,35,18v1,5,1,6,-1,30v-12,134,-52,278,-120,433v-8,19,-10,23,-13,25v-10,8,-24,2,-26,-10v0,-4,1,-7,8,-24v52,-122,83,-259,97,-425v3,-30,3,-30,6,-35v3,-6,9,-10,14,-12xm275,-310v15,-4,30,4,35,18v1,5,1,6,-1,30v-12,134,-52,278,-120,433v-8,19,-10,23,-13,25v-10,8,-24,2,-26,-10v0,-4,1,-7,8,-24v52,-122,83,-259,97,-425v3,-30,3,-30,6,-35v3,-6,9,-10,14,-12","w":500},"\ue189":{"d":"-14,125r0,-125r7,0r7,0r0,6v0,10,2,28,4,41v8,51,30,104,71,172r33,53v29,48,46,78,59,103v27,56,40,102,42,151v1,51,-7,109,-25,167v-7,23,-17,50,-20,53v-3,4,-9,7,-14,7v-5,0,-14,-4,-17,-7v-5,-6,-6,-14,-3,-22v5,-11,15,-40,19,-54v14,-46,20,-86,20,-130v0,-22,0,-27,-2,-37v-13,-69,-60,-142,-153,-238r-14,-15r-7,0r-7,0r0,-125","w":223},"\ue18a":{"d":"-14,250r0,-250r7,0r7,0r0,13v2,36,13,73,33,113v15,30,29,53,65,104v43,62,59,88,74,119v15,31,24,60,29,88v2,17,2,50,0,64v-4,20,-10,40,-18,57r-3,8r6,15v17,40,24,74,23,114v-2,51,-13,101,-33,151v-10,25,-12,27,-19,30v-5,3,-11,2,-16,0v-6,-3,-9,-6,-11,-11v-3,-6,-2,-10,4,-25v22,-47,34,-96,36,-144v0,-20,-1,-35,-6,-53v-10,-40,-33,-80,-75,-129v-14,-16,-51,-54,-73,-74r-15,-14r-1,37r0,37r-7,0r-7,0r0,-250xm41,253v-10,-10,-23,-24,-30,-29r-11,-11r0,5v0,10,2,27,5,42v9,43,29,87,68,143v5,8,20,29,32,46r36,51r14,21v1,0,4,-12,7,-24v2,-11,2,-40,0,-54v-11,-57,-48,-116,-121,-190","w":223},"\ue18b":{"d":"-14,375r0,-375r7,0r7,0r0,12v3,56,23,109,69,182r29,45v34,54,49,79,62,106v16,34,26,64,31,96v2,16,2,50,0,63v-3,14,-7,29,-12,42r-4,10r5,13v23,52,28,96,18,146v-3,15,-9,36,-15,49r-4,8r6,12v11,24,19,52,23,78v4,34,-2,84,-14,128v-9,31,-25,71,-31,77v-6,5,-15,6,-22,3v-6,-3,-9,-6,-11,-11v-3,-6,-2,-10,2,-20v7,-12,14,-30,19,-46v17,-47,23,-102,16,-136v-5,-27,-19,-56,-40,-85v-24,-33,-61,-71,-110,-111r-16,-14r-1,51r0,52r-7,0r-7,0r0,-375xm28,243v-9,-10,-18,-20,-22,-24r-6,-7r0,14v2,29,8,58,22,89v12,29,32,62,64,106v7,11,21,29,29,41v9,13,20,28,25,36r9,13r1,-4v4,-15,5,-35,3,-53v-9,-62,-46,-126,-125,-211xm19,446v-9,-7,-16,-14,-17,-15v-2,-1,-2,-1,-2,7v0,30,10,68,27,103v16,34,32,59,79,121v20,26,35,48,43,59v2,4,5,7,5,6v1,-1,6,-19,8,-29v2,-15,2,-44,-1,-58v-3,-16,-8,-30,-17,-48v-22,-45,-61,-90,-125,-146","w":209},"\ue18c":{"d":"-14,500r0,-500r7,0r7,0r0,12v2,40,13,79,34,122v13,27,28,52,60,103v27,42,35,56,45,74v29,50,45,93,52,136v2,18,2,52,-1,68v-3,16,-7,33,-12,45r-4,9r4,9v3,5,7,15,9,22v23,63,21,122,-6,182r-6,13r5,11v10,22,17,48,21,70v2,16,2,45,0,57v-5,19,-14,40,-26,56r-7,9r6,11v19,35,31,71,34,106v2,24,0,61,-7,93v-6,34,-21,78,-34,103v-5,11,-16,15,-26,10v-6,-3,-9,-6,-11,-11v-3,-6,-2,-10,1,-18v25,-50,41,-115,39,-163v-2,-34,-14,-67,-39,-105v-16,-24,-34,-45,-63,-74v-19,-19,-52,-50,-64,-59r-4,-3r0,56r0,56r-7,0r-7,0r0,-500xm17,231v-9,-10,-16,-18,-17,-18v0,0,0,23,1,34v5,49,25,98,68,161r36,51v13,19,28,40,33,48v5,8,10,15,10,15v0,0,1,-3,2,-7v12,-53,-5,-111,-49,-180v-21,-31,-45,-61,-84,-104xm19,455v-8,-7,-16,-14,-17,-15r-2,-2r0,11v0,40,10,78,31,120v15,30,32,56,75,117v13,18,28,39,34,48v7,11,11,17,11,16v2,-3,7,-21,10,-33v2,-11,2,-15,2,-32v0,-20,-1,-28,-6,-45v-10,-36,-34,-76,-71,-119v-13,-15,-48,-49,-67,-66xm19,679v-8,-7,-16,-13,-17,-14r-2,-2r0,11v0,47,15,94,46,145v14,24,24,38,62,91v12,15,25,33,29,40v5,7,10,12,10,12v2,0,10,-16,13,-25v3,-12,4,-21,4,-35v-2,-35,-15,-68,-40,-106v-19,-29,-37,-51,-69,-83v-12,-11,-28,-26,-36,-34","w":209},"\ue18d":{"d":"189,-721v8,-4,18,-2,23,5v2,2,7,12,13,22v28,59,42,112,44,170v1,43,-7,79,-27,121v-20,41,-42,71,-107,146v-45,51,-62,73,-80,99v-21,31,-37,64,-46,93v-5,18,-9,45,-9,60r0,5r-7,0r-7,0r0,-125r0,-125r7,0r7,0r16,-10v61,-37,104,-70,139,-104v26,-27,44,-51,57,-77v15,-31,20,-58,17,-96v-4,-52,-21,-106,-49,-156v-4,-7,-5,-11,-2,-17v2,-5,5,-8,11,-11","w":269},"\ue18e":{"d":"201,231v5,-3,11,-2,17,0v5,3,7,5,10,11v3,6,2,13,-1,18v-2,5,-363,296,-369,299v-5,3,-11,2,-16,0v-6,-3,-8,-5,-11,-11v-3,-6,-2,-13,1,-18v2,-5,363,-296,369,-299","w":209},"\ue18f":{"d":"-202,-531v6,-3,11,-4,16,-2v6,3,467,268,470,270v4,4,6,11,6,17v-1,7,-5,13,-12,16v-6,3,-11,4,-16,2v-6,-3,-467,-268,-470,-270v-4,-4,-6,-11,-6,-17v1,-7,5,-13,12,-16","w":269},"\ue190":{"d":"228,-751v9,-4,19,-2,24,5v5,5,11,29,15,52v2,11,2,45,0,55v-2,11,-5,26,-9,37v-4,11,-15,32,-22,44r-6,9r3,6v16,34,24,75,23,110v-2,41,-14,76,-42,115v-19,29,-33,44,-100,113v-52,52,-74,80,-92,115v-14,28,-22,60,-22,85r0,5r-7,0r-7,0r0,-250r0,-250r7,0r7,0r0,38r0,37r9,-5v37,-19,59,-33,84,-49v79,-52,123,-105,135,-161v1,-6,1,-13,1,-26v0,-20,-1,-31,-8,-50v-2,-6,-4,-13,-5,-15v-1,-8,4,-16,12,-20xm207,-501v-2,-6,-5,-12,-5,-12v-1,0,-1,1,-2,2v-2,4,-32,34,-65,66v-21,19,-42,39,-48,45v-51,54,-76,95,-85,144v-1,6,-1,18,-2,33r0,23r6,-3v31,-18,70,-43,94,-61v23,-17,34,-27,51,-43v62,-63,79,-122,56,-194","w":269},"\ue191":{"d":"228,-971v9,-4,19,-2,24,5v4,4,9,21,12,37v11,53,2,102,-27,149r-6,9r3,5v4,8,11,23,15,35v21,61,15,119,-19,174r-6,10r5,9v14,29,20,57,19,87v-2,43,-16,82,-45,126v-18,27,-31,43,-82,100v-17,18,-35,39,-42,47v-52,62,-75,112,-79,167r0,11r-7,0r-7,0r0,-375r0,-375r7,0r7,0r0,45r1,45r26,-13v46,-23,78,-42,108,-64v46,-34,76,-71,88,-107v5,-16,6,-24,6,-43v0,-19,-2,-30,-8,-49v-2,-6,-4,-13,-5,-15v-1,-8,4,-16,12,-20xm207,-731v-3,-6,-4,-7,-4,-5v-1,3,-34,36,-68,68v-39,36,-55,52,-69,68v-28,32,-46,62,-56,92v-7,19,-9,33,-10,55r0,18r22,-11v119,-62,183,-123,198,-190v2,-13,2,-40,-1,-55v-2,-13,-8,-30,-12,-40xm205,-491v-2,-5,-4,-11,-5,-15r-2,-6r-6,7r-61,61v-29,30,-59,60,-65,67v-36,43,-54,77,-63,117v-2,10,-2,17,-3,31r0,19r3,-2v25,-15,63,-42,84,-58v27,-22,58,-51,74,-72v42,-51,56,-102,44,-149","w":269},"\ue192":{"d":"236,-1096v10,-4,22,-1,26,8v6,11,9,70,5,88v-3,13,-7,26,-13,36r-5,10r3,9v1,4,3,15,4,23v9,48,6,85,-10,120r-4,11r2,14v9,45,11,94,7,121v-4,20,-10,41,-19,60r-5,9r1,14v2,34,2,90,1,102v-5,49,-22,92,-58,147v-15,23,-25,37,-58,79v-35,46,-50,66,-65,91v-31,50,-46,94,-48,140r0,14r-7,0r-7,0r0,-500r0,-500r7,0r7,0r0,62r0,63v0,0,7,-1,15,-3v74,-13,136,-37,173,-67v21,-18,34,-38,40,-60v2,-10,2,-38,-1,-55v-1,-6,-2,-13,-3,-16v-1,-8,4,-16,12,-20xm221,-891v-1,-6,-2,-14,-2,-18r-1,-6r-11,10v-14,12,-29,24,-56,43v-66,46,-75,53,-95,72v-25,23,-41,48,-49,73v-5,15,-6,21,-7,45r0,22r13,-6v61,-30,115,-65,149,-97v31,-30,51,-61,58,-94v2,-11,2,-29,1,-44xm212,-729v-1,-7,-2,-12,-2,-12v0,-1,-4,3,-8,8v-13,16,-33,36,-72,73v-49,46,-64,61,-82,83v-22,29,-36,58,-43,86v-3,14,-5,29,-5,48r0,18r16,-11v50,-34,86,-63,117,-94v49,-49,75,-95,80,-142v1,-13,1,-39,-1,-57xm190,-473v1,-5,1,-20,1,-33r0,-24r-9,11r-58,66v-59,64,-81,92,-99,130v-17,34,-24,59,-25,97v0,21,0,21,2,20v1,-1,10,-9,21,-18v22,-20,62,-60,77,-77v55,-63,84,-118,90,-172","w":269},"\ue193":{"d":"2,-498r3,-2r64,0r64,0r3,2r2,3r0,495r0,494r-2,3r-3,2r-64,0r-64,0r-3,-2r-2,-3r0,-494r0,-495xm181,-498v3,-2,3,-2,19,-2v15,0,15,0,18,2r2,3r0,238r0,237r3,-2v6,-6,24,-28,32,-40v10,-15,22,-39,28,-55v12,-30,19,-61,20,-93v1,-16,1,-18,3,-21v4,-6,12,-7,18,-4v6,3,7,5,8,22v2,29,7,52,19,74v17,34,42,56,74,64v7,2,11,2,26,2v15,0,19,0,28,-2v15,-4,27,-11,36,-20v19,-19,29,-47,33,-96v2,-18,2,-100,0,-119v-7,-86,-28,-129,-72,-150v-16,-7,-39,-12,-59,-12v-23,0,-43,9,-50,24v-2,5,-2,7,-2,14v0,8,0,10,2,15v4,8,8,13,21,22v14,10,19,16,23,24v2,5,2,7,2,16v0,13,-1,21,-7,32v-10,20,-29,36,-50,42v-9,2,-29,2,-38,0v-26,-8,-48,-29,-55,-56v-4,-14,-3,-39,3,-57v17,-55,66,-94,127,-103v28,-3,82,1,117,9v74,19,123,60,148,125v12,31,19,71,19,112v0,48,-16,91,-46,127v-42,50,-109,81,-176,81v-16,0,-17,0,-25,-3v-5,-1,-16,-7,-26,-12v-23,-12,-29,-14,-42,-14v-9,0,-10,0,-17,3v-19,10,-27,30,-27,70v0,41,8,61,27,71v7,3,8,3,17,3v13,0,19,-2,42,-14v10,-5,21,-11,26,-12v8,-3,9,-3,25,-3v67,0,134,31,176,80v30,37,46,80,46,128v0,50,-10,96,-28,132v-26,53,-73,88,-139,105v-35,8,-89,12,-117,9v-61,-9,-110,-48,-127,-103v-6,-18,-7,-43,-3,-58v5,-20,21,-40,41,-49v12,-6,19,-7,33,-7v14,0,21,1,34,7v14,7,28,21,35,35v6,12,7,20,7,32v0,10,0,12,-2,17v-4,8,-9,14,-23,24v-19,14,-23,21,-23,36v0,8,0,10,2,15v4,7,10,13,19,18v14,6,29,7,51,5v36,-5,60,-19,78,-44v18,-25,28,-61,33,-117v2,-19,2,-101,0,-119v-4,-49,-14,-77,-33,-96v-9,-9,-21,-16,-36,-20v-9,-2,-13,-2,-28,-2v-15,0,-19,0,-26,2v-38,9,-67,39,-82,83v-7,19,-10,33,-11,55v-1,17,-2,19,-8,22v-4,2,-9,2,-13,0v-7,-3,-7,-5,-8,-25v-3,-64,-26,-124,-67,-173v-6,-7,-12,-14,-13,-15r-3,-2r0,237r0,238r-2,3v-3,2,-3,2,-18,2v-16,0,-16,0,-19,-2r-2,-3r0,-494r0,-495","w":677},"\ue194":{"d":"2,-398r3,-2r51,0r51,0r3,2r2,3r0,394r0,395r-2,3r-3,2r-51,0r-51,0r-3,-2r-2,-3r0,-395r0,-394xm150,-398v3,-2,3,-2,16,-2v13,0,13,0,16,2r2,3r0,189r0,189r4,-3v12,-12,28,-34,37,-52v15,-28,25,-65,25,-94v0,-5,1,-10,1,-12v3,-7,12,-10,19,-6v4,3,5,5,6,19v2,35,14,64,34,84v19,19,42,27,71,23v16,-2,28,-8,39,-19v6,-6,8,-9,12,-18v12,-23,16,-53,16,-105v0,-97,-16,-144,-55,-164v-13,-7,-32,-11,-50,-11v-25,0,-40,12,-39,30v1,9,5,16,13,23v20,18,23,22,24,33v1,28,-20,56,-48,62v-21,4,-41,-2,-56,-17v-13,-13,-19,-28,-18,-49v3,-55,47,-99,107,-106v12,-2,40,-1,58,1v28,4,50,10,72,21v57,29,87,84,90,166v1,29,-3,51,-13,74v-26,57,-83,97,-150,104v-23,3,-31,1,-53,-13v-7,-4,-16,-8,-19,-10v-17,-7,-33,-2,-41,12v-5,11,-6,18,-6,43v0,26,1,33,6,43v8,15,24,20,41,13v3,-2,12,-6,19,-10v22,-14,30,-16,53,-13v76,8,138,57,157,125v5,17,6,31,6,53v-3,82,-33,137,-90,166v-22,11,-44,17,-72,21v-18,2,-46,3,-58,1v-60,-7,-104,-51,-107,-107v0,-8,0,-12,1,-18v7,-33,40,-54,73,-47v28,6,49,34,48,62v-1,11,-4,15,-24,33v-8,7,-12,14,-13,23v-1,18,14,30,39,30v18,0,37,-4,50,-11v39,-20,55,-67,55,-164v0,-52,-4,-82,-16,-105v-4,-9,-6,-12,-12,-18v-11,-11,-23,-17,-39,-19v-29,-4,-52,4,-71,23v-20,20,-32,49,-34,84v-1,14,-2,16,-6,19v-7,4,-16,1,-19,-6v0,-2,-1,-7,-1,-12v0,-29,-10,-66,-25,-94v-9,-18,-25,-40,-37,-52r-4,-3r0,189r0,189r-2,3v-3,2,-3,2,-16,2v-13,0,-13,0,-16,-2r-2,-3r0,-395r0,-394","w":546},"\ue195":{"d":"210,-260v12,-1,55,0,71,1v120,14,201,70,231,162v9,28,12,50,12,86v0,30,-1,45,-6,72v-26,126,-118,236,-279,332v-58,35,-129,71,-206,104v-13,6,-25,11,-27,12v-2,1,-5,2,-6,2v-5,0,-11,-6,-11,-11v0,-5,2,-7,17,-16v100,-59,171,-107,228,-158v17,-15,53,-50,65,-65v63,-73,96,-144,105,-226v2,-14,2,-59,0,-73v-8,-67,-31,-121,-68,-157v-21,-21,-43,-34,-69,-40v-54,-13,-112,5,-152,48v-18,20,-35,49,-44,74r-3,7r8,-4v13,-7,21,-8,37,-8v10,0,15,0,22,2v64,16,95,85,65,146v-15,33,-47,54,-82,57v-49,3,-97,-29,-113,-75v-6,-19,-7,-44,-1,-76v13,-75,61,-140,127,-173v23,-12,53,-20,79,-23xm596,-176v28,-7,57,10,63,38v10,39,-24,73,-63,63v-33,-7,-50,-44,-34,-75v6,-13,20,-23,34,-26xm596,74v28,-7,57,10,63,38v7,29,-10,57,-38,63v-29,7,-57,-10,-63,-38v-7,-28,10,-57,38,-63","w":671},"\ue196":{"d":"164,-210v8,-1,32,-1,45,0v108,8,181,58,203,138v16,57,8,128,-22,189v-51,106,-163,194,-361,281v-11,5,-22,10,-23,11v-4,2,-7,2,-11,0v-6,-2,-7,-9,-4,-14v1,-2,14,-11,30,-22v97,-64,158,-116,203,-172v41,-52,66,-105,74,-162v2,-13,2,-19,2,-42v0,-19,0,-30,-1,-39v-8,-52,-24,-92,-49,-118v-17,-16,-34,-25,-55,-28v-34,-5,-68,7,-92,33v-13,13,-28,36,-34,54r-3,7r6,-2v9,-3,22,-4,33,-2v30,6,54,28,62,58v3,11,3,32,0,43v-10,39,-44,65,-82,63v-43,-3,-77,-33,-84,-73v-2,-9,-1,-30,2,-44v13,-81,70,-143,146,-157v6,-1,12,-2,15,-2xm476,-142v7,-2,19,-1,26,2v15,6,25,20,27,36v1,18,-10,35,-27,42v-8,3,-22,3,-30,0v-21,-8,-31,-31,-25,-51v4,-14,15,-24,29,-29xm476,59v7,-2,19,-1,26,2v15,6,25,20,27,36v1,18,-10,35,-27,42v-8,3,-22,3,-30,0v-21,-8,-31,-31,-25,-51v4,-14,15,-24,29,-29","w":537},"\ue197":{"d":"323,-1247v3,-3,8,-2,12,0v4,3,19,20,32,37v59,78,107,195,119,291v7,52,1,109,-15,162v-23,77,-64,149,-141,248v-10,13,-19,24,-20,25v-1,2,0,5,6,26v18,56,46,148,56,183r3,14r13,0v49,3,92,17,132,43v61,41,102,107,113,184v3,15,4,43,2,58v-3,34,-11,61,-25,91v-28,57,-76,102,-134,124r-11,4r1,42v0,58,-2,121,-7,154v-15,102,-73,183,-149,207v-19,6,-30,7,-53,7v-23,0,-34,-1,-55,-7v-38,-9,-71,-28,-98,-55v-33,-33,-52,-72,-57,-118v-3,-27,2,-51,15,-71v21,-33,61,-52,100,-51v50,3,88,44,91,98v2,49,-31,90,-79,99v-15,2,-28,1,-43,-3r-7,-3r3,4v13,18,26,31,44,42v45,29,102,34,145,12v48,-24,84,-76,99,-145v5,-22,8,-50,10,-101v1,-28,1,-98,0,-100v-1,0,-6,0,-12,1v-22,4,-33,4,-61,4v-35,0,-57,-2,-87,-10v-134,-33,-237,-145,-260,-282v-4,-22,-4,-33,-4,-61v0,-28,0,-38,5,-64v12,-76,47,-154,110,-243r93,-120v18,-22,32,-40,32,-41v0,-1,-3,-11,-6,-23v-32,-107,-47,-176,-53,-256v-4,-46,-5,-103,-2,-132v11,-89,46,-169,104,-236v12,-14,33,-35,39,-38xm390,-1090v-5,-10,-10,-18,-10,-18v-1,0,-6,3,-10,7v-80,58,-129,140,-142,237v-2,18,-2,47,-1,63v3,43,16,104,36,174v5,14,8,26,8,27v0,1,28,-37,41,-56v63,-91,96,-169,105,-251v3,-19,4,-58,2,-79v-3,-41,-9,-64,-29,-104xm292,-400v-7,-24,-13,-44,-14,-44v0,0,-40,49,-62,79v-93,121,-134,210,-145,310v-1,7,-1,23,-1,36v0,23,0,25,2,37v4,18,9,33,17,49v23,46,61,85,114,114v32,18,69,30,107,36v26,4,68,4,98,-1v17,-2,15,-1,14,-10v-8,-111,-24,-211,-54,-336v-3,-13,-6,-25,-6,-26v-1,-1,-14,0,-27,3v-30,6,-55,19,-74,38v-24,24,-36,55,-35,88v2,32,16,61,41,86v12,12,24,21,39,30v11,6,12,7,14,11v6,14,-6,30,-20,29v-6,-1,-28,-12,-42,-21v-26,-17,-46,-37,-62,-61v-59,-90,-33,-214,57,-273v10,-7,27,-16,39,-21v10,-3,34,-10,38,-10v2,0,3,0,3,-1v1,-1,-17,-64,-41,-142xm418,-153v-6,-1,-12,-2,-13,-2v-1,0,0,7,6,34v25,106,42,210,50,303v2,24,0,23,17,14v26,-12,48,-32,66,-59v36,-54,40,-123,10,-185v-27,-54,-77,-93,-136,-105","w":641},"\ue198":{"d":"247,-997v4,-2,8,-3,12,-1v3,2,18,17,27,29v50,63,91,160,101,238v2,13,2,52,0,66v-10,81,-42,151,-119,253r-17,23r10,30v15,42,28,83,39,118v4,16,8,30,8,30v0,0,6,0,12,1v35,3,68,15,95,33v87,57,121,173,76,267v-22,46,-62,85,-107,102r-7,3r0,29v0,48,-3,108,-7,134v-10,62,-42,116,-85,144v-47,31,-106,33,-160,6v-34,-17,-60,-42,-76,-75v-11,-22,-17,-49,-16,-72v3,-38,34,-70,75,-78v26,-4,48,3,67,21v12,13,19,28,22,46v6,31,-5,59,-30,77v-15,11,-38,16,-52,13v-3,0,-5,-1,-5,-1v-1,2,23,23,30,28v39,26,86,26,124,1v10,-6,26,-22,34,-33v7,-11,16,-29,21,-42v13,-33,17,-66,18,-146r0,-42r-8,2v-27,4,-64,4,-89,0v-49,-7,-94,-27,-133,-58v-12,-10,-34,-31,-43,-43v-71,-88,-83,-204,-34,-319v24,-56,57,-107,134,-205r22,-28r-7,-19v-43,-114,-55,-175,-55,-270v0,-38,0,-46,5,-73v12,-58,42,-114,84,-158v11,-12,28,-27,34,-31xm304,-869v-3,-6,-7,-11,-7,-11v-2,0,-20,12,-31,20v-12,10,-34,32,-43,44v-30,39,-46,80,-53,128v-1,5,-1,18,-1,29v0,24,1,34,11,68v6,20,24,76,32,96r4,10r12,-18v62,-85,90,-149,101,-223v1,-10,1,-19,1,-42v0,-26,0,-31,-2,-41v-4,-21,-10,-37,-24,-60xm232,-322v-5,-15,-10,-28,-10,-28v-1,-2,-48,60,-71,92v-53,77,-81,143,-90,209v-2,20,-3,47,-1,58v6,34,21,63,49,91v34,34,80,57,133,67v13,2,17,2,41,2v16,0,31,0,37,-1v17,-2,15,-1,15,-12v-5,-70,-16,-150,-32,-225v-5,-24,-12,-53,-12,-54v-1,0,-6,1,-12,1v-28,4,-51,15,-69,32v-29,30,-34,73,-13,110v11,19,30,37,51,48v8,5,10,7,12,13v4,12,-7,26,-20,26v-6,0,-31,-13,-47,-25v-33,-24,-54,-59,-62,-99v-3,-17,-3,-45,0,-61v13,-66,62,-115,127,-129v5,-1,9,-2,9,-2v2,-1,-19,-66,-35,-113xm350,-115v-12,-4,-18,-5,-18,-4v2,2,14,62,19,89v12,61,20,126,23,172v0,7,1,12,1,12v1,0,5,-2,9,-4v28,-15,52,-43,63,-74v6,-17,7,-27,7,-48v0,-14,0,-19,-2,-28v-12,-55,-49,-98,-102,-115","w":513},"\ue199":{"d":"169,-248r3,-2r51,0r51,0r3,2r2,3r0,245r0,244r-2,3r-3,2r-51,0r-51,0r-3,-2r-2,-3r0,-244r0,-245xm390,-248r3,-2r51,0r51,0r3,2r2,3r0,245r0,244r-2,3r-3,2r-51,0r-51,0r-3,-2r-2,-3r0,-244r0,-245","w":500},"\ue19a":{"d":"136,-198r3,-2r40,0r40,0r3,2r2,3r0,194r0,195r-2,3r-3,2r-40,0r-40,0r-3,-2r-2,-3r0,-195r0,-194xm312,-198r3,-2r40,0r40,0r3,2r2,3r0,194r0,195r-2,3r-3,2r-40,0r-40,0r-3,-2r-2,-3r0,-195r0,-194","w":400},"\ue19b":{"d":"672,-722v8,-3,22,3,26,12v3,7,3,8,-6,18v-26,27,-62,48,-98,57v-29,8,-57,8,-85,1r-2,-1r-6,33v-11,66,-25,142,-32,168v-14,55,-42,106,-76,138v-19,19,-39,30,-62,36v-6,2,-11,2,-22,2v-18,0,-26,-1,-41,-9v-13,-6,-22,-13,-46,-33v-10,-8,-22,-17,-25,-19v-7,-5,-9,-8,-8,-15v2,-9,12,-20,20,-22v6,-1,11,2,30,19v16,14,24,19,31,23v22,11,46,7,65,-12v21,-20,35,-54,41,-100v1,-6,3,-22,3,-37v3,-52,11,-127,19,-183v2,-11,3,-21,3,-21v-1,-1,-27,-4,-34,-4v-19,0,-44,4,-60,11v-29,10,-45,26,-50,47v-6,21,3,42,22,55v4,2,5,4,6,8v3,9,2,19,-3,25v-1,2,-4,3,-9,5v-24,7,-48,5,-70,-6v-18,-9,-32,-22,-40,-38v-13,-25,-11,-58,5,-84v9,-14,28,-31,47,-41v39,-19,96,-30,160,-30v23,0,26,0,36,2v15,3,37,10,69,20v49,16,61,19,85,18v34,-2,68,-15,98,-37v4,-3,8,-6,9,-6xm476,-287v7,-1,27,0,36,2v16,4,30,14,33,23v1,3,1,5,0,9v0,3,-1,19,-2,36v-3,73,-8,192,-13,276v-2,51,-2,59,1,67v3,6,5,8,10,10v8,4,18,2,41,-9v19,-9,22,-10,25,-8v3,2,6,10,6,15v0,11,-5,17,-15,21v-4,1,-13,5,-20,9v-39,20,-46,23,-60,24v-15,1,-26,-3,-36,-13v-13,-14,-22,-40,-34,-104v-8,-41,-12,-69,-18,-125r0,-6r-12,15v-20,25,-44,53,-65,75v-5,6,-9,10,-9,11v0,0,2,3,5,6v8,11,20,19,35,23v6,2,10,2,23,2r16,1r2,4v5,9,1,24,-7,30v-6,4,-31,9,-47,9v-28,0,-53,-10,-66,-28r-4,-5r-15,14v-33,29,-51,44,-72,58v-26,17,-50,28,-70,31v-23,4,-40,1,-65,-14v-7,-4,-17,-9,-22,-12v-8,-4,-10,-6,-11,-8v-4,-9,5,-26,15,-31v8,-4,11,-3,30,6v20,10,28,13,40,12v29,-3,59,-23,108,-77v89,-95,153,-198,190,-305v5,-15,8,-21,14,-27v7,-8,20,-15,33,-17xm343,248v6,0,23,-1,39,0v109,1,179,25,200,69v15,33,4,80,-27,114v-8,9,-19,19,-28,25v-3,2,-6,3,-6,4v0,0,6,1,14,3v41,10,67,27,79,53v9,18,10,44,3,70v-4,12,-15,35,-23,45v-29,39,-70,64,-118,75v-12,2,-16,2,-32,2v-15,0,-22,0,-28,-1v-28,-6,-49,-17,-69,-37v-11,-11,-15,-17,-15,-21v0,-2,1,-5,2,-8v6,-11,23,-20,31,-16v1,1,5,4,9,8v13,14,33,19,54,15v35,-7,66,-34,77,-67v16,-51,-16,-85,-86,-89v-7,0,-12,-1,-14,-2v-5,-3,-7,-17,-2,-26v4,-9,8,-11,20,-12v19,-2,37,-14,50,-33v16,-24,18,-53,6,-77v-4,-7,-15,-18,-23,-23v-11,-7,-31,-15,-45,-17r-4,-1r-2,16v-18,98,-38,208,-43,225v-11,45,-31,87,-55,120v-10,12,-27,30,-38,38v-10,8,-28,16,-38,19v-11,3,-32,3,-42,1v-15,-4,-29,-13,-51,-32v-8,-5,-17,-13,-21,-16v-10,-7,-11,-11,-7,-21v4,-6,9,-12,14,-15v9,-4,12,-3,29,11v26,20,36,25,55,24v15,-1,27,-7,39,-18v29,-30,46,-95,46,-179v0,-20,1,-37,4,-68v2,-23,8,-67,11,-86v2,-8,2,-14,2,-14v0,0,-4,1,-7,3v-48,19,-72,55,-58,85v3,5,6,8,9,11v8,7,10,9,11,13v2,8,0,21,-5,26v-3,4,-18,7,-32,8v-34,1,-67,-16,-81,-44v-7,-15,-9,-30,-5,-48v2,-9,7,-22,13,-31v29,-43,100,-74,188,-81","w":700},"\ue19c":{"d":"534,-575v4,-3,5,-3,9,-3v7,1,17,10,17,15v0,2,-4,6,-10,12v-37,36,-85,53,-128,47v-5,-1,-11,-2,-13,-3v-2,-1,-3,-1,-4,-1v0,0,-2,14,-5,31v-11,67,-22,123,-28,142v-14,47,-41,89,-72,111v-9,6,-25,14,-33,16v-10,3,-24,3,-34,2v-15,-3,-28,-11,-51,-30v-7,-6,-17,-13,-21,-16v-9,-7,-10,-8,-10,-12v0,-4,3,-12,6,-15v3,-3,9,-6,12,-6v4,0,10,4,22,15v19,17,29,23,41,24v31,2,56,-26,66,-79v3,-13,5,-25,6,-51v2,-33,8,-93,14,-135v1,-10,2,-19,2,-20v0,-2,-1,-3,-5,-4v-30,-6,-71,2,-92,19v-12,10,-17,20,-17,34v0,15,5,25,16,33v2,2,5,6,6,8v4,8,1,21,-5,23v-8,4,-20,5,-30,5v-12,-1,-19,-3,-29,-8v-26,-12,-41,-37,-40,-64v4,-51,58,-83,154,-90v40,-3,49,-1,111,19v30,10,38,12,52,13v15,0,31,-2,46,-7v17,-6,27,-11,47,-25xm378,-229v13,-2,28,0,40,5v8,4,12,7,15,12r3,3r-2,47v-3,73,-6,132,-9,183v-1,26,-2,53,-2,62v0,14,0,16,2,19v2,6,6,8,12,9v6,0,13,-2,28,-9v15,-7,18,-8,21,-6v4,4,5,18,1,23v-1,1,-5,4,-9,6v-5,1,-10,4,-12,5v-29,15,-34,17,-42,19v-11,3,-20,2,-28,-2v-14,-6,-21,-20,-29,-48v-10,-41,-19,-99,-22,-136v0,-6,-1,-10,-1,-11r-2,2v-6,9,-37,46,-56,67r-11,11r2,3v2,5,9,11,16,14v9,6,17,8,32,9r13,0r1,4v4,7,1,19,-5,23v-1,1,-7,3,-12,5v-9,2,-13,2,-25,2v-16,0,-25,-1,-36,-7v-7,-4,-17,-12,-19,-16r-1,-3r-13,12v-49,45,-83,66,-114,71v-18,3,-32,-1,-54,-13v-7,-5,-15,-9,-18,-10v-6,-3,-7,-7,-4,-15v2,-9,11,-16,18,-16v1,0,9,3,16,7v16,8,23,9,33,9v20,-2,39,-14,70,-45v74,-74,134,-166,165,-253v7,-19,10,-26,16,-31v5,-5,14,-9,22,-11xm276,199v9,-1,56,0,67,1v64,6,105,23,121,50v6,11,8,17,7,31v0,15,-2,24,-8,37v-8,17,-23,34,-39,45v-7,5,-7,5,-4,6v24,5,37,10,50,19v13,8,21,19,25,32v3,10,3,32,0,43v-5,19,-13,35,-26,50v-22,25,-54,44,-87,51v-9,2,-14,2,-28,2v-16,0,-19,0,-27,-2v-20,-6,-35,-14,-49,-27v-12,-13,-14,-17,-10,-25v4,-7,13,-13,20,-13v3,0,5,1,11,6v4,4,9,8,12,9v32,16,82,-11,94,-50v8,-27,-1,-48,-23,-59v-13,-7,-34,-11,-51,-11v-6,0,-7,0,-9,-3v-2,-3,-2,-10,-1,-16v2,-6,6,-12,11,-12v13,-2,17,-3,24,-6v23,-12,37,-41,33,-67v-3,-17,-14,-30,-31,-39v-8,-4,-20,-9,-29,-10v-3,-1,-3,-1,-4,4v0,3,-7,37,-14,75v-22,125,-24,133,-42,169v-30,63,-75,95,-116,87v-13,-3,-23,-9,-46,-28v-21,-17,-21,-16,-21,-20v0,-6,6,-16,13,-19v7,-4,10,-3,27,11v5,5,13,10,18,12v6,4,9,4,16,5v33,2,57,-26,68,-82v4,-22,6,-38,7,-74v1,-41,4,-75,12,-121v1,-8,2,-15,2,-16v-2,-1,-22,9,-30,14v-2,2,-7,6,-11,10v-19,19,-20,45,-2,58v6,4,8,8,7,17v-1,10,-5,14,-19,16v-37,8,-74,-14,-81,-46v-3,-15,1,-34,10,-49v6,-8,20,-21,30,-28v30,-20,76,-34,123,-37","w":560},"\ue19d":{"d":"222,-261v24,-2,47,-1,66,3v40,9,78,32,103,64v21,27,36,62,38,91v2,34,-17,64,-48,74v-8,3,-10,3,-21,3v-11,0,-14,0,-21,-2v-22,-8,-38,-21,-47,-40v-5,-10,-7,-17,-8,-30v-2,-36,19,-68,52,-79v6,-2,9,-2,21,-2r15,0v2,1,3,1,3,0v0,0,-3,-4,-8,-9v-34,-37,-85,-56,-136,-50v-43,5,-68,23,-80,58v-12,31,-14,63,-14,179v0,99,2,132,8,159v8,36,21,56,47,69v27,13,72,15,106,3v43,-15,79,-51,101,-99v5,-11,11,-29,13,-39v3,-8,5,-12,10,-13v7,-1,14,5,13,12v-2,13,-14,46,-23,63v-32,60,-83,98,-141,106v-33,4,-86,-2,-121,-15v-32,-12,-57,-28,-80,-50v-37,-37,-60,-87,-68,-152v-2,-20,-2,-67,0,-87v8,-65,30,-115,67,-152v23,-22,48,-37,79,-49v21,-8,49,-14,74,-16","w":428},"\ue19e":{"d":"208,-348v3,-2,3,-2,16,-2v12,0,12,0,15,2r2,3r0,41r0,42r9,0v42,0,83,15,116,43v35,29,60,76,63,116v2,34,-17,64,-48,74v-8,3,-10,3,-21,3v-11,0,-14,0,-21,-2v-22,-8,-38,-21,-47,-40v-5,-10,-7,-17,-8,-30v-2,-36,19,-68,52,-79v6,-2,9,-2,21,-2r15,0v2,1,3,1,3,0v0,-1,-15,-17,-22,-23v-29,-23,-61,-35,-98,-37r-14,0r0,239r0,238r14,0v23,-1,41,-6,62,-17v35,-17,63,-49,82,-90v5,-11,11,-29,13,-39v3,-8,5,-12,10,-13v7,-1,14,5,13,12v-1,8,-7,26,-13,40v-32,79,-98,130,-171,130r-10,0r0,42r0,41r-2,3v-3,2,-3,2,-15,2v-13,0,-13,0,-16,-2r-2,-3r0,-43r0,-43r-7,-1v-41,-7,-77,-21,-107,-43v-5,-3,-15,-12,-22,-19v-37,-37,-60,-87,-68,-152v-2,-20,-2,-67,0,-87v8,-65,31,-115,68,-153v14,-13,26,-22,42,-31v25,-15,55,-25,87,-30r7,-1r0,-43r0,-43xm206,0v0,-186,0,-233,-1,-233v-5,0,-21,9,-29,15v-15,12,-25,31,-31,59v-6,27,-8,60,-8,158v0,99,2,132,8,159v8,36,22,56,47,69v4,2,10,4,11,5r3,0r0,-232","w":428},"\ue19f":{"d":"182,-498v22,-6,47,4,57,23v3,7,5,17,5,25v-1,11,-3,15,-21,31v-16,17,-19,21,-20,31v-1,12,10,23,25,25v11,1,19,-4,24,-14r3,-6r0,-23r0,-24r2,-5v6,-13,24,-23,39,-23v27,0,51,24,51,51v0,15,-10,33,-23,39r-5,2r-24,0r-23,0r-6,3v-10,5,-15,13,-14,24v2,15,13,26,25,25v10,-1,14,-4,31,-20v16,-18,20,-20,31,-21v18,-1,35,9,43,24v9,17,9,34,0,51v-8,15,-25,25,-43,24v-11,-1,-15,-3,-31,-21v-17,-16,-21,-19,-31,-20v-12,-1,-23,10,-25,25v-1,11,4,19,14,24r6,2r23,0r24,0r5,3v13,6,23,24,23,39v0,27,-24,51,-51,51v-15,0,-33,-10,-39,-23r-2,-6r0,-23r0,-24r-3,-5v-5,-10,-13,-15,-24,-14v-15,2,-26,13,-25,25v1,10,4,14,20,30v18,17,20,21,21,32v1,18,-9,35,-24,43v-17,9,-34,9,-51,0v-15,-8,-25,-25,-24,-43v1,-11,3,-15,21,-32v16,-16,19,-20,20,-30v1,-12,-10,-23,-25,-25v-11,-1,-19,4,-24,14r-2,5r0,24r0,23r-3,6v-6,13,-24,23,-39,23v-27,0,-51,-24,-51,-51v0,-15,10,-33,23,-39r6,-3r23,0r24,0r5,-2v10,-5,15,-13,14,-24v-2,-15,-13,-26,-25,-25v-10,1,-14,4,-30,20v-17,18,-21,20,-32,21v-8,0,-18,-2,-25,-5v-19,-10,-29,-34,-23,-57v5,-23,25,-38,48,-37v11,1,15,3,32,21v16,16,20,19,30,20v12,1,23,-10,25,-25v1,-11,-4,-19,-14,-24r-5,-3r-24,0r-23,0r-6,-2v-13,-6,-23,-24,-23,-39v0,-27,24,-51,51,-51v15,0,33,10,39,23r3,5r0,24r0,23r2,6v5,10,13,15,24,14v15,-2,26,-13,25,-25v-1,-10,-4,-14,-20,-31v-18,-16,-20,-20,-21,-31v-1,-23,14,-43,37,-48xm206,-331v-6,-3,-13,-4,-20,-2v-16,5,-25,24,-17,39v3,8,7,11,14,14v19,9,41,-6,40,-27v-1,-12,-7,-20,-17,-24","w":389},"\ue1a0":{"d":"191,-232v2,-2,5,-4,5,-4r-2,4v-2,3,-9,16,-17,29r-15,25r-7,4v-10,6,-18,8,-40,7v-11,0,-24,0,-33,-1v-40,-5,-63,-3,-77,7r-5,3r2,-4v2,-3,9,-16,17,-29r15,-25r7,-4v10,-6,18,-8,40,-7v11,0,24,0,33,1v40,5,63,3,77,-6","w":196},"\ue1a1":{"d":"22,-64v7,-3,18,-2,24,1v7,3,13,9,17,16v3,7,4,20,0,27v-3,7,-9,13,-16,17v-5,2,-7,2,-14,2v-7,0,-9,0,-14,-2v-11,-5,-18,-16,-19,-28v-1,-14,8,-28,22,-33","w":65},"\ue1a2":{"d":"223,-499v41,-3,76,2,107,17v43,20,75,62,83,107v1,11,1,28,-2,37v-4,18,-16,32,-31,40v-7,3,-8,3,-17,3v-8,0,-11,0,-16,-2v-17,-7,-34,-24,-50,-48v-8,-12,-18,-32,-18,-35v1,-2,7,-6,10,-6v2,0,3,1,7,7v6,11,20,21,34,24v17,4,33,-4,39,-20v3,-8,3,-24,0,-34v-8,-27,-28,-48,-56,-62v-18,-9,-41,-14,-61,-14r-7,0r-6,13v-9,17,-25,58,-32,78v-6,19,-6,23,-2,35v5,15,15,29,44,63v11,12,22,26,26,30v20,25,29,44,28,59v-1,11,-13,29,-43,67r-17,20r8,2v22,5,66,26,89,44v5,3,9,6,11,6v4,0,9,-3,33,-20v13,-10,25,-18,26,-18v3,0,7,4,8,8v0,4,-1,5,-20,22v-35,30,-81,66,-93,73v-14,6,-20,0,-34,-35v-12,-31,-17,-41,-22,-49v-5,-8,-11,-14,-16,-16v-3,-1,-3,0,-18,17v-22,26,-42,47,-49,52v-35,31,-60,41,-66,28v-3,-4,-2,-8,1,-15v6,-12,20,-29,40,-47v30,-27,57,-46,75,-51v6,-1,6,-2,15,-12v20,-24,24,-31,23,-44v-2,-17,-13,-36,-46,-76v-30,-36,-37,-47,-41,-61v-3,-8,-2,-12,1,-26v10,-35,45,-120,58,-142r3,-4r-3,0v-6,0,-29,4,-41,7v-43,11,-81,33,-107,61v-32,34,-43,71,-29,97v3,7,13,17,21,20v5,3,7,3,13,3v10,0,17,-3,26,-12v10,-10,18,-28,18,-40v0,-4,0,-5,2,-6v3,-2,8,-2,11,0v2,1,2,2,2,11v-2,34,-16,67,-38,88v-13,13,-25,20,-40,21v-19,1,-43,-14,-53,-36v-3,-5,-6,-13,-7,-17v-2,-9,-2,-32,0,-44v14,-66,73,-127,149,-153v22,-8,46,-13,70,-15","w":417},"\ue1a3":{"d":"-1,-435v2,-2,2,-2,13,0v67,14,138,46,194,87v25,19,54,47,70,67v39,49,61,103,59,147v-3,60,-41,116,-90,131v-7,2,-10,2,-22,2v-16,0,-22,-1,-36,-8v-41,-20,-73,-74,-76,-128v0,-14,1,-27,6,-40v12,-40,47,-80,87,-101v4,-2,7,-4,6,-4v-5,-8,-18,-23,-26,-31v-48,-54,-117,-95,-177,-108v-7,-1,-9,-2,-10,-3v-2,-3,-1,-9,2,-11xm224,-262r-4,-6v-3,0,-13,8,-20,15v-28,28,-44,80,-39,128v5,49,22,82,46,92v4,2,8,2,15,2v8,0,10,0,15,-2v8,-4,16,-12,21,-21v5,-8,9,-22,12,-34v2,-13,2,-46,0,-60v-7,-39,-22,-76,-46,-114","w":333},"\ue1a4":{"d":"81,-284v13,-2,28,-1,38,4v14,8,24,24,24,39v0,14,-8,32,-21,49v-10,14,-35,39,-54,55v-8,7,-15,13,-16,14v-1,1,-1,2,2,7v7,14,26,44,34,54v14,18,18,19,36,7v18,-12,60,-44,65,-50v3,-3,6,-3,9,1v5,5,4,8,-6,18v-20,18,-71,62,-86,74v-16,11,-31,15,-46,10v-12,-4,-20,-13,-31,-36v-6,-13,-13,-33,-17,-44v-1,-5,-2,-6,-4,-5v-3,0,-6,-1,-7,-5v-3,-4,-2,-7,1,-10r4,-3r-3,-14v-8,-39,-3,-78,15,-112v15,-30,36,-47,63,-53xm91,-253v-8,-3,-19,-4,-28,-1v-20,7,-31,25,-33,52v0,12,1,26,5,40v2,11,8,25,9,25v2,0,28,-24,37,-33v16,-16,24,-29,28,-42v5,-17,-2,-33,-18,-41","w":200},"\ue1a5":{"d":"223,-499v41,-3,76,2,107,17v43,20,75,62,83,107v1,11,1,28,-2,37v-4,18,-16,32,-31,40v-7,3,-8,3,-17,3v-8,0,-11,0,-16,-2v-17,-7,-34,-24,-50,-48v-8,-12,-18,-32,-18,-35v1,-2,7,-6,10,-6v2,0,3,1,7,7v6,11,20,21,34,24v17,4,33,-4,39,-20v3,-8,3,-24,0,-34v-8,-27,-28,-48,-56,-62v-18,-9,-41,-14,-61,-14r-7,0r-6,13v-9,17,-25,58,-32,78v-6,19,-6,23,-2,35v5,15,15,29,44,63v11,12,22,26,26,30v20,25,29,44,28,59v-1,11,-13,29,-43,67r-17,20r8,2v22,5,66,26,89,44v9,6,10,7,16,5v2,-1,45,-31,49,-35v1,0,0,-7,-2,-15v-5,-26,-5,-47,1,-73v6,-29,20,-56,37,-71v22,-21,56,-28,78,-17v14,8,24,24,24,39v0,14,-8,32,-21,49v-10,14,-35,39,-54,55v-8,7,-15,13,-16,14v-1,1,-1,2,2,7v7,14,26,44,34,54v11,14,15,16,22,15v6,-1,32,-20,61,-42v13,-11,12,-8,8,-25v-4,-21,-4,-44,3,-63v12,-40,47,-80,87,-101v4,-2,7,-4,6,-4v-53,-71,-131,-124,-203,-139v-8,-1,-9,-2,-10,-4v-2,-3,0,-8,2,-11v2,-1,2,-1,11,0v66,13,140,46,196,88v25,19,54,47,70,67v39,49,61,103,59,147v-3,60,-41,116,-90,131v-7,2,-10,2,-22,2v-16,0,-22,-1,-36,-8v-23,-11,-45,-35,-59,-63v-2,-6,-5,-10,-5,-10v-1,0,-6,4,-12,10v-30,27,-71,61,-80,66v-16,9,-36,8,-47,-2v-11,-10,-25,-37,-35,-69r-3,-10r-3,2v-27,26,-90,75,-103,82v-14,6,-20,0,-34,-35v-12,-31,-17,-41,-22,-49v-5,-8,-11,-14,-16,-16v-3,-1,-3,0,-18,17v-22,26,-42,47,-49,52v-35,31,-60,41,-66,28v-3,-4,-2,-8,1,-15v6,-12,20,-29,40,-47v30,-27,57,-46,75,-51v6,-1,6,-2,15,-12v20,-24,24,-31,23,-44v-2,-17,-13,-36,-46,-76v-30,-36,-37,-47,-41,-61v-3,-8,-2,-12,1,-26v10,-35,45,-120,58,-142r3,-4r-3,0v-6,0,-29,4,-41,7v-43,11,-81,33,-107,61v-32,34,-43,71,-29,97v3,7,13,17,21,20v5,3,7,3,13,3v10,0,17,-3,26,-12v10,-10,18,-28,18,-40v0,-4,0,-5,2,-6v3,-2,8,-2,11,0v2,1,2,2,2,11v-2,34,-16,67,-38,88v-13,13,-25,20,-40,21v-19,1,-43,-14,-53,-36v-3,-5,-6,-13,-7,-17v-2,-9,-2,-32,0,-44v14,-66,73,-127,149,-153v22,-8,46,-13,70,-15xm689,-262r-4,-6v-3,0,-13,8,-20,15v-28,28,-44,80,-39,128v5,49,22,82,46,92v4,2,8,2,15,2v8,0,10,0,15,-2v8,-4,16,-12,21,-21v5,-8,9,-22,12,-34v2,-13,2,-46,0,-60v-7,-39,-22,-76,-46,-114xm491,-253v-8,-3,-19,-4,-28,-1v-20,7,-31,25,-33,52v0,12,1,26,5,40v2,11,8,25,9,25v2,0,28,-24,37,-33v16,-16,24,-29,28,-42v5,-17,-2,-33,-18,-41","w":798},"\ue1a6":{"d":"447,-333v4,-1,8,-1,12,0v5,2,7,6,8,11v0,4,-1,7,-9,19v-4,5,-22,31,-41,59v-51,76,-73,107,-99,139v-38,47,-76,86,-111,111v-40,28,-81,45,-122,49v-6,1,-27,1,-48,1r-37,0r0,-47r0,-48r2,-5v3,-5,7,-9,12,-11v2,0,19,-1,39,-1v23,-1,38,-1,47,-2v74,-10,140,-45,210,-111v38,-36,79,-84,123,-146v8,-12,12,-17,14,-18","w":475},"\ue1a7":{"d":"0,-7r0,-49r37,0v39,0,52,1,68,4v54,11,104,41,158,95v46,46,77,87,157,205v18,27,36,53,40,58v7,10,8,12,7,18v-2,7,-10,12,-18,9v-4,-1,-6,-4,-20,-24v-58,-80,-106,-132,-156,-173v-46,-36,-91,-60,-139,-71v-27,-7,-46,-9,-87,-9r-30,0r-5,-3v-4,-2,-7,-4,-8,-7r-3,-4","w":475},"\ue1a8":{"d":"-31,-769v6,0,19,-1,29,-1v92,-1,181,31,253,90v14,12,41,40,53,55v47,59,74,124,83,200v2,15,2,65,0,79v-10,91,-47,168,-110,231v-33,33,-65,56,-106,76v-129,62,-283,49,-398,-34v-20,-13,-31,-23,-50,-42v-23,-23,-41,-46,-57,-73v-29,-47,-47,-100,-53,-158v-2,-14,-2,-64,0,-79v10,-90,47,-167,110,-230v19,-19,30,-29,50,-42v57,-41,125,-66,196,-72xm42,-747v-13,-2,-54,-3,-64,-2v-51,4,-91,14,-132,34v-68,33,-122,83,-158,147v-9,15,-23,45,-23,47v0,1,151,1,335,1v184,0,335,0,335,-1v0,0,-2,-5,-5,-11v-30,-69,-81,-128,-145,-167v-44,-26,-93,-42,-143,-48xm346,-487r-3,-13r-343,0r-343,0r-3,13v-10,31,-15,69,-15,102v0,33,5,71,15,102r3,13r343,0r343,0r3,-13v10,-31,15,-69,15,-102v0,-33,-5,-71,-15,-102xm335,-249v0,-1,-151,-1,-335,-1v-184,0,-335,0,-335,1v0,0,2,5,5,11v30,69,81,128,146,167v43,26,91,42,143,49v20,2,62,2,82,0v61,-8,119,-30,168,-66v53,-38,95,-90,121,-150v3,-6,5,-11,5,-11","w":389},"\ue1a9":{"d":"-13,-61v34,-8,67,14,75,47v6,34,-14,66,-47,74v-44,12,-87,-31,-75,-75v6,-23,25,-42,47,-46","w":63},"\ue1aa":{"d":"-25,-519v12,-1,49,0,62,1v62,10,115,37,156,81v36,38,58,82,68,136v2,14,2,18,2,41v0,23,0,27,-2,41v-10,54,-32,98,-68,136v-41,44,-94,71,-157,81v-16,2,-56,2,-72,0v-63,-10,-116,-37,-157,-81v-36,-38,-58,-82,-68,-136v-2,-14,-2,-18,-2,-41v0,-23,0,-27,2,-41v7,-38,19,-70,38,-99v45,-68,116,-110,198,-119xm34,-497v-10,-2,-43,-3,-52,-2v-31,2,-59,10,-85,24v-60,29,-106,84,-124,149v-3,12,-7,36,-8,49r-1,7r236,0r236,0r-1,-7v-1,-13,-5,-37,-8,-49v-16,-55,-51,-103,-98,-135v-29,-19,-61,-31,-95,-36xm235,-243r1,-7r-236,0r-236,0r1,7v1,13,5,37,8,49v16,55,51,103,98,135v29,19,61,31,96,37v16,2,50,2,66,0v35,-6,67,-18,96,-37v55,-37,93,-95,103,-161v2,-9,3,-19,3,-23","w":264},"\ue1ab":{"d":"-36,-1019v66,-4,135,4,196,24v65,22,119,51,173,96v16,13,48,46,62,62v66,80,106,173,117,276v1,13,1,26,1,51v0,25,0,38,-1,50v-10,94,-43,179,-99,254v-27,37,-66,76,-105,105v-147,109,-346,132,-515,58v-45,-20,-86,-45,-126,-78v-16,-13,-48,-46,-62,-62v-66,-80,-106,-173,-117,-277v-2,-23,-2,-77,0,-101v10,-93,43,-178,99,-253v27,-37,66,-76,105,-105v78,-58,174,-93,272,-100xm49,-998v-14,-1,-63,-2,-75,-1v-122,7,-232,56,-317,141v-23,24,-40,44,-59,73r-10,14r206,1r412,0r206,-1r-10,-14v-19,-29,-36,-49,-59,-73v-80,-79,-181,-127,-294,-140xm434,-731r-10,-19r-424,0r-424,0r-6,11v-33,62,-51,131,-56,207r0,12r486,0r486,0r0,-12v-5,-73,-21,-137,-52,-199xm486,-488r0,-12r-486,0r-486,0r0,12v5,73,21,137,52,199r10,18r424,0r424,0r10,-18v31,-62,47,-126,52,-199xm411,-249v1,-1,-82,-1,-411,-1v-329,0,-412,0,-411,1v3,5,16,24,23,34v44,59,105,110,171,143v53,27,107,43,169,50v22,2,74,2,97,0v79,-9,149,-34,216,-77v45,-29,91,-72,123,-116v7,-10,20,-29,23,-34","w":514},"\ue1ac":{"d":"-257,-776r3,-2r254,0v251,0,254,0,257,2v2,1,4,3,5,5v2,3,2,6,2,381v0,358,0,379,-2,382v-1,2,-3,4,-5,6r-3,2r-254,0v-251,0,-254,0,-257,-2v-2,-1,-4,-3,-5,-5v-2,-3,-2,-6,-2,-382v0,-357,0,-378,2,-381v1,-2,3,-4,5,-6xm236,-639r0,-111r-236,0r-236,0r0,111r0,111r236,0r236,0r0,-111xm236,-389r0,-111r-236,0r-236,0r0,111r0,111r236,0r236,0r0,-111xm236,-139r0,-111r-236,0r-236,0r0,111r0,111r236,0r236,0r0,-111","w":264},"\ue1ad":{"d":"-25,-519v12,-1,49,0,62,1v62,10,115,37,156,81v36,38,58,82,68,136v2,14,2,18,2,41v0,23,0,27,-2,41v-10,54,-32,98,-68,136v-41,44,-94,71,-157,81v-16,2,-56,2,-72,0v-63,-10,-116,-37,-157,-81v-36,-38,-58,-82,-68,-136v-2,-14,-2,-18,-2,-41v0,-23,0,-27,2,-41v7,-38,19,-70,38,-99v45,-68,116,-110,198,-119xm34,-497v-10,-2,-43,-3,-52,-2v-31,2,-59,10,-85,24v-47,23,-84,60,-107,107v-11,21,-18,43,-23,69v-2,14,-2,18,-2,39v0,21,0,25,2,39v4,19,8,33,14,49v32,81,101,136,186,150v16,2,50,2,66,0v35,-6,67,-18,96,-37v41,-28,72,-66,90,-113v6,-16,10,-30,14,-49v2,-14,2,-18,2,-39v0,-21,0,-25,-2,-39v-4,-19,-8,-33,-14,-49v-31,-80,-101,-137,-185,-149xm-12,-455v25,-7,51,8,58,34v2,11,1,22,-5,33v-3,7,-13,17,-20,20v-3,1,-5,3,-5,4v0,1,-1,15,-3,32v-1,18,-2,32,-2,33v0,1,2,2,4,3r4,2r22,-26r22,-26r-2,-7v-5,-16,0,-34,12,-46v18,-19,48,-19,66,0v19,18,19,48,0,66v-12,12,-30,17,-46,12r-7,-2r-23,20v-13,11,-25,20,-26,22v-2,1,-2,2,-1,6v1,2,2,4,3,4v1,0,15,-1,33,-2v17,-2,31,-3,32,-3v1,0,3,-2,4,-5v3,-7,13,-17,20,-20v19,-10,40,-7,55,8v19,18,19,48,0,66v-19,19,-48,19,-66,0v-4,-4,-8,-9,-9,-12v-1,-3,-3,-5,-4,-5v-1,0,-15,-1,-32,-3v-18,-1,-32,-2,-33,-2v-1,0,-2,2,-3,4v-1,4,-1,5,1,6v1,2,13,11,26,22r23,20r5,-1v2,-1,8,-2,13,-3v13,-1,25,4,35,14v19,18,19,48,0,66v-18,19,-48,19,-66,0v-12,-12,-17,-30,-12,-46r2,-7r-22,-26r-22,-25v-2,0,-8,3,-8,4v0,1,1,15,2,32v2,18,3,32,3,33v0,1,2,3,5,4v7,3,17,13,20,20v10,19,7,40,-8,55v-18,19,-48,19,-66,0v-19,-19,-19,-48,0,-67v4,-3,9,-7,12,-8v3,-1,5,-3,5,-4v0,-1,1,-15,3,-33v1,-17,2,-31,2,-32v0,-1,-2,-2,-4,-3v-4,-1,-5,-1,-6,1v-2,1,-11,13,-22,26r-20,23r1,5v1,2,2,8,3,13v1,13,-4,25,-14,35v-18,19,-48,19,-66,0v-19,-18,-19,-48,0,-66v12,-12,30,-17,46,-12r7,2r26,-22r25,-22v0,-2,-3,-8,-4,-8v-1,0,-15,1,-32,2v-18,2,-32,3,-33,3v-1,0,-3,2,-4,5v-1,3,-5,8,-8,12v-19,19,-48,19,-67,0v-19,-18,-19,-48,0,-66v19,-19,48,-19,67,0v3,4,7,9,8,12v1,3,3,5,4,5v1,0,15,1,33,3v17,1,31,2,32,2v1,0,2,-2,3,-4v1,-4,1,-5,-1,-6v-1,-2,-13,-11,-26,-22r-23,-20r-7,2v-16,5,-34,0,-46,-12v-19,-18,-19,-48,0,-66v18,-19,48,-19,66,0v12,12,17,30,12,46r-2,7r20,23v11,13,20,25,22,26v1,2,2,2,6,1v2,-1,4,-2,4,-3v0,-1,-1,-15,-2,-33v-2,-17,-3,-31,-3,-32v0,-1,-2,-3,-5,-4v-3,-1,-8,-5,-12,-9v-19,-18,-19,-47,0,-66v6,-6,13,-10,21,-12","w":264},"\ue1ae":{"d":"2,-248r3,-2r45,0r45,0r3,2r2,3r0,245r0,245r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-245r0,-245xm202,-248r3,-2r45,0r45,0r3,2r2,3r0,245r0,245r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-245r0,-245","w":300},"\ue1af":{"d":"2,-248r3,-2r45,0r45,0r3,2r2,3r0,245r0,245r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-245r0,-245","w":100},"\ue1b0":{"d":"2,-248r3,-2r45,0r45,0r3,2r2,3r0,120r0,120r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-120r0,-120","w":100},"\ue1b1":{"d":"2,2r3,-2r45,0r45,0r3,2r2,3r0,73r0,73r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-73r0,-73","w":100},"\ue1b2":{"d":"2,-154r3,-2r45,0r45,0r3,2r2,3r0,73r0,73r-2,3r-3,2r-45,0r-45,0r-3,-2r-2,-3r0,-73r0,-73","w":100},"\ue1b3":{"d":"14,-199v8,-2,9,-2,93,29v66,23,83,30,86,32v15,14,4,40,-16,38r-69,-24r-64,-23v-1,0,-1,29,-1,65v0,73,0,70,-7,77v-5,4,-10,5,-17,5v-6,-1,-12,-4,-16,-10r-2,-4r-1,-84v0,-59,0,-85,1,-87v1,-5,9,-13,13,-14","w":200},"\ue1b4":{"d":"173,-199v3,-1,10,-1,14,1v5,2,9,6,11,10v2,4,2,9,2,88v0,94,0,88,-7,95v-5,4,-10,5,-17,5v-6,-1,-12,-4,-16,-10r-2,-4r-1,-67v0,-36,0,-66,-1,-66r-66,24v-62,22,-65,23,-70,23v-11,-1,-19,-9,-20,-19v0,-8,1,-12,6,-17v4,-4,7,-6,85,-34v44,-16,81,-29,82,-29","w":200},"\ue1b5":{"d":"172,-249v10,-3,22,2,26,11v2,4,2,9,2,113v0,103,0,109,-2,113v-4,8,-12,13,-22,12v-6,-1,-12,-4,-16,-10r-2,-4r-1,-43r0,-42r-64,34r-68,34v-9,1,-19,-4,-23,-12v-4,-9,-2,-19,5,-25v1,-2,36,-20,76,-41r74,-39r0,-22r0,-23r-64,34r-64,33r-8,0v-6,0,-7,0,-11,-2v-6,-4,-9,-10,-10,-17v0,-8,2,-14,8,-18v4,-3,160,-85,164,-86","w":200},"\ue1b6":{"d":"49,-503v12,-3,22,-6,23,-6v2,0,2,18,2,375r0,375r-21,5v-22,6,-48,13,-50,13v-1,0,-1,-113,-1,-375r0,-375r12,-3v7,-2,22,-6,35,-9","w":75},"\ue1b7":{"d":"49,-253v12,-3,22,-6,23,-6v2,0,2,13,2,250r0,250r-21,5v-22,6,-48,13,-50,13v-1,0,-1,-76,-1,-250r0,-250r12,-3v7,-2,22,-6,35,-9","w":75},"\ue1b8":{"d":"49,-253v12,-3,22,-6,23,-6v2,0,2,7,2,125r0,125r-14,3v-19,6,-55,15,-57,15v-1,0,-1,-24,-1,-125r0,-125r12,-3v7,-2,22,-6,35,-9","w":75},"\ue1b9":{"d":"49,-3v12,-3,22,-6,23,-6v2,0,2,4,2,78r0,78r-15,4v-9,2,-25,7,-36,10r-21,5r0,-78r0,-79r12,-3v7,-2,22,-6,35,-9","w":75},"\ue1ba":{"d":"50,-160v13,-3,24,-6,24,-6r0,78r0,79r-14,3v-19,6,-55,15,-57,15v-1,0,-1,-15,-1,-78r0,-78r12,-3v6,-2,22,-6,36,-10","w":75},"\ue1bb":{"d":"14,-173r26,-15v0,0,117,37,144,45r16,5r-16,10r-33,19r-16,9r-35,-11r-34,-11r-1,52r0,51r-13,8v-7,4,-21,12,-32,19r-19,11r-1,-54r0,-54r-12,-29v-7,-16,-12,-30,-12,-31v-1,-1,4,-4,38,-24","w":200},"\ue1bc":{"d":"79,-173r26,-15r47,31r48,32r0,53r0,53r-13,8v-7,4,-21,12,-32,19r-19,11r-1,-53r0,-53r-13,-9v-8,-5,-24,-16,-37,-24r-24,-16r-10,6v-11,7,-45,27,-49,29v-1,1,7,-10,18,-24v18,-23,21,-26,27,-29v4,-3,18,-11,32,-19","w":200},"\ue1bd":{"d":"94,-235r10,-6v1,0,23,16,49,35r47,34r0,77r0,76r-13,8v-7,4,-21,12,-32,19r-19,11r-1,-30r0,-29r-34,-25v-25,-18,-34,-24,-34,-23v-1,1,-1,2,-2,3v0,0,-5,4,-11,7r-53,31v-1,0,8,-14,19,-31r20,-31r10,-6v25,-16,54,-32,55,-32v1,1,8,5,15,10v16,12,15,12,15,7r0,-4r-34,-24v-31,-23,-34,-25,-34,-23v-1,1,-10,7,-23,15v-29,17,-42,25,-43,24r20,-31r19,-31r22,-12v12,-7,26,-16,32,-19","w":200},"\ue1be":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,4,2,206,2v202,0,203,0,206,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,175v0,161,0,172,-2,175v-1,2,-3,4,-4,5v-4,3,-12,4,-16,1v-5,-2,-7,-6,-8,-15v-2,-15,-6,-24,-14,-28v-3,-2,-4,-3,-206,-3v-202,0,-203,1,-206,3v-5,2,-10,9,-12,15v-1,5,-2,21,-2,294v0,279,-1,290,-2,292v-3,4,-10,7,-15,7v-5,-1,-10,-5,-11,-8v-2,-4,-2,-42,-2,-458r1,-454r2,-3v1,-1,3,-3,5,-4xm458,-29r-5,-3r-203,0r-203,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r203,0r203,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7","w":500},"\ue1bf":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28r4,2r277,0r278,0r3,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,174v0,142,0,174,1,188v4,39,11,143,16,207v1,27,1,30,-3,35v-1,2,-4,4,-6,5v-10,5,-22,0,-25,-10v-1,-1,-4,-61,-7,-134v-6,-104,-7,-133,-8,-137v-2,-6,-7,-13,-12,-15r-3,-3r-278,0r-277,0r-4,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm608,-29r-5,-3r-278,0r-278,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r278,0r278,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7","w":650},"\ue1c0":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,4,2,206,2v202,0,203,0,206,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,174v0,142,0,174,1,188v4,39,11,143,16,207v1,27,1,30,-3,35v-1,2,-4,4,-6,5v-10,5,-22,0,-25,-10v-1,-1,-4,-61,-7,-134v-6,-104,-7,-133,-8,-137v-2,-6,-7,-13,-12,-15v-3,-2,-4,-3,-206,-3v-202,0,-203,1,-206,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm458,-29r-5,-3r-203,0r-203,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r203,0r203,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7","w":500},"\ue1c1":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,4,2,206,2v202,0,203,0,206,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,175v0,161,0,172,-2,175v-1,2,-3,4,-4,5v-4,3,-12,4,-16,1v-5,-2,-7,-6,-8,-15v-2,-15,-6,-24,-14,-28v-3,-2,-4,-3,-206,-3v-202,0,-203,1,-206,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm458,-29r-5,-3r-203,0r-203,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r203,0r203,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7","w":500},"\ue1c2":{"d":"156,-173v2,-3,5,-4,7,-4v1,0,4,1,6,4v3,3,39,42,80,87v64,70,76,84,76,86v0,2,-12,16,-79,89v-47,51,-80,87,-82,88r-4,0v-2,-1,-157,-170,-159,-174v-1,-1,-1,-3,-1,-5v1,-2,144,-159,156,-171xm191,-31r-64,-69v-1,0,-55,60,-55,61v0,1,125,138,126,138v1,0,56,-59,56,-60v0,-1,-29,-32,-63,-70","w":325},"\ue1c3":{"d":"121,-134v3,-2,6,-3,8,-1v1,1,29,31,61,67v50,55,60,66,60,68v0,2,-10,13,-60,68v-32,36,-60,66,-61,67v-2,2,-5,1,-8,-1v-4,-3,-118,-128,-120,-131v-1,-1,-1,-3,-1,-5v1,-2,115,-128,121,-132xm146,-24r-44,-49v-3,2,-42,47,-42,48v0,0,19,23,44,49r44,49r22,-24v11,-12,21,-23,21,-24"},"\ue1c4":{"d":"121,-134v3,-2,6,-3,8,-1v1,1,29,31,61,67v50,55,60,66,60,68v0,2,-10,13,-60,68v-32,36,-60,66,-61,67v-2,2,-5,1,-8,-1v-4,-3,-118,-128,-120,-131v-1,-1,-1,-3,-1,-5v1,-2,115,-128,121,-132xm146,-24r-44,-49v-3,2,-42,47,-42,48v0,0,19,23,44,49r44,49r22,-24v11,-12,21,-23,21,-24"},"\ue1c5":{"d":"121,-134v3,-2,6,-3,8,-1v1,1,29,31,61,67v50,55,60,66,60,68v0,2,-10,13,-60,68v-32,36,-60,66,-61,67v-2,2,-5,1,-8,-1v-4,-3,-118,-128,-120,-131v-1,-1,-1,-3,-1,-5v1,-2,115,-128,121,-132"},"\ue1c6":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,5,2,81,2v76,0,78,0,81,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,175v0,161,0,172,-2,175v-1,2,-3,4,-4,5v-4,3,-12,4,-16,1v-5,-2,-7,-6,-8,-15v-2,-15,-6,-24,-14,-28v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-2,21,-2,294v0,279,-1,290,-2,292v-3,4,-10,7,-15,7v-5,-1,-10,-5,-11,-8v-2,-4,-2,-42,-2,-458r1,-454r2,-3v1,-1,3,-3,5,-4xm208,-29r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1c7":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,4,2,206,2v202,0,203,0,206,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,27,2,457v0,439,0,453,-2,456v-2,4,-9,8,-13,8v-4,0,-11,-4,-13,-7v-1,-2,-2,-13,-2,-292v0,-273,-1,-289,-2,-294v-2,-6,-7,-13,-12,-15v-3,-2,-4,-3,-206,-3v-202,0,-203,1,-206,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm458,-29r-5,-3r-203,0r-203,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r203,0r203,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7","w":500},"\ue1c8":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,5,2,81,2v76,0,78,0,81,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,27,2,457v0,439,0,453,-2,456v-2,4,-9,8,-13,8v-4,0,-11,-4,-13,-7v-1,-2,-2,-13,-2,-292v0,-273,-1,-289,-2,-294v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm208,-29r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1c9":{"d":"8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,11v2,15,6,24,14,28v3,2,5,2,81,2v76,0,78,0,81,-2v8,-4,12,-13,14,-28v1,-9,3,-13,8,-15v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,14,2,175v0,161,0,172,-2,175v-1,2,-3,4,-4,5v-4,3,-12,4,-16,1v-5,-2,-7,-6,-8,-15v-2,-15,-6,-24,-14,-28v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-8,4,-12,13,-14,28v-1,9,-3,13,-8,15v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm208,-29r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,18r0,18r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-18r0,-18r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1ca":{"d":"73,-122v5,-3,9,-4,11,-2v0,1,14,26,30,56v32,58,32,56,28,66v-1,5,-58,110,-63,117v-5,7,-16,12,-19,9v0,-1,-14,-26,-30,-56v-32,-58,-32,-56,-28,-66v1,-5,58,-110,63,-117v2,-2,5,-5,8,-7xm90,-32v-12,-23,-23,-41,-23,-41v-1,0,-34,61,-35,64v0,1,44,82,45,82v1,0,34,-61,35,-64","w":144},"\ue1cb":{"d":"73,-122v5,-3,9,-4,11,-2v0,1,14,26,30,56v32,58,32,56,28,66v-1,5,-58,110,-63,117v-5,7,-16,12,-19,9v0,-1,-14,-26,-30,-56v-32,-58,-32,-56,-28,-66v1,-5,58,-110,63,-117v2,-2,5,-5,8,-7xm90,-32v-12,-23,-23,-41,-23,-41v-1,0,-34,61,-35,64v0,1,44,82,45,82v1,0,34,-61,35,-64","w":144},"\ue1cc":{"d":"73,-122v5,-3,9,-4,11,-2v0,1,14,26,30,56v32,58,32,56,28,66v-1,5,-58,110,-63,117v-5,7,-16,12,-19,9v0,-1,-14,-26,-30,-56v-32,-58,-32,-56,-28,-66v1,-5,58,-110,63,-117v2,-2,5,-5,8,-7","w":144},"\ue1cd":{"d":"120,-222v3,-3,7,-4,9,-2v2,2,121,222,121,224v0,2,-119,222,-121,224v-2,2,-6,1,-9,-2v-4,-4,-120,-220,-120,-223v1,-4,117,-218,120,-221xm194,54v1,-2,-1,-6,-48,-93r-49,-91r-20,37v-10,20,-20,38,-21,39v-1,2,1,6,48,93r49,91r20,-37v10,-20,20,-38,21,-39"},"\ue1ce":{"d":"120,-222v3,-3,7,-4,9,-2v2,2,121,222,121,224v0,2,-119,222,-121,224v-2,2,-6,1,-9,-2v-4,-4,-120,-220,-120,-223v1,-4,117,-218,120,-221xm194,54v1,-2,-1,-6,-48,-93r-49,-91r-20,37v-10,20,-20,38,-21,39v-1,2,1,6,48,93r49,91r20,-37v10,-20,20,-38,21,-39"},"\ue1cf":{"d":"120,-222v3,-3,7,-4,9,-2v2,2,121,222,121,224v0,2,-119,222,-121,224v-2,2,-6,1,-9,-2v-4,-4,-120,-220,-120,-223v1,-4,117,-218,120,-221"},"\ue1d0":{"d":"49,-83v24,-4,51,0,75,10v6,3,9,4,11,7r2,3r0,66v0,73,0,70,-6,73v-4,3,-7,2,-13,0v-29,-14,-70,-14,-99,0v-6,2,-9,3,-13,0v-6,-3,-6,0,-6,-73r0,-66r2,-3v4,-6,27,-14,47,-17","w":137},"\ue1d1":{"d":"49,-83v24,-4,51,0,75,10v6,3,9,4,11,7r2,3r0,66v0,73,0,70,-6,73v-4,3,-7,2,-13,0v-29,-14,-70,-14,-99,0v-6,2,-9,3,-13,0v-6,-3,-6,0,-6,-73r0,-66r2,-3v4,-6,27,-14,47,-17xm85,-62v-10,-1,-25,-1,-36,0v-8,2,-12,3,-21,6r-6,2r0,53v0,49,0,52,2,52v10,-4,31,-8,45,-8v13,0,34,4,44,8v2,0,2,-3,2,-52r0,-53r-6,-2v-8,-3,-16,-5,-24,-6","w":137},"\ue1d2":{"d":"2,-93v1,-1,3,-2,5,-2v1,0,3,1,4,2v2,2,2,3,2,93v0,90,0,91,-2,93v-1,1,-3,2,-4,2v-2,0,-4,-1,-5,-2v-2,-2,-2,-3,-2,-93v0,-90,0,-91,2,-93xm204,-93v1,-1,3,-2,5,-2v1,0,3,1,4,2v2,2,2,3,2,93v0,90,0,91,-2,93v-1,1,-3,2,-4,2v-2,0,-4,-1,-5,-2v-2,-2,-2,-3,-2,-93v0,-90,0,-91,2,-93xm88,-83v24,-4,51,0,75,10v6,3,9,4,11,7r2,3r0,66v0,73,0,70,-6,73v-4,3,-7,2,-13,0v-14,-7,-33,-11,-49,-11v-17,0,-36,4,-50,11v-6,2,-9,3,-13,0v-6,-3,-6,0,-6,-73r0,-66r2,-3v4,-6,27,-14,47,-17","w":215},"\ue1d3":{"d":"2,-93v1,-1,3,-2,5,-2v1,0,3,1,4,2v2,2,2,3,2,93v0,90,0,91,-2,93v-1,1,-3,2,-4,2v-2,0,-4,-1,-5,-2v-2,-2,-2,-3,-2,-93v0,-90,0,-91,2,-93xm204,-93v1,-1,3,-2,5,-2v1,0,3,1,4,2v2,2,2,3,2,93v0,90,0,91,-2,93v-1,1,-3,2,-4,2v-2,0,-4,-1,-5,-2v-2,-2,-2,-3,-2,-93v0,-90,0,-91,2,-93xm88,-83v24,-4,51,0,75,10v6,3,9,4,11,7r2,3r0,66v0,73,0,70,-6,73v-4,3,-7,2,-13,0v-14,-7,-33,-11,-49,-11v-17,0,-36,4,-50,11v-6,2,-9,3,-13,0v-6,-3,-6,0,-6,-73r0,-66r2,-3v4,-6,27,-14,47,-17xm124,-62v-10,-1,-25,-1,-36,0v-8,2,-12,3,-21,6r-6,2r0,53v0,49,0,52,2,52v10,-4,31,-8,45,-8v13,0,34,4,44,8v2,0,2,-3,2,-52r0,-53r-6,-2v-8,-3,-16,-5,-24,-6","w":215},"\ue1d4":{"d":"73,-108v3,-1,6,0,9,2v1,2,17,25,36,52v28,43,33,51,33,54v0,3,-5,11,-33,53v-19,28,-35,52,-37,53v-3,3,-7,3,-10,0v-2,-1,-18,-25,-37,-53v-28,-42,-33,-50,-33,-53v0,-3,5,-11,32,-52v39,-56,38,-55,40,-56","w":152},"\ue1d5":{"d":"5,-73v2,-2,4,-2,14,-2v33,2,60,2,84,1v24,-1,26,-1,29,1v2,1,3,3,4,4v1,2,1,27,1,69r0,66r-2,3v-3,5,-7,5,-31,7v-24,1,-47,1,-71,0v-24,-2,-28,-2,-31,-7r-2,-3r0,-66v0,-42,0,-67,1,-69v1,-1,2,-3,4,-4","w":137},"\ue1d6":{"d":"6,-44v3,-1,5,-2,6,-2v1,1,11,1,23,2v22,1,65,1,83,-1v7,-1,9,0,13,1v3,2,4,3,5,5v1,2,1,25,1,69r0,66r-2,3v-2,3,-4,4,-7,5v-12,3,-63,4,-95,2v-24,-2,-28,-2,-31,-7r-2,-3r0,-66v0,-44,0,-67,1,-69v1,-2,2,-4,5,-5","w":137},"\ue1d7":{"d":"30,-76v32,-2,86,-1,98,2v3,1,5,2,7,5r2,3r0,66v0,43,0,67,-1,69v-1,1,-2,3,-4,5v-4,1,-5,1,-26,0v-27,-1,-48,-1,-75,0v-21,1,-22,1,-26,0v-2,-2,-3,-4,-4,-5v-1,-2,-1,-26,-1,-69r0,-66r2,-3v3,-5,8,-6,28,-7","w":137},"\ue1d8":{"d":"30,-98v32,-2,86,-1,98,2v3,1,5,2,7,5r2,3r0,66v0,44,0,67,-1,69v-1,1,-2,4,-4,5v-3,2,-5,2,-29,0v-21,-1,-48,-1,-69,0v-24,2,-26,2,-29,0v-2,-1,-3,-4,-4,-5v-1,-2,-1,-25,-1,-69r0,-66r2,-3v3,-5,8,-6,28,-7","w":137},"\ue1d9":{"d":"24,-53v12,-3,21,-3,33,0v13,4,22,8,24,13v2,5,2,81,0,85v-4,7,-9,8,-19,3v-7,-4,-13,-5,-21,-5v-8,0,-14,1,-21,5v-10,5,-15,4,-19,-3v-2,-4,-2,-80,0,-85v2,-5,11,-9,23,-13","w":82},"\ue1da":{"d":"61,-53v5,-1,7,-1,12,1v3,2,5,4,7,6v1,4,2,7,2,45v0,29,0,43,-1,45v-1,4,-2,5,-6,6r-4,2r-5,-2r-4,-3r-7,4v-5,2,-9,3,-14,4v-10,1,-15,4,-20,10v-6,8,-14,8,-19,1v-2,-3,-2,-3,-2,-43v0,-39,0,-41,2,-45v5,-9,20,-19,34,-21v5,-1,9,-2,14,-5v4,-2,8,-4,11,-5","w":82},"\ue1db":{"d":"6,-109v4,-2,6,-2,10,0v4,2,6,6,8,19v1,14,3,24,5,28r1,3r9,0v26,-3,52,-11,74,-26v11,-7,13,-8,18,-5v6,3,6,1,6,73v0,64,0,66,-2,69v-4,7,-33,23,-53,29v-15,4,-33,8,-47,9v-11,0,-12,0,-16,-2v-6,-3,-9,-9,-13,-22v-1,-5,-3,-11,-4,-14v-2,-3,-2,-6,-2,-78v0,-48,0,-75,1,-77v1,-3,2,-4,5,-6","w":137},"\ue1dc":{"d":"6,-79v4,-2,6,-2,10,0v4,2,6,6,8,19v1,14,3,24,5,28r1,3r9,0v26,-3,52,-11,74,-26v10,-7,12,-8,17,-6v7,3,7,1,7,74v0,64,0,66,-2,69v-4,7,-33,23,-53,29v-15,4,-33,8,-47,9v-11,0,-12,0,-16,-2v-6,-3,-9,-9,-13,-22v-1,-5,-3,-11,-4,-14v-2,-3,-2,-6,-2,-78v0,-48,0,-75,1,-77v1,-3,2,-4,5,-6","w":137},"\ue1dd":{"d":"5,-49v4,-3,8,-2,15,1v7,4,13,5,21,5v8,0,14,-1,21,-5v10,-5,15,-4,19,3v2,4,2,80,0,85v-3,7,-25,15,-40,15v-15,0,-37,-8,-40,-15v-1,-2,-1,-11,-1,-43v0,-42,0,-43,5,-46","w":82},"\ue1de":{"d":"5,-33v1,-1,4,-2,6,-2v4,0,5,1,10,6v7,7,10,9,20,10v5,1,10,2,14,4v7,3,7,4,9,2v4,-3,7,-3,12,-1v6,3,6,3,6,50v0,41,-1,43,-3,46v-1,2,-3,4,-6,6v-6,3,-10,2,-21,-3v-6,-4,-11,-5,-16,-6v-14,-2,-29,-12,-34,-21v-2,-4,-2,-6,-2,-44v0,-43,0,-44,5,-47","w":82},"\ue1df":{"d":"19,-88v4,-2,5,-2,16,-2v14,1,32,5,47,9v20,6,49,22,53,29v2,3,2,5,2,69v0,72,0,70,-6,73v-5,3,-7,2,-18,-5v-22,-15,-48,-23,-74,-26r-9,0r-1,3v-2,4,-4,14,-5,28v-2,13,-4,17,-8,19v-4,2,-5,2,-10,0v-3,-2,-4,-3,-5,-6v-1,-2,-1,-29,-1,-77v0,-72,0,-75,2,-78v1,-3,3,-9,4,-14v4,-13,7,-19,13,-22","w":137},"\ue1e0":{"d":"19,-88v4,-2,5,-2,16,-2v14,1,32,5,47,9v20,6,49,22,53,29v2,3,2,5,2,69v0,72,0,70,-6,73v-5,3,-7,2,-18,-5v-22,-15,-48,-23,-74,-26r-9,0r-1,3v-2,4,-4,14,-5,28v-2,13,-4,17,-8,20r-3,1r0,95v0,105,0,99,-6,99v-8,0,-7,15,-7,-179v0,-171,0,-174,2,-178v1,-3,3,-9,4,-14v4,-13,7,-19,13,-22","w":137},"\ue1e1":{"d":"124,-96v2,-3,3,-2,6,3v3,7,5,16,8,54v3,30,3,36,3,57v-1,30,-3,45,-8,51r-3,2r-19,-13r-18,-14r-1,3v-1,2,-1,8,-2,13v-2,14,-5,24,-8,24v-1,0,-9,-6,-18,-13r-17,-13r-1,6v-3,20,-6,33,-10,34v0,0,-7,-6,-15,-12v-13,-12,-14,-13,-15,-18v-8,-25,-7,-88,1,-106v1,-3,2,-5,3,-5v1,-1,5,2,10,6r9,8r0,-6v3,-21,6,-32,10,-33r18,13r18,14r0,-8v1,-10,4,-23,5,-27v3,-7,3,-7,20,5r16,11r0,-4v0,-3,1,-8,2,-13v1,-9,3,-15,6,-19","w":137},"\ue1e2":{"d":"44,-66v3,-2,5,-2,8,0v4,2,43,63,43,66v0,4,-41,65,-44,67v-3,1,-3,1,-6,0v-3,-2,-44,-63,-44,-67v0,-3,39,-64,43,-66","w":95},"\ue1e3":{"d":"124,-144v4,-2,11,2,12,7v1,2,1,26,1,75r-1,72r-2,11v-10,39,-35,60,-68,60v-18,0,-51,-6,-60,-10v-6,-3,-6,-1,-6,-73v0,-72,0,-70,6,-73v4,-2,4,-2,15,0v16,4,25,5,42,5r16,0r6,-3v17,-8,30,-33,30,-57v0,-8,3,-12,9,-14","w":137},"\ue1e4":{"d":"44,-79v24,-3,39,-2,53,5v20,11,35,35,39,65v1,11,1,143,0,147v-3,8,-17,8,-20,0v-1,-2,-1,-5,-1,-8v0,-24,-13,-49,-30,-57r-6,-4r-16,1v-17,0,-26,1,-42,5v-11,2,-11,2,-15,0v-6,-3,-6,-1,-6,-73v0,-72,0,-70,6,-73v5,-2,26,-7,38,-8","w":137},"\ue1e5":{"d":"73,-108v3,-1,6,0,9,2v1,2,17,25,36,52v28,43,33,51,33,54v0,3,-5,11,-33,53v-19,28,-35,52,-37,53v-1,2,-3,2,-6,2v-2,0,-5,1,-7,2v-7,4,-17,5,-28,5v-6,-1,-14,-2,-17,-2v-15,-4,-29,-13,-32,-21v-2,-4,-2,-5,-1,-8v1,-4,2,-5,5,-6v6,-3,10,-1,15,4v4,5,9,8,19,10v14,2,31,1,30,-2v-1,-1,-14,-21,-30,-44v-24,-36,-28,-43,-28,-46v0,-3,5,-11,32,-52v39,-56,38,-55,40,-56","w":152},"\ue1e6":{"d":"74,-108v2,-1,5,0,8,2v1,2,17,25,36,52v28,43,33,51,33,54v0,3,-5,11,-33,53v-19,28,-35,52,-37,53v-2,3,-7,3,-10,1v-1,-1,-4,-5,-7,-9v-5,-8,-5,-9,-3,-9v4,0,10,-7,11,-12v1,-10,-2,-25,-9,-40v-10,-18,-28,-39,-43,-46v-5,-2,-9,-4,-10,-4v-1,0,-2,0,-2,-1v0,0,60,-89,62,-91v1,-1,2,-3,4,-3","w":152},"\ue1e7":{"d":"74,-108v2,-1,5,0,8,2v1,2,17,25,36,52v28,43,33,51,33,54v0,3,-5,11,-33,53v-19,28,-35,52,-37,53v-1,2,-3,2,-6,2v-2,0,-5,1,-7,2v-7,4,-17,5,-28,5v-6,-1,-14,-2,-17,-2v-15,-4,-29,-13,-32,-21v-2,-4,-2,-5,-1,-8v1,-4,2,-5,5,-6v6,-3,10,-1,15,4v4,5,9,8,19,10v13,2,30,1,30,-2v0,-1,1,-1,1,-1v3,0,10,-7,11,-10v1,-2,1,-6,1,-12v-1,-19,-15,-44,-34,-62v-8,-8,-12,-11,-18,-14v-5,-2,-9,-4,-10,-4v-1,0,-2,0,-2,-1v0,0,60,-89,62,-91v1,-1,2,-3,4,-3","w":152},"\ue1e8":{"d":"33,-80v5,-2,8,-2,15,-2v9,1,16,3,30,12v21,14,29,17,41,16v13,-1,26,-9,32,-21r4,-6v1,0,2,2,3,3v5,15,7,90,1,120v-2,17,-11,28,-24,35v-7,3,-10,4,-16,5v-12,1,-21,-3,-43,-17v-14,-9,-19,-10,-30,-10v-8,0,-11,0,-15,2v-8,3,-15,10,-19,18r-4,6v-8,0,-11,-85,-4,-123v3,-20,12,-32,29,-38","w":163},"\ue1e9":{"d":"100,-147v2,-2,4,-3,5,-3v1,0,3,1,5,3v2,1,25,34,51,73v41,60,48,71,48,74v0,3,-7,14,-48,74v-26,39,-49,72,-51,73v-4,4,-6,4,-10,0v-2,-1,-25,-34,-51,-73v-41,-60,-48,-71,-48,-74v0,-3,7,-14,48,-74v26,-39,49,-72,51,-73","w":210},"\ue1ea":{"d":"6,-121r1,-4r93,0r93,0r1,4v4,15,5,51,5,121v0,70,-1,106,-5,121r-1,4r-93,0r-93,0r-1,-4v-4,-14,-5,-51,-5,-121v0,-70,1,-107,5,-121","w":200},"\ue1eb":{"d":"5,-632v2,0,5,-1,6,-1v3,0,9,3,10,5v1,1,1,75,1,252r0,251r85,0r86,0r1,4v4,15,5,51,5,121v0,70,-1,106,-5,121r-1,4r-93,0r-93,0r-1,-4v-1,-5,-3,-18,-4,-37v-2,-22,-3,-709,-1,-712","w":200},"\ue1ec":{"d":"6,-121r1,-4r93,0r93,0r1,4v4,15,5,51,5,121v0,70,-1,106,-5,121r-1,4r-86,0r-85,0r0,251r0,251r-2,2v-5,5,-15,5,-19,0v-1,-2,-1,-43,-1,-350v1,-255,1,-352,2,-363v1,-19,3,-32,4,-37","w":200},"\ue1ed":{"d":"161,-114v9,-6,17,-11,18,-11v2,0,9,4,105,71v50,34,72,49,73,51v1,3,1,3,0,6v-1,2,-27,20,-87,61v-47,32,-87,59,-88,60v-2,1,-4,1,-6,0v-1,-1,-41,-28,-88,-60v-60,-41,-86,-59,-87,-61v-1,-3,-1,-3,0,-6v1,-2,78,-56,160,-111","w":357},"\ue1ee":{"d":"161,-114v9,-6,17,-11,18,-11v2,0,9,4,105,71v50,34,72,49,73,51v1,3,1,3,0,6v-1,2,-17,13,-54,38r-52,36r0,100r0,99r-6,6v-13,15,-65,53,-101,75v-15,9,-32,18,-35,18v-2,0,-2,-5,-2,-149r0,-149r-52,-36v-37,-25,-53,-36,-54,-38v-1,-3,-1,-3,0,-6v1,-2,78,-56,160,-111","w":357},"\ue1ef":{"d":"128,-96r3,-2r180,0v199,0,185,-1,188,7v1,3,1,4,0,7v-2,5,-123,177,-127,180r-3,2r-180,0v-199,0,-185,1,-188,-7v-1,-3,-1,-4,0,-7v2,-5,123,-177,127,-180","w":500},"\ue1f0":{"d":"63,-200v16,-4,35,-1,50,8v6,4,7,5,8,9v5,24,5,128,-1,144r-1,2r-6,-4v-26,-17,-61,-13,-84,9v-4,4,-9,10,-12,14r-3,6r0,12r0,11r3,7v3,4,8,10,12,14v23,22,58,26,84,9r6,-4r1,2v6,16,6,120,1,144v-1,4,-2,5,-8,9v-21,12,-45,13,-67,2v-28,-13,-40,-35,-44,-78v-2,-18,-2,-214,0,-232v3,-34,11,-52,27,-67v8,-7,23,-15,34,-17","w":125},"\ue1f1":{"d":"63,-200v16,-4,35,-1,50,8v6,4,7,5,8,9v5,24,5,128,-1,144r-1,2r-6,-4v-26,-17,-61,-13,-84,9v-4,4,-9,10,-12,14r-3,6r0,12r0,11r3,7v3,4,8,10,12,14v23,22,58,26,84,9r6,-4r1,2v6,16,6,120,1,144v-1,4,-2,5,-8,9v-21,12,-45,13,-67,2v-28,-13,-40,-35,-44,-78v-2,-18,-2,-214,0,-232v3,-34,11,-52,27,-67v8,-7,23,-15,34,-17","w":125},"\ue1f2":{"d":"200,-200v15,-4,30,-2,44,5v11,6,14,8,15,15v6,34,4,139,-3,142r-4,-2v-5,-4,-16,-9,-24,-11v-22,-4,-45,3,-61,19v-4,4,-9,10,-12,14r-3,6r0,12r0,11r3,7v3,4,8,10,12,14v6,6,10,9,17,12v15,8,29,10,44,7v8,-2,19,-7,24,-11r4,-2v4,2,6,32,6,74v0,32,-1,55,-3,68v-1,7,-4,9,-15,15v-20,10,-40,9,-61,-1v-21,-11,-34,-29,-40,-56v-4,-20,-4,-27,-5,-125v0,-95,0,-122,4,-143v3,-24,11,-40,25,-53v8,-7,23,-15,33,-17xm37,-73v23,-5,48,0,68,13v9,5,13,9,15,12v1,1,2,7,3,13v1,9,1,31,2,208r0,197r-2,3v-3,3,-6,3,-9,0r-2,-2r0,-151r0,-151r-8,-5v-27,-16,-59,-18,-90,-5v-4,2,-8,3,-9,3v0,0,-1,-4,-1,-8v-5,-22,-5,-90,0,-111v1,-5,2,-5,6,-7v7,-4,19,-8,27,-9","w":263},"\ue1f3":{"d":"200,-200v15,-4,30,-2,44,5v11,6,14,8,15,15v6,34,4,139,-3,142r-4,-2v-5,-4,-16,-9,-24,-11v-22,-4,-45,3,-61,19v-4,4,-9,10,-12,14r-3,6r0,12r0,11r3,7v3,4,8,10,12,14v6,6,10,9,17,12v15,8,29,10,44,7v8,-2,19,-7,24,-11r4,-2v4,2,6,32,6,74v0,32,-1,55,-3,68v-1,7,-4,9,-15,15v-20,10,-40,9,-61,-1v-21,-11,-34,-29,-40,-56v-4,-20,-4,-27,-5,-125v0,-95,0,-122,4,-143v3,-24,11,-40,25,-53v8,-7,23,-15,33,-17xm37,-73v23,-5,48,0,68,13v9,5,13,9,15,12v1,1,2,7,3,13v1,9,1,31,2,208r0,197r-2,3v-3,3,-6,3,-9,0r-2,-2r0,-151r0,-151r-8,-5v-27,-16,-59,-18,-90,-5v-4,2,-8,3,-9,3v0,0,-1,-4,-1,-8v-5,-22,-5,-90,0,-111v1,-5,2,-5,6,-7v7,-4,19,-8,27,-9","w":263},"\ue1f4":{"d":"6,-373v4,-2,5,-2,10,-1v6,3,6,2,6,69r0,61r109,60v88,48,110,60,111,62v4,7,7,25,8,50v1,35,-5,72,-11,71v-1,0,-50,-26,-109,-58r-107,-59v-1,0,-1,28,-1,62r0,62r109,60v88,48,110,60,111,62v4,7,7,25,8,50v1,35,-5,72,-11,71v-1,0,-50,-26,-109,-58r-107,-59v-1,0,-1,52,-1,117v0,100,0,117,-1,120v-4,7,-15,8,-19,0v-2,-3,-2,-23,-2,-369v0,-402,-1,-371,6,-373"},"\ue1f5":{"d":"2,-298v2,-1,4,-2,6,-2v3,0,5,1,7,2r2,3r0,37r0,37r88,48v66,37,88,49,90,51v3,7,4,20,4,47v0,21,0,28,-1,35v-2,10,-4,15,-6,15v-1,0,-166,-90,-174,-95v-1,0,-1,34,-1,75r0,74r88,48v66,37,88,49,90,51v0,1,2,7,3,12v2,13,2,57,0,70v-2,11,-4,15,-7,15v-1,-1,-40,-22,-87,-48r-86,-47r-1,83r0,83r-3,2v-3,3,-9,3,-12,0r-2,-3r0,-295r0,-295","w":200},"\ue1f6":{"d":"181,-373v4,-2,5,-2,10,-1v6,3,6,2,6,69r0,61r109,60v88,48,110,60,111,62v4,7,7,25,8,50v1,35,-5,72,-11,71v-1,0,-50,-26,-109,-58r-107,-59v-1,0,-1,28,-1,62r0,62r109,60v88,48,110,60,111,62v4,7,7,25,8,50v1,35,-5,72,-11,71v-1,0,-50,-26,-109,-58r-107,-59v-1,0,-1,52,-1,117v0,100,0,117,-1,120v-4,7,-15,8,-19,0v-2,-3,-2,-23,-2,-369v0,-402,-1,-371,6,-373xm9,-123r1,-2r46,0r45,0r2,6v2,7,4,25,6,49v1,11,1,80,2,225v0,234,1,216,-7,219v-3,1,-4,1,-7,0v-9,-3,-8,7,-8,-129r0,-120r-40,0r-39,-1r-2,-5v-2,-7,-5,-26,-6,-52v-2,-24,-2,-110,0,-135v2,-30,4,-47,7,-55","w":425},"\ue1f7":{"d":"142,-298v2,-1,4,-2,7,-2v2,0,4,1,6,2r2,3r0,37r0,37r88,48v66,37,88,49,90,51v3,7,4,20,4,47v0,21,0,28,-1,35v-2,10,-4,15,-6,15v-1,0,-166,-90,-174,-95v-1,0,-1,34,-1,75r0,74r88,48v66,37,88,49,90,51v3,7,4,20,4,47v0,21,0,28,-1,35v-2,11,-4,15,-7,15v-1,-1,-40,-22,-87,-48r-86,-47r-1,83r0,83r-3,2v-3,3,-9,3,-12,0r-2,-3r0,-295r0,-295xm7,-97r1,-3r36,0r37,0r1,2v3,7,6,43,6,71v0,15,0,20,2,22v1,2,1,24,1,149v0,162,0,151,-6,154v-4,2,-6,2,-10,0v-6,-3,-6,2,-6,-102r0,-96r-31,0r-30,0r-1,-3v-9,-21,-9,-173,0,-194","w":340},"\ue1f8":{"d":"8,-499v6,-2,15,-1,20,3r2,2r0,494r0,494r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-494r0,-494r2,-2v1,-1,4,-2,6,-3xm73,-499v6,-2,15,-1,20,3r2,2r0,168v0,157,1,168,2,173v2,6,7,13,12,15v3,2,4,2,206,2v202,0,203,0,206,-2v5,-2,10,-9,12,-15v1,-5,2,-16,2,-173r0,-168r2,-2v7,-5,20,-5,26,0r2,2r0,494r0,494r-2,2v-6,5,-19,5,-26,0r-2,-2r0,-168v0,-157,-1,-168,-2,-173v-2,-6,-7,-13,-12,-15v-3,-2,-4,-3,-206,-3v-202,0,-203,1,-206,3v-5,2,-10,9,-12,15v-1,5,-2,16,-2,173r0,168r-2,2v-7,5,-20,5,-26,0r-2,-2r0,-494r0,-494r2,-2v1,-1,4,-2,6,-3xm523,-40r-5,-3r-203,0r-203,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r203,0r203,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm608,-499v6,-2,15,-1,20,3r2,2r0,494r0,494r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-494r0,-494r2,-2v1,-1,4,-2,6,-3","w":630},"\ue1f9":{"d":"5,-399v5,-2,12,-1,16,2r3,2r0,395r0,395r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-395v0,-314,0,-395,1,-396v1,-1,3,-2,4,-3xm57,-399v5,-2,12,-1,16,2r3,2r0,135r1,134r3,6v1,4,4,7,6,9r3,2r163,0r163,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-134r1,-135r2,-2v4,-3,14,-3,18,0r3,2r0,395r0,395r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-135r0,-135r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-163,0r-163,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,135r0,135r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-395v0,-314,0,-395,1,-396v1,-1,3,-2,4,-3xm418,-33v-4,-2,-9,-2,-166,-2v-146,0,-163,0,-165,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,9,2,166,2v157,0,162,0,166,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm485,-399v5,-2,12,-1,16,2r3,2r0,395r0,395r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-395v0,-314,0,-395,1,-396v1,-1,3,-2,4,-3","w":504},"\ue1fa":{"d":"8,-799v6,-2,15,-1,20,3r2,2r0,255v0,242,1,256,2,261v2,6,7,13,12,15v3,2,5,2,81,2v76,0,78,0,81,-2v5,-2,10,-9,12,-15v1,-5,2,-14,2,-140r0,-134r2,-2v7,-5,20,-5,26,0r2,2r0,424v0,402,0,425,-2,428v-1,2,-3,4,-4,5v-4,3,-12,4,-16,1v-5,-3,-7,-6,-8,-14v-2,-16,-6,-25,-14,-29v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-1,10,-2,36v0,29,0,30,-2,32v-7,5,-20,5,-26,0r-2,-2r0,-569r0,-569r2,-2v1,-1,4,-2,6,-3xm208,-165r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm208,85r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1fb":{"d":"9,-715v4,-1,8,0,13,3v1,1,2,28,2,231r1,230r3,6v1,4,4,7,6,9r3,2r63,0r63,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-122r1,-122r2,-2v5,-4,14,-4,19,1r2,2r0,379v0,336,0,378,-2,381v-3,6,-11,8,-17,4v-4,-2,-5,-4,-5,-14v-1,-8,-5,-17,-10,-20r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,29r0,29r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-508r0,-510v1,-2,4,-4,9,-5xm166,-158v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm166,-14r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r63,0r63,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm166,92v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":200},"\ue1fc":{"d":"8,-799v6,-2,15,-1,20,3r2,2r0,255v0,242,1,256,2,261v2,6,7,13,12,15v3,2,5,2,81,2v76,0,78,0,81,-2v5,-2,10,-9,12,-15v1,-5,2,-14,2,-140r0,-134r2,-2v7,-5,20,-5,26,0r2,2r0,548r0,548r-2,2v-6,5,-19,5,-26,0r-2,-2r0,-130v0,-122,-1,-131,-2,-136v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-2,15,-2,161r0,155r-2,2v-7,5,-20,5,-26,0r-2,-2r0,-694r0,-694r2,-2v1,-1,4,-2,6,-3xm208,-165r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm208,85r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1fd":{"d":"11,-715v2,-1,7,0,11,3v1,1,2,28,2,231r1,230r3,6v1,4,4,7,6,9r3,2r63,0r63,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-122r1,-122r2,-2v5,-4,14,-4,19,1r2,2r0,490r0,490r-2,2v-5,5,-14,5,-19,1r-2,-2r-1,-118r0,-119r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,141v0,139,-1,141,-2,143v-6,4,-16,3,-20,-1v-2,-2,-2,-9,-2,-622r0,-621r2,-2v1,-1,4,-2,5,-3r4,0xm166,-158v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm166,-14r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r63,0r63,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm166,92v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":200},"\ue1fe":{"d":"8,-799v6,-2,15,-1,20,3r2,2r0,255v0,242,1,256,2,261v2,6,7,13,12,15v3,2,5,2,81,2v76,0,78,0,81,-2v5,-2,10,-9,12,-15v1,-5,2,-14,2,-140r0,-134r2,-2v7,-5,20,-5,26,0r2,2r0,552r0,552r-2,2v-6,5,-19,5,-26,0r-2,-2r0,-134v0,-126,-1,-135,-2,-140v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-2,19,-2,261r0,255r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-794r0,-794r2,-2v1,-1,4,-2,6,-3xm208,-165r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm208,85r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue1ff":{"d":"11,-715v2,-1,7,0,11,3v1,1,2,28,2,231r1,230r3,6v1,4,4,7,6,9r3,2r63,0r63,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-122r1,-122r2,-2v5,-4,14,-4,19,1r2,2r0,494r0,494r-2,2v-5,5,-14,5,-19,1r-2,-2r-1,-122r0,-123r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,231v0,203,-1,230,-2,231v-4,3,-6,3,-10,3v-5,0,-8,-1,-10,-4v-2,-1,-2,-66,-2,-711r0,-710r2,-2v1,-1,4,-2,5,-3r4,0xm166,-158v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm166,-14r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r63,0r63,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm166,92v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":200},"\ue200":{"d":"8,-599v6,-2,15,-1,20,3r2,2r0,155v0,146,1,156,2,161v2,6,7,13,12,15v3,2,5,2,81,2v76,0,78,0,81,-2v5,-2,10,-9,12,-15v1,-5,2,-14,2,-136r0,-130r2,-2v7,-5,20,-5,26,0r2,2r0,548r0,548r-2,2v-6,5,-19,5,-26,0r-2,-2r0,-134v0,-126,-1,-135,-2,-140v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-2,19,-2,261r0,255r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-694r0,-694r2,-2v1,-1,4,-2,6,-3xm208,-165r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm208,85r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue201":{"d":"7,-536v6,-2,15,1,16,5v1,1,1,65,1,141r1,139r3,6v1,4,4,7,6,9r3,2r63,0r63,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-118r1,-118r2,-2v5,-4,14,-4,19,1r2,2r0,490r0,490r-2,2v-5,5,-14,5,-19,1r-2,-2r-1,-122r0,-123r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,231v0,203,-1,230,-2,231v-4,3,-6,3,-10,3v-5,0,-8,-1,-10,-4v-2,-1,-2,-58,-2,-621v0,-498,0,-621,1,-622v1,-1,3,-3,6,-4xm166,-158v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm166,-14r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r63,0r63,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm166,92v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":200},"\ue202":{"d":"8,-349v6,-2,15,-1,20,3v2,2,2,3,2,32v1,26,1,31,2,36v2,6,7,13,12,15v3,2,5,2,81,2v76,0,78,0,81,-2v8,-4,12,-13,14,-29v1,-8,3,-11,8,-14v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,26,2,428r0,424r-2,2v-6,5,-19,5,-26,0r-2,-2r0,-134v0,-126,-1,-135,-2,-140v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-5,2,-10,9,-12,15v-1,5,-2,19,-2,261r0,255r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-569r0,-569r2,-2v1,-1,4,-2,6,-3xm208,-165r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7xm208,85r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7"},"\ue203":{"d":"6,-312v4,-1,12,-1,15,2r3,2r0,29r1,28r3,6v1,4,4,7,6,9r3,2r63,0r63,0r3,-2v5,-3,9,-12,10,-21v0,-9,1,-11,5,-13v6,-4,14,-2,17,4v2,3,2,45,2,381r0,379r-2,2v-5,5,-14,5,-19,1r-2,-2r-1,-122r0,-123r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,231v0,203,-1,230,-2,231v-4,3,-6,3,-10,3v-5,0,-8,-1,-10,-4v-2,-1,-2,-48,-2,-510v0,-485,0,-509,2,-510v1,-1,3,-2,4,-3xm166,-158v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm166,-14r-3,-2r-63,0r-63,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r63,0r63,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm166,92v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":200},"\ue204":{"d":"5,-549v6,-2,15,-1,18,3v0,1,1,68,1,148r1,147r3,6v1,4,4,7,6,9r3,2r213,0r213,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-51r1,-51r2,-2v5,-4,14,-4,19,1r2,2r0,477r0,477r-2,2v-5,5,-14,5,-19,1r-2,-2r-1,-51r0,-52r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-213,0r-213,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,148r0,147r-3,2v-5,4,-14,4,-19,-1r-2,-2r0,-669v0,-476,0,-669,1,-670xm466,-158v-4,-2,-10,-2,-216,-2v-192,0,-213,0,-215,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,10,2,216,2v206,0,212,0,216,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm466,-14r-3,-2r-213,0r-213,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r213,0r213,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm466,92v-4,-2,-10,-2,-216,-2v-192,0,-213,0,-215,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,10,2,216,2v206,0,212,0,216,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm466,236r-3,-2r-213,0r-213,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r213,0r213,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm466,342v-4,-2,-10,-2,-216,-2v-192,0,-213,0,-215,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,10,2,216,2v206,0,212,0,216,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":500},"\ue205":{"d":"5,-491v6,-2,15,-1,18,3v0,1,1,55,1,119r1,118r3,6v1,4,4,7,6,9r3,2r163,0r163,0r3,-2v2,-2,5,-5,7,-9r3,-6r0,-32r1,-33r2,-2v4,-3,14,-3,18,0r3,2r0,441r0,441r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-33r0,-33r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-163,0r-163,0r-3,2v-2,2,-5,5,-6,8r-3,6r-1,119r0,118r-3,2v-5,4,-14,4,-19,-1r-2,-2r0,-611v0,-434,0,-611,1,-612xm366,-158v-4,-2,-9,-2,-166,-2v-146,0,-163,0,-165,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,9,2,166,2v157,0,162,0,166,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm366,-14r-3,-2r-163,0r-163,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r163,0r163,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm366,92v-4,-2,-9,-2,-166,-2v-146,0,-163,0,-165,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,9,2,166,2v157,0,162,0,166,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33xm366,236r-3,-2r-163,0r-163,0r-3,2v-2,2,-5,5,-6,8r-3,6r3,6v1,3,4,6,6,8r3,2r163,0r163,0r3,-2v2,-2,5,-5,6,-8r3,-6r-3,-6v-1,-3,-4,-6,-6,-8xm366,342v-4,-2,-9,-2,-166,-2v-146,0,-163,0,-165,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,9,2,166,2v157,0,162,0,166,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":400},"\ue206":{"d":"408,-624v6,-2,15,-1,20,3r2,2r0,158r0,158r44,75v23,42,43,76,44,78v0,1,-1,4,-1,7v-1,3,-17,31,-36,63r-33,58r35,60v19,33,34,60,35,62v0,1,-1,4,-1,7v-2,5,-88,155,-95,166v-2,2,-8,9,-13,14r-9,9r0,411r0,412r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-408r0,-408r-44,-75v-23,-41,-43,-76,-44,-78v0,-1,1,-4,1,-7v1,-3,17,-31,36,-63r33,-58r-35,-60v-19,-33,-34,-60,-35,-62v0,-1,1,-4,1,-7v3,-7,91,-159,98,-169v3,-4,8,-10,12,-13r7,-6r0,-162r0,-162r2,-2v1,-1,4,-2,6,-3xm421,-159r-33,-56v-1,-1,0,-3,-22,36r-19,32r29,49r28,50r6,-4v4,-1,9,-3,11,-4r5,0r13,-24r14,-23xm425,98r-29,-49v0,0,-3,1,-6,3v-4,1,-9,3,-11,4r-5,0r-13,24r-14,23r33,57v25,44,33,56,34,55v0,-1,9,-16,20,-35r19,-33xm8,-181v2,-1,5,-2,7,-2v4,0,10,3,12,6v1,2,3,6,3,10v2,16,6,25,14,29v3,2,5,2,81,2v76,0,78,0,81,-2v8,-4,12,-13,14,-29v1,-8,3,-11,8,-14v4,-3,12,-2,16,1v1,1,3,3,4,5v2,3,2,34,2,590r0,587r-2,2v-7,5,-19,5,-26,0r-2,-2r0,-422v0,-401,-1,-422,-2,-427v-2,-6,-7,-13,-12,-15v-3,-2,-5,-3,-81,-3v-76,0,-78,1,-81,3v-8,4,-12,13,-14,29v-1,8,-3,11,-8,14v-4,3,-12,2,-16,-1v-1,-1,-3,-3,-4,-5v-2,-3,-2,-13,-2,-176r1,-173r2,-3v1,-1,3,-3,5,-4xm208,-40r-5,-3r-78,0r-78,0r-5,3v-4,2,-7,4,-8,7r-3,4r0,29r0,29r3,4v1,3,4,5,8,7r5,3r78,0r78,0r5,-3v4,-2,7,-4,8,-7r4,-4r0,-29r0,-29r-4,-4v-1,-3,-4,-5,-8,-7","w":487},"\ue207":{"d":"326,-499v4,-1,12,-1,15,2r3,2r0,114r1,114r34,61v25,42,35,60,35,63v0,1,-1,4,-2,7v-1,2,-17,29,-35,61r-32,57v0,1,15,29,34,62v25,41,35,61,35,63v0,1,-1,5,-2,7v-5,11,-73,128,-78,134r-9,9r-5,4r0,317r0,317r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-314r0,-315r-35,-60v-25,-42,-35,-60,-35,-63v0,-1,1,-4,2,-7v1,-2,17,-29,35,-61r32,-57v0,-1,-15,-29,-34,-62v-25,-43,-35,-60,-35,-63v0,-4,2,-8,41,-75v34,-60,35,-60,44,-70r9,-10r0,-116v0,-110,0,-117,2,-118v1,-1,3,-2,4,-3xm337,-152v-14,-24,-26,-44,-26,-45v-1,-1,-6,6,-17,26r-16,28v0,1,50,88,51,90v1,1,6,-6,17,-26r16,-28v0,-1,-11,-21,-25,-45xm337,98v-14,-24,-26,-44,-26,-45v-1,-1,-6,6,-17,26r-16,28v0,1,50,88,51,90v1,1,6,-6,17,-26r16,-28v0,-1,-11,-21,-25,-45xm6,-145v4,-3,9,-2,13,0v4,2,5,4,5,13v1,9,5,18,10,21r3,2r63,0r63,0r3,-2v5,-3,9,-12,10,-21v0,-9,1,-11,5,-13v6,-4,14,-2,17,4v2,3,2,54,2,474r0,470r-3,2v-4,3,-14,3,-18,0r-2,-2r-1,-339r0,-339r-3,-6v-2,-3,-5,-6,-7,-8r-3,-2r-63,0r-63,0r-3,2v-5,3,-9,12,-10,20v0,10,-1,12,-5,14v-4,2,-9,3,-13,0v-6,-3,-6,7,-6,-145v0,-151,0,-142,6,-145xm166,-33v-4,-2,-7,-2,-66,-2v-55,0,-63,0,-65,2v-10,5,-11,8,-11,33v0,25,1,28,10,33v4,2,7,2,66,2v59,0,62,0,66,-2v9,-5,10,-8,10,-33v0,-25,-1,-28,-10,-33","w":389},"\ue208":{"d":"32,-173v3,-1,7,-1,17,-1v42,2,80,20,108,52v37,43,49,96,35,151v-14,54,-56,103,-108,128v-22,10,-54,18,-73,17v-6,0,-7,-1,-9,-4v-2,-3,-2,-4,-2,-8v2,-10,14,-26,26,-32v6,-3,7,-3,18,-4v58,-4,96,-37,105,-92v3,-19,0,-46,-7,-67v-19,-53,-67,-88,-128,-93v-9,0,-9,-1,-12,-4v-3,-4,-2,-10,2,-18v6,-11,16,-20,28,-25xm258,-149v10,-3,10,-3,26,14v8,8,15,16,15,17v2,6,-4,19,-13,28v-8,8,-16,12,-24,13r-6,0r-14,-14v-16,-17,-16,-18,-14,-27v5,-13,18,-27,30,-31xm258,78v10,-3,10,-3,26,14v8,8,15,16,15,17v2,6,-3,18,-12,28v-8,7,-17,12,-25,13r-6,0r-14,-14v-16,-17,-16,-18,-14,-27v5,-13,18,-27,30,-31","w":300},"\ue209":{"d":"23,-137v7,-3,25,-2,40,2v46,12,82,50,93,99v2,13,2,38,-1,52v-7,31,-22,58,-47,81v-25,25,-59,40,-93,42r-11,0r-2,-3v-2,-2,-2,-3,-2,-7v1,-6,5,-12,12,-19v8,-7,12,-9,26,-9v31,-3,58,-19,70,-43v25,-46,9,-107,-36,-138v-17,-11,-39,-19,-59,-21v-8,0,-9,-1,-11,-3v-3,-3,-2,-9,1,-16v4,-5,15,-15,20,-17xm203,-117v10,-5,11,-5,25,9r12,13r0,5v-2,12,-19,28,-30,28v-4,0,-5,-1,-16,-12v-11,-11,-12,-12,-12,-16v0,-8,10,-22,21,-27xm203,65v10,-5,11,-5,25,9r12,13r0,5v-1,2,-2,6,-3,9v-4,6,-15,15,-21,17v-9,4,-9,3,-22,-10v-11,-11,-12,-12,-12,-16v0,-8,10,-22,21,-27","w":240},"\ue20a":{"d":"192,-950v9,-2,28,-1,38,2v27,8,45,26,54,55v5,14,7,26,8,44v1,28,-2,52,-13,98v-21,93,-42,151,-72,202r-8,13r15,24v35,60,50,91,60,123v13,48,12,86,-6,140v-3,10,-11,32,-18,50v-6,17,-12,32,-12,32v0,1,6,1,13,1v21,1,40,7,54,16v6,4,6,5,6,8v-1,13,-30,56,-43,62r-4,2r-6,-4v-3,-2,-9,-5,-13,-7v-10,-4,-24,-8,-25,-7v-1,2,-1,21,0,31v1,12,5,25,9,35r3,7r4,-4v8,-8,5,-7,49,-7r39,0r0,5v0,4,1,16,2,27v3,49,-1,70,-23,104v-20,32,-44,56,-65,64v-13,6,-30,7,-46,5v-51,-9,-96,-57,-108,-114v-3,-15,-3,-45,0,-60v3,-15,7,-27,16,-45v12,-25,31,-52,51,-72v20,-21,38,-33,60,-40r7,-3r6,-14v18,-46,25,-73,26,-102v1,-43,-11,-85,-42,-142v-6,-11,-39,-69,-40,-70v-1,0,-5,4,-9,10v-13,15,-30,32,-59,59v-44,41,-61,62,-74,91v-19,42,-17,85,6,119v10,15,28,31,43,37v4,1,7,3,8,4v1,4,-12,25,-22,36v-8,8,-9,8,-19,4v-35,-15,-60,-53,-62,-96v-2,-48,26,-110,80,-174v15,-18,29,-32,60,-61v26,-24,41,-40,41,-41v-13,-22,-33,-61,-42,-82v-25,-55,-40,-109,-45,-154v-6,-57,13,-115,53,-165v21,-26,42,-41,65,-46xm198,-903v-10,-3,-25,-3,-34,-2v-13,3,-24,9,-34,17v-9,7,-16,26,-21,52v-3,16,-3,50,-1,67v10,64,33,126,74,202r10,19r3,-6v10,-18,21,-47,29,-73v10,-32,27,-99,31,-125v4,-28,5,-58,1,-79v-6,-38,-26,-63,-58,-72xm195,-98v0,0,-3,0,-15,3v-14,2,-30,9,-40,17v-3,3,-4,4,-6,11v-8,29,-4,65,11,94v20,40,56,69,94,76v13,2,22,1,35,-2r4,-1r0,-8v0,-4,-1,-19,-2,-33r-1,-25r-37,0v-41,0,-39,0,-37,-7v1,-3,1,-4,0,-5v-3,-3,-8,-16,-10,-26v-7,-24,-6,-50,2,-83v1,-6,2,-10,2,-11","w":313},"\ue20b":{"d":"152,-760v21,-4,44,2,59,18v16,16,23,41,22,77v-1,19,-4,37,-11,68v-17,72,-33,118,-57,158r-6,10r13,22v31,53,41,74,49,104v8,32,7,61,-4,98v-4,11,-9,27,-21,58r-6,14r12,0v14,1,26,4,38,11v8,4,9,5,9,7v0,10,-27,50,-36,52v-1,0,-5,-1,-12,-5v-6,-3,-14,-6,-18,-7r-7,-2r-1,3v-1,5,1,25,3,35v3,9,7,20,8,20r5,-5r5,-4r31,0r32,0r0,14v1,7,1,23,1,34v0,20,0,21,-2,29v-9,28,-36,65,-57,79v-45,28,-111,-6,-131,-69v-12,-36,-6,-72,17,-110v23,-39,52,-67,80,-76r8,-3r7,-19v19,-52,22,-81,13,-119v-8,-30,-19,-52,-50,-106r-11,-18v-1,0,-4,3,-8,8v-9,11,-22,24,-47,48v-39,37,-54,55,-62,82v-10,28,-9,52,3,75v8,18,19,29,35,37r10,4v2,1,0,8,-6,17v-6,8,-16,19,-18,19v-3,0,-18,-8,-24,-12v-10,-9,-17,-17,-24,-31v-19,-40,-8,-89,34,-149v18,-25,35,-44,81,-87v10,-10,19,-19,19,-20v1,-1,-1,-6,-6,-15v-43,-77,-65,-147,-63,-200v2,-30,10,-57,26,-84v21,-33,45,-55,68,-60xm155,-724v-18,-3,-36,1,-50,12v-7,5,-8,8,-13,21v-6,19,-9,45,-7,66v5,50,25,107,60,171v7,13,9,15,10,13v2,-3,12,-26,17,-41v14,-40,31,-107,34,-138v4,-38,-4,-70,-21,-87v-8,-8,-19,-14,-30,-17xm156,-76v0,-2,-1,-2,-9,-1v-11,2,-23,7,-33,13v-5,4,-6,5,-7,10v-1,3,-3,10,-3,16v-6,50,30,104,79,118v9,3,29,3,35,1r4,-1r0,-14v-1,-8,-1,-20,-2,-27r0,-12r-30,0r-30,0r0,-4v1,-3,0,-6,-2,-10v-2,-4,-4,-11,-6,-17v-2,-9,-2,-11,-2,-28v0,-15,0,-19,2,-30v2,-7,3,-13,4,-14"},"\ue20c":{"d":"192,-950v9,-2,28,-1,38,2v27,8,45,26,54,55v5,14,7,26,8,44v1,28,-2,52,-13,98v-21,93,-42,151,-72,202r-8,13r15,24v35,60,50,91,60,123v13,48,12,86,-6,140v-3,10,-11,32,-18,50v-6,17,-12,32,-12,32v0,1,6,1,13,1v21,1,40,7,54,16v6,4,6,5,6,8v-1,13,-30,56,-43,62r-4,2r-6,-4v-3,-2,-9,-5,-13,-7v-10,-4,-24,-8,-25,-7v-1,2,-1,21,0,31v1,12,5,25,9,35r3,7r4,-4v8,-8,5,-7,49,-7r39,0r0,5v0,4,1,16,2,27v3,49,-1,70,-23,104v-20,32,-44,56,-65,64v-13,6,-30,7,-46,5v-51,-9,-96,-57,-108,-114v-3,-15,-3,-45,0,-60v3,-15,7,-27,16,-45v12,-25,31,-52,51,-72v20,-21,38,-33,60,-40r7,-3r6,-14v18,-46,25,-73,26,-102v1,-43,-11,-85,-42,-142v-6,-11,-39,-69,-40,-70v-1,0,-5,4,-9,10v-13,15,-30,32,-59,59v-44,41,-61,62,-74,91v-19,42,-17,85,6,119v10,15,28,31,43,37v4,1,7,3,8,4v1,4,-12,25,-22,36v-8,8,-9,8,-19,4v-35,-15,-60,-53,-62,-96v-2,-48,26,-110,80,-174v15,-18,29,-32,60,-61v26,-24,41,-40,41,-41v-13,-22,-33,-61,-42,-82v-25,-55,-40,-109,-45,-154v-6,-57,13,-115,53,-165v21,-26,42,-41,65,-46xm198,-903v-10,-3,-25,-3,-34,-2v-13,3,-24,9,-34,17v-9,7,-16,26,-21,52v-3,16,-3,50,-1,67v10,64,33,126,74,202r10,19r3,-6v10,-18,21,-47,29,-73v10,-32,27,-99,31,-125v4,-28,5,-58,1,-79v-6,-38,-26,-63,-58,-72xm195,-98v0,0,-3,0,-15,3v-14,2,-30,9,-40,17v-3,3,-4,4,-6,11v-8,29,-4,65,11,94v20,40,56,69,94,76v13,2,22,1,35,-2r4,-1r0,-8v0,-4,-1,-19,-2,-33r-1,-25r-37,0v-41,0,-39,0,-37,-7v1,-3,1,-4,0,-5v-3,-3,-8,-16,-10,-26v-7,-24,-6,-50,2,-83v1,-6,2,-10,2,-11","w":313},"\ue20d":{"d":"152,-760v21,-4,44,2,59,18v16,16,23,41,22,77v-1,19,-4,37,-11,68v-17,72,-33,118,-57,158r-6,10r13,22v31,53,41,74,49,104v8,32,7,61,-4,98v-4,11,-9,27,-21,58r-6,14r12,0v14,1,26,4,38,11v8,4,9,5,9,7v0,10,-27,50,-36,52v-1,0,-5,-1,-12,-5v-6,-3,-14,-6,-18,-7r-7,-2r-1,3v-1,5,1,25,3,35v3,9,7,20,8,20r5,-5r5,-4r31,0r32,0r0,14v1,7,1,23,1,34v0,20,0,21,-2,29v-9,28,-36,65,-57,79v-45,28,-111,-6,-131,-69v-12,-36,-6,-72,17,-110v23,-39,52,-67,80,-76r8,-3r7,-19v19,-52,22,-81,13,-119v-8,-30,-19,-52,-50,-106r-11,-18v-1,0,-4,3,-8,8v-9,11,-22,24,-47,48v-39,37,-54,55,-62,82v-10,28,-9,52,3,75v8,18,19,29,35,37r10,4v2,1,0,8,-6,17v-6,8,-16,19,-18,19v-3,0,-18,-8,-24,-12v-10,-9,-17,-17,-24,-31v-19,-40,-8,-89,34,-149v18,-25,35,-44,81,-87v10,-10,19,-19,19,-20v1,-1,-1,-6,-6,-15v-43,-77,-65,-147,-63,-200v2,-30,10,-57,26,-84v21,-33,45,-55,68,-60xm155,-724v-18,-3,-36,1,-50,12v-7,5,-8,8,-13,21v-6,19,-9,45,-7,66v5,50,25,107,60,171v7,13,9,15,10,13v2,-3,12,-26,17,-41v14,-40,31,-107,34,-138v4,-38,-4,-70,-21,-87v-8,-8,-19,-14,-30,-17xm156,-76v0,-2,-1,-2,-9,-1v-11,2,-23,7,-33,13v-5,4,-6,5,-7,10v-1,3,-3,10,-3,16v-6,50,30,104,79,118v9,3,29,3,35,1r4,-1r0,-14v-1,-8,-1,-20,-2,-27r0,-12r-30,0r-30,0r0,-4v1,-3,0,-6,-2,-10v-2,-4,-4,-11,-6,-17v-2,-9,-2,-11,-2,-28v0,-15,0,-19,2,-30v2,-7,3,-13,4,-14"},"\ue20e":{"d":"169,-196v4,-1,9,-3,10,-3v1,0,4,2,8,6v15,13,33,23,53,28v9,3,15,3,27,4r16,0r-1,3v0,5,-9,17,-23,31v-21,21,-48,42,-68,55v-15,9,-18,9,-37,9v-16,-1,-29,-3,-41,-8r-5,-2r0,69r1,68r24,13v24,12,24,13,24,15v-1,15,-65,73,-99,90v-13,6,-11,7,-39,-8v-13,-7,-25,-13,-26,-13v-1,-1,-1,-25,-1,-116r1,-115r4,-7v12,-18,52,-53,80,-71v9,-6,78,-42,92,-48","w":275},"\ue20f":{"d":"141,-159r4,-1r6,6v17,16,41,25,66,25v9,0,9,0,9,2v0,5,-6,12,-21,28v-21,20,-46,39,-61,46v-12,5,-37,3,-56,-4v-2,-1,-2,2,-2,53r0,55r20,10v17,9,20,10,20,12v0,13,-63,69,-86,75r-5,2r-21,-11r-20,-11r-1,-91r0,-92r4,-6v9,-13,39,-41,61,-55v9,-6,75,-40,83,-43","w":220},"\ue210":{"d":"173,-260v3,-1,6,-2,7,-2v0,0,3,2,6,5v15,15,34,25,56,31v8,2,12,2,25,2v18,1,18,1,14,8v-9,18,-57,61,-89,80v-16,10,-17,10,-36,10v-14,0,-18,0,-27,-2v-6,-2,-13,-4,-15,-5v-3,-1,-5,-2,-6,-2r0,47r0,47v1,0,15,-7,33,-16v18,-9,34,-16,36,-17v4,0,4,0,10,5v15,14,33,24,53,29v9,3,15,3,27,4r16,0r-1,3v-1,15,-64,72,-100,91r-7,3r-18,0v-15,0,-19,0,-28,-2v-6,-2,-13,-4,-16,-5r-5,-2r0,90r0,90r-4,6v-20,29,-90,85,-107,85r-4,0r-1,-226v0,-224,0,-227,2,-231v8,-17,53,-57,84,-77v8,-6,88,-46,95,-49","w":300},"\ue211":{"d":"141,-209r4,-1r6,6v17,16,41,25,66,25v9,0,9,0,9,2v0,5,-6,12,-21,28v-21,20,-46,39,-61,46v-12,5,-37,3,-56,-4v-2,-1,-2,1,-2,37r0,38r26,-13r29,-14v3,-1,4,-1,6,1v4,6,17,15,26,19v14,7,24,9,40,10v12,0,13,0,13,2v0,12,-54,61,-81,73v-5,3,-8,3,-16,4v-13,0,-27,-2,-41,-8v-2,0,-2,4,-2,71v0,81,1,73,-10,86v-21,24,-66,59,-79,60r-3,0r-1,-182r0,-182r4,-6v9,-13,39,-41,61,-55v9,-6,75,-40,83,-43","w":240},"\ue212":{"d":"169,-196v4,-1,9,-3,10,-3v1,0,4,2,8,6v15,13,33,23,53,28v9,3,15,3,27,4r16,0r-1,3v0,5,-9,17,-23,31v-21,21,-48,42,-68,55v-15,9,-18,9,-37,9v-16,-1,-29,-3,-41,-8r-5,-2r0,69r1,68r24,13v24,12,24,13,24,15v-1,15,-65,73,-99,90v-13,6,-11,7,-39,-8v-13,-7,-25,-13,-26,-13v-1,-1,-1,-25,-1,-116r1,-115r4,-7v12,-18,52,-53,80,-71v9,-6,78,-42,92,-48xm173,240v3,-1,6,-2,7,-2v0,0,3,2,6,5v15,15,34,25,56,31v8,2,12,2,25,2v14,0,16,1,16,2v1,5,-10,19,-28,37v-26,24,-54,46,-74,55r-8,4r-17,0v-14,0,-18,0,-27,-2v-6,-2,-13,-4,-15,-5v-3,-1,-5,-2,-6,-2r0,47r0,47v1,0,15,-7,33,-16v18,-9,34,-16,36,-17v4,0,4,0,10,5v15,14,33,24,53,29v9,3,15,3,27,4r16,0r-1,3v-1,16,-67,75,-101,91r-7,3r-17,0v-15,0,-19,0,-28,-2v-6,-2,-13,-4,-16,-5r-5,-2r0,90r0,90r-4,6v-20,29,-90,85,-107,85r-4,0r-1,-226v0,-224,0,-227,2,-231v9,-17,52,-57,84,-78v9,-5,87,-45,95,-48","w":300},"\ue213":{"d":"141,-159r4,-1r6,6v17,16,41,25,66,25v9,0,9,0,9,2v0,5,-6,12,-21,28v-21,20,-46,39,-61,46v-12,5,-37,3,-56,-4v-2,-1,-2,2,-2,53r0,55r20,10v17,9,20,10,20,12v0,13,-63,69,-86,75r-5,2r-21,-11r-20,-11r-1,-91r0,-92r4,-6v9,-13,39,-41,61,-55v9,-6,75,-40,83,-43xm141,291r4,-1r6,6v17,16,42,25,66,25v8,0,9,0,9,2v0,4,-5,12,-21,27v-21,21,-45,40,-61,47v-12,5,-37,3,-56,-4v-2,-1,-2,1,-2,37r0,38r26,-13r29,-14v3,-1,4,-1,6,1v4,6,17,15,26,19v14,7,24,9,40,10v12,0,13,0,13,2v0,5,-6,13,-21,28v-21,20,-46,39,-61,46v-12,5,-36,4,-56,-5v-2,0,-2,4,-2,71v0,81,1,73,-10,86v-21,24,-66,59,-79,60r-3,0r-1,-181v0,-177,0,-182,2,-186v6,-12,37,-41,62,-58v9,-6,76,-40,84,-43","w":240},"\ue214":{"d":"328,-240v13,-10,13,-2,1,20v-3,7,-9,17,-13,23v-9,14,-195,280,-207,296v-15,19,-32,37,-37,37v-1,0,-15,-27,-37,-72r-35,-72r1,-4v3,-11,21,-43,39,-69v23,-31,46,-57,51,-55v1,1,16,30,33,65r32,65r2,-2v1,-1,31,-44,66,-94v36,-50,67,-95,70,-100v14,-17,27,-32,34,-38","w":163},"\ue215":{"d":"417,-366v6,-4,9,-4,8,0v-1,9,-12,30,-28,55v-16,24,-278,397,-288,410v-15,19,-32,37,-37,37v-1,0,-15,-27,-37,-72r-35,-72r1,-4v3,-11,21,-43,39,-69v23,-31,46,-57,51,-55v1,1,16,30,33,65v18,36,33,65,33,64r111,-157v60,-87,114,-162,118,-169v11,-13,26,-29,31,-33","w":163},"\ue216":{"d":"376,-305v3,-2,5,-2,6,-1v1,3,-8,24,-22,46v-9,15,-239,343,-251,359v-15,19,-32,37,-37,37v-1,0,-15,-27,-37,-72r-35,-72r1,-4v3,-11,21,-43,39,-69v23,-31,46,-57,51,-55v1,1,16,30,33,65r32,65r2,-2v1,-1,41,-57,87,-124v47,-67,89,-126,93,-132v14,-18,31,-36,38,-41","w":163},"\ue217":{"d":"35,-64v22,-45,36,-72,37,-72v5,0,21,17,37,37v15,19,200,285,210,300v12,20,19,36,19,41v0,6,-10,-1,-23,-15v-14,-16,-24,-30,-91,-125v-35,-50,-65,-93,-66,-94r-2,-2r-32,65v-17,35,-32,64,-33,65v-5,2,-28,-24,-51,-55v-18,-26,-36,-58,-39,-69r-1,-5","w":163},"\ue218":{"d":"35,-64v22,-45,36,-72,37,-72v5,0,21,17,37,37v12,15,282,401,294,419v11,18,21,38,22,44v0,4,0,5,-2,5v-5,0,-20,-15,-36,-36r-119,-169v-60,-85,-110,-156,-110,-157v-1,-1,-8,12,-34,64v-17,35,-32,64,-33,65v-5,2,-28,-24,-51,-55v-18,-26,-36,-58,-39,-69r-1,-5","w":163},"\ue219":{"d":"35,-64v22,-45,36,-72,37,-72v5,0,21,17,37,37v12,15,241,342,250,357v18,28,26,49,21,49v-5,0,-24,-20,-39,-40v-6,-7,-49,-68,-96,-135v-46,-67,-86,-123,-87,-124r-2,-2r-32,65v-17,35,-32,64,-33,65v-5,2,-28,-24,-51,-55v-18,-26,-36,-58,-39,-69r-1,-5","w":163},"\ue21a":{"d":"57,-254v2,-3,7,-3,10,0v1,3,1,18,1,168v-1,105,-1,171,-2,182v-1,19,-3,35,-4,38v0,2,-2,2,-28,2v-26,0,-28,0,-28,-2v-4,-10,-5,-54,-5,-134v0,-70,1,-105,3,-125v2,-12,-1,-11,27,-11r24,0r0,-58v0,-50,0,-58,2,-60","w":68},"\ue21b":{"d":"57,-379v2,-3,7,-3,10,0v1,3,1,24,1,230v-1,155,-1,233,-2,245v-1,19,-3,35,-4,38v0,2,-2,2,-28,2v-26,0,-28,0,-28,-2v-4,-10,-5,-54,-5,-134v0,-70,1,-105,3,-125v2,-12,-1,-11,27,-11r24,0r0,-120v0,-108,0,-121,2,-123","w":68},"\ue21c":{"d":"57,-317v3,-3,6,-3,9,0r2,3r0,196v-1,130,-1,203,-2,214v-1,19,-3,35,-4,38v0,2,-2,2,-28,2v-26,0,-28,0,-28,-2v-4,-10,-5,-54,-5,-134v0,-70,1,-105,3,-125v2,-12,-1,-11,27,-11r24,0r0,-89r0,-89","w":68},"\ue21d":{"d":"6,-134v0,-2,2,-2,28,-2v26,0,28,0,28,2v1,3,3,19,4,37v1,12,1,78,2,183v0,184,1,170,-6,170v-7,0,-7,2,-7,-62r0,-58r-24,0v-28,0,-25,1,-27,-11v-2,-20,-3,-55,-3,-125v0,-80,1,-124,5,-134","w":68},"\ue21e":{"d":"6,-134v0,-2,2,-2,28,-2v26,0,28,0,28,2v1,3,3,19,4,37v1,13,1,91,2,246v0,253,1,232,-6,232v-7,0,-7,9,-7,-125r0,-120r-24,0v-28,0,-25,1,-27,-11v-2,-20,-3,-55,-3,-125v0,-80,1,-124,5,-134","w":68},"\ue21f":{"d":"6,-134v0,-2,2,-2,28,-2v26,0,28,0,28,2v1,3,3,19,4,37v1,12,1,85,2,215r0,196r-2,3v-3,3,-6,3,-9,0r-2,-2r0,-90r0,-89r-24,0v-28,0,-25,1,-27,-11v-2,-20,-3,-55,-3,-125v0,-80,1,-124,5,-134","w":68},"\ue220":{"d":"54,-248v3,-3,6,-3,9,0r2,2r0,165r0,165r-14,0v-13,-1,-14,-1,-20,-4v-3,-2,-10,-7,-16,-12v-6,-4,-11,-9,-13,-10r-2,-1r0,-76r0,-76r3,2v1,1,7,5,12,10v14,11,20,14,30,15r7,0r0,-89r0,-88","w":65},"\ue221":{"d":"54,-373v3,-3,6,-3,9,0r2,2r0,228r0,227r-14,0v-13,-1,-14,-1,-20,-4v-3,-2,-10,-7,-16,-12v-6,-4,-11,-9,-13,-10r-2,-1r0,-76r0,-76r3,2v1,1,7,5,12,10v14,11,20,14,30,15r7,0r0,-151r0,-151","w":65},"\ue222":{"d":"54,-310v1,-1,2,-2,5,-2v7,0,6,-17,6,200r0,196r-14,0v-13,-1,-14,-1,-20,-4v-3,-2,-10,-7,-16,-12v-6,-4,-11,-9,-13,-10r-2,-1r0,-76r0,-76r3,2v1,1,7,5,12,10v14,11,20,14,30,15r7,0r0,-120v0,-107,0,-120,2,-122","w":65},"\ue223":{"d":"37,-83v2,0,8,-1,16,-1r12,0r0,171r0,171r-2,3v-3,3,-6,3,-9,0r-2,-2r0,-96r0,-95r-7,0v-10,1,-16,4,-30,15v-5,5,-11,9,-12,10r-3,2r0,-76r0,-76r2,-1v2,-1,7,-6,13,-10v10,-9,16,-12,22,-15","w":65},"\ue224":{"d":"37,-83v2,0,8,-1,16,-1r12,0r0,234r0,233r-2,3v-3,3,-6,3,-9,0r-2,-2r0,-158r0,-158r-7,0v-10,1,-16,4,-30,15v-5,5,-11,9,-12,10r-3,2r0,-76r0,-76r2,-1v2,-1,7,-6,13,-10v10,-9,16,-12,22,-15","w":65},"\ue225":{"d":"37,-83v2,0,8,-1,16,-1r12,0r0,202v0,225,1,207,-7,207v-6,0,-6,10,-6,-131r0,-126r-7,0v-10,1,-16,4,-30,15v-5,5,-11,9,-12,10r-3,2r0,-76r0,-76r2,-1v2,-1,7,-6,13,-10v10,-9,16,-12,22,-15","w":65},"\ue226":{"d":"489,-245v7,-6,11,-6,12,-3v1,5,-7,23,-18,40v-9,14,-181,259,-189,269v-9,12,-22,25,-28,28v-3,1,-4,1,-4,0r-59,-80v0,0,-9,11,-19,25v-21,29,-30,40,-39,48v-5,5,-11,9,-13,8v0,-1,-14,-19,-30,-41r-29,-41r-5,7v-31,42,-41,54,-48,62v-17,17,-24,17,-18,0v3,-7,11,-22,17,-32r43,-59v40,-54,52,-70,62,-74r4,-2v1,0,14,18,30,40v16,22,29,41,30,41r22,-29v20,-28,33,-43,41,-48v8,-6,5,-8,37,36v16,22,29,40,30,40r68,-97v87,-125,91,-129,103,-138","w":326},"\ue227":{"d":"579,-372v2,-2,5,-3,7,-3v1,0,1,1,1,6v0,6,-6,19,-15,33v-7,12,-268,384,-278,397v-9,12,-22,25,-28,28v-3,1,-4,1,-4,0r-59,-80v0,0,-9,11,-19,25v-21,29,-30,40,-39,48v-5,5,-11,9,-13,8v0,-1,-14,-19,-30,-41r-29,-41r-5,7v-31,42,-41,54,-48,62v-17,17,-24,17,-18,0v3,-7,11,-22,17,-32r43,-59v40,-54,52,-70,62,-74r4,-2v1,0,14,18,30,40v16,22,29,41,30,41r22,-29v20,-28,33,-43,41,-48v8,-6,5,-8,37,36v16,22,29,40,30,40r113,-160v61,-88,115,-166,120,-172v10,-14,22,-26,28,-30","w":326},"\ue228":{"d":"536,-310v5,-4,7,-4,8,-1v2,5,-6,24,-19,43v-13,19,-224,320,-231,329v-9,12,-22,25,-28,28v-3,1,-4,1,-4,0r-59,-80v0,0,-9,11,-19,25v-21,29,-30,40,-39,48v-5,5,-11,9,-13,8v0,-1,-14,-19,-30,-41r-29,-41r-5,7v-31,42,-41,54,-48,62v-17,17,-24,17,-18,0v3,-7,11,-22,17,-32r43,-59v40,-54,52,-70,62,-74r4,-2v1,0,14,18,30,40v16,22,29,41,30,41r22,-29v20,-28,33,-43,41,-48v8,-6,5,-8,37,36v16,22,29,40,30,40r92,-130v50,-72,94,-135,98,-140v8,-12,22,-26,28,-30","w":326},"\ue229":{"d":"1,-88v2,-2,1,-2,6,0v9,5,22,20,58,69r8,11r29,-41v33,-45,31,-42,38,-37v9,6,20,19,52,63r11,14v1,0,13,-18,29,-39v15,-20,28,-38,29,-40v2,-2,3,-2,5,-1v5,2,20,17,28,28v8,10,180,255,189,269v11,17,19,35,18,40v-1,3,-5,3,-12,-3v-12,-9,-14,-11,-102,-137r-69,-99r-29,40r-30,40v-2,2,-9,-3,-18,-12v-7,-7,-17,-20,-31,-39r-22,-29v-1,0,-14,18,-29,39v-15,21,-29,39,-30,40v-1,2,-2,2,-6,0v-9,-5,-22,-20,-61,-74v-44,-60,-47,-64,-54,-79v-6,-12,-9,-21,-7,-23","w":326},"\ue22a":{"d":"1,-88v2,-2,1,-2,6,0v9,5,22,20,58,69r8,11r29,-41v33,-45,31,-42,38,-37v9,6,20,19,52,63r11,14v1,0,13,-18,29,-39v15,-20,28,-38,29,-40v2,-2,3,-2,5,-1v5,2,20,17,28,28v10,13,271,385,278,397v13,19,19,37,14,39v-4,2,-19,-12,-33,-31v-4,-5,-59,-83,-121,-172r-114,-163r-29,40r-30,40v-2,2,-9,-3,-18,-12v-7,-7,-17,-20,-31,-39r-22,-29v-1,0,-14,18,-29,39v-15,21,-29,39,-30,40v-1,2,-2,2,-6,0v-9,-5,-22,-20,-61,-74v-44,-60,-47,-64,-54,-79v-6,-12,-9,-21,-7,-23","w":326},"\ue22b":{"d":"1,-88v2,-2,1,-2,6,0v9,5,22,20,58,69r8,11r29,-41v33,-45,31,-42,38,-37v9,6,20,19,52,63r11,14v1,0,13,-18,29,-39v15,-20,28,-38,29,-40v2,-2,3,-2,5,-1v5,2,20,17,28,28v9,11,221,314,232,331v8,12,16,28,18,35v1,5,0,8,-2,8v-5,0,-23,-17,-36,-35r-98,-140r-90,-129r-29,40r-30,40v-2,2,-9,-3,-18,-12v-7,-7,-17,-20,-31,-39r-22,-29v-1,0,-14,18,-29,39v-15,21,-29,39,-30,40v-1,2,-2,2,-6,0v-9,-5,-22,-20,-61,-74v-44,-60,-47,-64,-54,-79v-6,-12,-9,-21,-7,-23","w":326},"\ue22c":{"d":"-2,-249v1,-2,2,-3,3,-3v1,0,3,4,4,11v1,4,1,32,1,76r0,70r8,-6v16,-12,42,-23,59,-26v21,-4,42,4,58,21v12,14,18,29,17,46v-2,26,-13,49,-52,103v-19,25,-32,45,-41,60v-6,12,-8,15,-13,18v-7,5,-25,14,-32,16v-5,1,-5,1,-5,6v-2,13,-6,12,-9,-2v-1,-9,-1,-373,0,-382v1,-3,2,-7,2,-8xm57,-98v-4,-1,-10,-2,-13,-3v-6,0,-7,0,-14,5v-5,2,-12,5,-16,7r-8,2r0,106r0,106r6,-9v7,-12,19,-29,35,-51v24,-34,34,-49,42,-66v9,-19,12,-37,9,-50v-5,-20,-23,-40,-41,-47","w":150},"\ue22d":{"d":"-1,-227v1,-1,2,-1,3,0v3,4,3,6,4,117r0,107r5,5v5,5,7,6,41,22v5,3,14,7,19,10v18,9,34,13,43,10r5,-2r0,-16r0,-16r-5,-5v-8,-8,-14,-11,-48,-26v-11,-4,-18,-8,-26,-14v-6,-5,-7,-9,-7,-30v0,-16,1,-26,4,-27v0,0,4,2,7,4v7,5,18,10,33,16v38,16,48,25,52,52v2,13,2,84,0,96v-3,14,-6,20,-14,23v-9,3,-27,-1,-45,-10v-5,-3,-14,-7,-19,-10v-41,-19,-43,-21,-49,-33v-7,-14,-7,-14,-7,-147v1,-119,1,-121,4,-126","w":130},"\ue22e":{"d":"-3,-187v1,-2,3,-3,3,-3v2,0,5,3,7,9v1,4,2,12,2,66r1,62r41,-23r41,-22r0,-4v1,-5,4,-11,6,-12v3,-2,5,1,8,7v1,4,2,12,2,141v0,122,0,137,-2,144v-1,7,-4,12,-6,12v-2,0,-5,-3,-7,-9v-1,-4,-2,-12,-2,-66r0,-62r-42,23r-41,22r0,4v-1,5,-4,11,-6,12v-3,2,-5,-1,-8,-7v-1,-4,-2,-12,-2,-141v0,-122,0,-137,2,-144v0,-4,2,-8,3,-9xm90,-3v1,0,1,-9,1,-20r0,-19r-40,22v-22,12,-41,22,-41,23v-1,0,-1,9,-1,20r1,19r40,-22v22,-12,40,-22,40,-23","w":109},"\ue22f":{"d":"-86,-106v2,-1,3,-2,6,-2v5,0,4,-1,46,52r34,42v0,0,4,-4,7,-9r7,-8r-3,-4v-2,-3,-14,-17,-26,-33v-20,-25,-23,-29,-23,-32v0,-5,3,-8,8,-8v5,0,5,1,32,35v12,15,23,27,23,27v0,0,11,-12,23,-27v27,-34,27,-35,32,-35v5,0,8,3,8,8v0,3,-2,7,-23,32v-12,16,-24,30,-26,33r-3,4r7,8v3,5,7,9,7,9r34,-42v42,-53,41,-52,46,-52v5,0,8,3,8,8v0,3,-3,8,-39,52r-38,48r38,48v35,43,39,49,39,52v0,5,-3,8,-8,8v-5,0,-4,1,-46,-52r-34,-42v0,0,-4,4,-7,9r-7,8r3,4v2,3,14,17,26,33v20,25,23,29,23,32v0,5,-3,8,-8,8v-5,0,-5,-1,-32,-35v-12,-15,-23,-27,-23,-27v0,0,-11,12,-23,27v-27,34,-27,35,-32,35v-5,0,-8,-3,-8,-8v0,-3,2,-7,23,-32v12,-16,24,-30,26,-33r3,-4r-7,-8v-3,-5,-7,-9,-7,-9r-34,42v-42,53,-41,52,-46,52v-5,0,-8,-3,-8,-8v0,-3,3,-8,39,-52r38,-48r-38,-48v-35,-43,-39,-49,-39,-52v0,-2,1,-4,2,-6xm32,-8v-3,-5,-6,-9,-7,-9v0,0,-4,4,-7,9r-7,8r7,8v3,5,7,8,7,8v0,0,4,-3,7,-8r7,-8","w":139},"\ue230":{"d":"4,-461v5,-1,7,-1,7,1v1,0,1,87,1,191r0,190r8,-4v42,-16,85,1,103,41v5,12,6,18,6,34v0,14,0,16,-2,24v-9,30,-34,57,-61,67v-25,10,-54,7,-73,-7r-4,-3r0,-259v0,-234,0,-259,2,-262v2,-4,9,-11,13,-13xm48,-64v-12,-3,-23,-2,-33,2r-3,1r0,57r0,57r6,3v4,2,10,5,15,7v15,5,36,3,49,-5v12,-7,20,-20,24,-37v5,-25,-6,-53,-27,-70v-7,-7,-22,-14,-31,-15","w":116},"\ue231":{"d":"13,-466v2,-2,5,-3,5,-3v1,0,1,93,1,206r0,206r37,-18r37,-19r6,3v36,17,53,58,41,97v-6,17,-16,32,-35,51v-20,20,-36,30,-53,35r-7,2r-31,-19r-32,-19r0,-245r0,-245r4,-6v6,-8,20,-22,27,-26xm64,-53v-3,-2,-5,-3,-6,-3v0,0,-9,4,-20,10r-19,9r0,28r0,28r30,18v34,20,33,20,43,16v6,-2,7,-3,9,-8v14,-31,3,-71,-25,-91v-4,-3,-9,-6,-12,-7","w":147},"\ue232":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r38,44v20,24,39,47,41,49v20,26,21,66,3,118v-6,18,-119,306,-123,313v-11,21,-32,31,-30,14v1,-2,27,-70,58,-150v32,-81,59,-151,61,-157v14,-38,14,-66,2,-88v-2,-3,-21,-26,-42,-51r-38,-45r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23","w":150},"\ue233":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r11,12v13,13,20,22,28,35v31,50,36,118,15,181v-9,25,-20,49,-35,71v-8,12,-61,80,-70,89v-7,8,-17,11,-19,7v-3,-5,0,-15,6,-25v3,-3,17,-21,32,-40v30,-37,36,-46,43,-62v25,-52,26,-112,3,-163v-9,-17,-18,-30,-34,-47r-10,-11r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23","w":150},"\ue234":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r25,28v13,15,26,30,28,32v10,13,16,27,21,43v9,39,3,90,-20,145v-5,11,-85,182,-93,198v-6,11,-12,19,-19,23v-5,4,-10,4,-12,1v-3,-6,-1,-9,45,-107v46,-97,49,-104,55,-122v8,-24,11,-42,11,-65v1,-24,-3,-38,-12,-57v-6,-12,-9,-15,-36,-46r-23,-26r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23","w":150},"\ue235":{"d":"5,-520v8,-6,14,-3,13,6v-1,2,-27,70,-58,150v-32,81,-59,151,-61,157v-14,38,-14,66,-2,88v2,3,21,26,42,51r39,45r18,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-38,-44v-20,-24,-39,-47,-41,-49v-20,-26,-21,-66,-3,-118v6,-18,119,-306,123,-313v4,-8,11,-16,17,-20","w":0},"\ue236":{"d":"5,-370v11,-7,16,-2,11,12v-2,8,-6,13,-37,52v-30,37,-36,46,-43,62v-25,52,-26,112,-3,162v9,18,18,31,34,48r10,11r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-11,-12v-13,-13,-20,-22,-28,-35v-31,-50,-36,-118,-15,-182v9,-24,20,-48,35,-70v8,-12,61,-80,70,-89v1,-2,5,-5,7,-6","w":0},"\ue237":{"d":"5,-445v5,-4,10,-4,12,-1v3,6,1,9,-45,107v-46,97,-49,104,-55,121v-8,25,-11,43,-11,66v-1,24,3,38,12,56v6,13,9,16,36,47r23,26r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-25,-28v-13,-15,-26,-30,-28,-32v-10,-13,-16,-27,-21,-43v-9,-39,-3,-90,20,-145v5,-11,85,-182,93,-198v6,-11,12,-19,19,-23","w":0},"\ue238":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r38,44v20,24,39,47,41,49v20,26,21,66,3,118v-4,14,-118,305,-121,310v-2,4,-2,4,0,4v11,-5,25,-5,35,-1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-4,-3,-3,-8,4,-23v0,-1,-1,-1,-2,-1v-3,0,-4,-3,-4,-9v1,-2,27,-70,58,-150v32,-81,59,-151,61,-157v14,-38,14,-66,2,-88v-2,-3,-21,-26,-42,-51r-38,-45r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23","w":150},"\ue239":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r11,12v13,13,20,22,28,35v31,50,36,118,15,181v-9,25,-20,49,-35,71v-8,12,-61,80,-70,89v-7,8,-17,11,-19,7v-3,-5,0,-15,6,-25v3,-3,17,-21,32,-40v30,-37,36,-46,43,-62v25,-52,26,-112,3,-163v-9,-17,-18,-30,-34,-47r-10,-11r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm20,374v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13","w":150},"\ue23a":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r25,28v13,15,26,30,28,32v10,13,16,27,21,43v9,39,3,90,-20,145v-5,11,-85,182,-93,198v-6,11,-12,19,-19,23v-5,4,-10,4,-12,1v-3,-6,-1,-9,45,-107v46,-97,49,-104,55,-122v8,-24,11,-42,11,-65v1,-24,-3,-38,-12,-57v-6,-12,-9,-15,-36,-46r-23,-26r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm21,436v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4","w":150},"\ue23b":{"d":"7,-747v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v4,3,3,8,-4,23v0,1,1,1,2,1v3,0,4,3,4,9v-1,2,-27,70,-58,150v-32,81,-59,151,-61,157v-14,38,-14,66,-2,88v2,3,21,26,42,51r39,45r18,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-38,-44v-20,-24,-39,-47,-41,-49v-20,-26,-21,-66,-3,-118v4,-14,118,-305,121,-310v2,-4,2,-4,0,-4v-11,5,-25,5,-35,1v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9","w":0},"\ue23c":{"d":"7,-622v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm5,-370v11,-7,16,-2,11,12v-2,8,-6,13,-37,52v-30,37,-36,46,-43,62v-25,52,-26,112,-3,162v9,18,18,31,34,48r10,11r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-11,-12v-13,-13,-20,-22,-28,-35v-31,-50,-36,-118,-15,-182v9,-24,20,-48,35,-70v8,-12,61,-80,70,-89v1,-2,5,-5,7,-6","w":0},"\ue23d":{"d":"7,-684v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm5,-445v5,-4,10,-4,12,-1v3,6,1,9,-45,107v-46,97,-49,104,-55,121v-8,25,-11,43,-11,66v-1,24,3,38,12,56v6,13,9,16,36,47r23,26r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-25,-28v-13,-15,-26,-30,-28,-32v-10,-13,-16,-27,-21,-43v-9,-39,-3,-90,20,-145v5,-11,85,-182,93,-198v6,-11,12,-19,19,-23","w":0},"\ue23e":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r38,44v20,24,39,47,41,49v20,26,21,66,3,118v-4,14,-118,305,-121,310v-2,4,-2,4,0,4v11,-5,25,-5,35,-1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-4,-3,-3,-8,4,-23v0,-1,-1,-1,-2,-1v-3,0,-4,-3,-4,-9v1,-2,27,-70,58,-150v32,-81,59,-151,61,-157v14,-38,14,-66,2,-88v-2,-3,-21,-26,-42,-51r-38,-45r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm20,749v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13","w":150},"\ue23f":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r11,12v13,13,20,22,28,35v31,50,36,118,15,181v-9,25,-20,49,-35,71v-8,12,-61,80,-70,89v-7,8,-17,11,-19,7v-3,-5,0,-15,6,-25v3,-3,17,-21,32,-40v30,-37,36,-46,43,-62v25,-52,26,-112,3,-163v-9,-17,-18,-30,-34,-47r-10,-11r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm20,374v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13xm20,624v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13","w":150},"\ue240":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r25,28v13,15,26,30,28,32v10,13,16,27,21,43v9,39,3,90,-20,145v-5,11,-85,182,-93,198v-6,11,-12,19,-19,23v-5,4,-10,4,-12,1v-3,-6,-1,-9,45,-107v46,-97,49,-104,55,-122v8,-24,11,-42,11,-65v1,-24,-3,-38,-12,-57v-6,-12,-9,-15,-36,-46r-23,-26r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm21,436v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4xm21,686v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4","w":150},"\ue241":{"d":"7,-997v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-747v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v4,3,3,8,-4,23v0,1,1,1,2,1v3,0,4,3,4,9v-1,2,-27,70,-58,150v-32,81,-59,151,-61,157v-14,38,-14,66,-2,88v2,3,21,26,42,51r39,45r18,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-38,-44v-20,-24,-39,-47,-41,-49v-20,-26,-21,-66,-3,-118v4,-14,118,-305,121,-310v2,-4,2,-4,0,-4v-11,5,-25,5,-35,1v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9","w":0},"\ue242":{"d":"7,-872v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-622v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm5,-370v11,-7,16,-2,11,12v-2,8,-6,13,-37,52v-30,37,-36,46,-43,62v-25,52,-26,112,-3,162v9,18,18,31,34,48r10,11r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-11,-12v-13,-13,-20,-22,-28,-35v-31,-50,-36,-118,-15,-182v9,-24,20,-48,35,-70v8,-12,61,-80,70,-89v1,-2,5,-5,7,-6","w":0},"\ue243":{"d":"7,-934v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm7,-684v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm5,-445v5,-4,10,-4,12,-1v3,6,1,9,-45,107v-46,97,-49,104,-55,121v-8,25,-11,43,-11,66v-1,24,3,38,12,56v6,13,9,16,36,47r23,26r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-25,-28v-13,-15,-26,-30,-28,-32v-10,-13,-16,-27,-21,-43v-9,-39,-3,-90,20,-145v5,-11,85,-182,93,-198v6,-11,12,-19,19,-23","w":0},"\ue244":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r38,44v20,24,39,47,41,49v20,26,21,66,3,118v-4,14,-118,305,-121,310v-2,4,-2,4,0,4v11,-5,25,-5,35,-1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-4,-3,-3,-8,4,-23v0,-1,-1,-1,-2,-1v-3,0,-4,-3,-4,-9v1,-2,27,-70,58,-150v32,-81,59,-151,61,-157v14,-38,14,-66,2,-88v-2,-3,-21,-26,-42,-51r-38,-45r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm20,749v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13xm20,999v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13","w":150},"\ue245":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r11,12v13,13,20,22,28,35v31,50,36,118,15,181v-9,25,-20,49,-35,71v-8,12,-61,80,-70,89v-7,8,-17,11,-19,7v-3,-5,0,-15,6,-25v3,-3,17,-21,32,-40v30,-37,36,-46,43,-62v25,-52,26,-112,3,-163v-9,-17,-18,-30,-34,-47r-10,-11r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm20,374v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13xm20,624v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13xm20,874v8,-3,22,-2,29,1v23,9,45,35,56,68v7,19,8,27,8,46v0,16,0,18,-2,27v-6,22,-21,46,-38,62v-13,13,-26,20,-62,36v-22,9,-26,10,-28,7v-4,-6,4,-26,16,-37v6,-5,10,-7,32,-16v21,-9,35,-16,40,-21v5,-5,6,-10,6,-25v0,-14,-1,-22,-7,-39v-12,-36,-37,-61,-63,-64v-5,0,-7,0,-14,2v-7,3,-7,3,-9,1v-5,-4,2,-23,12,-35v6,-6,14,-11,24,-13","w":150},"\ue246":{"d":"7,-22v4,-2,6,-2,25,-2r21,0r25,28v13,15,26,30,28,32v10,13,16,27,21,43v9,39,3,90,-20,145v-5,11,-85,182,-93,198v-6,11,-12,19,-19,23v-5,4,-10,4,-12,1v-3,-6,-1,-9,45,-107v46,-97,49,-104,55,-122v8,-24,11,-42,11,-65v1,-24,-3,-38,-12,-57v-6,-12,-9,-15,-36,-46r-23,-26r-19,0v-17,0,-19,0,-20,-2v-3,-3,-2,-11,2,-20v5,-8,15,-20,21,-23xm21,436v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4xm21,686v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4xm21,936v8,-3,21,-2,28,1v24,9,47,38,58,72v5,17,7,25,6,43v0,15,0,18,-2,26v-8,29,-29,58,-54,75v-12,8,-18,11,-44,22v-27,12,-26,12,-29,10v-8,-8,8,-36,24,-45v3,-1,15,-6,26,-11v30,-13,40,-19,42,-28v1,-2,1,-8,2,-13v2,-30,-17,-75,-40,-93v-16,-13,-33,-17,-45,-11v-8,4,-12,2,-11,-7v2,-12,15,-30,27,-37v3,-2,8,-4,12,-4","w":150},"\ue247":{"d":"7,-1247v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-997v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-747v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v4,3,3,8,-4,23v0,1,1,1,2,1v3,0,4,3,4,9v-1,2,-27,70,-58,150v-32,81,-59,151,-61,157v-14,38,-14,66,-2,88v2,3,21,26,42,51r39,45r18,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-38,-44v-20,-24,-39,-47,-41,-49v-20,-26,-21,-66,-3,-118v4,-14,118,-305,121,-310v2,-4,2,-4,0,-4v-11,5,-25,5,-35,1v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9","w":0},"\ue248":{"d":"7,-1122v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-872v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm7,-622v4,-2,9,-1,10,1v5,10,-10,35,-26,43v-2,2,-12,6,-22,10v-21,9,-35,16,-40,21v-5,5,-6,10,-6,25v0,14,1,22,7,38v12,37,37,62,63,65v5,0,7,0,14,-2v7,-3,7,-3,9,-1v5,4,-2,23,-12,35v-13,13,-36,19,-53,12v-23,-9,-45,-35,-56,-68v-7,-19,-8,-27,-8,-46v0,-16,0,-18,2,-28v6,-21,21,-45,38,-61v13,-13,28,-21,61,-35v9,-4,18,-8,19,-9xm5,-370v11,-7,16,-2,11,12v-2,8,-6,13,-37,52v-30,37,-36,46,-43,62v-25,52,-26,112,-3,162v9,18,18,31,34,48r10,11r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-11,-12v-13,-13,-20,-22,-28,-35v-31,-50,-36,-118,-15,-182v9,-24,20,-48,35,-70v8,-12,61,-80,70,-89v1,-2,5,-5,7,-6","w":0},"\ue249":{"d":"7,-1184v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm7,-934v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm7,-684v5,-2,7,-2,9,0v6,5,-2,24,-14,36v-6,6,-9,7,-36,19v-30,13,-40,19,-42,28v-1,2,-1,8,-2,12v-2,31,17,76,41,94v15,13,32,17,44,11v8,-4,12,-2,11,7v-2,12,-15,30,-27,37v-12,6,-28,8,-40,3v-24,-9,-47,-38,-58,-72v-5,-17,-7,-25,-6,-43v0,-15,0,-18,2,-27v6,-21,21,-45,38,-61v14,-13,27,-21,58,-34v10,-4,20,-9,22,-10xm5,-445v5,-4,10,-4,12,-1v3,6,1,9,-45,107v-46,97,-49,104,-55,121v-8,25,-11,43,-11,66v-1,24,3,38,12,56v6,13,9,16,36,47r23,26r19,0v17,0,19,0,20,2v5,5,-1,22,-13,34v-10,11,-11,11,-35,11r-21,0r-25,-28v-13,-15,-26,-30,-28,-32v-10,-13,-16,-27,-21,-43v-9,-39,-3,-90,20,-145v5,-11,85,-182,93,-198v6,-11,12,-19,19,-23","w":0},"\ue24a":{"d":"185,-192v13,-1,47,0,60,1v39,6,75,21,103,43v8,7,15,14,15,16v0,2,-9,5,-21,4v-16,0,-18,-1,-28,-10v-13,-11,-25,-19,-40,-27v-25,-12,-42,-16,-64,-16v-22,-1,-41,4,-62,14v-69,33,-112,111,-104,185v9,80,67,146,142,162v12,2,35,2,47,0v30,-7,58,-22,81,-42v10,-9,12,-10,28,-10v12,-1,21,2,21,4v0,2,-7,9,-15,16v-29,23,-65,37,-107,43v-14,2,-53,2,-67,0v-39,-6,-72,-20,-100,-41v-71,-54,-94,-152,-55,-232v30,-63,90,-103,166,-110","w":418},"\ue24b":{"d":"147,-312r2,-2r38,0r38,0r2,2v4,3,5,13,5,21v-1,8,-4,16,-11,32v-11,23,-11,25,-12,47r0,19r13,0v42,3,80,14,111,35v14,9,30,23,30,26v0,2,-9,5,-21,4v-16,0,-18,-1,-28,-10v-13,-11,-25,-19,-40,-27v-21,-10,-39,-15,-56,-17r-9,0r0,182r0,182r9,0v17,-2,35,-7,56,-17v15,-8,27,-16,40,-27v10,-9,12,-10,28,-10v12,-1,21,2,21,4v0,2,-7,9,-15,16v-34,26,-77,42,-126,45r-13,0r0,58r0,58r-2,3v-3,2,-3,2,-19,2v-17,0,-17,0,-20,-2r-2,-3r0,-60r0,-60r-11,-2v-44,-9,-84,-33,-111,-66v-59,-70,-59,-172,0,-243v27,-32,67,-56,111,-65r11,-3r0,-20v-1,-24,-1,-24,-11,-46v-8,-17,-12,-27,-13,-35v0,-8,1,-18,5,-21xm166,0v0,-96,0,-174,-1,-174v-1,0,-16,7,-22,9v-60,31,-100,97,-100,165v0,65,37,128,93,160v7,5,22,12,28,13r2,1r0,-174","w":418},"\ue24c":{"d":"185,-192v14,-1,47,0,60,2v70,10,125,48,154,108v39,80,16,178,-55,232v-28,21,-61,35,-99,41v-17,2,-55,2,-71,0v-39,-6,-72,-20,-100,-41v-71,-54,-94,-152,-55,-232v30,-63,90,-103,166,-110xm233,-179v-16,-4,-38,-3,-56,1v-77,21,-134,96,-134,178v0,78,51,150,124,174v17,6,26,7,42,7v21,0,40,-4,62,-14v68,-33,111,-111,103,-186v-9,-79,-66,-144,-141,-160","w":418},"\ue24d":{"d":"185,-192v13,-1,47,0,60,1v39,6,75,21,103,43v8,7,15,14,15,16v0,2,-9,5,-21,4v-16,0,-18,-1,-28,-10v-13,-11,-25,-19,-40,-27v-25,-12,-42,-16,-64,-16v-22,-1,-41,4,-62,14v-69,33,-112,111,-104,185v9,80,67,146,142,162v12,2,35,2,47,0v30,-7,58,-22,81,-42v10,-9,12,-10,28,-10v12,-1,21,2,21,4v0,2,-7,9,-15,16v-29,23,-65,37,-107,43v-14,2,-53,2,-67,0v-39,-6,-72,-20,-100,-41v-71,-54,-94,-152,-55,-232v30,-63,90,-103,166,-110xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue24e":{"d":"185,-192v14,-1,47,0,60,2v70,10,125,48,154,108v39,80,16,178,-55,232v-28,21,-61,35,-99,41v-17,2,-55,2,-71,0v-39,-6,-72,-20,-100,-41v-71,-54,-94,-152,-55,-232v30,-63,90,-103,166,-110xm233,-179v-16,-4,-38,-3,-56,1v-77,21,-134,96,-134,178v0,78,51,150,124,174v17,6,26,7,42,7v21,0,40,-4,62,-14v68,-33,111,-111,103,-186v-9,-79,-66,-144,-141,-160xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue24f":{"d":"169,-312r2,-2r38,0r38,0r2,2v4,3,5,13,5,21v-1,8,-4,16,-12,33v-10,23,-10,23,-11,46r0,20r13,1v70,10,126,49,155,109v25,51,25,113,0,164v-29,60,-85,99,-155,109r-13,1r0,59r0,59r-3,2v-2,2,-4,2,-19,2v-17,0,-17,0,-20,-2r-2,-3r0,-58r0,-59r-13,-1v-70,-10,-126,-49,-155,-109v-25,-51,-25,-113,0,-164v29,-60,85,-99,155,-109r13,-1r0,-17v0,-21,-1,-26,-12,-49v-8,-17,-11,-28,-11,-36v0,-7,2,-15,5,-18xm187,0v0,-99,0,-180,-1,-180v-3,0,-19,5,-27,8v-68,27,-116,97,-116,172v0,69,41,135,102,165v8,5,23,10,32,12v4,1,7,2,8,3v2,0,2,-9,2,-180xm241,-177v-4,-1,-7,-2,-8,-3v-2,0,-2,9,-2,180v0,171,0,180,2,180v1,-1,4,-2,8,-3v48,-12,92,-48,115,-97v25,-51,25,-109,0,-160v-23,-49,-67,-85,-115,-97","w":418},"\ue250":{"d":"169,-312r2,-2r38,0r38,0r2,2v4,3,5,13,5,21v-1,8,-4,16,-12,33v-10,23,-10,23,-11,46r0,20r10,1v39,5,78,20,107,43v8,7,15,14,15,16v0,2,-9,5,-21,4v-16,0,-18,-1,-28,-10v-23,-20,-49,-34,-80,-41r-3,-1r0,71r0,72r4,3v10,6,17,21,17,34v0,13,-7,28,-17,34r-4,3r0,72r0,71r3,-1v31,-7,57,-21,80,-41v10,-9,12,-10,28,-10v12,-1,21,2,21,4v0,2,-7,9,-15,16v-29,22,-68,38,-107,43r-10,1r0,59r0,59r-3,2v-2,2,-4,2,-19,2v-17,0,-17,0,-20,-2r-2,-3r0,-58r0,-59r-13,-1v-70,-10,-126,-49,-155,-109v-25,-51,-25,-113,0,-164v29,-60,85,-99,155,-109r13,-1r0,-17v0,-21,-1,-26,-12,-49v-8,-17,-11,-28,-11,-36v0,-7,2,-15,5,-18xm187,-109v0,-39,0,-71,-1,-71v-3,0,-19,5,-27,8v-68,27,-116,97,-116,172v0,69,41,135,102,165v8,5,23,10,32,12v4,1,7,2,8,3v2,0,2,-4,2,-71r0,-71r-7,-6v-5,-5,-8,-9,-10,-14v-3,-7,-3,-8,-3,-18v0,-10,0,-11,3,-18v2,-5,5,-9,10,-14r7,-6r0,-71","w":418},"\ue251":{"d":"169,-312r2,-2r38,0r38,0r2,2v4,3,5,13,5,21v-1,8,-4,16,-12,33v-10,23,-10,23,-11,46r0,20r13,1v70,10,126,49,155,109v25,51,25,113,0,164v-29,60,-85,99,-155,109r-13,1r0,59r0,59r-3,2v-2,2,-4,2,-19,2v-17,0,-17,0,-20,-2r-2,-3r0,-58r0,-59r-13,-1v-70,-10,-126,-49,-155,-109v-25,-51,-25,-113,0,-164v29,-60,85,-99,155,-109r13,-1r0,-17v0,-21,-1,-26,-12,-49v-8,-17,-11,-28,-11,-36v0,-7,2,-15,5,-18xm187,-109v0,-39,0,-71,-1,-71v-3,0,-19,5,-27,8v-68,27,-116,97,-116,172v0,69,41,135,102,165v8,5,23,10,32,12v4,1,7,2,8,3v2,0,2,-4,2,-71r0,-71r-7,-6v-5,-5,-8,-9,-10,-14v-3,-7,-3,-8,-3,-18v0,-10,0,-11,3,-18v2,-5,5,-9,10,-14r7,-6r0,-71xm241,-177v-4,-1,-7,-2,-8,-3v-2,0,-2,4,-2,71r0,72r4,3v10,6,17,21,17,34v0,13,-7,28,-17,34r-4,3r0,72v0,67,0,71,2,71v1,-1,4,-2,8,-3v48,-12,92,-48,115,-97v25,-51,25,-109,0,-160v-23,-49,-67,-85,-115,-97","w":418},"\ue252":{"d":"191,-192v8,-1,43,0,54,2v70,10,125,48,154,108v39,80,16,178,-55,232v-28,21,-61,35,-99,41v-15,2,-54,2,-67,0v-43,-6,-79,-20,-107,-43v-9,-7,-16,-14,-16,-16v0,-2,9,-5,21,-4v16,0,18,1,28,10v13,11,25,19,41,27v24,12,41,16,64,16v21,1,40,-4,62,-14v68,-33,111,-111,103,-186v-9,-79,-67,-145,-142,-161v-12,-2,-35,-2,-46,0v-31,7,-59,22,-82,42v-10,9,-12,10,-28,10v-12,1,-21,-2,-21,-4v0,-1,2,-4,5,-6v24,-23,60,-42,97,-50v9,-2,19,-3,34,-4","w":418},"\ue253":{"d":"191,-192v8,-1,43,0,54,2v70,10,125,48,154,108v39,80,16,178,-55,232v-28,21,-61,35,-99,41v-15,2,-54,2,-67,0v-43,-6,-79,-20,-107,-43v-9,-7,-16,-14,-16,-16v0,-2,9,-5,21,-4v16,0,18,1,28,10v13,11,25,19,41,27v24,12,41,16,64,16v21,1,40,-4,62,-14v68,-33,111,-111,103,-186v-9,-79,-67,-145,-142,-161v-12,-2,-35,-2,-46,0v-31,7,-59,22,-82,42v-10,9,-12,10,-28,10v-12,1,-21,-2,-21,-4v0,-1,2,-4,5,-6v24,-23,60,-42,97,-50v9,-2,19,-3,34,-4xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue254":{"d":"147,-312r2,-2r38,0r38,0r2,2v4,3,5,13,5,21v-1,8,-4,16,-11,32v-11,23,-11,25,-12,47r0,19r13,0v47,3,88,17,122,43v85,65,99,189,30,271v-36,43,-89,68,-152,72r-13,0r0,58r0,58r-2,3v-3,2,-3,2,-19,2v-17,0,-17,0,-20,-2r-2,-3r0,-60r0,-60r-9,-1v-22,-5,-52,-17,-71,-30v-15,-9,-31,-23,-31,-26v0,-2,9,-5,21,-4v16,0,18,1,28,10v10,8,21,17,32,23v7,4,23,11,28,12r2,1r0,-174r0,-174r-2,1v-5,1,-21,8,-28,12v-11,6,-22,15,-32,23v-10,9,-12,10,-28,10v-12,1,-21,-2,-21,-4v0,-3,16,-17,31,-26v19,-13,49,-25,71,-30r9,-1r0,-21v-1,-24,-1,-24,-11,-46v-8,-17,-12,-27,-13,-35v0,-8,1,-18,5,-21xm233,-179v-5,-1,-12,-2,-16,-3r-8,0r0,182r0,182r8,0v16,-2,36,-7,54,-15v62,-30,104,-97,104,-167v0,-85,-62,-163,-142,-179","w":418},"\ue255":{"d":"186,-208v11,-1,45,0,56,1v38,7,72,23,101,47v16,13,20,18,20,28v0,17,-22,27,-35,16v-1,-1,-6,-6,-11,-9v-15,-14,-36,-26,-55,-32v-15,-5,-23,-7,-40,-8v-55,-5,-111,20,-146,66v-24,32,-36,74,-32,112v5,59,39,109,92,136v59,29,132,20,181,-24v5,-3,10,-8,11,-9v13,-11,35,-1,35,16v0,10,-4,15,-20,28v-29,25,-63,40,-102,47v-15,2,-50,2,-64,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v5,-28,14,-52,27,-74v34,-57,90,-94,157,-102","w":418},"\ue256":{"d":"169,-207r3,-2r30,0v33,1,40,2,61,7v28,7,57,22,80,42v16,13,20,18,20,28v0,11,-10,21,-21,21v-6,0,-11,-2,-19,-9v-30,-28,-64,-43,-105,-46r-9,0r0,166r0,166r9,0v41,-3,75,-18,105,-46v8,-7,13,-9,19,-9v11,0,21,10,21,21v0,10,-4,15,-20,28v-23,20,-52,35,-80,42v-21,5,-28,6,-61,7r-30,0r-3,-2v-2,-1,-8,-4,-14,-5v-44,-11,-85,-39,-113,-76v-21,-28,-33,-58,-40,-94v-2,-14,-2,-50,0,-64v7,-36,19,-66,40,-94v28,-37,69,-65,113,-76v6,-1,12,-4,14,-5xm166,0v0,-88,0,-160,-1,-160v-3,0,-20,7,-29,11v-53,27,-87,77,-92,136v-5,51,18,106,58,139v18,15,43,29,61,33r3,1r0,-160","w":418},"\ue257":{"d":"186,-208v11,-1,45,0,56,2v47,8,86,29,118,61v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-51,2,-65,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v8,-44,26,-81,56,-113v34,-35,78,-57,128,-63xm226,-165v-44,-5,-89,9,-124,39v-40,33,-63,88,-58,139v5,59,39,109,92,136v46,22,100,22,146,0v45,-23,76,-61,88,-109v7,-27,7,-53,0,-80v-18,-68,-74,-117,-144,-125","w":418},"\ue258":{"d":"186,-208v11,-1,45,0,56,1v38,7,72,23,101,47v16,13,20,18,20,28v0,17,-22,27,-35,16v-1,-1,-6,-6,-11,-9v-15,-14,-36,-26,-55,-32v-15,-5,-23,-7,-40,-8v-55,-5,-111,20,-146,66v-24,32,-36,74,-32,112v5,59,39,109,92,136v59,29,132,20,181,-24v5,-3,10,-8,11,-9v13,-11,35,-1,35,16v0,10,-4,15,-20,28v-29,25,-63,40,-102,47v-15,2,-50,2,-64,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v5,-28,14,-52,27,-74v34,-57,90,-94,157,-102xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue259":{"d":"186,-208v11,-1,45,0,56,2v47,8,86,29,118,61v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-51,2,-65,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v8,-44,26,-81,56,-113v34,-35,78,-57,128,-63xm226,-165v-44,-5,-89,9,-124,39v-40,33,-63,88,-58,139v5,59,39,109,92,136v46,22,100,22,146,0v45,-23,76,-61,88,-109v7,-27,7,-53,0,-80v-18,-68,-74,-117,-144,-125xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue25a":{"d":"183,-208v13,-1,46,-1,59,1v47,9,86,30,118,62v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-51,2,-65,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v8,-44,26,-81,56,-113v33,-34,75,-55,125,-63xm187,0r0,-164r-3,0v-9,0,-33,8,-48,15v-53,27,-87,77,-92,136v-5,58,23,118,73,151v20,13,45,23,65,26r5,0r0,-164xm251,-160v-6,-2,-12,-3,-15,-4r-5,0r0,164r0,164r5,0v20,-3,45,-13,65,-26v50,-33,78,-93,73,-151v-6,-71,-55,-130,-123,-147","w":418},"\ue25b":{"d":"186,-208v3,0,13,-1,22,-1v49,0,97,17,135,49v16,13,20,18,20,28v0,11,-10,21,-21,21v-6,0,-11,-2,-19,-9v-22,-20,-45,-33,-72,-40v-5,-2,-11,-3,-15,-4r-5,0r0,63r0,64r4,3v10,6,17,21,17,34v0,13,-7,28,-17,34r-4,3r0,64r0,63r5,0v4,-1,10,-2,15,-4v27,-7,50,-20,72,-40v8,-7,13,-9,19,-9v11,0,21,10,21,21v0,10,-4,15,-20,28v-39,33,-89,50,-140,49v-19,-1,-29,-2,-48,-7v-52,-13,-98,-49,-126,-96v-13,-22,-22,-46,-27,-74v-2,-14,-2,-50,0,-64v5,-28,14,-52,27,-74v34,-57,90,-94,157,-102xm187,-101r0,-63r-3,0v-9,0,-33,8,-48,15v-53,27,-87,77,-92,136v-5,58,23,118,73,151v20,13,45,23,65,26r5,0r0,-63r0,-63r-7,-6v-5,-5,-8,-9,-10,-14v-3,-7,-3,-8,-3,-18v0,-10,0,-11,3,-18v2,-5,5,-9,10,-14r7,-6r0,-63","w":418},"\ue25c":{"d":"183,-208v13,-1,46,-1,59,1v47,9,86,30,118,62v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-51,2,-65,0v-48,-9,-87,-29,-119,-62v-30,-32,-48,-69,-56,-113v-2,-14,-2,-50,0,-64v8,-44,26,-81,56,-113v33,-34,75,-55,125,-63xm187,-101r0,-63r-3,0v-9,0,-33,8,-48,15v-53,27,-87,77,-92,136v-5,58,23,118,73,151v20,13,45,23,65,26r5,0r0,-63r0,-63r-7,-6v-5,-5,-8,-9,-10,-14v-3,-7,-3,-8,-3,-18v0,-10,0,-11,3,-18v2,-5,5,-9,10,-14r7,-6r0,-63xm251,-160v-6,-2,-12,-3,-15,-4r-5,0r0,63r0,64r4,3v10,6,17,21,17,34v0,13,-7,28,-17,34r-4,3r0,64r0,63r5,0v20,-3,45,-13,65,-26v50,-33,78,-93,73,-151v-6,-71,-55,-130,-123,-147","w":418},"\ue25d":{"d":"190,-208v8,-1,43,0,52,2v47,8,86,29,118,61v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-50,2,-65,0v-39,-7,-73,-22,-102,-47v-16,-13,-20,-18,-20,-28v0,-17,22,-27,35,-16v1,1,6,6,11,9v15,14,36,26,56,32v14,5,22,7,39,8v55,5,111,-20,146,-66v24,-32,36,-74,32,-112v-5,-59,-39,-109,-92,-136v-59,-29,-132,-20,-181,24v-5,3,-10,8,-11,9v-13,11,-35,1,-35,-16v0,-10,4,-15,20,-28v32,-28,69,-43,115,-48","w":418},"\ue25e":{"d":"190,-208v8,-1,43,0,52,2v47,8,86,29,118,61v30,32,48,69,56,113v2,14,2,50,0,64v-8,44,-26,81,-56,113v-32,33,-71,53,-118,62v-15,2,-50,2,-65,0v-39,-7,-73,-22,-102,-47v-16,-13,-20,-18,-20,-28v0,-17,22,-27,35,-16v1,1,6,6,11,9v15,14,36,26,56,32v14,5,22,7,39,8v55,5,111,-20,146,-66v24,-32,36,-74,32,-112v-5,-59,-39,-109,-92,-136v-59,-29,-132,-20,-181,24v-5,3,-10,8,-11,9v-13,11,-35,1,-35,-16v0,-10,4,-15,20,-28v32,-28,69,-43,115,-48xm198,-42v6,-2,17,-1,24,1v33,10,41,56,12,76v-22,16,-52,7,-64,-18v-3,-6,-3,-7,-3,-17v0,-10,0,-11,3,-18v5,-11,15,-20,28,-24","w":418},"\ue25f":{"d":"169,-207v2,-2,3,-2,33,-2v33,1,39,1,61,7v52,13,98,49,126,96v13,22,22,46,27,74v1,7,1,15,1,32v0,24,-1,33,-6,52v-18,72,-77,132,-148,150v-22,6,-28,6,-61,7v-30,0,-30,0,-33,-2v-2,-1,-8,-4,-14,-5v-29,-8,-57,-22,-80,-42v-16,-13,-20,-18,-20,-28v0,-11,10,-21,21,-21v7,0,11,2,20,10v11,10,21,17,34,24v8,5,26,13,33,15r3,0r0,-160r0,-160r-3,0v-7,2,-25,10,-33,15v-13,7,-23,14,-34,24v-9,8,-13,10,-20,10v-11,0,-21,-10,-21,-21v0,-10,4,-15,20,-28v23,-20,51,-34,80,-42v6,-2,12,-4,14,-5xm226,-165v-5,-1,-11,-1,-13,-1r-4,0r0,166r0,166r4,0v2,0,8,0,13,-1v80,-9,141,-71,148,-152v3,-32,-5,-68,-22,-97v-27,-45,-73,-75,-126,-81","w":418},"\ue260":{"d":"-4,-43v2,-1,3,-1,3,-1v2,0,7,2,9,4r3,2r0,38r0,38r-3,2v-4,5,-12,5,-16,0r-3,-2r0,-38r0,-38r3,-2v1,-2,3,-3,4,-3","w":11},"\ue261":{"d":"11,-42v6,-4,14,-2,18,4v2,4,3,9,1,12v-2,4,-39,62,-42,66v-6,8,-19,3,-19,-7v0,-2,8,-17,19,-38v15,-26,21,-35,23,-37","w":31},"\ue262":{"d":"-25,-42v3,-2,9,-2,12,0v1,1,12,19,36,57v6,9,8,13,8,16v0,7,-6,13,-13,13v-7,0,-9,-2,-30,-39v-10,-20,-19,-36,-19,-38v0,-3,3,-8,6,-9","w":31},"\ue263":{"d":"-11,-37v24,-7,46,8,50,32r0,5r-6,0r-7,0r0,-4v-1,-6,-6,-14,-12,-18v-9,-5,-19,-5,-28,0v-6,4,-11,12,-12,18r0,4r-7,0r-6,0r0,-5v3,-16,14,-29,28,-32","w":39},"\ue264":{"d":"-39,5r0,-5r6,0r7,0r0,4v2,10,12,20,22,22r8,0v10,-2,20,-12,22,-22r0,-4r7,0r6,0r0,5v-4,24,-26,39,-50,32v-14,-3,-25,-16,-28,-32","w":39},"\ue265":{"d":"-11,-37v7,-2,18,-2,25,0v2,1,7,4,10,6v16,13,20,38,7,54v-13,17,-38,21,-54,8v-17,-13,-21,-38,-8,-55v5,-6,13,-11,20,-13xm13,-22v-6,-4,-15,-5,-21,-3v-17,7,-23,27,-13,40v9,12,25,14,36,6v15,-12,14,-33,-2,-43","w":39},"\ue266":{"d":"-11,-29v6,-3,19,-2,25,1v5,3,11,9,14,14v2,4,2,6,2,14v0,11,-1,15,-8,22v-6,6,-11,8,-20,9v-10,0,-16,-2,-24,-9v-9,-10,-11,-21,-7,-33v2,-6,12,-16,18,-18","w":31},"\ue267":{"d":"85,-563v10,-1,32,-1,41,1v8,3,20,9,26,14v4,3,5,5,5,8v0,17,-43,79,-60,84v-2,1,-3,0,-7,-3v-22,-19,-55,-21,-99,-4v-21,7,-59,27,-76,39r-7,5r-1,8v-4,30,8,65,33,92v16,17,28,26,66,44v32,16,43,23,56,35v22,23,34,51,36,85v1,25,-4,48,-17,76v-14,30,-44,72,-68,95v-15,14,-36,28,-42,28v-3,0,-3,0,-3,-4v0,-17,38,-71,58,-84v5,-3,6,-4,7,-8v1,-8,1,-30,-1,-39v-5,-24,-16,-43,-33,-61v-13,-12,-24,-19,-56,-35v-16,-8,-32,-16,-36,-19v-32,-21,-53,-48,-62,-83v-2,-8,-2,-12,-2,-27v-1,-20,1,-27,7,-45v11,-33,40,-78,71,-111v23,-25,49,-44,84,-61v33,-17,56,-26,80,-30xm-134,-31v7,-3,16,-2,23,2v17,8,23,27,15,43v-2,4,-5,8,-7,10v-13,11,-31,11,-44,0v-5,-4,-10,-14,-11,-21v-1,-15,9,-30,24,-34xm116,-31v7,-3,16,-2,23,2v17,8,23,27,15,43v-2,4,-5,8,-7,10v-13,11,-31,11,-44,0v-5,-4,-10,-14,-11,-21v-1,-15,9,-30,24,-34","w":157},"\ue268":{"d":"18,-39v5,-3,9,-5,11,-5v3,0,3,0,3,4v0,17,-38,71,-58,84v-5,3,-6,4,-7,8v-1,8,-1,30,1,39v5,24,16,43,33,61v13,12,24,19,57,35v15,8,31,16,36,19v31,21,52,48,61,83v2,8,2,12,2,27v1,20,-1,27,-7,45v-11,33,-40,78,-71,111v-24,26,-50,44,-87,63v-28,14,-49,22,-71,27v-12,2,-37,2,-46,0v-9,-3,-21,-9,-27,-14v-4,-3,-5,-5,-5,-8v0,-17,43,-79,60,-84v2,-1,3,0,7,3v22,19,55,21,99,4v21,-7,59,-27,76,-39r7,-5r1,-8v4,-30,-8,-65,-33,-92v-16,-17,-28,-26,-65,-44v-33,-16,-44,-23,-57,-35v-17,-18,-28,-39,-34,-64v-2,-10,-3,-32,-1,-44v4,-33,22,-71,52,-110v21,-29,41,-48,63,-61xm-134,-31v7,-3,16,-2,23,2v17,8,23,27,15,43v-2,4,-5,8,-7,10v-13,11,-31,11,-44,0v-5,-4,-10,-14,-11,-21v-1,-15,9,-30,24,-34xm116,-31v7,-3,16,-2,23,2v17,8,23,27,15,43v-2,4,-5,8,-7,10v-13,11,-31,11,-44,0v-5,-4,-10,-14,-11,-21v-1,-15,9,-30,24,-34","w":157},"\ue269":{"d":"27,-37v11,-4,24,-2,33,5v10,7,17,20,17,31v0,13,-7,25,-17,32v-21,15,-51,5,-58,-20v-6,-20,5,-42,25,-48","w":76},"\u00a0":{"w":193}}}); diff --git a/font_generator/font_editor.html b/font_generator/font_editor.html new file mode 100644 index 0000000000000000000000000000000000000000..ebbb4385b9d93dbe5205f78bb4d0262c92bb65fb --- /dev/null +++ b/font_generator/font_editor.html @@ -0,0 +1,146 @@ + + + + + + + abcjs: Font Editor + + + + + + + + +

abcjs Font Editor

+

+

+ +
+
+

Editor Instructions

+
Use this to play with different character shapes in Raphael. After creating the shape you want, paste the output code into + abc_glyphs.js. The legal things to put in the textarea are "M", "l", or "m" followed by two numbers. The last character must be a "z". +
+
+ The "l" element MUST be lower case (that is, use relative coordinates). The path is calculated from the beginning of the entire SVG, so + absolute coordinates will end up in the upper left of the engraving. +
+
+ For instance, the following is the shape of a whole rest: +
+
M0.06 0.03
+l0.09 -0.06
+l5.46 0.00
+l5.49 0.00
+l0.09 0.06
+l0.06 0.09
+l0.00 2.19
+l0.00 2.19
+l-0.06 0.09
+l-0.09 0.06
+l-5.49 0.00
+l-5.46 0.00
+l-0.09 -0.06
+l-0.06 -0.09
+l0.00 -2.19
+l0.00 -2.19
+z
+ + diff --git a/font_generator/font_gen.html b/font_generator/font_gen.html new file mode 100644 index 0000000000000000000000000000000000000000..4b8160d02910ad2ae642d6df2c8133c6600d3115 --- /dev/null +++ b/font_generator/font_gen.html @@ -0,0 +1,25 @@ + + + + + + abcjs: Font Generator + + + + + + + +

abcjs Character Table

+

These are all the characters available to abcjs. Not all of them are used, however. The actual + characters that are in abcjs are listed in scale_font.js in the object glyphnames. + Click "Generate Font Code" to generate the code that should be put into abc_glyphs.js to make the characters available. +

+

Emmentaler Font:

+ +
+
+
+ + diff --git a/font_generator/scale_font.js b/font_generator/scale_font.js new file mode 100644 index 0000000000000000000000000000000000000000..c1e1b4e86ac0df5d2c41a0b8ff6e9d10dbd68bb1 --- /dev/null +++ b/font_generator/scale_font.js @@ -0,0 +1,262 @@ +/*global Raphael */ + +var glyphnames = { + "\ue100":"rests.whole", // whole + "\ue101":"rests.half", // half + "\ue107":"rests.quarter", // quarter + "\ue109":"rests.8th", // eigth + "\ue10a":"rests.16th", // 16th + "\ue10b":"rests.32nd", // 32nd + "\ue10c":"rests.64th", // 64th + "\ue10d":"rests.128th", // 128th + "\ue10e":"accidentals.sharp", + "\ue10f":"accidentals.halfsharp", + "\ue111":"accidentals.nat", + "\ue112":"accidentals.flat", + "\ue113":"accidentals.halfflat", + "\ue114":"accidentals.dblflat", + "\ue116":"accidentals.dblsharp", + "\ue121":"dots.dot", + "\ue124":"noteheads.dbl", + "\ue125":"noteheads.whole", + "\ue126":"noteheads.half", + "\ue127":"noteheads.quarter", + "\ue135":"noteheads.indeterminate", + "\ue152":"scripts.ufermata", + "\ue153":"scripts.dfermata", + "\ue15b":"scripts.sforzato", + "\ue15d":"scripts.staccato", + "\ue160":"scripts.tenuto", + "\ue163":"scripts.umarcato", + "\ue164":"scripts.dmarcato", + "\ue166":"scripts.stopped", + "\ue167":"scripts.upbow", + "\ue168":"scripts.downbow", + "\ue16a":"scripts.turn", + "\ue16b":"scripts.trill", + "\ue171":"scripts.segno", + "\ue172":"scripts.coda", + "\ue174":"scripts.comma", + "\ue179":"scripts.roll", + "\ue17d":"scripts.prall", + "\ue17e":"scripts.mordent", + "\ue189":"flags.u8th", + "\ue18a":"flags.u16th", + "\ue18b":"flags.u32nd", + "\ue18c":"flags.u64th", + //"\ue18d":"flags.u128th", + "\ue18d":"flags.d8th", + "\ue18e":"flags.ugrace", + "\ue18f":"flags.dgrace", + "\ue190":"flags.d16th", + "\ue191":"flags.d32nd", + "\ue192":"flags.d64th", + //"\ue193":"flags.d128th", + "\ue193":"clefs.C", + "\ue195":"clefs.F", + "\ue197":"clefs.G", + "\ue199":"clefs.perc", + "\ue19b": "tab.big", + "\ue19c": "tab.tiny", + "\ue19d": "timesig.common", + "\ue19e":"timesig.cut", + "0":"0", + "1":"1", + "2":"2", + "3":"3", + "4":"4", + "5":"5", + "6":"6", + "7":"7", + "8":"8", + "9":"9", + "f":"f", + "m":"m", + "p":"p", + "r":"r", + "s":"s", + "z":"z", + "+":"+", + ",":",", + "-":"-", + ".":".", + "\ue120": "scripts.wedge", + "\ue15a": "scripts.thumb", + "\ue165": "scripts.open", + "\ue1b6": "scripts.longphrase", + "\ue1b7": "scripts.mediumphrase", + "\ue1b8": "scripts.shortphrase", + "\ue14e": "scripts.snap", + + // The custom characters from the abcjs font. + "\ue300":"noteheads.slash.whole", + "\ue301":"noteheads.slash.half", + "\ue302":"noteheads.slash.quarter", + "\ue303":"noteheads.harmonic.whole", + "\ue304":"noteheads.harmonic.quarter" +}; + +Raphael.fn.toRelative = function(pathArray) { + "use strict"; + var R = this.raphael; + var push = "push"; + var length = "length"; + var proto = "prototype"; + var lowerCase = String[ proto].toLowerCase; + var toString = "toString"; + if (!R.is(pathArray, "array") || !R.is(pathArray && pathArray[0], "array")) { // rough assumption + pathArray = R.parsePathString(pathArray); + } + var res = [], + x = 0, + y = 0, + mx = 0, + my = 0, + start = 0; + if (pathArray[0][0] === "M") { + x = pathArray[0][1]; + y = pathArray[0][2]; + mx = x; + my = y; + start++; + res[push](["M", x, y]); + } + for (var i = start, ii = pathArray[length]; i < ii; i++) { + var r = res[i] = [], + pa = pathArray[i]; + if (pa[0] !== lowerCase.call(pa[0])) { + r[0] = lowerCase.call(pa[0]); + switch (r[0]) { + case "a": + r[1] = pa[1]; + r[2] = pa[2]; + r[3] = pa[3]; + r[4] = pa[4]; + r[5] = pa[5]; + r[6] = +(pa[6] - x).toFixed(3); + r[7] = +(pa[7] - y).toFixed(3); + break; + case "v": + r[1] = +(pa[1] - y).toFixed(3); + break; + case "m": + mx = pa[1]; + my = pa[2]; + for (var z = 1, zz = pa[length]; z < zz; z++) { + r[z] = +(pa[z] - ((z % 2) ? x : y)).toFixed(3); + } + break; + default: + for (var j = 1, jj = pa[length]; j < jj; j++) { + r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3); + } + } + } else { + r = res[i] = []; + if (pa[0] === "m") { + mx = pa[1] + x; + my = pa[2] + y; + } + for (var k = 0, kk = pa[length]; k < kk; k++) { + res[i][k] = pa[k]; + } + } + var len = res[i][length]; + switch (res[i][0]) { + case "z": + x = mx; + y = my; + break; + case "h": + x += +res[i][len - 1]; + break; + case "v": + y += +res[i][len - 1]; + break; + default: + x += +res[i][len - 2]; + y += +res[i][len - 1]; + } + } + res[toString] = R._path2string; + return res; +}; + +window.scale_font = function(font, size, raphael, output) { + "use strict"; + var scale = size / font.face["units-per-em"]; + var res = []; + for (var glyph in glyphnames) { + if (glyphnames.hasOwnProperty(glyph)) { + var symb; + try { + symb = raphael.path(font.glyphs[glyph].d).attr({fill: "#000", stroke: "none"}); + //symb.scale(scale,scale,0,0); + } catch (e) {continue;} + var path = symb.attrs.path; + if (path === null) { + continue; + } + path = raphael.toRelative(path); + // Do the scaling + for (var i = 0; i < path.length; i++) { + path[i][0] = "'" + path[i][0] + "'"; + for (var j = 1; j < path[i].length; j++) { + path[i][j] = (path[i][j] * scale).toFixed(2); + } + } +// path[0][1] = +path[0][1].toFixed(3); // round out the M part +// path[0][2] = +path[0][2].toFixed(3); + var w = Math.round(symb.getBBox().width * scale * 1000) / 1000; + var h = Math.round(symb.getBBox().height * scale * 1000) / 1000; + var gstr = "'"; + gstr += glyphnames[glyph]; + gstr += "':{d:"; + var arr = []; + for (i = 0; i < path.length; i++) { + arr.push('[' + path[i].join(',') + ']'); + } + gstr += '[' + arr.join(',') + ']'; + gstr += ",w:" + w + ",h:" + h + "}"; + res[res.length] = gstr; + } + } + var div = document.getElementById(output); + div.innerHTML = "{" + res.join(",
") + "};"; +}; + +window.doScale = function(canvas, output, fontFace) { + "use strict"; + var el = document.getElementById(canvas); + el.style = "display:block;"; + var paper = Raphael(el, 1000, 600); + var font = paper.getFont(fontFace, 500); + window.scale_font(font, 30, paper, output); + el.style = "display:none;"; +}; + +window.showFont = function(outputId, fontFace) { + "use strict"; + var paper = Raphael(document.getElementById(outputId), 1000, 10000); + var font = paper.getFont(fontFace, 500); + var scale = 30 / 1000; + var x = 0; + var y = 50; + for (var glyph in font.glyphs) { + if (font.glyphs.hasOwnProperty(glyph)) { + try { + var symb = paper.path(font.glyphs[glyph].d).attr({fill: "#000", stroke: "none"}); + symb.scale(scale, scale, x + 50, y); + paper.text(x + 20, y, glyph.charCodeAt(0).toString(16)).attr("text-anchor", "left"); + } catch (e) { + + } + x = (x + 100) % 900; + if (x === 0) y += 50; + } + } + y += 40; + paper.canvas.parentNode.style.height=""+y+"px"; + paper.setSize(1000,y); +}; + diff --git a/font_generator/test.html b/font_generator/test.html new file mode 100644 index 0000000000000000000000000000000000000000..a688e6192694569ed4f2984fcda58790744aa9d1 --- /dev/null +++ b/font_generator/test.html @@ -0,0 +1,4870 @@ + + + + + + abcjs: Font Test + + + + +

Bravura Font

+ + + Created by FontForge 20190801 at Fri Jan 29 23:57:12 2021 + By Unknown + Copyright \(c\) 2021, Steinberg Media Technologies GmbH \(http://www.steinberg.net/\), with Reserved Font Name "Bravura". This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License \(OFL\) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder\(s\) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement\(s\). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder\(s\). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1\) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2\) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3\) No Modified Version of the Font Software may use the Reserved Font Name\(s\) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4\) The name\(s\) of the Copyright Holder\(s\) or the Author\(s\) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution\(s\) of the Copyright Holder\(s\) and the Author\(s\) or with their explicit written permission. 5\) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ffdd120a7643b3d4a708b55c71ae22ea0cfbbdc6 --- /dev/null +++ b/index.js @@ -0,0 +1,82 @@ +/**! +Copyright (c) 2009-2024 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 = require('./version'); +var animation = require('./src/api/abc_animation'); +var tuneBook = require('./src/api/abc_tunebook'); +var sequence = require('./src/synth/abc_midi_sequencer'); +var strTranspose = require('./src/str/output'); + +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 = require('./src/api/abc_tunebook_svg'); +abcjs.tuneMetrics = require('./src/api/tune-metrics'); +abcjs.TimingCallbacks = require('./src/api/abc_timing_callbacks'); + +var glyphs = require('./src/write/creation/glyphs'); +abcjs.setGlyph = glyphs.setSymbol; +abcjs.strTranspose = strTranspose; + +var CreateSynth = require('./src/synth/create-synth'); +var instrumentIndexToName = require('./src/synth/instrument-index-to-name'); +var pitchToNoteName = require('./src/synth/pitch-to-note-name'); +var SynthSequence = require('./src/synth/synth-sequence'); +var CreateSynthControl = require('./src/synth/create-synth-control'); +var registerAudioContext = require('./src/synth/register-audio-context'); +var activeAudioContext = require('./src/synth/active-audio-context'); +var supportsAudio = require('./src/synth/supports-audio'); +var playEvent = require('./src/synth/play-event'); +var SynthController = require('./src/synth/synth-controller'); +var getMidiFile = require('./src/synth/get-midi-file'); +var midiRenderer = require('./src/synth/abc_midi_renderer'); + +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, + midiRenderer: midiRenderer, +}; + +abcjs['Editor'] = require('./src/edit/abc_editor'); +abcjs['EditArea'] = require('./src/edit/abc_editarea'); + +module.exports = abcjs; diff --git a/license.js b/license.js new file mode 100644 index 0000000000000000000000000000000000000000..71d445c3b6a2a998a14b0de2e7f075c330f5cf84 --- /dev/null +++ b/license.js @@ -0,0 +1,23 @@ +/**! +Copyright (c) 2009-2024 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** +!**/ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..f983fddc1a3e4a5ec5e77d8367e541ecbff647a6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12939 @@ +{ + "name": "abcjs", + "version": "6.4.3", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "abcjs", + "version": "6.4.3", + "license": "MIT", + "devDependencies": { + "@babel/core": "7.20.12", + "@babel/preset-env": "7.20.2", + "@tarp/require": "1.4.3", + "babel-loader": "9.1.2", + "chai": "4.3.7", + "concurrently": "7.6.0", + "http-server": "14.1.1", + "mocha": "10.2.0", + "opener": "1.5.2", + "vuepress": "2.0.0-beta.60", + "vuex": "4.1.0", + "webpack-bundle-analyzer": "4.7.0", + "webpack-cli": "4.10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/paulrosen" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz", + "integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz", + "integrity": "sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", + "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", + "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.13", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", + "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@mdit-vue/plugin-component": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-component/-/plugin-component-0.11.2.tgz", + "integrity": "sha512-ucFiEULCkLcCG1Tf1MfG5u5PS4BIXWIeKGHRGsXxz1ix2GbZWKFVgWEdNEckBu8s75Fv1WJLIOiAYZyri2f1nw==", + "dev": true, + "dependencies": { + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/plugin-frontmatter": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-frontmatter/-/plugin-frontmatter-0.11.1.tgz", + "integrity": "sha512-AdZJInjD1pTJXlfhuoBS5ycuIQ3ewBfY0R/XHM3TRDEaDHQJHxouUCpCyijZmpdljTU45lFetIowaKtAi7GBog==", + "dev": true, + "dependencies": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "gray-matter": "^4.0.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/plugin-headers": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-headers/-/plugin-headers-0.11.2.tgz", + "integrity": "sha512-hH2zm4m+2tWe7dya/nxbbpB95pa9RjwYxl++kyZuRrqyhNTtsi2HWojX02peQ1nQMKKIWPDHtpeAHGP7dOLKFw==", + "dev": true, + "dependencies": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/plugin-sfc": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-sfc/-/plugin-sfc-0.11.1.tgz", + "integrity": "sha512-3AjQXqExzT9FWGNOeTBqK1pbt1UA5anrZvjo7OO2PJ3lrfZd0rbjionFkmW/VW1912laHUraIP6n74mUNqPuWw==", + "dev": true, + "dependencies": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/plugin-title": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-title/-/plugin-title-0.11.2.tgz", + "integrity": "sha512-R91WCN16CePWRT2bSXaDJGXvj0MuaCz4m2GbYqUbQxd+dqf18uuGPdbhr1rwhIqCvy7GD/g7hSgOFi3DNDAIzA==", + "dev": true, + "dependencies": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/plugin-toc": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-toc/-/plugin-toc-0.11.2.tgz", + "integrity": "sha512-0OcGG4TnYIZJ6SLZtk24Nj0oP2vcLn0FyMTao/nB/2Z17/fP3whoo6dVV+0G4Oi8HZ+MMDi661lvS2b4b/glYA==", + "dev": true, + "dependencies": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/shared": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/shared/-/shared-0.11.2.tgz", + "integrity": "sha512-Z/GS/v9DURZE13Hv41meKzdnprMwenVJoM3t82OE5HIGvtE6QovsZ+mMF/rMvLgaLLMDjT3EwvrrBmemWkHYTQ==", + "dev": true, + "dependencies": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "node_modules/@mdit-vue/types": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@mdit-vue/types/-/types-0.11.0.tgz", + "integrity": "sha512-ygCGP7vFpqS02hpZwEe1uz8cfImWX06+zRs08J+tCZRKb6k+easIaIHFtY9ZSxt7j9L/gAPLDo/5RmOT6z0DPQ==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "node_modules/@tarp/require": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@tarp/require/-/require-1.4.3.tgz", + "integrity": "sha512-VF6LBtAovzil+58GlDdSXIxSqtkEd/0nd9gr1prlRxuw1x3GpBrMjmo8cY4ANMBYq7AVqj82NAMq4HixwISnYg==", + "dev": true + }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", + "integrity": "sha512-EMpxUyystd3uZVByZap1DACsMXvb82ypQnGn89e1Y0a+LYu3JJscUd/gqhRsVFDkaD2MIiWo0MT8EfXr3DGRKw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hash-sum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/hash-sum/-/hash-sum-1.0.0.tgz", + "integrity": "sha512-FdLBT93h3kcZ586Aee66HPCVJ6qvxVjBlDWNmxSGSbCZe9hTsjRKdSsl4y1T+3zfujxo9auykQMnFsfyHWD7wg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-2ln8Wjbcj/0oRi/6VnuMeWEHHuK8uapFttvcLmDIe1GKCsFBLOLBX+D+xhDa9oWOQV0IpvxwrSfKKssAqqroog==", + "dev": true, + "dependencies": { + "@types/markdown-it": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz", + "integrity": "sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", + "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", + "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz", + "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.47", + "@vue/compiler-dom": "3.2.47", + "@vue/compiler-ssr": "3.2.47", + "@vue/reactivity-transform": "3.2.47", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", + "source-map": "^0.6.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz", + "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==", + "dev": true + }, + "node_modules/@vue/reactivity": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", + "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", + "dev": true, + "dependencies": { + "@vue/shared": "3.2.47" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz", + "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.47", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", + "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", + "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", + "dev": true, + "dependencies": { + "@vue/runtime-core": "3.2.47", + "@vue/shared": "3.2.47", + "csstype": "^2.6.8" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz", + "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.2.47", + "@vue/shared": "3.2.47" + }, + "peerDependencies": { + "vue": "3.2.47" + } + }, + "node_modules/@vue/shared": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", + "dev": true + }, + "node_modules/@vuepress/bundler-vite": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/bundler-vite/-/bundler-vite-2.0.0-beta.60.tgz", + "integrity": "sha512-nf+UAKNlAEZXZqu2Ztvr8Hg/5CtevWxvQGfYKV4lhw8UmoDjKKHoHPpPhF1QTUbnZ8W+jPLzIVz+hjunzsxl/A==", + "dev": true, + "dependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "autoprefixer": "^10.4.13", + "connect-history-api-fallback": "^2.0.0", + "postcss": "^8.4.20", + "postcss-load-config": "^4.0.1", + "rollup": "^3.9.0", + "vite": "~4.0.3", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "node_modules/@vuepress/cli": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/cli/-/cli-2.0.0-beta.60.tgz", + "integrity": "sha512-ibC6ezsn1m+r3PB382ZZfmwBFlkR/9LVk5u2cUBmhBj4t+W2XPgWkKTTmG81ny7lnUJweloQc9fa1ww77se2Ug==", + "dev": true, + "dependencies": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "cac": "^6.7.14", + "chokidar": "^3.5.3", + "envinfo": "^7.8.1", + "esbuild": "^0.16.12" + }, + "bin": { + "vuepress-cli": "bin/vuepress.js" + } + }, + "node_modules/@vuepress/client": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/client/-/client-2.0.0-beta.60.tgz", + "integrity": "sha512-WU5VGeDp41A2dVXqp18YBggflIjTq68mA+s5TCz93wk+7elAmPAkWKcobQBYQgvsuwHyg9nWulZAfMN6OEygKQ==", + "dev": true, + "dependencies": { + "@vue/devtools-api": "^6.4.5", + "@vuepress/shared": "2.0.0-beta.60", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "node_modules/@vuepress/core": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-2.0.0-beta.60.tgz", + "integrity": "sha512-HkUkqBnBI7GMVZGxdzV4C/iyFwPo215sVLYvZVEWpQIaLk/47WkK0sHtz/1i00ujwJC3uGOH1+f0IHkxzqjUmg==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "node_modules/@vuepress/markdown": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-2.0.0-beta.60.tgz", + "integrity": "sha512-97AT4aZr1k1VrJZoUvzbrX6nU/TwxlFpLNi8KNtWK3TMZT6+hAU0aCg6TwuwirShvey8mr9GaMNSssAdpSK4mg==", + "dev": true, + "dependencies": { + "@mdit-vue/plugin-component": "^0.11.2", + "@mdit-vue/plugin-frontmatter": "^0.11.1", + "@mdit-vue/plugin-headers": "^0.11.2", + "@mdit-vue/plugin-sfc": "^0.11.1", + "@mdit-vue/plugin-title": "^0.11.2", + "@mdit-vue/plugin-toc": "^0.11.2", + "@mdit-vue/shared": "^0.11.2", + "@mdit-vue/types": "^0.11.0", + "@types/markdown-it": "^12.2.3", + "@types/markdown-it-emoji": "^2.0.2", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "markdown-it": "^13.0.1", + "markdown-it-anchor": "^8.6.6", + "markdown-it-emoji": "^2.0.2", + "mdurl": "^1.0.1" + } + }, + "node_modules/@vuepress/plugin-active-header-links": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-2.0.0-beta.60.tgz", + "integrity": "sha512-L+KijW7FvoDWMTd6wiIZhMA/uZYgMhiukL6IaVWtQ0COyWGIjaZUlX+mHd1munSzz4aWBMbck7no82bPswCh0g==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "ts-debounce": "^4.0.0", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "node_modules/@vuepress/plugin-back-to-top": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-beta.60.tgz", + "integrity": "sha512-vpVTA6EwWjjYyl6Op5J16RV6rEvwUYkLnjYhJ2qWroDb8U2x32HGWFJZQFIyatGO+oU6UBVYow90j2+Ery2g6g==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "ts-debounce": "^4.0.0", + "vue": "^3.2.45" + } + }, + "node_modules/@vuepress/plugin-container": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-container/-/plugin-container-2.0.0-beta.60.tgz", + "integrity": "sha512-yQBAm7sFRGMvCz8Ju2qFG0iLQs/XvWd11UAsywSdvps3A0nZuANSb68QTYJPN3JJfZ5d0LCxlhJ4rbBWT49+wQ==", + "dev": true, + "dependencies": { + "@types/markdown-it": "^12.2.3", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "markdown-it": "^13.0.1", + "markdown-it-container": "^3.0.0" + } + }, + "node_modules/@vuepress/plugin-external-link-icon": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-external-link-icon/-/plugin-external-link-icon-2.0.0-beta.60.tgz", + "integrity": "sha512-We4YmS4G7sWoOec/FKYhTM86qRCMBbDThcxOiPm6sWHrhTdxk3bFgJq/DfqJU/ply1ta72AWep0rEY6fj6JJ2A==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "node_modules/@vuepress/plugin-git": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-git/-/plugin-git-2.0.0-beta.60.tgz", + "integrity": "sha512-Yu+D8gItxD8BFueV5fQd7AxIgjcxyDY1AFCTmPsP9VDMJ0AuJuaPTLWOf5o0uKzWd5z1mDw0ZwWFh8j3FyHv+A==", + "dev": true, + "dependencies": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "execa": "^6.1.0" + } + }, + "node_modules/@vuepress/plugin-medium-zoom": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-2.0.0-beta.60.tgz", + "integrity": "sha512-KiJui/sTIHa321jJ/dc11ysyqTMj4Sz9tWoTSnwBJ4nebaO/0OFGQcFajk2+1ELs4poUh/w0THxc+NskR+bf+g==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "medium-zoom": "^1.0.8", + "vue": "^3.2.45" + } + }, + "node_modules/@vuepress/plugin-nprogress": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-2.0.0-beta.60.tgz", + "integrity": "sha512-zRdJP39qFO8q9TAwlCS4tLOd2rLGtkKqkPTsfhjtWwDqSbtTHy0GqVBL8KJUy3H0+qSiyvtC647yLNRbJ9LOlw==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "node_modules/@vuepress/plugin-palette": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-palette/-/plugin-palette-2.0.0-beta.60.tgz", + "integrity": "sha512-KPIQCLUEIsgsdxINR6mYJRhHmWCo0850QEvy9+ikdv+ds1z6wJ5xwq/xWy/pRJ6lXdgHQrtuVkroWl+IdppcRw==", + "dev": true, + "dependencies": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "chokidar": "^3.5.3" + } + }, + "node_modules/@vuepress/plugin-prismjs": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-prismjs/-/plugin-prismjs-2.0.0-beta.60.tgz", + "integrity": "sha512-yWRWAsUX6iO7uUN67yyy20x3H1clQZ519rHh2dvs6wMyXsO0E3vlNB8jrveOdr+0lfoUll58t2AsxpvzTObY0A==", + "dev": true, + "dependencies": { + "@vuepress/core": "2.0.0-beta.60", + "prismjs": "^1.29.0" + } + }, + "node_modules/@vuepress/plugin-theme-data": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-theme-data/-/plugin-theme-data-2.0.0-beta.60.tgz", + "integrity": "sha512-3b34sXEAzShvUzeEMA/0JE4VrLxoMqGJOGMl0I9m0DKg2apgjRG6nYYq6gUnJW0gcUVK+tOOOHsMT6mTMs3xdA==", + "dev": true, + "dependencies": { + "@vue/devtools-api": "^6.4.5", + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "node_modules/@vuepress/shared": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/shared/-/shared-2.0.0-beta.60.tgz", + "integrity": "sha512-bwFksEtSQpbyAGJZkvRK9Z2zGmS144nv759vOzbRUZPPlGffeauzrPw9w7wxqp3gTJvIE/4Ufqt0AZTuSP/F/g==", + "dev": true, + "dependencies": { + "@mdit-vue/types": "^0.11.0", + "@vue/shared": "^3.2.45" + } + }, + "node_modules/@vuepress/theme-default": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-2.0.0-beta.60.tgz", + "integrity": "sha512-j9ybX31HWlmITnuGFt/IxQOt8ttBDI8ebzh4uKs70Yv8z4m1pMrlPNY2Qs2ubLpJIuCQNtMY2cfQKgaUiDYAuQ==", + "dev": true, + "dependencies": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/plugin-active-header-links": "2.0.0-beta.60", + "@vuepress/plugin-back-to-top": "2.0.0-beta.60", + "@vuepress/plugin-container": "2.0.0-beta.60", + "@vuepress/plugin-external-link-icon": "2.0.0-beta.60", + "@vuepress/plugin-git": "2.0.0-beta.60", + "@vuepress/plugin-medium-zoom": "2.0.0-beta.60", + "@vuepress/plugin-nprogress": "2.0.0-beta.60", + "@vuepress/plugin-palette": "2.0.0-beta.60", + "@vuepress/plugin-prismjs": "2.0.0-beta.60", + "@vuepress/plugin-theme-data": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "@vueuse/core": "^9.9.0", + "sass": "^1.57.1", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + }, + "peerDependencies": { + "sass-loader": "^13.2.0" + }, + "peerDependenciesMeta": { + "sass-loader": { + "optional": true + } + } + }, + "node_modules/@vuepress/utils": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/utils/-/utils-2.0.0-beta.60.tgz", + "integrity": "sha512-R5m5/AtKWAnlH+Su2yxoHQNp2JdJZ7gHV5531RbFySq9FTlKHtvE5RFceeppc0/UpzPE6KggRdaRqyjc77vg4g==", + "dev": true, + "dependencies": { + "@types/debug": "^4.1.7", + "@types/fs-extra": "^9.0.13", + "@types/hash-sum": "^1.0.0", + "@vuepress/shared": "2.0.0-beta.60", + "debug": "^4.3.4", + "fs-extra": "^11.1.0", + "globby": "^13.1.3", + "hash-sum": "^2.0.0", + "ora": "^6.1.2", + "picocolors": "^1.0.0", + "upath": "^2.0.1" + } + }, + "node_modules/@vueuse/core": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.12.0.tgz", + "integrity": "sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.12.0", + "@vueuse/shared": "9.12.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.12.0.tgz", + "integrity": "sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.12.0.tgz", + "integrity": "sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==", + "dev": true, + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "peer": true + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001431", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", + "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chrome-trace-event/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "peer": true + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concurrently": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", + "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "date-fns": "^2.29.1", + "lodash": "^4.17.21", + "rxjs": "^7.0.0", + "shell-quote": "^1.7.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^17.3.1" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/concurrently/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concurrently/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/concurrently/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "dev": true + }, + "node_modules/date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "dev": true, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz", + "integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true, + "peer": true + }, + "node_modules/esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==", + "dev": true + }, + "node_modules/import-local": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", + "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.5.tgz", + "integrity": "sha512-HTjEPZtcNKZ4LnhSp02NEH4vE+5OpJ0EsOWYvGQpHgUMLngydESAAMH5Wd/asPf29+XUDQZszxpLg1BkIIA2aw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it-container": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz", + "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==", + "dev": true + }, + "node_modules/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/medium-zoom": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", + "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/ora": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz", + "integrity": "sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==", + "dev": true, + "dependencies": { + "bl": "^5.0.0", + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.14.0.tgz", + "integrity": "sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", + "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", + "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sirv": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.17.tgz", + "integrity": "sha512-qx9go5yraB7ekT7bCMqUHJ5jEaOC/GXBxUWv+jeWnb7WzHUFdcQPGWk7YmAwFBaQBrogpuSqd/azbC2lZRqqmw==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mime": "^2.3.1", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sirv/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true + }, + "node_modules/spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "dev": true, + "peer": true, + "dependencies": { + "jest-worker": "^27.0.6", + "p-limit": "^3.1.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-debounce": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz", + "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz", + "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", + "dev": true, + "dependencies": { + "esbuild": "^0.16.3", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz", + "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.2.47", + "@vue/compiler-sfc": "3.2.47", + "@vue/runtime-dom": "3.2.47", + "@vue/server-renderer": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "node_modules/vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "dev": true, + "dependencies": { + "@vue/devtools-api": "^6.4.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vuepress": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-2.0.0-beta.60.tgz", + "integrity": "sha512-evkv5PtX5pdlEyY5EcEV+rN/HTmi8iG7ZcvAnMFfYKWdvKiUjE+/DPwZfmE8emx33FEE2htbAKgtruABTocEjA==", + "dev": true, + "dependencies": { + "vuepress-vite": "2.0.0-beta.60" + }, + "bin": { + "vuepress": "bin/vuepress.js" + } + }, + "node_modules/vuepress-vite": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/vuepress-vite/-/vuepress-vite-2.0.0-beta.60.tgz", + "integrity": "sha512-ljHvo419nbfYl/cQecVbYL4bwJjUOX0+z76v/4yX6ODeGIpdHIs7ARZ4t52mr0EEfwP6aZbZa+qFZTTQutxAuQ==", + "dev": true, + "dependencies": { + "@vuepress/bundler-vite": "2.0.0-beta.60", + "@vuepress/cli": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/theme-default": "2.0.0-beta.60" + }, + "bin": { + "vuepress": "bin/vuepress.js", + "vuepress-vite": "bin/vuepress.js" + }, + "peerDependencies": { + "@vuepress/client": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "node_modules/vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "dev": true, + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webpack": { + "version": "5.76.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", + "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "dev": true, + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "dev": true + }, + "@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.2.tgz", + "integrity": "sha512-k22GoYRAHPYr9I+Gvy2ZQlAe5mGy8BqWst2wRt8cwIufWTxrsVshhIBvYNqC80N0GSFWTsqRVexOtfzlgOEDvA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", + "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.1.0" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "requires": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", + "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", + "dev": true, + "requires": { + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", + "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.18.9", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/traverse": "^7.19.1", + "@babel/types": "^7.19.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "requires": { + "@babel/types": "^7.20.2" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", + "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" + } + }, + "@babel/helpers": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz", + "integrity": "sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", + "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "dev": true + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", + "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.18.9" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz", + "integrity": "sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", + "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", + "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz", + "integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.1" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", + "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", + "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", + "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", + "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-remap-async-to-generator": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz", + "integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz", + "integrity": "sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.19.1", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", + "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz", + "integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz", + "integrity": "sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz", + "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-simple-access": "^7.19.4" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.19.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz", + "integrity": "sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.19.6", + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-validator-identifier": "^7.19.1" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", + "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.19.0", + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz", + "integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", + "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "regenerator-transform": "^0.15.0" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", + "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "dependencies": { + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz", + "integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.10" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", + "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.13", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@discoveryjs/json-ext": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", + "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", + "dev": true + }, + "@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", + "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "dev": true, + "optional": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@mdit-vue/plugin-component": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-component/-/plugin-component-0.11.2.tgz", + "integrity": "sha512-ucFiEULCkLcCG1Tf1MfG5u5PS4BIXWIeKGHRGsXxz1ix2GbZWKFVgWEdNEckBu8s75Fv1WJLIOiAYZyri2f1nw==", + "dev": true, + "requires": { + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/plugin-frontmatter": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-frontmatter/-/plugin-frontmatter-0.11.1.tgz", + "integrity": "sha512-AdZJInjD1pTJXlfhuoBS5ycuIQ3ewBfY0R/XHM3TRDEaDHQJHxouUCpCyijZmpdljTU45lFetIowaKtAi7GBog==", + "dev": true, + "requires": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "gray-matter": "^4.0.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/plugin-headers": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-headers/-/plugin-headers-0.11.2.tgz", + "integrity": "sha512-hH2zm4m+2tWe7dya/nxbbpB95pa9RjwYxl++kyZuRrqyhNTtsi2HWojX02peQ1nQMKKIWPDHtpeAHGP7dOLKFw==", + "dev": true, + "requires": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/plugin-sfc": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-sfc/-/plugin-sfc-0.11.1.tgz", + "integrity": "sha512-3AjQXqExzT9FWGNOeTBqK1pbt1UA5anrZvjo7OO2PJ3lrfZd0rbjionFkmW/VW1912laHUraIP6n74mUNqPuWw==", + "dev": true, + "requires": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/plugin-title": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-title/-/plugin-title-0.11.2.tgz", + "integrity": "sha512-R91WCN16CePWRT2bSXaDJGXvj0MuaCz4m2GbYqUbQxd+dqf18uuGPdbhr1rwhIqCvy7GD/g7hSgOFi3DNDAIzA==", + "dev": true, + "requires": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/plugin-toc": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/plugin-toc/-/plugin-toc-0.11.2.tgz", + "integrity": "sha512-0OcGG4TnYIZJ6SLZtk24Nj0oP2vcLn0FyMTao/nB/2Z17/fP3whoo6dVV+0G4Oi8HZ+MMDi661lvS2b4b/glYA==", + "dev": true, + "requires": { + "@mdit-vue/shared": "0.11.2", + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/shared": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@mdit-vue/shared/-/shared-0.11.2.tgz", + "integrity": "sha512-Z/GS/v9DURZE13Hv41meKzdnprMwenVJoM3t82OE5HIGvtE6QovsZ+mMF/rMvLgaLLMDjT3EwvrrBmemWkHYTQ==", + "dev": true, + "requires": { + "@mdit-vue/types": "0.11.0", + "@types/markdown-it": "^12.2.3", + "markdown-it": "^13.0.1" + } + }, + "@mdit-vue/types": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@mdit-vue/types/-/types-0.11.0.tgz", + "integrity": "sha512-ygCGP7vFpqS02hpZwEe1uz8cfImWX06+zRs08J+tCZRKb6k+easIaIHFtY9ZSxt7j9L/gAPLDo/5RmOT6z0DPQ==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "@tarp/require": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@tarp/require/-/require-1.4.3.tgz", + "integrity": "sha512-VF6LBtAovzil+58GlDdSXIxSqtkEd/0nd9gr1prlRxuw1x3GpBrMjmo8cY4ANMBYq7AVqj82NAMq4HixwISnYg==", + "dev": true + }, + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, + "@types/eslint": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", + "integrity": "sha512-EMpxUyystd3uZVByZap1DACsMXvb82ypQnGn89e1Y0a+LYu3JJscUd/gqhRsVFDkaD2MIiWo0MT8EfXr3DGRKw==", + "dev": true, + "peer": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "peer": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true, + "peer": true + }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/hash-sum": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/hash-sum/-/hash-sum-1.0.0.tgz", + "integrity": "sha512-FdLBT93h3kcZ586Aee66HPCVJ6qvxVjBlDWNmxSGSbCZe9hTsjRKdSsl4y1T+3zfujxo9auykQMnFsfyHWD7wg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-2ln8Wjbcj/0oRi/6VnuMeWEHHuK8uapFttvcLmDIe1GKCsFBLOLBX+D+xhDa9oWOQV0IpvxwrSfKKssAqqroog==", + "dev": true, + "requires": { + "@types/markdown-it": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "@types/node": { + "version": "16.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.3.tgz", + "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", + "dev": true + }, + "@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "dev": true + }, + "@vitejs/plugin-vue": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz", + "integrity": "sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==", + "dev": true, + "requires": {} + }, + "@vue/compiler-core": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", + "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", + "dev": true, + "requires": { + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-dom": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", + "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "@vue/compiler-sfc": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz", + "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.47", + "@vue/compiler-dom": "3.2.47", + "@vue/compiler-ssr": "3.2.47", + "@vue/reactivity-transform": "3.2.47", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7", + "postcss": "^8.1.10", + "source-map": "^0.6.1" + } + }, + "@vue/compiler-ssr": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz", + "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==", + "dev": true, + "requires": { + "@vue/compiler-dom": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==", + "dev": true + }, + "@vue/reactivity": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", + "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", + "dev": true, + "requires": { + "@vue/shared": "3.2.47" + } + }, + "@vue/reactivity-transform": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz", + "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==", + "dev": true, + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.47", + "@vue/shared": "3.2.47", + "estree-walker": "^2.0.2", + "magic-string": "^0.25.7" + } + }, + "@vue/runtime-core": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", + "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", + "dev": true, + "requires": { + "@vue/reactivity": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "@vue/runtime-dom": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", + "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", + "dev": true, + "requires": { + "@vue/runtime-core": "3.2.47", + "@vue/shared": "3.2.47", + "csstype": "^2.6.8" + } + }, + "@vue/server-renderer": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz", + "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==", + "dev": true, + "requires": { + "@vue/compiler-ssr": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "@vue/shared": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", + "dev": true + }, + "@vuepress/bundler-vite": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/bundler-vite/-/bundler-vite-2.0.0-beta.60.tgz", + "integrity": "sha512-nf+UAKNlAEZXZqu2Ztvr8Hg/5CtevWxvQGfYKV4lhw8UmoDjKKHoHPpPhF1QTUbnZ8W+jPLzIVz+hjunzsxl/A==", + "dev": true, + "requires": { + "@vitejs/plugin-vue": "^4.0.0", + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "autoprefixer": "^10.4.13", + "connect-history-api-fallback": "^2.0.0", + "postcss": "^8.4.20", + "postcss-load-config": "^4.0.1", + "rollup": "^3.9.0", + "vite": "~4.0.3", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "@vuepress/cli": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/cli/-/cli-2.0.0-beta.60.tgz", + "integrity": "sha512-ibC6ezsn1m+r3PB382ZZfmwBFlkR/9LVk5u2cUBmhBj4t+W2XPgWkKTTmG81ny7lnUJweloQc9fa1ww77se2Ug==", + "dev": true, + "requires": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "cac": "^6.7.14", + "chokidar": "^3.5.3", + "envinfo": "^7.8.1", + "esbuild": "^0.16.12" + } + }, + "@vuepress/client": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/client/-/client-2.0.0-beta.60.tgz", + "integrity": "sha512-WU5VGeDp41A2dVXqp18YBggflIjTq68mA+s5TCz93wk+7elAmPAkWKcobQBYQgvsuwHyg9nWulZAfMN6OEygKQ==", + "dev": true, + "requires": { + "@vue/devtools-api": "^6.4.5", + "@vuepress/shared": "2.0.0-beta.60", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "@vuepress/core": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-2.0.0-beta.60.tgz", + "integrity": "sha512-HkUkqBnBI7GMVZGxdzV4C/iyFwPo215sVLYvZVEWpQIaLk/47WkK0sHtz/1i00ujwJC3uGOH1+f0IHkxzqjUmg==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "@vuepress/markdown": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-2.0.0-beta.60.tgz", + "integrity": "sha512-97AT4aZr1k1VrJZoUvzbrX6nU/TwxlFpLNi8KNtWK3TMZT6+hAU0aCg6TwuwirShvey8mr9GaMNSssAdpSK4mg==", + "dev": true, + "requires": { + "@mdit-vue/plugin-component": "^0.11.2", + "@mdit-vue/plugin-frontmatter": "^0.11.1", + "@mdit-vue/plugin-headers": "^0.11.2", + "@mdit-vue/plugin-sfc": "^0.11.1", + "@mdit-vue/plugin-title": "^0.11.2", + "@mdit-vue/plugin-toc": "^0.11.2", + "@mdit-vue/shared": "^0.11.2", + "@mdit-vue/types": "^0.11.0", + "@types/markdown-it": "^12.2.3", + "@types/markdown-it-emoji": "^2.0.2", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "markdown-it": "^13.0.1", + "markdown-it-anchor": "^8.6.6", + "markdown-it-emoji": "^2.0.2", + "mdurl": "^1.0.1" + } + }, + "@vuepress/plugin-active-header-links": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-2.0.0-beta.60.tgz", + "integrity": "sha512-L+KijW7FvoDWMTd6wiIZhMA/uZYgMhiukL6IaVWtQ0COyWGIjaZUlX+mHd1munSzz4aWBMbck7no82bPswCh0g==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "ts-debounce": "^4.0.0", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "@vuepress/plugin-back-to-top": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-2.0.0-beta.60.tgz", + "integrity": "sha512-vpVTA6EwWjjYyl6Op5J16RV6rEvwUYkLnjYhJ2qWroDb8U2x32HGWFJZQFIyatGO+oU6UBVYow90j2+Ery2g6g==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "ts-debounce": "^4.0.0", + "vue": "^3.2.45" + } + }, + "@vuepress/plugin-container": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-container/-/plugin-container-2.0.0-beta.60.tgz", + "integrity": "sha512-yQBAm7sFRGMvCz8Ju2qFG0iLQs/XvWd11UAsywSdvps3A0nZuANSb68QTYJPN3JJfZ5d0LCxlhJ4rbBWT49+wQ==", + "dev": true, + "requires": { + "@types/markdown-it": "^12.2.3", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "markdown-it": "^13.0.1", + "markdown-it-container": "^3.0.0" + } + }, + "@vuepress/plugin-external-link-icon": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-external-link-icon/-/plugin-external-link-icon-2.0.0-beta.60.tgz", + "integrity": "sha512-We4YmS4G7sWoOec/FKYhTM86qRCMBbDThcxOiPm6sWHrhTdxk3bFgJq/DfqJU/ply1ta72AWep0rEY6fj6JJ2A==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/markdown": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "@vuepress/plugin-git": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-git/-/plugin-git-2.0.0-beta.60.tgz", + "integrity": "sha512-Yu+D8gItxD8BFueV5fQd7AxIgjcxyDY1AFCTmPsP9VDMJ0AuJuaPTLWOf5o0uKzWd5z1mDw0ZwWFh8j3FyHv+A==", + "dev": true, + "requires": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "execa": "^6.1.0" + } + }, + "@vuepress/plugin-medium-zoom": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-2.0.0-beta.60.tgz", + "integrity": "sha512-KiJui/sTIHa321jJ/dc11ysyqTMj4Sz9tWoTSnwBJ4nebaO/0OFGQcFajk2+1ELs4poUh/w0THxc+NskR+bf+g==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "medium-zoom": "^1.0.8", + "vue": "^3.2.45" + } + }, + "@vuepress/plugin-nprogress": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-2.0.0-beta.60.tgz", + "integrity": "sha512-zRdJP39qFO8q9TAwlCS4tLOd2rLGtkKqkPTsfhjtWwDqSbtTHy0GqVBL8KJUy3H0+qSiyvtC647yLNRbJ9LOlw==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "@vuepress/plugin-palette": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-palette/-/plugin-palette-2.0.0-beta.60.tgz", + "integrity": "sha512-KPIQCLUEIsgsdxINR6mYJRhHmWCo0850QEvy9+ikdv+ds1z6wJ5xwq/xWy/pRJ6lXdgHQrtuVkroWl+IdppcRw==", + "dev": true, + "requires": { + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "chokidar": "^3.5.3" + } + }, + "@vuepress/plugin-prismjs": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-prismjs/-/plugin-prismjs-2.0.0-beta.60.tgz", + "integrity": "sha512-yWRWAsUX6iO7uUN67yyy20x3H1clQZ519rHh2dvs6wMyXsO0E3vlNB8jrveOdr+0lfoUll58t2AsxpvzTObY0A==", + "dev": true, + "requires": { + "@vuepress/core": "2.0.0-beta.60", + "prismjs": "^1.29.0" + } + }, + "@vuepress/plugin-theme-data": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-theme-data/-/plugin-theme-data-2.0.0-beta.60.tgz", + "integrity": "sha512-3b34sXEAzShvUzeEMA/0JE4VrLxoMqGJOGMl0I9m0DKg2apgjRG6nYYq6gUnJW0gcUVK+tOOOHsMT6mTMs3xdA==", + "dev": true, + "requires": { + "@vue/devtools-api": "^6.4.5", + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "vue": "^3.2.45" + } + }, + "@vuepress/shared": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/shared/-/shared-2.0.0-beta.60.tgz", + "integrity": "sha512-bwFksEtSQpbyAGJZkvRK9Z2zGmS144nv759vOzbRUZPPlGffeauzrPw9w7wxqp3gTJvIE/4Ufqt0AZTuSP/F/g==", + "dev": true, + "requires": { + "@mdit-vue/types": "^0.11.0", + "@vue/shared": "^3.2.45" + } + }, + "@vuepress/theme-default": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-2.0.0-beta.60.tgz", + "integrity": "sha512-j9ybX31HWlmITnuGFt/IxQOt8ttBDI8ebzh4uKs70Yv8z4m1pMrlPNY2Qs2ubLpJIuCQNtMY2cfQKgaUiDYAuQ==", + "dev": true, + "requires": { + "@vuepress/client": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/plugin-active-header-links": "2.0.0-beta.60", + "@vuepress/plugin-back-to-top": "2.0.0-beta.60", + "@vuepress/plugin-container": "2.0.0-beta.60", + "@vuepress/plugin-external-link-icon": "2.0.0-beta.60", + "@vuepress/plugin-git": "2.0.0-beta.60", + "@vuepress/plugin-medium-zoom": "2.0.0-beta.60", + "@vuepress/plugin-nprogress": "2.0.0-beta.60", + "@vuepress/plugin-palette": "2.0.0-beta.60", + "@vuepress/plugin-prismjs": "2.0.0-beta.60", + "@vuepress/plugin-theme-data": "2.0.0-beta.60", + "@vuepress/shared": "2.0.0-beta.60", + "@vuepress/utils": "2.0.0-beta.60", + "@vueuse/core": "^9.9.0", + "sass": "^1.57.1", + "vue": "^3.2.45", + "vue-router": "^4.1.6" + } + }, + "@vuepress/utils": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/@vuepress/utils/-/utils-2.0.0-beta.60.tgz", + "integrity": "sha512-R5m5/AtKWAnlH+Su2yxoHQNp2JdJZ7gHV5531RbFySq9FTlKHtvE5RFceeppc0/UpzPE6KggRdaRqyjc77vg4g==", + "dev": true, + "requires": { + "@types/debug": "^4.1.7", + "@types/fs-extra": "^9.0.13", + "@types/hash-sum": "^1.0.0", + "@vuepress/shared": "2.0.0-beta.60", + "debug": "^4.3.4", + "fs-extra": "^11.1.0", + "globby": "^13.1.3", + "hash-sum": "^2.0.0", + "ora": "^6.1.2", + "picocolors": "^1.0.0", + "upath": "^2.0.1" + } + }, + "@vueuse/core": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.12.0.tgz", + "integrity": "sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==", + "dev": true, + "requires": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.12.0", + "@vueuse/shared": "9.12.0", + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.12.0.tgz", + "integrity": "sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==", + "dev": true + }, + "@vueuse/shared": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.12.0.tgz", + "integrity": "sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==", + "dev": true, + "requires": { + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "dev": true, + "requires": {} + } + } + }, + "@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true, + "peer": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true, + "peer": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true, + "peer": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true, + "peer": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "peer": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "peer": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true, + "peer": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "peer": true, + "requires": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peer": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peer": true, + "requires": {} + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "autoprefixer": { + "version": "10.4.13", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz", + "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==", + "dev": true, + "requires": { + "browserslist": "^4.21.4", + "caniuse-lite": "^1.0.30001426", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.21.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", + "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001400", + "electron-to-chromium": "^1.4.251", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.9" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "peer": true + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001431", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", + "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "peer": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "peer": true + } + } + }, + "cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "requires": { + "restore-cursor": "^4.0.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concurrently": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", + "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "date-fns": "^2.29.1", + "lodash": "^4.17.21", + "rxjs": "^7.0.0", + "shell-quote": "^1.7.3", + "spawn-command": "^0.0.2-1", + "supports-color": "^8.1.0", + "tree-kill": "^1.2.2", + "yargs": "^17.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-js-compat": { + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.26.1.tgz", + "integrity": "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==", + "dev": true, + "requires": { + "browserslist": "^4.21.4" + } + }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "dev": true + }, + "date-fns": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", + "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz", + "integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "peer": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true, + "peer": true + }, + "esbuild": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz", + "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "peer": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "peer": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true + }, + "execa": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", + "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^3.0.1", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "fs-extra": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz", + "integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "requires": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "human-signals": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", + "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "immutable": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", + "integrity": "sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w==", + "dev": true + }, + "import-local": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", + "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "jest-worker": { + "version": "27.2.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.5.tgz", + "integrity": "sha512-HTjEPZtcNKZ4LnhSp02NEH4vE+5OpJ0EsOWYvGQpHgUMLngydESAAMH5Wd/asPf29+XUDQZszxpLg1BkIIA2aw==", + "dev": true, + "peer": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true, + "peer": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.6.tgz", + "integrity": "sha512-jRW30YGywD2ESXDc+l17AiritL0uVaSnWsb26f+68qaW9zgbIIr1f4v2Nsvc0+s0Z2N3uX6t/yAw7BwCQ1wMsA==", + "dev": true, + "requires": {} + }, + "markdown-it-container": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz", + "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==", + "dev": true + }, + "markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "medium-zoom": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", + "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true, + "peer": true + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "peer": true, + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, + "node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "ora": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.1.2.tgz", + "integrity": "sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==", + "dev": true, + "requires": { + "bl": "^5.0.0", + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "dev": true + }, + "is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true + }, + "log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "requires": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + } + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "requires": { + "resolve": "^1.9.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.10", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz", + "integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==", + "dev": true + }, + "regenerator-transform": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", + "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "requires": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + } + } + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rollup": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.14.0.tgz", + "integrity": "sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", + "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sass": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.58.0.tgz", + "integrity": "sha512-PiMJcP33DdKtZ/1jSjjqVIKihoDc6yWmYr9K/4r3fVVIEDAluD0q7XZiRKrNJcPK3qkLRF/79DND1H5q1LBjgg==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", + "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sirv": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.17.tgz", + "integrity": "sha512-qx9go5yraB7ekT7bCMqUHJ5jEaOC/GXBxUWv+jeWnb7WzHUFdcQPGWk7YmAwFBaQBrogpuSqd/azbC2lZRqqmw==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.20", + "mime": "^2.3.1", + "totalist": "^1.0.0" + }, + "dependencies": { + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + } + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "peer": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "spawn-command": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", + "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true + }, + "terser": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "peer": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", + "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", + "dev": true, + "peer": true, + "requires": { + "jest-worker": "^27.0.6", + "p-limit": "^3.1.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1", + "terser": "^5.7.2" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "peer": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-debounce": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz", + "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==", + "dev": true + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "requires": { + "qs": "^6.4.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "vite": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.0.4.tgz", + "integrity": "sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==", + "dev": true, + "requires": { + "esbuild": "^0.16.3", + "fsevents": "~2.3.2", + "postcss": "^8.4.20", + "resolve": "^1.22.1", + "rollup": "^3.7.0" + } + }, + "vue": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz", + "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==", + "dev": true, + "requires": { + "@vue/compiler-dom": "3.2.47", + "@vue/compiler-sfc": "3.2.47", + "@vue/runtime-dom": "3.2.47", + "@vue/server-renderer": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "vue-router": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz", + "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", + "dev": true, + "requires": { + "@vue/devtools-api": "^6.4.5" + } + }, + "vuepress": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-2.0.0-beta.60.tgz", + "integrity": "sha512-evkv5PtX5pdlEyY5EcEV+rN/HTmi8iG7ZcvAnMFfYKWdvKiUjE+/DPwZfmE8emx33FEE2htbAKgtruABTocEjA==", + "dev": true, + "requires": { + "vuepress-vite": "2.0.0-beta.60" + } + }, + "vuepress-vite": { + "version": "2.0.0-beta.60", + "resolved": "https://registry.npmjs.org/vuepress-vite/-/vuepress-vite-2.0.0-beta.60.tgz", + "integrity": "sha512-ljHvo419nbfYl/cQecVbYL4bwJjUOX0+z76v/4yX6ODeGIpdHIs7ARZ4t52mr0EEfwP6aZbZa+qFZTTQutxAuQ==", + "dev": true, + "requires": { + "@vuepress/bundler-vite": "2.0.0-beta.60", + "@vuepress/cli": "2.0.0-beta.60", + "@vuepress/core": "2.0.0-beta.60", + "@vuepress/theme-default": "2.0.0-beta.60" + } + }, + "vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "dev": true, + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "webpack": { + "version": "5.76.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", + "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "dev": true, + "peer": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "peer": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==", + "dev": true, + "requires": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "peer": true + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", + "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", + "dev": true, + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + } + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..ab110c5ba7c8b39a2fc1655fb45cfda13c789ec9 --- /dev/null +++ b/package.json @@ -0,0 +1,61 @@ +{ + "name": "abcjs", + "version": "6.4.3", + "description": "Renderer for abc music notation", + "main": "index.js", + "types": "types/index.d.ts", + "scripts": { + "webpack": "webpack", + "build": "npm run build:basic && npm run webpack", + "build:basic": "npm run webpack -- --mode development --config-name basic", + "build:basic-min": "npm run webpack -- --mode production --config-name basic", + "build:plugin": "npm run webpack -- --mode production --config-name plugin", + "test-server": "npx http-server", + "test": "concurrently \"http-server\" \"npx opener http://localhost:8080/tests/all.html\"", + "docs:dev": "vuepress dev docs", + "docs:build": "vuepress build docs", + "build:analyze": "npm run build:basic -- --env analyze" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/paulrosen/abcjs.git" + }, + "keywords": [ + "abc", + "music", + "notation", + "midi", + "webaudio" + ], + "author": "Paul Rosen (https://abcjs.net)", + "contributors": [ + "Gregory Dyke", + "Jean-Yves Mengant", + "Todd Brown", + "Thomas Chandelle" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/paulrosen/abcjs/issues" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/paulrosen" + }, + "homepage": "https://abcjs.net", + "devDependencies": { + "@babel/core": "7.20.12", + "@babel/preset-env": "7.20.2", + "@tarp/require": "1.4.3", + "babel-loader": "9.1.2", + "chai": "4.3.7", + "concurrently": "7.6.0", + "http-server": "14.1.1", + "mocha": "10.2.0", + "opener": "1.5.2", + "vuepress": "2.0.0-beta.60", + "vuex": "4.1.0", + "webpack-bundle-analyzer": "4.7.0", + "webpack-cli": "4.10.0" + } +} diff --git a/plugin.js b/plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..7f8ed337b5c55dc0afc53ddacb79f8c5e619ee40 --- /dev/null +++ b/plugin.js @@ -0,0 +1,254 @@ +/**! +Copyright (c) 2009-2024 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** +!**/ +// abc_plugin.js: Find everything which looks like abc and convert it + +/*global abcjs_is_user_script, abcjs_plugin_autostart */ +"use strict"; + +var TuneBook = require('./src/api/abc_tunebook').TuneBook; +var Parse = require('./src/parse/abc_parse'); +var EngraverController = require('./src/write/engraver-controller'); + +var Plugin = function() { + "use strict"; + var is_user_script = false; + try { + is_user_script = abcjs_is_user_script; + } catch (ex) { + } + this.show_midi = !is_user_script; // TODO-PER: jQuery isn't yet available, so this part of the test will crash. || $.browser.mozilla; // midi currently only works in Firefox, so in the userscript, don't complicate it. + this.hide_abc = false; + this.render_before = false; + this.midi_options = {}; + //this.parse_options = {}; + this.render_options = {}; + this.render_classname = "abcrendered"; + this.text_classname = "abctext"; + this.auto_render_threshold = 20; + this.show_text = "show score for: "; + //this.hide_text = "hide score for: "; + this.debug = false; +}; +var plugin = new Plugin(); + +plugin.start = function() { + var body = window.document.body; + this.errors=""; + var elems = this.getABCContainingElements(body); + if (elems.length === 0) return; + if (this.debug) { + for (var i = 0; i < elems.length; i++) { + var str = "Possible ABC found (" + (i+1) + " of " + elems.length + "):\n\n" + elems[i].innerText; + alert(str); + } + } + var divs = elems.map(this.convertToDivs, this) + .reduce(function (a, b) { + return a.concat(b); + }); + this.auto_render = (divs.length <= this.auto_render_threshold); + divs.forEach(function (elem) { + this.render(elem, elem.abctext_); + }, this); +}; + +// returns an array of the descendants (including self) of elem which have a text node which matches "X:" +plugin.getABCContainingElements = function(elem) { + var results = []; + var includeself = false; // whether self is already included (no need to include it again) + // TODO maybe look to see whether it's even worth it by using textContent ? + + var child; + for (var i = 0, ii = elem.childNodes.length; i < ii; ++i) { + child = elem.childNodes[i]; + if (child.nodeType === 3 && !includeself) { + if (child.nodeValue.match(/^\s*X:/m)) { + if (child.parentNode.tagName !== 'TEXTAREA') { + results.push(elem); + includeself = true; + } + } + } else if (child.nodeType === 1 && child.tagName !== 'TEXTAREA') { + results = results.concat(this.getABCContainingElements(child)); + } + } + + return results; +}; + +// in this element there are one or more pieces of abc +// (and it is not in a subelem) +// for each abc piece, we surround it with a div, store the abctext in the +// div's data("abctext") and return an array +plugin.convertToDivs = function (elem) { + var abctext = ""; + var abcdiv = null; + var inabc = false; + var brcount = 0; + var results = []; + var node; + var childNodes = Array.prototype.slice.call(elem.childNodes); + for (var i = 0, ii = childNodes.length; i < ii; ++i) { + node = childNodes[i]; + if (node.nodeType===3 && !node.nodeValue.match(/^\s*$/)) { + brcount=0; + var text = node.nodeValue; + if (text.match(/^\s*X:/m)) { + inabc=true; + abctext=""; + abcdiv = document.createElement("DIV"); + abcdiv.className = this.text_classname; + + node.parentElement.insertBefore(abcdiv, node); + + if (this.hide_abc) { + abcdiv.style.display = 'none'; + } + } + if (inabc) { + abctext += text.replace(/^\n+/,""); + abcdiv.appendChild(node) + } + } else if (inabc && node.tagName === 'BR') { + abctext += "\n"; + abcdiv.appendChild(node) + brcount++; + } else if (inabc && node.nodeType === 1) { + abctext += "\n"; + abcdiv.appendChild(node) + // just swallow this. + } else if (inabc) { // second br or whitespace textnode + inabc = false; + brcount=0; + abctext = abctext.replace(/\n+/,"\n"); // get rid of extra blank lines + abcdiv.abctext_ = abctext; + + results.push(abcdiv); + } + } + if (inabc) { + abctext = abctext.replace(/\n+$/,"\n").replace(/^\n+/,"\n"); // get rid of extra blank lines + abcdiv.abctext_ = abctext; + results.push(abcdiv); + } + return results; +}; + +plugin.render = function (contextnode, abcstring) { + var abcdiv = document.createElement('DIV'); + abcdiv.className = this.render_classname; + if (this.render_before) { + contextnode.parentElement.insertBefore(abcdiv, contextnode); + } else { + if (contextnode.nextSibling) { + contextnode.parentElement.insertBefore(abcdiv, contextnode.nextSibling); + } else { + contextnode.parentElement.appendChild(abcdiv); + } + } + var self = this; + try { + if (this.debug) { + alert("About to render:\n\n" + abcstring); + } + var tunebook = new TuneBook(abcstring); + var abcParser = new Parse(); + abcParser.parse(tunebook.tunes[0].abc); + var tune = abcParser.getTune(); + + var doPrint = function() { + try { + var engraver_controller = new EngraverController(abcdiv, self.render_options); + engraver_controller.engraveABC(tune); + } catch (ex) { // f*** internet explorer doesn't like innerHTML in weird situations + // can't remember why we don't do this in the general case, but there was a good reason + abcdiv.remove(); + abcdiv = document.createElement('DIV'); + abcdiv.className = self.render_classname; + engraver_controller = new EngraverController(abcdiv); + engraver_controller.engraveABC(tune); + if (self.render_before) { + contextnode.parentElement.insertBefore(abcdiv, contextnode); + } else { + if (contextnode.nextSibling) { + contextnode.parentElement.insertBefore(abcdiv, contextnode.nextSibling); + } else { + contextnode.parentElement.appendChild(abcdiv); + } + } + } + // if (ABCJS.MidiWriter && self.show_midi) { + // var midiwriter = new ABCJS.midi.MidiWriter(abcdiv.get(0),self.midi_options); + // midiwriter.writeABC(tune); + // } + }; + + var showtext = document.createElement('A'); + showtext.className = 'abcshow'; + showtext.href = '#'; + showtext.innerHTML = this.show_text + (tune.metaText.title || 'untitled'); + + if (this.auto_render) { + doPrint(); + } else { + showtext.onclick = function () { + doPrint(); + showtext.remove(); + return false; + }; + + abcdiv.parentElement.insertBefore(showtext, abcdiv); + } + + } catch (e) { + this.errors += e; + } +}; + + +// There may be a variable defined which controls whether to automatically run the script. If it isn't +// there then it will throw an exception, so we'll catch it here. +var autostart = true; +if (typeof abcjs_plugin_autostart !== 'undefined') { + autostart = abcjs_plugin_autostart; +} + +if (autostart && + typeof window !== 'undefined' && + typeof (window.document) !== 'undefined') { + + if (window.document.readyState !== 'loading') { + plugin.start(); + } else { + document.addEventListener('DOMContentLoaded', function () { + plugin.start(); + }); + } +} + +var abcjs = { + plugin: plugin +}; + +module.exports = abcjs; diff --git a/src/api/abc_animation.js b/src/api/abc_animation.js new file mode 100644 index 0000000000000000000000000000000000000000..2677de461c7bd80c8cf01f16243825c204a097c7 --- /dev/null +++ b/src/api/abc_animation.js @@ -0,0 +1,112 @@ +// abc_animation.js: handles animating the music in real time. + +var TimingCallbacks = require('./abc_timing_callbacks'); + +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; diff --git a/src/api/abc_timing_callbacks.js b/src/api/abc_timing_callbacks.js new file mode 100644 index 0000000000000000000000000000000000000000..ed7c1e948eeb6315ed5218a464d1ca2809bd04c6 --- /dev/null +++ b/src/api/abc_timing_callbacks.js @@ -0,0 +1,331 @@ +var TimingCallbacks = function(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) + self.noteTimings = 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; + diff --git a/src/api/abc_tunebook.js b/src/api/abc_tunebook.js new file mode 100644 index 0000000000000000000000000000000000000000..0feac22d789cb055437825894737aa8e49348638 --- /dev/null +++ b/src/api/abc_tunebook.js @@ -0,0 +1,263 @@ +// abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes. + +var Parse = require('../parse/abc_parse'); +var bookParser = require('../parse/abc_parse_book'); +var tablatures = require('../tablatures/abc_tablatures'); + + +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(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) { + 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; diff --git a/src/api/abc_tunebook_svg.js b/src/api/abc_tunebook_svg.js new file mode 100644 index 0000000000000000000000000000000000000000..030f074e1a71341882ff57321ae782da611a8b72 --- /dev/null +++ b/src/api/abc_tunebook_svg.js @@ -0,0 +1,154 @@ +var tunebook = require('./abc_tunebook'); +var Tune = require('../data/abc_tune'); + +var EngraverController = require('../write/engraver-controller'); +var Parse = require('../parse/abc_parse'); +var wrap = require('../parse/wrap_lines'); + + +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 = '
'; + 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.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(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 (!removeDiv && params.wrap && params.staffwidth) { + tune = doLineWrapping(div, tune, tuneNumber, abcString, params); + return tune; + } + if (params.afterParsing) + params.afterParsing(tune, tuneNumber, abcString); + 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; + } + if (params.afterParsing) + params.afterParsing(tune, tuneNumber, abcString); + renderOne(div, tune, ret.revisedParams, tuneNumber, 0); + tune.explanation = ret.explanation; + return tune; +} + +module.exports = renderAbc; diff --git a/src/api/tune-metrics.js b/src/api/tune-metrics.js new file mode 100644 index 0000000000000000000000000000000000000000..0701bb2d6b48de5eb6ed939303efd7dd4b5264d7 --- /dev/null +++ b/src/api/tune-metrics.js @@ -0,0 +1,18 @@ +var tunebook = require('./abc_tunebook'); +var EngraverController = require('../write/engraver-controller'); + +var tuneMetrics = function(abc, params) { + function callback(div, tune, tuneNumber, abcString) { + div = document.createElement("div"); + div.setAttribute("style", "visibility: hidden;"); + document.body.appendChild(div); + var engraver_controller = new EngraverController(div, params); + var widths = engraver_controller.getMeasureWidths(tune); + div.parentNode.removeChild(div); + return {sections: widths}; + } + + return tunebook.renderEngine(callback, "*", abc, params); +}; + +module.exports = tuneMetrics; diff --git a/src/const/key-accidentals.js b/src/const/key-accidentals.js new file mode 100644 index 0000000000000000000000000000000000000000..03c7f50684d5a72d872a87a6debff0650ead7b8b --- /dev/null +++ b/src/const/key-accidentals.js @@ -0,0 +1,53 @@ +var { relativeMajor } = require("./relative-major"); + +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; diff --git a/src/const/relative-major.js b/src/const/relative-major.js new file mode 100644 index 0000000000000000000000000000000000000000..dbdc2daec7a8f9635b15a8de3cf15710652502c2 --- /dev/null +++ b/src/const/relative-major.js @@ -0,0 +1,92 @@ +// 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}; diff --git a/src/data/abc_tune.js b/src/data/abc_tune.js new file mode 100644 index 0000000000000000000000000000000000000000..fd472033e359e11da83aaba484a9565c425e72e4 --- /dev/null +++ b/src/data/abc_tune.js @@ -0,0 +1,634 @@ +// abc_tune.js: a computer usable internal structure representing one tune. + +var parseCommon = require('../parse/abc_common'); +var spacing = require('../write/helpers/spacing'); +var sequence = require('../synth/abc_midi_sequencer'); +var flatten = require('../synth/abc_midi_flattener'); +var delineTune = require("./deline-tune"); + +/** + * 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() { + 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; + this.runningFonts = {}; + delete this.visualTranspose; + }; + this.reset(); + + function copy(dest, src, prop, attrs) { + for (var i = 0; i < attrs.length; i++) + dest[prop][attrs[i]] = src[prop][attrs[i]]; + } + + this.copyTopInfo = function(src) { + var attrs = ['tempo', 'title', 'header', 'rhythm', 'origin', 'composer', 'author', 'partOrder']; + copy(this, src, "metaText", attrs); + copy(this, src, "metaTextInfo", attrs); + }; + + this.copyBottomInfo = function(src) { + var attrs = ['unalignedWords', + 'book', + 'source', + 'discography', + 'notes', + 'transcription', + 'history', + 'abc-copyright', + 'abc-creator', + 'abc-edited-by', + 'footer'] + copy(this, src, "metaText", attrs); + copy(this, src, "metaTextInfo", attrs); + }; + + // The structure consists of a hash with the following two items: + // metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc... + // tempo: { noteLength: number (e.g. .125), bpm: number } + // lines: an array of elements, or one of the following: + // + // STAFF: array of elements + // SUBTITLE: string + // + // TODO: actually, the start and end char should modify each part of the note type + // The elements all have a type field and a start and end char + // field. The rest of the fields depend on the type and are listed below: + // REST: duration=1,2,4,8; chord: string + // NOTE: accidental=none,dbl_flat,flat,natural,sharp,dbl_sharp + // pitch: "C" is 0. The numbers refer to the pitch letter. + // duration: .5 (sixteenth), .75 (dotted sixteenth), 1 (eighth), 1.5 (dotted eighth) + // 2 (quarter), 3 (dotted quarter), 4 (half), 6 (dotted half) 8 (whole) + // chord: { name:chord, position: one of 'default', 'above', 'below' } + // end_beam = true or undefined if this is the last note in a beam. + // lyric: array of { syllable: xxx, divider: one of " -_" } + // startTie = true|undefined + // endTie = true|undefined + // startTriplet = num <- that is the number to print + // endTriplet = true|undefined (the last note of the triplet) + // TODO: actually, decoration should be an array. + // decoration: upbow, downbow, accent + // BAR: type=bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat + // number: 1 or 2: if it is the start of a first or second ending + // CLEF: type=treble,bass + // KEY-SIG: + // accidentals[]: { acc:sharp|dblsharp|natural|flat|dblflat, note:a|b|c|d|e|f|g } + // METER: type: common_time,cut_time,specified + // if specified, { num: 99, den: 99 } + + this.getBeatLength = function() { + // This returns a fraction: for instance 1/4 for a quarter + // There are two types of meters: compound and regular. Compound meter has 3 beats counted as one. + var meter = this.getMeterFraction(); + var multiplier = 1; + if (meter.num === 6 || meter.num === 9 || meter.num === 12) + multiplier = 3; + else if (meter.num === 3 && meter.den === 8) + multiplier = 3; + + return multiplier / meter.den; + }; + + function computePickupLength(lines, barLength) { + var pickupLength = 0; + for (var i = 0; i < lines.length; i++) { + if (lines[i].staff) { + for (var j = 0; j < lines[i].staff.length; j++) { + for (var v = 0; v < lines[i].staff[j].voices.length; v++) { + var voice = lines[i].staff[j].voices[v]; + var tripletMultiplier = 1; + for (var el = 0; el < voice.length; el++) { + var isSpacer = voice[el].rest && voice[el].rest.type === "spacer"; + if (voice[el].startTriplet) + tripletMultiplier = voice[el].tripletMultiplier; + if (voice[el].duration && !isSpacer && voice[el].el_type !== "tempo") + pickupLength += voice[el].duration * tripletMultiplier; + if (voice[el].endTriplet) + tripletMultiplier = 1; + if (pickupLength >= barLength) + pickupLength -= barLength; + if (voice[el].el_type === 'bar') + return pickupLength; + } + } + } + } + } + + return pickupLength; + } + this.getPickupLength = function() { + var barLength = this.getBarLength(); + var pickupLength = computePickupLength(this.lines, barLength); + + // If computed pickup length is very close to 0 or the bar length, we assume + // that we actually have a full bar and hence no pickup. + return (pickupLength < 1e-8 || barLength-pickupLength < 1e-8) ? 0 : pickupLength; + }; + + this.getBarLength = function() { + var meter = this.getMeterFraction(); + return meter.num / meter.den; + }; + + this.getTotalTime = function() { + return this.totalTime; + }; + + this.getTotalBeats = function() { + return this.totalBeats; + }; + + this.millisecondsPerMeasure = function(bpmOverride) { + var bpm; + if (bpmOverride) { + bpm = bpmOverride; + } else { + var tempo = this.metaText ? this.metaText.tempo : null; + bpm = this.getBpm(tempo); + } + if (bpm <= 0) + bpm = 1; // I don't think this can happen, but we don't want a possibility of dividing by zero. + + var beatsPerMeasure = this.getBeatsPerMeasure(); + + var minutesPerMeasure = beatsPerMeasure / bpm; + return minutesPerMeasure * 60000; + }; + + this.getBeatsPerMeasure = function() { + var beatLen = this.getBeatLength(); + var barLen = this.getBarLength(); + return barLen / beatLen; + }; + + this.getMeter = function() { + for (var i = 0; i < this.lines.length; i++) { + var line = this.lines[i]; + if (line.staff) { + for (var j = 0; j < line.staff.length; j++) { + var meter = line.staff[j].meter; + if (meter) { + return meter; + } + } + } + } + return { type: "common_time" }; + }; + + this.getMeterFraction = function() { + var meter = this.getMeter(); + var num = 4; + var den = 4; + if (meter) { + if (meter.type === 'specified') { + num = parseInt(meter.value[0].num, 10); + den = parseInt(meter.value[0].den,10); + } else if (meter.type === 'cut_time') { + num = 2; + den = 2; + } else if (meter.type === 'common_time') { + num = 4; + den = 4; + } + } + this.meter = { num: num, den: den }; + return this.meter; // TODO-PER: is this saved value used anywhere? A get function shouldn't change state. + }; + + this.getKeySignature = function() { + for (var i = 0; i < this.lines.length; i++) { + var line = this.lines[i]; + if (line.staff) { + for (var j = 0; j < line.staff.length; j++) { + if (line.staff[j].key) + return line.staff[j].key; + } + } + } + return { }; + }; + + this.getElementFromChar = function(char) { + for (var i = 0; i < this.lines.length; i++) { + var line = this.lines[i]; + if (line.staff) { + for (var j = 0; j < line.staff.length; j++) { + var staff = line.staff[j]; + for (var k = 0; k < staff.voices.length; k++) { + var voice = staff.voices[k]; + for (var ii = 0; ii < voice.length; ii++) { + var elem = voice[ii]; + if (elem.startChar && elem.endChar && + elem.startChar <= char && elem.endChar > char) + return elem; + } + } + } + } + } + return null; + }; + + function addVerticalInfo(timingEvents) { + // Add vertical info to the bar events: put the next event's top, and the event after the next measure's top. + var lastBarTop; + var lastBarBottom; + var lastEventTop; + var lastEventBottom; + for (var e = timingEvents.length - 1; e >= 0; e--) { + var ev = timingEvents[e]; + if (ev.type === 'bar') { + ev.top = lastEventTop; + ev.nextTop = lastBarTop; + lastBarTop = lastEventTop; + + ev.bottom = lastEventBottom; + ev.nextBottom = lastBarBottom; + lastBarBottom = lastEventBottom; + } else if (ev.type === 'event') { + lastEventTop = ev.top; + lastEventBottom = ev.top + ev.height; + } + } + } + + function makeSortedArray(hash) { + var arr = []; + for (var k in hash) { + if (hash.hasOwnProperty(k)) + arr.push(hash[k]); + } + arr = arr.sort(function (a, b) { + var diff = a.milliseconds - b.milliseconds; + // if the events have the same time, make sure a bar comes before a note + if (diff !== 0) { + return diff; + } + else { + return a.type === "bar" ? -1 : 1; + } + }); + return arr; + } + + this.addElementToEvents = function(eventHash, element, voiceTimeMilliseconds, top, height, line, measureNumber, timeDivider, isTiedState, nextIsBar) { + if (element.hint) + return { isTiedState: undefined, duration: 0 }; + var realDuration = element.durationClass ? element.durationClass : element.duration; + if (element.abcelem.rest && element.abcelem.rest.type === "spacer") + realDuration = 0; + if (realDuration > 0) { + var es = []; + // If there is an invisible rest, then there are not elements, so don't push a null one. + for (var i = 0; i < element.elemset.length; i++) { + if (element.elemset[i] !== null) + es.push(element.elemset[i]); + } + var isTiedToNext = element.startTie; + if (isTiedState !== undefined) { + eventHash["event" + isTiedState].elements.push(es); // Add the tied note to the first note that it is tied to + if (nextIsBar) { + if (!eventHash["event" + voiceTimeMilliseconds]) { + eventHash["event" + voiceTimeMilliseconds] = { + type: "event", + milliseconds: voiceTimeMilliseconds, + line: line, + measureNumber: measureNumber, + top: top, + height: height, + left: null, + width: 0, + elements: [], + startChar: null, + endChar: null, + startCharArray: [], + endCharArray: [] + }; + } + eventHash["event" + voiceTimeMilliseconds].measureStart = true; + nextIsBar = false; + } + if (!isTiedToNext) + isTiedState = undefined; + } else { + // the last note wasn't tied. + if (!eventHash["event" + voiceTimeMilliseconds]) { + eventHash["event" + voiceTimeMilliseconds] = { + type: "event", + milliseconds: voiceTimeMilliseconds, + line: line, + measureNumber: measureNumber, + top: top, + height: height, + left: element.x, + width: element.w, + elements: [es], + startChar: element.abcelem.startChar, + endChar: element.abcelem.endChar, + startCharArray: [element.abcelem.startChar], + endCharArray: [element.abcelem.endChar], + midiPitches: element.abcelem.midiPitches ? parseCommon.cloneArray(element.abcelem.midiPitches) : [] + }; + if (element.abcelem.midiGraceNotePitches) + eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches = parseCommon.cloneArray(element.abcelem.midiGraceNotePitches); + } else { + // If there is more than one voice then two notes can fall at the same time. Usually they would be lined up in the same place, but if it is a whole rest, then it is placed funny. In any case, the left most element wins. + if (eventHash["event" + voiceTimeMilliseconds].left) + eventHash["event" + voiceTimeMilliseconds].left = Math.min(eventHash["event" + voiceTimeMilliseconds].left, element.x); + else + eventHash["event" + voiceTimeMilliseconds].left = element.x; + eventHash["event" + voiceTimeMilliseconds].elements.push(es); + eventHash["event" + voiceTimeMilliseconds].startCharArray.push(element.abcelem.startChar); + eventHash["event" + voiceTimeMilliseconds].endCharArray.push(element.abcelem.endChar); + if (eventHash["event" + voiceTimeMilliseconds].startChar === null) + eventHash["event" + voiceTimeMilliseconds].startChar =element.abcelem.startChar; + if (eventHash["event" + voiceTimeMilliseconds].endChar === null) + eventHash["event" + voiceTimeMilliseconds].endChar =element.abcelem.endChar; + if (element.abcelem.midiPitches && element.abcelem.midiPitches.length) { + if (!eventHash["event" + voiceTimeMilliseconds].midiPitches) + eventHash["event" + voiceTimeMilliseconds].midiPitches = []; + for (var i = 0; i < element.abcelem.midiPitches.length; i++) + eventHash["event" + voiceTimeMilliseconds].midiPitches.push(element.abcelem.midiPitches[i]); + } + if (element.abcelem.midiGraceNotePitches && element.abcelem.midiGraceNotePitches.length) { + if (!eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches) + eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches = []; + for (var j = 0; j < element.abcelem.midiGraceNotePitches.length; j++) + eventHash["event" + voiceTimeMilliseconds].midiGraceNotePitches.push(element.abcelem.midiGraceNotePitches[j]); + } + } + if (nextIsBar) { + eventHash["event" + voiceTimeMilliseconds].measureStart = true; + nextIsBar = false; + } + // TODO-PER: There doesn't seem to be a harm in letting ties be two different notes and it fixes a bug when a tie goes to a new line. If there aren't other problems with this change, then the variable can be removed completely. + // if (isTiedToNext) + // isTiedState = voiceTimeMilliseconds; + } + } + return { isTiedState: isTiedState, duration: realDuration / timeDivider, nextIsBar: nextIsBar || element.type === 'bar' }; + }; + + this.makeVoicesArray = function() { + // First make a new array that is arranged by voice so that the repeats that span different lines are handled correctly. + var voicesArr = []; + var measureNumber = []; + var tempos = {}; + for (var line = 0; line < this.engraver.staffgroups.length; line++) { + var group = this.engraver.staffgroups[line]; + if (group && group.staffs && group.staffs.length > 0) { + var firstStaff = group.staffs[0]; + var middleC = firstStaff.absoluteY; + var top = middleC - firstStaff.top * spacing.STEP; + var lastStaff = group.staffs[group.staffs.length - 1]; + middleC = lastStaff.absoluteY; + var bottom = middleC - lastStaff.bottom * spacing.STEP; + var height = bottom - top; + + var voices = group.voices; + for (var v = 0; v < voices.length; v++) { + if (voices[v].staff && voices[v].staff.isTabStaff) + continue; + var noteFound = false; + if (!voicesArr[v]) + voicesArr[v] = []; + if (measureNumber[v] === undefined) + measureNumber[v] = 0; + var elements = voices[v].children; + for (var elem = 0; elem < elements.length; elem++) { + if (elements[elem].type === "tempo") + tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem); + voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]}); + if (elements[elem].type === 'bar' && noteFound) // Count the measures by counting the bar lines, but skip a bar line that appears at the left of the music, before any notes. + measureNumber[v]++; + if (elements[elem].type === 'note' || elements[elem].type === 'rest') + noteFound = true; + } + } + } + } + this.tempoLocations = tempos; // This should be passed back, but the function is accessible publicly so that would break the interface. + return voicesArr; + }; + + this.setupEvents = function(startingDelay, timeDivider, startingBpm, warp) { + if (!warp) warp = 1; + var timingEvents = []; + + var eventHash = {}; + // The time is the number of seconds from the beginning of the piece. + // The units we are scanning are in notation units (i.e. 0.25 is a quarter note) + var time = startingDelay; + var isTiedState; + var nextIsBar = true; + var voices = this.makeVoicesArray(); + var maxVoiceTimeMilliseconds = 0; + for (var v = 0; v < voices.length; v++) { + var voiceTime = time; + var voiceTimeMilliseconds = Math.round(voiceTime * 1000); + var startingRepeatElem = 0; + var endingRepeatElem = -1; + var elements = voices[v]; + var bpm = startingBpm; + timeDivider = this.getBeatLength() * bpm / 60; + var tempoDone = -1; + for (var elem = 0; elem < elements.length; elem++) { + var thisMeasure = elements[elem].measureNumber; + if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) { + bpm = this.tempoLocations[thisMeasure]; + timeDivider = warp * this.getBeatLength() * bpm / 60; + tempoDone = thisMeasure; + } + var element = elements[elem].elem; + var ret = this.addElementToEvents(eventHash, element, voiceTimeMilliseconds, elements[elem].top, elements[elem].height, elements[elem].line, elements[elem].measureNumber, timeDivider, isTiedState, nextIsBar); + isTiedState = ret.isTiedState; + nextIsBar = ret.nextIsBar; + voiceTime += ret.duration; + var lastHash; + if (element.duration > 0 && eventHash["event" + voiceTimeMilliseconds]) // This won't exist if this is the end of a tie. + lastHash = "event" + voiceTimeMilliseconds; + voiceTimeMilliseconds = Math.round(voiceTime * 1000); + if (element.type === 'bar') { + var barType = element.abcelem.type; + var endRepeat = (barType === "bar_right_repeat" || barType === "bar_dbl_repeat"); + var startEnding = (element.abcelem.startEnding === '1'); + var startRepeat = (barType === "bar_left_repeat" || barType === "bar_dbl_repeat" || barType === "bar_right_repeat"); + if (endRepeat) { + // Force the end of the previous note to the position of the measure - the cursor won't go past the end repeat + if (elem > 0) { + eventHash[lastHash].endX = element.x; + } + + if (endingRepeatElem === -1) + endingRepeatElem = elem; + var lastVoiceTimeMilliseconds = 0; + tempoDone = -1; + for (var el2 = startingRepeatElem; el2 < endingRepeatElem; el2++) { + thisMeasure = elements[el2].measureNumber; + if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) { + bpm = this.tempoLocations[thisMeasure]; + timeDivider = warp * this.getBeatLength() * bpm / 60; + tempoDone = thisMeasure; + } + var element2 = elements[el2].elem; + ret = this.addElementToEvents(eventHash, element2, voiceTimeMilliseconds, elements[el2].top, elements[el2].height, elements[el2].line, elements[el2].measureNumber, timeDivider, isTiedState, nextIsBar); + isTiedState = ret.isTiedState; + nextIsBar = ret.nextIsBar; + voiceTime += ret.duration; + lastVoiceTimeMilliseconds = voiceTimeMilliseconds; + voiceTimeMilliseconds = Math.round(voiceTime * 1000); + } + if (eventHash["event" + lastVoiceTimeMilliseconds]) // This won't exist if it is the beginning of the next line. That's ok because we will just count the end of the last line as the end. + eventHash["event" + lastVoiceTimeMilliseconds].endX = elements[endingRepeatElem].elem.x; + nextIsBar = true; + endingRepeatElem = -1; + } + if (startEnding) + endingRepeatElem = elem; + if (startRepeat) + startingRepeatElem = elem; + } + } + maxVoiceTimeMilliseconds = Math.max(maxVoiceTimeMilliseconds, voiceTimeMilliseconds) + } + // now we have all the events, but if there are multiple voices then there may be events out of order or duplicated, so normalize it. + timingEvents = makeSortedArray(eventHash); + addVerticalInfo(timingEvents); + addEndPoints(this.lines, timingEvents) + timingEvents.push({ type: "end", milliseconds: maxVoiceTimeMilliseconds }); + this.addUsefulCallbackInfo(timingEvents, bpm*warp); + return timingEvents; + }; + + this.addUsefulCallbackInfo = function(timingEvents, bpm) { + var millisecondsPerMeasure = this.millisecondsPerMeasure(bpm); + for (var i = 0; i < timingEvents.length; i++) { + var ev = timingEvents[i]; + ev.millisecondsPerMeasure = millisecondsPerMeasure; + } + }; + + function skipTies(elements, index) { + while (index < elements.length && elements[index].left === null) + index++; + return elements[index]; + } + function addEndPoints(lines, elements) { + if (elements.length < 1) + return; + for (var i = 0; i < elements.length-1; i++) { + var el = elements[i]; + var next = skipTies(elements, i+1); + if (el.left !== null) { + // If there is no left element that is because this is a tie so it should be skipped. + var endX = (next && el.top === next.top) ? next.left : lines[el.line].staffGroup.w; + // If this is already set, it is because the notes aren't sequential here, like the next thing is a repeat bar line. + // In that case, the right-most position is passed in. There could still be an intervening note in another voice, so always look for the closest position. + // If there is a repeat that stays on the same line, the endX set above won't be right because the next note will be before. In that case, use the endX that was calculated. + if (el.endX !== undefined) { + if (endX > el.left) + el.endX = Math.min(el.endX, endX); + } else + el.endX = endX; + } + } + var lastEl = elements[elements.length-1]; + lastEl.endX = lines[lastEl.line].staffGroup.w; + } + + this.getBpm = function(tempo) { + var bpm; + if (!tempo) + tempo = this.metaText ? this.metaText.tempo : null; + if (tempo) { + bpm = tempo.bpm; + var beatLength = this.getBeatLength(); + var statedBeatLength = tempo.duration && tempo.duration.length > 0 ? tempo.duration[0] : beatLength; + bpm = bpm * statedBeatLength / beatLength; + } + if (!bpm) { + bpm = 180; + // Compensate for compound meter, where the beat isn't a beat. + var meter = this.getMeterFraction(); + if (meter && meter.num !== 3 && (meter.num % 3 === 0)) { + bpm = 120; + } + } + return bpm; + }; + + this.setTiming = function (bpm, measuresOfDelay) { + measuresOfDelay = measuresOfDelay || 0; + if (!this.engraver || !this.engraver.staffgroups) { + console.log("setTiming cannot be called before the tune is drawn."); + this.noteTimings = []; + return this.noteTimings; + } + + var tempo = this.metaText ? this.metaText.tempo : null; + var naturalBpm = this.getBpm(tempo); + var warp = 1; + if (bpm) { + if (tempo) + warp = bpm / naturalBpm; + } else + bpm = naturalBpm; + + // Calculate the basic midi data. We only care about the qpm variable here. + //this.setUpAudio({qpm: bpm}); + + var beatLength = this.getBeatLength(); + var beatsPerSecond = bpm / 60; + + var measureLength = this.getBarLength(); + + var startingDelay = measureLength / beatLength * measuresOfDelay / beatsPerSecond; + if (startingDelay) + startingDelay -= this.getPickupLength() / beatLength / beatsPerSecond; + var timeDivider = beatLength * beatsPerSecond; + + this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm, warp); + if (this.noteTimings.length > 0) { + this.totalTime = this.noteTimings[this.noteTimings.length - 1].milliseconds / 1000; + this.totalBeats = this.totalTime * beatsPerSecond; + } else { + this.totalTime = undefined; + this.totalBeats = undefined; + } + return this.noteTimings; + }; + + this.setUpAudio = function(options) { + if (!options) options = {}; + var seq = sequence(this, options); + return flatten(seq, options, this.formatting.percmap, this.formatting.midi); + }; + this.deline = function(options) { + return delineTune(this.lines, options); + } + this.findSelectableElement = function(target) { + if (this.engraver && this.engraver.selectables) + return this.engraver.findSelectableElement(target) + return null + } + this.getSelectableArray = function() { + if (this.engraver && this.engraver.selectables) + return this.engraver.selectables + return [] + } +}; + +module.exports = Tune; diff --git a/src/data/deline-tune.js b/src/data/deline-tune.js new file mode 100644 index 0000000000000000000000000000000000000000..d428c365683e7cf58166888b3b45c984ed6ff7f1 --- /dev/null +++ b/src/data/deline-tune.js @@ -0,0 +1,199 @@ +function delineTune(inputLines, options) { + if (!options) options = {}; + var lineBreaks = !!options.lineBreaks; + var outputLines = []; + var inMusicLine = false; + var currentMeter = []; + var currentKey = []; + var currentClef = []; + var currentVocalFont = []; + var currentGChordFont = []; + var currentTripletFont = []; + var currentAnnotationFont = []; + for (var i = 0; i < inputLines.length; i++) { + var inputLine = inputLines[i]; + if (inputLine.staff) { + if (inMusicLine && !inputLine.vskip) { + var outputLine = outputLines[outputLines.length-1]; + //findMismatchKeys(inputLine, outputLine, ["staff", "staffGroup"], "line", i) + for (var s = 0; s < outputLine.staff.length; s++) { + var inputStaff = inputLine.staff[s]; + var outputStaff = outputLine.staff[s]; + if (inputStaff) { + if (!objEqual(inputStaff.meter, currentMeter[s])) { + // The meter changed for this line, otherwise it wouldn't have been set + addMeterToVoices(inputStaff.meter, inputStaff.voices) + currentMeter[s] = inputStaff.meter; + delete inputStaff.meter; + } + if (!objEqual(inputStaff.key, currentKey[s])) { + addKeyToVoices(inputStaff.key, inputStaff.voices); + currentKey[s] = inputStaff.key; + delete inputStaff.key; + } + if (inputStaff.title) + outputStaff.abbrevTitle = inputStaff.title; + if (!objEqual(inputStaff.clef, currentClef[s])) { + addClefToVoices(inputStaff.clef, inputStaff.voices); + currentClef[s] = inputStaff.clef; + delete inputStaff.clef; + } + if (!objEqual(inputStaff.vocalfont, currentVocalFont[s])) { + addFontToVoices(inputStaff.vocalfont, inputStaff.voices, 'vocalfont') + currentVocalFont[s] = inputStaff.vocalfont; + delete inputStaff.vocalfont; + } + if (!objEqual(inputStaff.gchordfont, currentGChordFont[s])) { + addFontToVoices(inputStaff.gchordfont, inputStaff.voices, 'gchordfont') + currentGChordFont[s] = inputStaff.gchordfont; + delete inputStaff.gchordfont; + } + if (!objEqual(inputStaff.tripletfont, currentTripletFont[s])) { + addFontToVoices(inputStaff.tripletfont, inputStaff.voices, 'tripletfont') + currentTripletFont[s] = inputStaff.tripletfont; + delete inputStaff.tripletfont; + } + if (!objEqual(inputStaff.annotationfont, currentAnnotationFont[s])) { + addFontToVoices(inputStaff.annotationfont, inputStaff.voices, 'annotationfont') + currentAnnotationFont[s] = inputStaff.annotationfont; + delete inputStaff.annotationfont; + } + } + //findMismatchKeys(inputStaff, outputStaff, ["voices", "title", "abbrevTitle", "barNumber", "meter", "key", "clef", "vocalfont", "gchordfont", "tripletfont", "annotationfont"], "staff", i + ' ' + s) + if (inputStaff) { + for (var v = 0; v < outputStaff.voices.length; v++) { + var outputVoice = outputStaff.voices[v]; + var inputVoice = inputStaff.voices[v]; + if (lineBreaks) + outputVoice.push({el_type: "break"}); + if (inputVoice) + outputStaff.voices[v] = outputVoice.concat(inputVoice) + } + } + } + } else { + for (var ii = 0; ii < inputLine.staff.length; ii++) { + currentKey[ii] = inputLine.staff[ii].key; + currentMeter[ii] = inputLine.staff[ii].meter; + currentClef[ii] = inputLine.staff[ii].clef; + } + // copy this because we are going to change it and we don't want to change the original. + outputLines.push(cloneLine(inputLine)); + } + inMusicLine = true; + } else { + inMusicLine = false; + outputLines.push(inputLine); + } + } + return outputLines; +} +// function findMismatchKeys(input, output, ignore, context, context2) { +// if (!input) { +// return; +// } +// var outputKeys = Object.keys(output); +// var inputKeys = Object.keys(input); +// for (var ii = 0; ii < ignore.length; ii++) { +// if (outputKeys.indexOf(ignore[ii]) >= 0) { +// outputKeys.splice(outputKeys.indexOf(ignore[ii]), 1); +// } +// if (inputKeys.indexOf(ignore[ii]) >= 0) { +// inputKeys.splice(inputKeys.indexOf(ignore[ii]), 1); +// } +// } +// if (inputKeys.join(",") !== outputKeys.join(",")) { +// console.log("keys mismatch "+context + ' ' + context2, input, output); +// } +// for (var k = 0; k < inputKeys.length; k++) { +// var key = inputKeys[k]; +// if (ignore.indexOf(key) < 0) { +// var inputValue = JSON.stringify(input[key], replacer); +// var outputValue = JSON.stringify(output[key], replacer); +// if (inputValue !== outputValue) +// console.log("value mismatch "+context + ' ' + context2 + ' ' + key, inputValue, outputValue) +// } +// } +// } +function replacer(key, value) { + // Filtering out properties + if (key === 'abselem') { + return 'abselem'; + } + return value; +} + +function addMeterToVoices(meter, voices) { + meter.el_type = "meter"; + meter.startChar = -1; + meter.endChar = -1; + for (var i = 0; i < voices.length; i++) { + voices[i].unshift(meter); + } +} + +function addKeyToVoices(key, voices) { + key.el_type = "key"; + key.startChar = -1; + key.endChar = -1; + for (var i = 0; i < voices.length; i++) { + voices[i].unshift(key); + } +} + +function addClefToVoices(clef, voices) { + clef.el_type = "clef"; + clef.startChar = -1; + clef.endChar = -1; + for (var i = 0; i < voices.length; i++) { + voices[i].unshift(clef); + } +} + +function addFontToVoices(font, voices, type) { + font.el_type = "font"; + font.type = type; + font.startChar = -1; + font.endChar = -1; + for (var i = 0; i < voices.length; i++) { + voices[i].unshift(font); + } +} + +function objEqual(input, output) { + if (!input) + return true; // the default is whatever the old output is. + var inputValue = JSON.stringify(input, replacer); + var outputValue = JSON.stringify(output, replacer); + return inputValue === outputValue; +} + +function cloneLine(line) { + var output = {}; + var keys = Object.keys(line); + for (var i = 0; i < keys.length; i++) { + if (keys[i] !== "staff") + output[keys[i]] = line[keys[i]]; + else { + output.staff = []; + for (var j = 0; j < line.staff.length; j++) { + var staff = {}; + var keys2 = Object.keys(line.staff[j]); + for (var k = 0; k < keys2.length; k++) { + if (keys2[k] !== "voices") + staff[keys2[k]] = line.staff[j][keys2[k]]; + else { + staff.voices = []; + for (var v = 0; v < line.staff[j].voices.length; v++) { + staff.voices.push([].concat(line.staff[j].voices[v])); + } + } + } + output.staff.push(staff) + } + } + } + return output; +} + +module.exports = delineTune; diff --git a/src/edit/abc_editarea.js b/src/edit/abc_editarea.js new file mode 100644 index 0000000000000000000000000000000000000000..c7dcc316b452f07e33aa9a58f13c49be4e22784b --- /dev/null +++ b/src/edit/abc_editarea.js @@ -0,0 +1,115 @@ +// abc_editor.js + +// window.ABCJS.Editor is the interface class for the area that contains the ABC text. It is responsible for +// holding the text of the tune and calling the parser and the rendering engines. +// +// EditArea is an example of using a textarea as the control that is shown to the user. As long as +// the same interface is used, window.ABCJS.Editor can use a different type of object. +// +// EditArea: +// - constructor(textareaid) +// This contains the id of a textarea control that will be used. +// - addSelectionListener(listener) +// A callback class that contains the entry point fireSelectionChanged() +// - addChangeListener(listener) +// A callback class that contains the entry point fireChanged() +// - getSelection() +// returns the object { start: , end: } with the current selection in characters +// - setSelection(start, end) +// start and end are the character positions that should be selected. +// - getString() +// returns the ABC text that is currently displayed. +// - setString(str) +// sets the ABC text that is currently displayed, and resets the initialText variable +// - getElem() +// returns the textarea element +// - string initialText +// Contains the starting text. This can be compared against the current text to see if anything changed. +// + +// Polyfill for CustomEvent for old IE versions +try { + if (typeof window.CustomEvent !== "function") { + var CustomEvent = function (event, params) { + params = params || {bubbles: false, cancelable: false, detail: undefined}; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + window.CustomEvent = CustomEvent; + } +} catch (e) { + // if we aren't in a browser, this code will crash, but it is not needed then either. +} + +var EditArea = function(textareaid) { + if (typeof textareaid === "string") + this.textarea = document.getElementById(textareaid); + else + this.textarea = textareaid; + this.initialText = this.textarea.value; + this.isDragging = false; +} + +EditArea.prototype.addSelectionListener = function(listener) { + this.textarea.onmousemove = function(ev) { + if (this.isDragging) + listener.fireSelectionChanged(); + }; +}; + +EditArea.prototype.addChangeListener = function(listener) { + this.changelistener = listener; + this.textarea.onkeyup = function() { + listener.fireChanged(); + }; + this.textarea.onmousedown = function() { + this.isDragging = true; + listener.fireSelectionChanged(); + }; + this.textarea.onmouseup = function() { + this.isDragging = false; + listener.fireChanged(); + }; + this.textarea.onchange = function() { + listener.fireChanged(); + }; +}; + +//TODO won't work under IE? +EditArea.prototype.getSelection = function() { + return {start: this.textarea.selectionStart, end: this.textarea.selectionEnd}; +}; + +EditArea.prototype.setSelection = function(start, end) { + if(this.textarea.setSelectionRange) + this.textarea.setSelectionRange(start, end); + else if(this.textarea.createTextRange) { + // For IE8 + var e = this.textarea.createTextRange(); + e.collapse(true); + e.moveEnd('character', end); + e.moveStart('character', start); + e.select(); + } + this.textarea.focus(); +}; + +EditArea.prototype.getString = function() { + return this.textarea.value; +}; + +EditArea.prototype.setString = function(str) { + this.textarea.value = str; + this.initialText = this.getString(); + if (this.changelistener) { + this.changelistener.fireChanged(); + } +}; + +EditArea.prototype.getElem = function() { + return this.textarea; +}; + +module.exports = EditArea; diff --git a/src/edit/abc_editor.js b/src/edit/abc_editor.js new file mode 100644 index 0000000000000000000000000000000000000000..1e0266fe4241f5a893b3e9130551815a47ad09b3 --- /dev/null +++ b/src/edit/abc_editor.js @@ -0,0 +1,405 @@ +// window.ABCJS.Editor: +// +// constructor(editarea, params) +// if editarea is a string, then it is an HTML id of a textarea control. +// Otherwise, it should be an instantiation of an object that expresses the EditArea interface. +// +// params is a hash of: +// canvas_id: or paper_id: HTML id to draw in. If not present, then the drawing happens just below the editor. +// generate_midi: if present, then midi is generated. +// midi_id: if present, the HTML id to place the midi control. Otherwise it is placed in the same div as the paper. +// midi_download_id: if present, the HTML id to place the midi download link. Otherwise it is placed in the same div as the paper. +// generate_warnings: if present, then parser warnings are displayed on the page. +// warnings_id: if present, the HTML id to place the warnings. Otherwise they are placed in the same div as the paper. +// onchange: if present, the callback function to call whenever there has been a change. +// gui: if present, the paper can send changes back to the editor (presumably because the user changed something directly.) +// parser_options: options to send to the parser engine. +// midi_options: options to send to the midi engine. +// render_options: options to send to the render engine. +// indicate_changed: the dirty flag is set if this is true. +// +// - setReadOnly(bool) +// adds or removes the class abc_textarea_readonly, and adds or removes the attribute readonly=yes +// - setDirtyStyle(bool) +// adds or removes the class abc_textarea_dirty +// - modelChanged() +// Called when the model has been changed to trigger re-rendering +// - parseABC() +// Called internally by fireChanged() +// returns true if there has been a change since last call. +// - updateSelection() +// Called when the user has changed the selection. This calls the engraver to show the selection. +// - fireSelectionChanged() +// Called by the textarea object when the user has changed the selection. +// - paramChanged(engraverparams) +// Called to signal that the engraver params have changed, so re-rendering should occur. +// - fireChanged() +// Called by the textarea object when the user has changed something. +// - setNotDirty() +// Called by the client app to reset the dirty flag +// - isDirty() +// Returns true or false, whether the textarea contains the same text that it started with. +// - highlight(abcelem) +// Called by the engraver to highlight an area. +// - pause(bool) +// Stops the automatic rendering when the user is typing. +// +var parseCommon = require('../parse/abc_common'); +var SynthController = require('../synth/synth-controller'); +var supportsAudio = require('../synth/supports-audio'); +var renderAbc = require('../api/abc_tunebook_svg'); +var EditArea = require('./abc_editarea'); + +function gatherAbcParams(params) { + // There used to be a bunch of ways parameters can be passed in. This just simplifies it. + var abcjsParams = {}; + var key; + if (params.abcjsParams) { + for (key in params.abcjsParams) { + if (params.abcjsParams.hasOwnProperty(key)) { + abcjsParams[key] = params.abcjsParams[key]; + } + } + } + if (params.midi_options) { + for (key in params.midi_options) { + if (params.midi_options.hasOwnProperty(key)) { + abcjsParams[key] = params.midi_options[key]; + } + } + } + if (params.parser_options) { + for (key in params.parser_options) { + if (params.parser_options.hasOwnProperty(key)) { + abcjsParams[key] = params.parser_options[key]; + } + } + } + if (params.render_options) { + for (key in params.render_options) { + if (params.render_options.hasOwnProperty(key)) { + abcjsParams[key] = params.render_options[key]; + } + } + } + /* + if (params.tablature_options) { + abcjsParams['tablatures'] = params.tablature_options; + } + */ + if (abcjsParams.tablature) { + if (params.warnings_id) { + // store for plugin error handling + abcjsParams.tablature.warnings_id = params.warnings_id; + } + } + return abcjsParams; +} + +var Editor = function(editarea, params) { + // Copy all the options that will be passed through + this.abcjsParams = gatherAbcParams(params); + + if (params.indicate_changed) + this.indicate_changed = true; + if (typeof editarea === "string") { + this.editarea = new EditArea(editarea); + } else { + this.editarea = editarea; + } + this.editarea.addSelectionListener(this); + this.editarea.addChangeListener(this); + + if (params.canvas_id) { + this.div = params.canvas_id; + } else if (params.paper_id) { + this.div = params.paper_id; + } else { + this.div = document.createElement("DIV"); + this.editarea.getElem().parentNode.insertBefore(this.div, this.editarea.getElem()); + } + if (typeof this.div === 'string') + this.div = document.getElementById(this.div); + + if (params.selectionChangeCallback) { + this.selectionChangeCallback = params.selectionChangeCallback; + } + + this.clientClickListener = this.abcjsParams.clickListener; + this.abcjsParams.clickListener = this.highlight.bind(this); + + if (params.synth) { + if (supportsAudio()) { + this.synth = { + el: params.synth.el, + cursorControl: params.synth.cursorControl, + options: params.synth.options + }; + } + } + // If the user wants midi, then store the elements that it will be written to. The element could either be passed in as an id, + // an element, or nothing. If nothing is passed in, then just put the midi on top of the generated music. + if (params.generate_midi) { + this.generate_midi = params.generate_midi; + if (this.abcjsParams.generateDownload) { + if (typeof params.midi_download_id === 'string') + this.downloadMidi = document.getElementById(params.midi_download_id); + else if (params.midi_download_id) // assume, if the var is not a string it is an element. If not, it will crash soon enough. + this.downloadMidi = params.midi_download_id; + } + if (this.abcjsParams.generateInline !== false) { // The default for this is true, so undefined is also true. + if (typeof params.midi_id === 'string') + this.inlineMidi = document.getElementById(params.midi_id); + else if (params.midi_id) // assume, if the var is not a string it is an element. If not, it will crash soon enough. + this.inlineMidi = params.midi_id; + } + } + + if (params.warnings_id) { + if (typeof(params.warnings_id) === "string") + this.warningsdiv = document.getElementById(params.warnings_id); + else + this.warningsdiv = params.warnings_id; + } else if (params.generate_warnings) { + this.warningsdiv = document.createElement("div"); + this.div.parentNode.insertBefore(this.warningsdiv, this.div); + } + + this.onchangeCallback = params.onchange; + + this.currentAbc = ""; + this.tunes = []; + this.bReentry = false; + this.parseABC(); + this.modelChanged(); + + this.addClassName = function(element, className) { + var hasClassName = function(element, className) { + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName === className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }; + + if (!hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }; + + this.removeClassName = function(element, className) { + element.className = parseCommon.strip(element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ')); + return element; + }; + + this.setReadOnly = function(readOnly) { + var readonlyClass = 'abc_textarea_readonly'; + var el = this.editarea.getElem(); + if (readOnly) { + el.setAttribute('readonly', 'yes'); + this.addClassName(el, readonlyClass); + } else { + el.removeAttribute('readonly'); + this.removeClassName(el, readonlyClass); + } + }; +}; + +Editor.prototype.redrawMidi = function() { + if (this.generate_midi && !this.midiPause) { + var event = new window.CustomEvent("generateMidi", { + detail: { + tunes: this.tunes, + abcjsParams: this.abcjsParams, + downloadMidiEl: this.downloadMidi, + inlineMidiEl: this.inlineMidi, + engravingEl: this.div + } + }); + window.dispatchEvent(event); + } + if (this.synth) { + var userAction = this.synth.synthControl; // Can't really tell if there was a user action before drawing, but we assume that if the synthControl was created already there was a user action. + if (!this.synth.synthControl) { + this.synth.synthControl = new SynthController(); + this.synth.synthControl.load(this.synth.el, this.synth.cursorControl, this.synth.options); + } + this.synth.synthControl.setTune(this.tunes[0], userAction, this.synth.options); + } +}; + +Editor.prototype.modelChanged = function() { + if (this.bReentry) + return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc + this.bReentry = true; + try { + this.timerId = null; + if (this.synth && this.synth.synthControl) + this.synth.synthControl.disable(true); + + this.tunes = renderAbc(this.div, this.currentAbc, this.abcjsParams); + if (this.tunes.length > 0) { + this.warnings = this.tunes[0].warnings; + } + this.redrawMidi(); + } catch(error) { + console.error("ABCJS error: ", error); + if (!this.warnings) + this.warnings = []; + this.warnings.push(error.message); + } + + if (this.warningsdiv) { + this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("
") : "No errors"; + } + this.updateSelection(); + this.bReentry = false; +}; + +// Call this to reparse in response to the client changing the parameters on the fly +Editor.prototype.paramChanged = function(engraverParams) { + if (engraverParams) { + for (var key in engraverParams) { + if (engraverParams.hasOwnProperty(key)) { + this.abcjsParams[key] = engraverParams[key]; + } + } + } + this.currentAbc = ""; + this.fireChanged(); +}; + +Editor.prototype.synthParamChanged = function(options) { + if (!this.synth) + return; + this.synth.options = {}; + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + this.synth.options[key] = options[key]; + } + } + } + this.currentAbc = ""; + this.fireChanged(); +}; + +// return true if the model has changed +Editor.prototype.parseABC = function() { + var t = this.editarea.getString(); + if (t===this.currentAbc) { + this.updateSelection(); + return false; + } + + this.currentAbc = t; + return true; +}; + +Editor.prototype.updateSelection = function() { + var selection = this.editarea.getSelection(); + try { + if (this.tunes.length > 0 && this.tunes[0].engraver) + this.tunes[0].engraver.rangeHighlight(selection.start, selection.end); + } catch (e) {} // maybe printer isn't defined yet? + if (this.selectionChangeCallback) + this.selectionChangeCallback(selection.start, selection.end); +}; + +// Called when the textarea's selection is in the process of changing (after mouse down, dragging, or keyboard arrows) +Editor.prototype.fireSelectionChanged = function() { + this.updateSelection(); +}; + +Editor.prototype.setDirtyStyle = function(isDirty) { + if (this.indicate_changed === undefined) + return; + var addClassName = function(element, className) { + var hasClassName = function(element, className) { + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName === className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }; + + if (!hasClassName(element, className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }; + + var removeClassName = function(element, className) { + element.className = parseCommon.strip(element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ')); + return element; + }; + + var readonlyClass = 'abc_textarea_dirty'; + var el = this.editarea.getElem(); + if (isDirty) { + addClassName(el, readonlyClass); + } else { + removeClassName(el, readonlyClass); + } +}; + +// call when the textarea alerts us that the abc text is changed and needs re-parsing +Editor.prototype.fireChanged = function() { + if (this.bIsPaused) + return; + if (this.parseABC()) { + var self = this; + if (this.timerId) // If the user is still typing, cancel the update + clearTimeout(this.timerId); + this.timerId = setTimeout(function () { + self.modelChanged(); + }, 300); // Is this a good compromise between responsiveness and not redrawing too much? + var isDirty = this.isDirty(); + if (this.wasDirty !== isDirty) { + this.wasDirty = isDirty; + this.setDirtyStyle(isDirty); + } + if (this.onchangeCallback) + this.onchangeCallback(this); + } +}; + +Editor.prototype.setNotDirty = function() { + this.editarea.initialText = this.editarea.getString(); + this.wasDirty = false; + this.setDirtyStyle(false); +}; + +Editor.prototype.isDirty = function() { + if (this.indicate_changed === undefined) + return false; + return this.editarea.initialText !== this.editarea.getString(); +}; + +Editor.prototype.highlight = function(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) { + // TODO-PER: The marker appears to get off by one for each tune parsed. I'm not sure why, but adding the tuneNumber in corrects it for the time being. +// var offset = (tuneNumber !== undefined) ? this.startPos[tuneNumber] + tuneNumber : 0; + + this.editarea.setSelection(abcelem.startChar, abcelem.endChar); + if (this.selectionChangeCallback) + this.selectionChangeCallback(abcelem.startChar, abcelem.endChar); + if (this.clientClickListener) + this.clientClickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent); +}; + +Editor.prototype.pause = function(shouldPause) { + this.bIsPaused = shouldPause; + if (!shouldPause) + this.fireChanged(); +}; + +Editor.prototype.millisecondsPerMeasure = function() { + if (!this.synth || !this.synth.synthControl || !this.synth.synthControl.visualObj) + return 0; + return this.synth.synthControl.visualObj.millisecondsPerMeasure(); +}; + +Editor.prototype.pauseMidi = function(shouldPause) { + this.midiPause = shouldPause; + if (!shouldPause) + this.redrawMidi(); +}; + +module.exports = Editor; diff --git a/src/midi/abc_midi_create.js b/src/midi/abc_midi_create.js new file mode 100644 index 0000000000000000000000000000000000000000..ff299d44fad750abfdfa874987d16b6d7d0c6b06 --- /dev/null +++ b/src/midi/abc_midi_create.js @@ -0,0 +1,115 @@ +// abc_midi_create.js: Turn a linear series of events into a midi file. + +var rendererFactory = require('../synth/abc_midi_renderer'); + +var create; + +(function() { + "use strict"; + + var baseDuration = 480*4; // nice and divisible, equals 1 whole note + + create = function create(abcTune, options) { + if (options === undefined) options = {}; + var commands = abcTune.setUpAudio(options); + var midi = rendererFactory(); + var title = abcTune.metaText ? abcTune.metaText.title : undefined; + if (title && title.length > 128) title = title.substring(0, 124) + '...'; + var key = abcTune.getKeySignature(); + var time = abcTune.getMeterFraction(); + + // MAE 7 July 2024 - Fix for */8 meter tempos + var tempo = commands.tempo; + + var beatsPerSecond = tempo / 60; + + // Fix tempo for */8 meters + if (time.den == 8){ + + // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm. + var msPerMeasure = abcTune.millisecondsPerMeasure(); + + tempo = (60000 / (msPerMeasure/time.num)) / 2; + + beatsPerSecond = tempo/60; + + } + + //var beatLength = abcTune.getBeatLength(); + midi.setGlobalInfo(tempo, title, key, time); + + for (var i = 0; i < commands.tracks.length; i++) { + midi.startTrack(); + var notePlacement = {}; + for (var j = 0; j < commands.tracks[i].length; j++) { + var event = commands.tracks[i][j]; + switch (event.cmd) { + case 'text': + midi.setText(event.type, event.text); + break; + case 'program': + var pan = 0; + if (options.pan && options.pan.length > i) + pan = options.pan[i]; + if (event.instrument === 128) { + // If we're using the percussion voice, change to Channel 10 + midi.setChannel(9, pan); + midi.setInstrument(0); + } else { + midi.setChannel(event.channel, pan); + midi.setInstrument(event.instrument); + } + break; + case 'note': + var gapLengthInBeats = event.gap * beatsPerSecond; + var start = event.start; + // The staccato and legato are indicated by event.gap. + // event.gap is in seconds but the durations are in whole notes. + var end = start + event.duration - gapLengthInBeats; + if (!notePlacement[start]) + notePlacement[start] = []; + notePlacement[start].push({ pitch: event.pitch, volume: event.volume, cents: event.cents }); + if (!notePlacement[end]) + notePlacement[end] = []; + notePlacement[end].push({ pitch: event.pitch, volume: 0 }); + break; + default: + console.log("MIDI create Unknown: " + event.cmd); + } + } + addNotes(midi, notePlacement, baseDuration); + midi.endTrack(); + } + + return midi.getData(); + }; + + function addNotes(midi, notePlacement, baseDuration) { + var times = Object.keys(notePlacement); + for (var h = 0; h < times.length; h++) + times[h] = parseFloat(times[h]); + times.sort(function(a,b) { + return a - b; + }); + var lastTime = 0; + for (var i = 0; i < times.length; i++) { + var events = notePlacement[times[i]]; + if (times[i] > lastTime) { + var distance = (times[i] - lastTime) * baseDuration; + midi.addRest(distance); + lastTime = times[i]; + } + for (var j = 0; j < events.length; j++) { + var event = events[j]; + if (event.volume) { + midi.startNote(event.pitch, event.volume, event.cents); + } else { + midi.endNote(event.pitch); + } + } + } + } + +})(); + +module.exports = create; diff --git a/src/parse/abc_common.js b/src/parse/abc_common.js new file mode 100644 index 0000000000000000000000000000000000000000..a4a1e762d90c7ad9cfb6abc94d4f7eac8dbb9604 --- /dev/null +++ b/src/parse/abc_common.js @@ -0,0 +1,49 @@ +// abc_common.js: Some common utility functions. + +var parseCommon = {}; + +parseCommon.cloneArray = function(source) { + var destination = []; + for (var i = 0; i < source.length; i++) { + destination.push(Object.assign({},source[i])); + } + return destination; +}; + +parseCommon.cloneHashOfHash = function(source) { + var destination = {}; + for (var property in source) + if (source.hasOwnProperty(property)) + destination[property] = Object.assign({},source[property]); + return destination; +}; + +parseCommon.cloneHashOfArrayOfHash = function(source) { + var destination = {}; + for (var property in source) + if (source.hasOwnProperty(property)) + destination[property] = parseCommon.cloneArray(source[property]); + return destination; +}; + +parseCommon.strip = function(str) { + return str.replace(/^\s+/, '').replace(/\s+$/, ''); +}; + +parseCommon.startsWith = function(str, pattern) { + return str.indexOf(pattern) === 0; +}; + +parseCommon.endsWith = function(str, pattern) { + var d = str.length - pattern.length; + return d >= 0 && str.lastIndexOf(pattern) === d; +}; + +parseCommon.last = function(arr) { + if (arr.length === 0) + return null; + return arr[arr.length-1]; +}; + + +module.exports = parseCommon; diff --git a/src/parse/abc_parse.js b/src/parse/abc_parse.js new file mode 100644 index 0000000000000000000000000000000000000000..f9643c8d81be2a83431bf2a280aca85c7eebf719 --- /dev/null +++ b/src/parse/abc_parse.js @@ -0,0 +1,599 @@ +// abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure. + +var parseCommon = require('./abc_common'); +var parseDirective = require('./abc_parse_directive'); +var ParseHeader = require('./abc_parse_header'); +var ParseMusic = require('./abc_parse_music'); +var Tokenizer = require('./abc_tokenizer'); +var wrap = require('./wrap_lines'); + +var Tune = require('../data/abc_tune'); +var TuneBuilder = require('../parse/tune-builder'); + +var Parse = function() { + "use strict"; + var tune = new Tune(); + var tuneBuilder = new TuneBuilder(tune); + var tokenizer; + var wordsContinuation = ''; + var symbolContinuation = ''; + + this.getTune = function() { + var t = { + formatting: tune.formatting, + lines: tune.lines, + media: tune.media, + metaText: tune.metaText, + metaTextInfo: tune.metaTextInfo, + version: tune.version, + + addElementToEvents: tune.addElementToEvents, + addUsefulCallbackInfo: tune.addUsefulCallbackInfo, + getTotalTime: tune.getTotalTime, + getTotalBeats: tune.getTotalBeats, + getBarLength: tune.getBarLength, + getBeatLength: tune.getBeatLength, + getBeatsPerMeasure: tune.getBeatsPerMeasure, + getBpm: tune.getBpm, + getMeter: tune.getMeter, + getMeterFraction: tune.getMeterFraction, + getPickupLength: tune.getPickupLength, + getKeySignature: tune.getKeySignature, + getElementFromChar: tune.getElementFromChar, + makeVoicesArray: tune.makeVoicesArray, + millisecondsPerMeasure: tune.millisecondsPerMeasure, + setupEvents: tune.setupEvents, + setTiming: tune.setTiming, + setUpAudio: tune.setUpAudio, + deline: tune.deline, + findSelectableElement: tune.findSelectableElement, + getSelectableArray: tune.getSelectableArray, + }; + if (tune.lineBreaks) + t.lineBreaks = tune.lineBreaks; + if (tune.visualTranspose) + t.visualTranspose = tune.visualTranspose; + return t; + }; + + function addPositioning(el, type, value) { + if (!el.positioning) el.positioning = {}; + el.positioning[type] = value; + } + + function addFont(el, type, value) { + if (!el.fonts) el.fonts = {}; + el.fonts[type] = value; + } + + var multilineVars = { + reset: function() { + for (var property in this) { + if (this.hasOwnProperty(property) && typeof this[property] !== "function") { + delete this[property]; + } + } + this.iChar = 0; + this.key = {accidentals: [], root: 'none', acc: '', mode: '' }; + this.meter = null; // if no meter is specified, free meter is assumed + this.origMeter = null; // this is for new voices that are created after we set the meter. + this.hasMainTitle = false; + this.default_length = 0.125; + this.clef = { type: 'treble', verticalPos: 0 }; + this.octave = 0; + this.next_note_duration = 0; + this.start_new_line = true; + this.is_in_header = true; + this.partForNextLine = {}; + this.tempoForNextLine = []; + this.havent_set_length = true; + this.voices = {}; + this.staves = []; + this.macros = {}; + this.currBarNumber = 1; + this.barCounter = {}; + this.ignoredDecorations = []; + this.score_is_present = false; // Can't have original V: lines when there is the score directive + this.inEnding = false; + this.inTie = []; + this.inTieChord = {}; + this.vocalPosition = "auto"; + this.dynamicPosition = "auto"; + this.chordPosition = "auto"; + this.ornamentPosition = "auto"; + this.volumePosition = "auto"; + this.openSlurs = []; + this.freegchord = false; + this.endingHoldOver = {}; + }, + differentFont: function(type, defaultFonts) { + if (this[type].decoration !== defaultFonts[type].decoration) return true; + if (this[type].face !== defaultFonts[type].face) return true; + if (this[type].size !== defaultFonts[type].size) return true; + if (this[type].style !== defaultFonts[type].style) return true; + if (this[type].weight !== defaultFonts[type].weight) return true; + return false; + }, + addFormattingOptions: function(el, defaultFonts, elType) { + if (elType === 'note') { + if (this.vocalPosition !== 'auto') addPositioning(el, 'vocalPosition', this.vocalPosition); + if (this.dynamicPosition !== 'auto') addPositioning(el, 'dynamicPosition', this.dynamicPosition); + if (this.chordPosition !== 'auto') addPositioning(el, 'chordPosition', this.chordPosition); + if (this.ornamentPosition !== 'auto') addPositioning(el, 'ornamentPosition', this.ornamentPosition); + if (this.volumePosition !== 'auto') addPositioning(el, 'volumePosition', this.volumePosition); + if (this.differentFont("annotationfont", defaultFonts)) addFont(el, 'annotationfont', this.annotationfont); + if (this.differentFont("gchordfont", defaultFonts)) addFont(el, 'gchordfont', this.gchordfont); + if (this.differentFont("vocalfont", defaultFonts)) addFont(el, 'vocalfont', this.vocalfont); + if (this.differentFont("tripletfont", defaultFonts)) addFont(el, 'tripletfont', this.tripletfont); + } else if (elType === 'bar') { + if (this.dynamicPosition !== 'auto') addPositioning(el, 'dynamicPosition', this.dynamicPosition); + if (this.chordPosition !== 'auto') addPositioning(el, 'chordPosition', this.chordPosition); + if (this.ornamentPosition !== 'auto') addPositioning(el, 'ornamentPosition', this.ornamentPosition); + if (this.volumePosition !== 'auto') addPositioning(el, 'volumePosition', this.volumePosition); + if (this.differentFont("measurefont", defaultFonts)) addFont(el, 'measurefont', this.measurefont); + if (this.differentFont("repeatfont", defaultFonts)) addFont(el, 'repeatfont', this.repeatfont); + } + }, + duplicateStartEndingHoldOvers: function() { + this.endingHoldOver = { + inTie: [], + inTieChord: {} + }; + for (var i = 0; i < this.inTie.length; i++) { + this.endingHoldOver.inTie.push([]); + if (this.inTie[i]) { // if a voice is suppressed there might be a gap in the array. + for (var j = 0; j < this.inTie[i].length; j++) { + this.endingHoldOver.inTie[i].push(this.inTie[i][j]); + } + } + } + for (var key in this.inTieChord) { + if (this.inTieChord.hasOwnProperty(key)) + this.endingHoldOver.inTieChord[key] = this.inTieChord[key]; + } + }, + restoreStartEndingHoldOvers: function() { + if (!this.endingHoldOver.inTie) + return; + this.inTie = []; + this.inTieChord = {}; + for (var i = 0; i < this.endingHoldOver.inTie.length; i++) { + this.inTie.push([]); + for (var j = 0; j < this.endingHoldOver.inTie[i].length; j++) { + this.inTie[i].push(this.endingHoldOver.inTie[i][j]); + } + } + for (var key in this.endingHoldOver.inTieChord) { + if (this.endingHoldOver.inTieChord.hasOwnProperty(key)) + this.inTieChord[key] = this.endingHoldOver.inTieChord[key]; + } + }, + }; + + var addWarning = function(str) { + if (!multilineVars.warnings) + multilineVars.warnings = []; + multilineVars.warnings.push(str); + }; + + var addWarningObject = function(warningObject) { + if (!multilineVars.warningObjects) + multilineVars.warningObjects = []; + multilineVars.warningObjects.push(warningObject); + }; + + var encode = function(str) { + var ret = str.replace(/\x12/g, ' '); + ret = ret.replace(/&/g, '&'); + ret = ret.replace(//g, '>'); + }; + + var warn = function(str, line, col_num) { + if (!line) line = " "; + var bad_char = line[col_num]; + if (bad_char === ' ' || !bad_char) + bad_char = "SPACE"; + var clean_line = encode(line.substring(col_num - 64, col_num)) + '' + bad_char + '' + encode(line.substring(col_num + 1).substring(0,64)); + addWarning("Music Line:" + tokenizer.lineIndex + ":" + (col_num+1) + ': ' + str + ": " + clean_line); + addWarningObject({message:str, line:line, startChar: multilineVars.iChar + col_num, column: col_num}); + }; + + var header; + var music; + + this.getWarnings = function() { + return multilineVars.warnings; + }; + this.getWarningObjects = function() { + return multilineVars.warningObjects; + }; + + var addWords = function(line, words) { + if (words.indexOf('\x12') >= 0) { + wordsContinuation += words + return + } + words = wordsContinuation + words + wordsContinuation = '' + + if (!line) { warn("Can't add words before the first line of music", line, 0); return; } + words = parseCommon.strip(words); + if (words[words.length-1] !== '-') + words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it. + var word_list = []; + // first make a list of words from the string we are passed. A word is divided on either a space or dash. + var last_divider = 0; + var replace = false; + var addWord = function(i) { + var word = parseCommon.strip(words.substring(last_divider, i)); + word = word.replace(/\\([-_*|~])/g, '$1') + last_divider = i+1; + if (word.length > 0) { + if (replace) + word = word.replace(/~/g, ' '); + var div = words[i]; + if (div !== '_' && div !== '-') + div = ' '; + word_list.push({syllable: tokenizer.translateString(word), divider: div}); + replace = false; + return true; + } + return false; + }; + var escNext = false; + for (var i = 0; i < words.length; i++) { + switch (words[i]) { + case ' ': + case '\x12': + addWord(i); + break; + case '-': + if (!escNext && !addWord(i) && word_list.length > 0) { + parseCommon.last(word_list).divider = '-'; + word_list.push({skip: true, to: 'next'}); + } + break; + case '_': + if (!escNext) { + addWord(i); + word_list.push({skip: true, to: 'slur'}); + } + break; + case '*': + if (!escNext) { + addWord(i); + word_list.push({skip: true, to: 'next'}); + } + break; + case '|': + if (!escNext) { + addWord(i); + word_list.push({skip: true, to: 'bar'}); + } + break; + case '~': + if (!escNext) { + replace = true; + } + break; + } + escNext = words[i] === '\\' + } + + var inSlur = false; + line.forEach(function(el) { + if (word_list.length !== 0) { + if (word_list[0].skip) { + switch (word_list[0].to) { + case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break; + case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break; + case 'bar': if (el.el_type === 'bar') word_list.shift(); break; + } + if (el.el_type !== 'bar') { + if (el.lyric === undefined) + el.lyric = [{syllable: "", divider: " "}]; + else + el.lyric.push({syllable: "", divider: " "}); + } + } else { + if (el.el_type === 'note' && el.rest === undefined && !inSlur) { + var lyric = word_list.shift(); + if (lyric.syllable) + lyric.syllable = lyric.syllable.replace(/ +/g,'\xA0'); + if (el.lyric === undefined) + el.lyric = [ lyric ]; + else + el.lyric.push(lyric); + } + } + } + }); + }; + + var addSymbols = function(line, words) { + if (words.indexOf('\x12') >= 0) { + symbolContinuation += words + return + } + words = symbolContinuation + words + symbolContinuation = '' + + // TODO-PER: Currently copied from w: line. This needs to be read as symbols instead. + if (!line) { warn("Can't add symbols before the first line of music", line, 0); return; } + words = parseCommon.strip(words); + if (words[words.length-1] !== '-') + words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it. + var word_list = []; + // first make a list of words from the string we are passed. A word is divided on either a space or dash. + var last_divider = 0; + var replace = false; + var addWord = function(i) { + var word = parseCommon.strip(words.substring(last_divider, i)); + last_divider = i+1; + if (word.length > 0) { + if (replace) + word = word.replace(/~/g, ' '); + var div = words[i]; + if (div !== '_' && div !== '-') + div = ' '; + word_list.push({syllable: tokenizer.translateString(word), divider: div}); + replace = false; + return true; + } + return false; + }; + for (var i = 0; i < words.length; i++) { + switch (words[i]) { + case ' ': + case '\x12': + addWord(i); + break; + case '-': + if (!addWord(i) && word_list.length > 0) { + parseCommon.last(word_list).divider = '-'; + word_list.push({skip: true, to: 'next'}); + } + break; + case '_': + addWord(i); + word_list.push({skip: true, to: 'slur'}); + break; + case '*': + addWord(i); + word_list.push({skip: true, to: 'next'}); + break; + case '|': + addWord(i); + word_list.push({skip: true, to: 'bar'}); + break; + case '~': + replace = true; + break; + } + } + + var inSlur = false; + line.forEach(function(el) { + if (word_list.length !== 0) { + if (word_list[0].skip) { + switch (word_list[0].to) { + case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break; + case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break; + case 'bar': if (el.el_type === 'bar') word_list.shift(); break; + } + } else { + if (el.el_type === 'note' && el.rest === undefined && !inSlur) { + var lyric = word_list.shift(); + if (el.lyric === undefined) + el.lyric = [ lyric ]; + else + el.lyric.push(lyric); + } + } + } + }); + }; + + var parseLine = function(line) { + if (parseCommon.startsWith(line, '%%')) { + var err = parseDirective.addDirective(line.substring(2)); + if (err) warn(err, line, 2); + return; + } + + var i = line.indexOf('%'); + if (i >= 0) + line = line.substring(0, i); + line = line.replace(/\s+$/, ''); + + if (line.length === 0) + return; + + if (wordsContinuation) { + addWords(tuneBuilder.getCurrentVoice(), line.substring(2)); + return + } + if (symbolContinuation) { + addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2)); + return + } + if (line.length < 2 || line[1] !== ':' || music.lineContinuation) { + music.parseMusic(line); + return + } + + var ret = header.parseHeader(line); + if (ret.regular) + music.parseMusic(line); + if (ret.newline) + music.startNewLine(); + if (ret.words) + addWords(tuneBuilder.getCurrentVoice(), line.substring(2)); + if (ret.symbols) + addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2)); + }; + + function appendLastMeasure(voice, nextVoice) { + voice.push({ + el_type: 'hint' + }); + for (var i = 0; i < nextVoice.length; i++) { + var element = nextVoice[i]; + var hint = Object.assign({},element); + voice.push(hint); + if (element.el_type === 'bar') + return; + } + } + + function addHintMeasure(staff, nextStaff) { + for (var i = 0; i < staff.length; i++) { + var stave = staff[i]; + var nextStave = nextStaff[i]; + if (nextStave) { // Be sure there is the same number of staves on the next line. + for (var j = 0; j < nextStave.voices.length; j++) { + var nextVoice = nextStave.voices[j]; + var voice = stave.voices[j]; + if (voice) { // Be sure there are the same number of voices on the previous line. + appendLastMeasure(voice, nextVoice); + } + } + } + } + } + + function addHintMeasures() { + for (var i = 0; i < tune.lines.length; i++) { + var line = tune.lines[i].staff; + if (line) { + var j = i+1; + while (j < tune.lines.length && tune.lines[j].staff === undefined) + j++; + if (j < tune.lines.length) { + var nextLine = tune.lines[j].staff; + addHintMeasure(line, nextLine); + } + } + } + } + + this.parse = function(strTune, switches, startPos) { + // the switches are optional and cause a difference in the way the tune is parsed. + // switches.header_only : stop parsing when the header is finished + // switches.stop_on_warning : stop at the first warning encountered. + // switches.print: format for the page instead of the browser. + // switches.format: a hash of the desired formatting commands. + // switches.hint_measures: put the next measure at the end of the current line. + // switches.transpose: change the key signature, chords, and notes by a number of half-steps. + if (!switches) switches = {}; + if (!startPos) startPos = 0; + tune.reset(); + + // Take care of whatever line endings come our way + // Tack on newline temporarily to make the last line continuation work + strTune = strTune.replace(/\r\n?/g, '\n') + '\n'; + + // get rid of latex commands. If a line starts with a backslash, then it is replaced by spaces to keep the character count the same. + var arr = strTune.split("\n\\"); + if (arr.length > 1) { + for (var i2 = 1; i2 < arr.length; i2++) { + while (arr[i2].length > 0 && arr[i2][0] !== "\n") { + arr[i2] = arr[i2].substr(1); + arr[i2-1] += ' '; + } + } + strTune = arr.join(" "); //. the split removed two characters, so this puts them back + } + // take care of line continuations right away, but keep the same number of characters + strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, function(all, backslash, comment){ + var padding = comment ? Array(comment.length +1).join(' ') : ""; + return backslash + "\x12" + padding + '\n'; + }); + var lines = strTune.split('\n') + if (parseCommon.last(lines).length === 0) // remove the blank line we added above. + lines.pop(); + tokenizer = new Tokenizer(lines, multilineVars); + header = new ParseHeader(tokenizer, warn, multilineVars, tune, tuneBuilder); + music = new ParseMusic(tokenizer, warn, multilineVars, tune, tuneBuilder, header); + + if (switches.print) + tune.media = 'print'; + multilineVars.reset(); + multilineVars.iChar = startPos; + if (switches.visualTranspose) { + multilineVars.globalTranspose = parseInt(switches.visualTranspose); + if (multilineVars.globalTranspose === 0) + multilineVars.globalTranspose = undefined; + else + tuneBuilder.setVisualTranspose(switches.visualTranspose); + } else + multilineVars.globalTranspose = undefined; + if (switches.lineBreaks) { + // The line break numbers are 0-based and they reflect the last measure of the current line. + multilineVars.lineBreaks = switches.lineBreaks; + //multilineVars.continueall = true; + } + header.reset(tokenizer, warn, multilineVars, tune); + + try { + if (switches.format) { + parseDirective.globalFormatting(switches.format); + } + var line = tokenizer.nextLine(); + while (line) { + if (switches.header_only && multilineVars.is_in_header === false) + throw "normal_abort"; + if (switches.stop_on_warning && multilineVars.warnings) + throw "normal_abort"; + + var wasInHeader = multilineVars.is_in_header; + parseLine(line); + if (wasInHeader && !multilineVars.is_in_header) { + tuneBuilder.setRunningFont("annotationfont", multilineVars.annotationfont); + tuneBuilder.setRunningFont("gchordfont", multilineVars.gchordfont); + tuneBuilder.setRunningFont("tripletfont", multilineVars.tripletfont); + tuneBuilder.setRunningFont("vocalfont", multilineVars.vocalfont); + } + line = tokenizer.nextLine(); + } + + if (wordsContinuation) { + addWords(tuneBuilder.getCurrentVoice(), ''); + } + if (symbolContinuation) { + addSymbols(tuneBuilder.getCurrentVoice(), ''); + } + multilineVars.openSlurs = tuneBuilder.cleanUp(multilineVars.barsperstaff, multilineVars.staffnonote, multilineVars.openSlurs); + + } catch (err) { + if (err !== "normal_abort") + throw err; + } + + var ph = 11*72; + var pl = 8.5*72; + switch (multilineVars.papersize) { + //case "letter": ph = 11*72; pl = 8.5*72; break; + case "legal": ph = 14*72; pl = 8.5*72; break; + case "A4": ph = 11.7*72; pl = 8.3*72; break; + } + if (multilineVars.landscape) { + var x = ph; + ph = pl; + pl = x; + } + if (!tune.formatting.pagewidth) + tune.formatting.pagewidth = pl; + if (!tune.formatting.pageheight) + tune.formatting.pageheight = ph; + + if (switches.hint_measures) { + addHintMeasures(); + } + + wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers); + }; +}; + +module.exports = Parse; diff --git a/src/parse/abc_parse_book.js b/src/parse/abc_parse_book.js new file mode 100644 index 0000000000000000000000000000000000000000..f929f30ef3af3aec256ea1a665acae82452bba5e --- /dev/null +++ b/src/parse/abc_parse_book.js @@ -0,0 +1,63 @@ +// abc_parse_book.js: parses a string representing ABC Music Notation into a usable internal structure. + +var parseCommon = require('./abc_common'); + +var bookParser = function(book) { + "use strict"; + + var directives = ""; + var initialWhiteSpace = book.match(/(\s*)/) + book = parseCommon.strip(book); + var tuneStrings = book.split("\nX:"); + // Put back the X: that we lost when splitting the tunes. + for (var i = 1; i < tuneStrings.length; i++) + tuneStrings[i] = "X:" + tuneStrings[i]; + // Keep track of the character position each tune starts with. If the string starts with white space, count that, too. + var pos = initialWhiteSpace ? initialWhiteSpace[0].length : 0; + var tunes = []; + tuneStrings.forEach(function(tune) { + tunes.push({ abc: tune, startPos: pos}); + pos += tune.length + 1; // We also lost a newline when splitting, so count that. + }); + if (tunes.length > 1 && !parseCommon.startsWith(tunes[0].abc, 'X:')) { // If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune" + // There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with + // just looking for file-wide directives here (before the first tune) and inserting them at the bottom of each tune, since + // the tune is parsed all at once. The directives will be seen before the engraver begins processing. + var dir = tunes.shift(); + var arrDir = dir.abc.split('\n'); + arrDir.forEach(function(line) { + if (parseCommon.startsWith(line, '%%')) + directives += line + '\n'; + }); + } + var header = directives; + + // Now, the tune ends at a blank line, so truncate it if needed. There may be "intertune" stuff. + tunes.forEach(function(tune) { + var end = tune.abc.indexOf('\n\n'); + if (end > 0) + tune.abc = tune.abc.substring(0, end); + tune.pure = tune.abc; + tune.abc = directives + tune.abc; + + // for the user's convenience, parse and store the title separately. The title is between the first T: and the next \n + tune.title = ""; + var title = tune.pure.split("T:"); + if (title.length > 1) { + title = title[1].split("\n"); + tune.title = parseCommon.strip(title[0]); + } + + // for the user's convenience, parse and store the id separately. The id is between the first X: and the next \n + var id = tune.pure.substring(2, tune.pure.indexOf("\n")); + tune.id = parseCommon.strip(id); + }); + + return { + header: header, + tunes: tunes + }; +}; + +module.exports = bookParser; + diff --git a/src/parse/abc_parse_directive.js b/src/parse/abc_parse_directive.js new file mode 100644 index 0000000000000000000000000000000000000000..384125d20beae1514b8c37fd5096df8568c75547 --- /dev/null +++ b/src/parse/abc_parse_directive.js @@ -0,0 +1,1278 @@ +var parseCommon = require('./abc_common'); + +var parseDirective = {}; + +(function() { + "use strict"; + var tokenizer; + var warn; + var multilineVars; + var tune; + var tuneBuilder; + parseDirective.initialize = function(tokenizer_, warn_, multilineVars_, tune_, tuneBuilder_) { + tokenizer = tokenizer_; + warn = warn_; + multilineVars = multilineVars_; + tune = tune_; + tuneBuilder = tuneBuilder_; + initializeFonts(); + }; + + function initializeFonts() { + multilineVars.annotationfont = { face: "Helvetica", size: 12, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.gchordfont = { face: "Helvetica", size: 12, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.historyfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.infofont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; + multilineVars.measurefont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; + multilineVars.partsfont = { face: "\"Times New Roman\"", size: 15, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.repeatfont = { face: "\"Times New Roman\"", size: 13, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.textfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; + multilineVars.tripletfont = {face: "Times", size: 11, weight: "normal", style: "italic", decoration: "none"}; + multilineVars.vocalfont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" }; + multilineVars.wordsfont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; + + // These fonts are global for the entire tune. + tune.formatting.composerfont = { face: "\"Times New Roman\"", size: 14, weight: "normal", style: "italic", decoration: "none" }; + tune.formatting.subtitlefont = { face: "\"Times New Roman\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.tempofont = { face: "\"Times New Roman\"", size: 15, weight: "bold", style: "normal", decoration: "none" }; + tune.formatting.titlefont = { face: "\"Times New Roman\"", size: 20, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.footerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.headerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.voicefont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" }; + tune.formatting.tablabelfont = { face: "\"Trebuchet MS\"", size: 16, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.tabnumberfont = { face: "\"Arial\"", size: 11, weight: "normal", style: "normal", decoration: "none" }; + tune.formatting.tabgracefont = { face: "\"Arial\"", size: 8, weight: "normal", style: "normal", decoration: "none" }; + + // these are the default fonts for these element types. In the printer, these fonts might change as the tune progresses. + tune.formatting.annotationfont = multilineVars.annotationfont; + tune.formatting.gchordfont = multilineVars.gchordfont; + tune.formatting.historyfont = multilineVars.historyfont; + tune.formatting.infofont = multilineVars.infofont; + tune.formatting.measurefont = multilineVars.measurefont; + tune.formatting.partsfont = multilineVars.partsfont; + tune.formatting.repeatfont = multilineVars.repeatfont; + tune.formatting.textfont = multilineVars.textfont; + tune.formatting.tripletfont = multilineVars.tripletfont; + tune.formatting.vocalfont = multilineVars.vocalfont; + tune.formatting.wordsfont = multilineVars.wordsfont; + } + + var fontTypeCanHaveBox = { gchordfont: true, measurefont: true, partsfont: true, annotationfont: true, composerfont: true, historyfont: true, infofont: true, subtitlefont: true, textfont: true, titlefont: true, voicefont: true }; + + var fontTranslation = function(fontFace) { + // This translates Postscript fonts for a web alternative. + // Note that the postscript fonts contain italic and bold info in them, so what is returned is a hash. + + switch (fontFace) { + case "Arial-Italic": + return { face: "Arial", weight: "normal", style: "italic", decoration: "none" }; + case "Arial-Bold": + return { face: "Arial", weight: "bold", style: "normal", decoration: "none" }; + case "Bookman-Demi": + return { face: "Bookman,serif", weight: "bold", style: "normal", decoration: "none" }; + case "Bookman-DemiItalic": + return { face: "Bookman,serif", weight: "bold", style: "italic", decoration: "none" }; + case "Bookman-Light": + return { face: "Bookman,serif", weight: "normal", style: "normal", decoration: "none" }; + case "Bookman-LightItalic": + return { face: "Bookman,serif", weight: "normal", style: "italic", decoration: "none" }; + case "Courier": + return { face: "\"Courier New\"", weight: "normal", style: "normal", decoration: "none" }; + case "Courier-Oblique": + return { face: "\"Courier New\"", weight: "normal", style: "italic", decoration: "none" }; + case "Courier-Bold": + return { face: "\"Courier New\"", weight: "bold", style: "normal", decoration: "none" }; + case "Courier-BoldOblique": + return { face: "\"Courier New\"", weight: "bold", style: "italic", decoration: "none" }; + case "AvantGarde-Book": + return { face: "AvantGarde,Arial", weight: "normal", style: "normal", decoration: "none" }; + case "AvantGarde-BookOblique": + return { face: "AvantGarde,Arial", weight: "normal", style: "italic", decoration: "none" }; + case "AvantGarde-Demi": + case "Avant-Garde-Demi": + return { face: "AvantGarde,Arial", weight: "bold", style: "normal", decoration: "none" }; + case "AvantGarde-DemiOblique": + return { face: "AvantGarde,Arial", weight: "bold", style: "italic", decoration: "none" }; + case "Helvetica-Oblique": + return { face: "Helvetica", weight: "normal", style: "italic", decoration: "none" }; + case "Helvetica-Bold": + return { face: "Helvetica", weight: "bold", style: "normal", decoration: "none" }; + case "Helvetica-BoldOblique": + return { face: "Helvetica", weight: "bold", style: "italic", decoration: "none" }; + case "Helvetica-Narrow": + return { face: "\"Helvetica Narrow\",Helvetica", weight: "normal", style: "normal", decoration: "none" }; + case "Helvetica-Narrow-Oblique": + return { face: "\"Helvetica Narrow\",Helvetica", weight: "normal", style: "italic", decoration: "none" }; + case "Helvetica-Narrow-Bold": + return { face: "\"Helvetica Narrow\",Helvetica", weight: "bold", style: "normal", decoration: "none" }; + case "Helvetica-Narrow-BoldOblique": + return { face: "\"Helvetica Narrow\",Helvetica", weight: "bold", style: "italic", decoration: "none" }; + case "Palatino-Roman": + return { face: "Palatino", weight: "normal", style: "normal", decoration: "none" }; + case "Palatino-Italic": + return { face: "Palatino", weight: "normal", style: "italic", decoration: "none" }; + case "Palatino-Bold": + return { face: "Palatino", weight: "bold", style: "normal", decoration: "none" }; + case "Palatino-BoldItalic": + return { face: "Palatino", weight: "bold", style: "italic", decoration: "none" }; + case "NewCenturySchlbk-Roman": + return { face: "\"New Century\",serif", weight: "normal", style: "normal", decoration: "none" }; + case "NewCenturySchlbk-Italic": + return { face: "\"New Century\",serif", weight: "normal", style: "italic", decoration: "none" }; + case "NewCenturySchlbk-Bold": + return { face: "\"New Century\",serif", weight: "bold", style: "normal", decoration: "none" }; + case "NewCenturySchlbk-BoldItalic": + return { face: "\"New Century\",serif", weight: "bold", style: "italic", decoration: "none" }; + case "Times": + case "Times-Roman": + case "Times-Narrow": + case "Times-Courier": + case "Times-New-Roman": + return { face: "\"Times New Roman\"", weight: "normal", style: "normal", decoration: "none" }; + case "Times-Italic": + case "Times-Italics": + return { face: "\"Times New Roman\"", weight: "normal", style: "italic", decoration: "none" }; + case "Times-Bold": + return { face: "\"Times New Roman\"", weight: "bold", style: "normal", decoration: "none" }; + case "Times-BoldItalic": + return { face: "\"Times New Roman\"", weight: "bold", style: "italic", decoration: "none" }; + case "ZapfChancery-MediumItalic": + return { face: "\"Zapf Chancery\",cursive,serif", weight: "normal", style: "normal", decoration: "none" }; + default: + return null; + } + }; + + var getFontParameter = function(tokens, currentSetting, str, position, cmd) { + // Every font parameter has the following format: + // + // Where: + // face: either a standard web font name, or a postscript font, enumerated in fontTranslation. This could also be an * or be missing if the face shouldn't change. + // utf8: This is optional, and specifies utf8. That's all that is supported so the field is just silently ignored. + // size: The size, in pixels. This may be omitted if the size is not changing. + // modifiers: zero or more of "bold", "italic", "underline" + // box: Only applies to the measure numbers, gchords, and the parts. If present, then a box is drawn around the characters. + // If face is present, then all the modifiers are cleared. If face is absent, then the modifiers are illegal. + // The face can be a single word, a set of words separated by hyphens, or a quoted string. + // + // So, in practicality, there are three types of font definitions: a number only, an asterisk and a number only, or the full definition (with an optional size). + function processNumberOnly() { + var size = parseInt(tokens[0].token); + tokens.shift(); + if (!currentSetting) { + warn("Can't set just the size of the font since there is no default value.", str, position); + return { face: "\"Times New Roman\"", weight: "normal", style: "normal", decoration: "none", size: size}; + } + if (tokens.length === 0) { + return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size}; + } + if (tokens.length === 1 && tokens[0].token === "box" && fontTypeCanHaveBox[cmd]) + return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size, box: true}; + warn("Extra parameters in font definition.", str, position); + return { face: currentSetting.face, weight: currentSetting.weight, style: currentSetting.style, decoration: currentSetting.decoration, size: size}; + } + + // format 1: asterisk and number only + if (tokens[0].token === '*') { + tokens.shift(); + if (tokens[0].type === 'number') + return processNumberOnly(); + else { + warn("Expected font size number after *.", str, position); + } + } + + // format 2: number only + if (tokens[0].type === 'number') { + return processNumberOnly(); + } + + // format 3: whole definition + var face = []; + var size; + var weight = "normal"; + var style = "normal"; + var decoration = "none"; + var box = false; + var state = 'face'; + var hyphenLast = false; + while (tokens.length) { + var currToken = tokens.shift(); + var word = currToken.token.toLowerCase(); + switch (state) { + case 'face': + if (hyphenLast || (word !== 'utf' && currToken.type !== 'number' && word !== "bold" && word !== "italic" && word !== "underline" && word !== "box")) { + if (face.length > 0 && currToken.token === '-') { + hyphenLast = true; + face[face.length-1] = face[face.length-1] + currToken.token; + } + else { + if (hyphenLast) { + hyphenLast = false; + face[face.length-1] = face[face.length-1] + currToken.token; + } else + face.push(currToken.token); + } + } else { + if (currToken.type === 'number') { + if (size) { + warn("Font size specified twice in font definition.", str, position); + } else { + size = currToken.token; + } + state = 'modifier'; + } else if (word === "bold") + weight = "bold"; + else if (word === "italic") + style = "italic"; + else if (word === "underline") + decoration = "underline"; + else if (word === "box") { + if (fontTypeCanHaveBox[cmd]) + box = true; + else + warn("This font style doesn't support \"box\"", str, position); + state = "finished"; + } else if (word === "utf") { + currToken = tokens.shift(); // this gets rid of the "8" after "utf" + state = "size"; + } else + warn("Unknown parameter " + currToken.token + " in font definition.", str, position); + } + break; + case "size": + if (currToken.type === 'number') { + if (size) { + warn("Font size specified twice in font definition.", str, position); + } else { + size = currToken.token; + } + } else { + warn("Expected font size in font definition.", str, position); + } + state = 'modifier'; + break; + case "modifier": + if (word === "bold") + weight = "bold"; + else if (word === "italic") + style = "italic"; + else if (word === "underline") + decoration = "underline"; + else if (word === "box") { + if (fontTypeCanHaveBox[cmd]) + box = true; + else + warn("This font style doesn't support \"box\"", str, position); + state = "finished"; + } else + warn("Unknown parameter " + currToken.token + " in font definition.", str, position); + break; + case "finished": + warn("Extra characters found after \"box\" in font definition.", str, position); + break; + } + } + + if (size === undefined) { + if (!currentSetting) { + warn("Must specify the size of the font since there is no default value.", str, position); + size = 12; + } else + size = currentSetting.size; + } else + size = parseFloat(size); + + face = face.join(' '); + if (face === '') { + if (!currentSetting) { + warn("Must specify the name of the font since there is no default value.", str, position); + face = "sans-serif"; + } else + face = currentSetting.face; + } + var psFont = fontTranslation(face); + var font = {}; + if (psFont) { + font.face = psFont.face; + font.weight = psFont.weight; + font.style = psFont.style; + font.decoration = psFont.decoration; + font.size = size; + if (box) + font.box = true; + return font; + } + font.face = face; + font.weight = weight; + font.style = style; + font.decoration = decoration; + font.size = size; + if (box) + font.box = true; + return font; + }; + + var getChangingFont = function(cmd, tokens, str) { + if (tokens.length === 0) + return "Directive \"" + cmd + "\" requires a font as a parameter."; + multilineVars[cmd] = getFontParameter(tokens, multilineVars[cmd], str, 0, cmd); + if (multilineVars.is_in_header) // If the font appears in the header, then it becomes the default font. + tune.formatting[cmd] = multilineVars[cmd]; + return null; + }; + var getGlobalFont = function(cmd, tokens, str) { + if (tokens.length === 0) + return "Directive \"" + cmd + "\" requires a font as a parameter."; + tune.formatting[cmd] = getFontParameter(tokens, tune.formatting[cmd], str, 0, cmd); + return null; + }; + + var setScale = function(cmd, tokens) { + var scratch = ""; + tokens.forEach(function(tok) { + scratch += tok.token; + }); + var num = parseFloat(scratch); + if (isNaN(num) || num === 0) + return "Directive \"" + cmd + "\" requires a number as a parameter."; + tune.formatting.scale = num; + + }; + // starts at 35 + var drumNames = [ + "acoustic-bass-drum", + "bass-drum-1", + "side-stick", + "acoustic-snare", + "hand-clap", + "electric-snare", + "low-floor-tom", + "closed-hi-hat", + "high-floor-tom", + "pedal-hi-hat", + "low-tom", + "open-hi-hat", + "low-mid-tom", + "hi-mid-tom", + "crash-cymbal-1", + "high-tom", + "ride-cymbal-1", + "chinese-cymbal", + "ride-bell", + "tambourine", + "splash-cymbal", + "cowbell", + "crash-cymbal-2", + "vibraslap", + "ride-cymbal-2", + "hi-bongo", + "low-bongo", + "mute-hi-conga", + "open-hi-conga", + "low-conga", + "high-timbale", + "low-timbale", + "high-agogo", + "low-agogo", + "cabasa", + "maracas", + "short-whistle", + "long-whistle", + "short-guiro", + "long-guiro", + "claves", + "hi-wood-block", + "low-wood-block", + "mute-cuica", + "open-cuica", + "mute-triangle", + "open-triangle", + ]; + + var interpretPercMap = function(restOfString) { + var tokens = restOfString.split(/\s+/); // Allow multiple spaces. + if (tokens.length !== 2 && tokens.length !== 3) + return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'}; + var key = tokens[0]; + // The percussion sound can either be a MIDI number or a drum name. If it is not a number then check for a name. + var pitch = parseInt(tokens[1], 10); + if ((isNaN(pitch) || pitch < 35 || pitch > 81) && tokens[1]) { + pitch = drumNames.indexOf(tokens[1].toLowerCase()) + 35; + } + if ((isNaN(pitch) || pitch < 35 || pitch > 81)) + return { error: 'Expected drum name, received "' + tokens[1] + '"' }; + var value = { sound: pitch }; + if (tokens.length === 3) + value.noteHead = tokens[2]; + return { key: key, value: value }; + }; + + var getRequiredMeasurement = function(cmd, tokens) { + var points = tokenizer.getMeasurement(tokens); + if (points.used === 0 || tokens.length !== 0) + return { error: "Directive \"" + cmd + "\" requires a measurement as a parameter."}; + return points.value; + }; + var oneParameterMeasurement = function(cmd, tokens) { + var points = tokenizer.getMeasurement(tokens); + if (points.used === 0 || tokens.length !== 0) + return "Directive \"" + cmd + "\" requires a measurement as a parameter."; + tune.formatting[cmd] = points.value; + return null; + }; + + var addMultilineVar = function(key, cmd, tokens, min, max) { + if (tokens.length !== 1 || tokens[0].type !== 'number') + return "Directive \"" + cmd + "\" requires a number as a parameter."; + var i = tokens[0].intt; + if (min !== undefined && i < min) + return "Directive \"" + cmd + "\" requires a number greater than or equal to " + min + " as a parameter."; + if (max !== undefined && i > max) + return "Directive \"" + cmd + "\" requires a number less than or equal to " + max + " as a parameter."; + multilineVars[key] = i; + return null; + }; + + var addMultilineVarBool = function(key, cmd, tokens) { + if (tokens.length === 1 && (tokens[0].token === 'true' || tokens[0].token === 'false')) { + multilineVars[key] = tokens[0].token === 'true'; + return null; + } + var str = addMultilineVar(key, cmd, tokens, 0, 1); + if (str !== null) return str; + multilineVars[key] = (multilineVars[key] === 1); + return null; + }; + + var addMultilineVarOneParamChoice = function(key, cmd, tokens, choices) { + if (tokens.length !== 1) + return "Directive \"" + cmd + "\" requires one of [ " + choices.join(", ") + " ] as a parameter."; + var choice = tokens[0].token; + var found = false; + for (var i = 0; !found && i < choices.length; i++) { + if (choices[i] === choice) + found = true; + } + if (!found) + return "Directive \"" + cmd + "\" requires one of [ " + choices.join(", ") + " ] as a parameter."; + multilineVars[key] = choice; + return null; + }; + + var midiCmdParam0 = [ + "nobarlines", + "barlines", + "beataccents", + "nobeataccents", + "droneon", + "droneoff", + "drumon", + "drumoff", + "fermatafixed", + "fermataproportional", + "gchordon", + "gchordoff", + "controlcombo", + "temperamentnormal", + "noportamento" + ]; + var midiCmdParam1String = [ + "gchord", + "ptstress", + "beatstring" + ]; + var midiCmdParam1Integer = [ + "bassvol", + "chordvol", + "c", + "channel", + "beatmod", + "deltaloudness", + "drumbars", + "gracedivider", + "makechordchannels", + "randomchordattack", + "chordattack", + "stressmodel", + "transpose", + "rtranspose", + "vol", + "volinc", + "gchordbars" + ]; + var midiCmdParam1Integer1OptionalInteger = [ + "program" + ]; + var midiCmdParam2Integer = [ + "ratio", + "snt", + "bendvelocity", + "pitchbend", + "control", + "temperamentlinear" + ]; + var midiCmdParam4Integer = [ + "beat" + ]; + var midiCmdParam5Integer = [ + "drone" + ]; + var midiCmdParam1String1Integer = [ + "portamento" + ]; + var midiCmdParamFraction = [ + "expand", + "grace", + "trim" + ]; + var midiCmdParam1StringVariableIntegers = [ + "drum", + "chordname" + ]; + var midiCmdParam1Integer1OptionalString = [ + "bassprog", "chordprog" + ]; + + + var parseMidiCommand = function(midi, tune, restOfString) { + var midi_cmd = midi.shift().token; + var midi_params = []; + if (midiCmdParam0.indexOf(midi_cmd) >= 0) { + // NO PARAMETERS + if (midi.length !== 0) + warn("Unexpected parameter in MIDI " + midi_cmd, restOfString, 0); + } else if (midiCmdParam1String.indexOf(midi_cmd) >= 0) { + // ONE STRING PARAMETER + if (midi.length !== 1) + warn("Expected one parameter in MIDI " + midi_cmd, restOfString, 0); + else + midi_params.push(midi[0].token); + } else if (midiCmdParam1Integer.indexOf(midi_cmd) >= 0) { + // ONE INT PARAMETER + if (midi.length !== 1) + warn("Expected one parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number") + warn("Expected one integer parameter in MIDI " + midi_cmd, restOfString, 0); + else + midi_params.push(midi[0].intt); + } else if (midiCmdParam1Integer1OptionalInteger.indexOf(midi_cmd) >= 0) { + // ONE INT PARAMETER, ONE OPTIONAL PARAMETER + if (midi.length !== 1 && midi.length !== 2) + warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number") + warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi.length === 2 && midi[1].type !== "number") + warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + if (midi.length === 2) + midi_params.push(midi[1].intt); + } + } else if (midiCmdParam2Integer.indexOf(midi_cmd) >= 0) { + // TWO INT PARAMETERS + if (midi.length !== 2) + warn("Expected two parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number" || midi[1].type !== "number") + warn("Expected two integer parameters in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + midi_params.push(midi[1].intt); + } + } else if (midiCmdParam1String1Integer.indexOf(midi_cmd) >= 0) { + // ONE STRING PARAMETER, ONE INT PARAMETER + if (midi.length !== 2) + warn("Expected two parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "alpha" || midi[1].type !== "number") + warn("Expected one string and one integer parameters in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].token); + midi_params.push(midi[1].intt); + } + } else if (midi_cmd === 'drummap') { + // BUILD AN OBJECT OF ABC NOTE => MIDI NOTE + if (midi.length === 2 && midi[0].type === 'alpha' && midi[1].type === 'number') { + if (!tune.formatting) tune.formatting = {}; + if (!tune.formatting.midi) tune.formatting.midi = {}; + if (!tune.formatting.midi.drummap) tune.formatting.midi.drummap = {}; + tune.formatting.midi.drummap[midi[0].token] = midi[1].intt; + midi_params = tune.formatting.midi.drummap; + } else if (midi.length === 3 && midi[0].type === 'punct' && midi[1].type === 'alpha' && midi[2].type === 'number') { + if (!tune.formatting) tune.formatting = {}; + if (!tune.formatting.midi) tune.formatting.midi = {}; + if (!tune.formatting.midi.drummap) tune.formatting.midi.drummap = {}; + tune.formatting.midi.drummap[midi[0].token+midi[1].token] = midi[2].intt; + midi_params = tune.formatting.midi.drummap; + } else { + warn("Expected one note name and one integer parameter in MIDI " + midi_cmd, restOfString, 0); + } + } else if (midiCmdParamFraction.indexOf(midi_cmd) >= 0) { + // ONE FRACTION PARAMETER + if (midi.length !== 3) + warn("Expected fraction parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number" || midi[1].token !== "/" || midi[2].type !== "number") + warn("Expected fraction parameter in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + midi_params.push(midi[2].intt); + } + } else if (midiCmdParam4Integer.indexOf(midi_cmd) >= 0) { + // FOUR INT PARAMETERS + if (midi.length !== 4) + warn("Expected four parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number" || midi[1].type !== "number" || midi[2].type !== "number" || midi[3].type !== "number") + warn("Expected four integer parameters in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + midi_params.push(midi[1].intt); + midi_params.push(midi[2].intt); + midi_params.push(midi[3].intt); + } + } else if (midiCmdParam5Integer.indexOf(midi_cmd) >= 0) { + // FIVE INT PARAMETERS + if (midi.length !== 5) + warn("Expected five parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number" || midi[1].type !== "number" || midi[2].type !== "number" || midi[3].type !== "number" || midi[4].type !== "number") + warn("Expected five integer parameters in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + midi_params.push(midi[1].intt); + midi_params.push(midi[2].intt); + midi_params.push(midi[3].intt); + midi_params.push(midi[4].intt); + } + } else if (midiCmdParam1Integer1OptionalInteger.indexOf(midi_cmd) >= 0) { + // ONE INT PARAMETER, ONE OPTIONAL OCTAVE PARAMETER + if (midi.length !== 1 || midi.length !== 4) + warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number") + warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi.length === 4) { + if (midi[1].token !== "octave") + warn("Expected octave parameter in MIDI " + midi_cmd, restOfString, 0); + if (midi[2].token !== "=") + warn("Expected octave parameter in MIDI " + midi_cmd, restOfString, 0); + if (midi[3].type !== "number") + warn("Expected integer parameter for octave in MIDI " + midi_cmd, restOfString, 0); + } else { + midi_params.push(midi[0].intt); + if (midi.length === 4) + midi_params.push(midi[3].intt); + } + } else if (midiCmdParam1StringVariableIntegers.indexOf(midi_cmd) >= 0) { + // ONE STRING, VARIABLE INT PARAMETERS + if (midi.length < 2) + warn("Expected string parameter and at least one integer parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "alpha") + warn("Expected string parameter and at least one integer parameter in MIDI " + midi_cmd, restOfString, 0); + else { + var p = midi.shift(); + midi_params.push(p.token); + while (midi.length > 0) { + p = midi.shift(); + if (p.type !== "number") + warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); + midi_params.push(p.intt); + } + } + } + else if (midiCmdParam1Integer1OptionalString.indexOf(midi_cmd) >= 0){ + + // ONE INT PARAMETER, ONE OPTIONAL string + if (midi.length !== 1 && midi.length !== 2) + warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0); + else if (midi[0].type !== "number") + warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0); + else if (midi.length === 2 && midi[1].type !== "alpha") + warn("Expected alpha parameter in MIDI " + midi_cmd, restOfString, 0); + else { + midi_params.push(midi[0].intt); + + // Currently only bassprog and chordprog with optional octave shifts use this path + if (midi.length === 2){ + var cmd = midi[1].token; + if (cmd.indexOf("octave=") != -1){ + cmd = cmd.replace("octave=",""); + cmd = parseInt(cmd); + if (!isNaN(cmd)){ + // Limit range from -1 to 3 octaves + if (cmd < -1){ + warn("Expected octave= in MIDI " + midi_cmd + ' to be >= -1 (recv:'+cmd+')'); + cmd = -1; + } + if (cmd > 3){ + warn("Expected octave= in MIDI " + midi_cmd + ' to be <= 3 (recv:'+cmd+')'); + cmd = 3; + } + midi_params.push(cmd); + } else + warn("Expected octave value in MIDI" + midi_cmd); + } + else{ + warn("Expected octave= in MIDI" + midi_cmd); + } + } + } + } + + if (tuneBuilder.hasBeginMusic()) + tuneBuilder.appendElement('midi', -1, -1, { cmd: midi_cmd, params: midi_params }); + else { + if (tune.formatting['midi'] === undefined) + tune.formatting['midi'] = {}; + tune.formatting['midi'][midi_cmd] = midi_params; + } + }; + + parseDirective.parseFontChangeLine = function(textstr) { + // We don't want to match two dollar signs, so change those temporarily + textstr = textstr.replace(/\$\$/g,"\x03") + var textParts = textstr.split('$'); + if (textParts.length > 1 && multilineVars.setfont) { + var textarr = [ ]; + if (textParts[0] !== '') // did the original string start with `$`? + textarr.push({ text: textParts[0] }) + for (var i = 1; i < textParts.length; i++) { + if (textParts[i][0] === '0') + textarr.push({ text: textParts[i].substring(1).replace(/\x03/g,"$$") }); + else { + var whichFont = parseInt(textParts[i][0],10) + if (multilineVars.setfont[whichFont]) + textarr.push({font: multilineVars.setfont[whichFont], text: textParts[i].substring(1).replace(/\x03/g,"$$") }); + else + textarr[textarr.length-1].text += '$' + textParts[i].replace(/\x03/g,"$$"); + } + } + return textarr; + } + return textstr.replace(/\x03/g,"$$"); + }; + + var positionChoices = [ 'auto', 'above', 'below', 'hidden' ]; + parseDirective.addDirective = function(str) { + var tokens = tokenizer.tokenize(str, 0, str.length); // 3 or more % in a row, or just spaces after %% is just a comment + if (tokens.length === 0 || tokens[0].type !== 'alpha') return null; + var restOfString = str.substring(str.indexOf(tokens[0].token)+tokens[0].token.length); + restOfString = tokenizer.stripComment(restOfString); + var cmd = tokens.shift().token.toLowerCase(); + var scratch = ""; + var line; + switch (cmd) + { + // The following directives were added to abc_parser_lint, but haven't been implemented here. + // Most of them are direct translations from the directives that will be parsed in. See abcm2ps's format.txt for info on each of these. + // alignbars: { type: "number", optional: true }, + // aligncomposer: { type: "string", Enum: [ 'left', 'center','right' ], optional: true }, + // bstemdown: { type: "boolean", optional: true }, + // continueall: { type: "boolean", optional: true }, + // dynalign: { type: "boolean", optional: true }, + // exprabove: { type: "boolean", optional: true }, + // exprbelow: { type: "boolean", optional: true }, + // gchordbox: { type: "boolean", optional: true }, + // gracespacebefore: { type: "number", optional: true }, + // gracespaceinside: { type: "number", optional: true }, + // gracespaceafter: { type: "number", optional: true }, + // infospace: { type: "number", optional: true }, + // lineskipfac: { type: "number", optional: true }, + // maxshrink: { type: "number", optional: true }, + // maxstaffsep: { type: "number", optional: true }, + // maxsysstaffsep: { type: "number", optional: true }, + // notespacingfactor: { type: "number", optional: true }, + // parskipfac: { type: "number", optional: true }, + // slurheight: { type: "number", optional: true }, + // splittune: { type: "boolean", optional: true }, + // squarebreve: { type: "boolean", optional: true }, + // stemheight: { type: "number", optional: true }, + // straightflags: { type: "boolean", optional: true }, + // stretchstaff: { type: "boolean", optional: true }, + // titleformat: { type: "string", optional: true }, + case "bagpipes":tune.formatting.bagpipes = true;break; + case "flatbeams":tune.formatting.flatbeams = true;break; + case "jazzchords":tune.formatting.jazzchords = true;break; + case "accentAbove":tune.formatting.accentAbove = true;break; + case "germanAlphabet":tune.formatting.germanAlphabet = true;break; + case "landscape":multilineVars.landscape = true;break; + case "papersize":multilineVars.papersize = restOfString;break; + case "graceslurs": + if (tokens.length !== 1) + return "Directive graceslurs requires one parameter: 0 or 1"; + if (tokens[0].token === '0' || tokens[0].token === 'false') + tune.formatting.graceSlurs = false; + else if (tokens[0].token === '1' || tokens[0].token === 'true') + tune.formatting.graceSlurs = true; + else + return "Directive graceslurs requires one parameter: 0 or 1 (received " + tokens[0].token + ')'; + break; + case "lineThickness": + var lt = parseStretchLast(tokens); + if (lt.value !== undefined) + tune.formatting.lineThickness = lt.value; + if (lt.error) + return lt.error; + break; + case "stretchlast": + var sl = parseStretchLast(tokens); + if (sl.value !== undefined) + tune.formatting.stretchlast = sl.value; + if (sl.error) + return sl.error; + break; + case "titlecaps":multilineVars.titlecaps = true;break; + case "titleleft":tune.formatting.titleleft = true;break; + case "measurebox":tune.formatting.measurebox = true;break; + + case "vocal": return addMultilineVarOneParamChoice("vocalPosition", cmd, tokens, positionChoices); + case "dynamic": return addMultilineVarOneParamChoice("dynamicPosition", cmd, tokens, positionChoices); + case "gchord": return addMultilineVarOneParamChoice("chordPosition", cmd, tokens, positionChoices); + case "ornament": return addMultilineVarOneParamChoice("ornamentPosition", cmd, tokens, positionChoices); + case "volume": return addMultilineVarOneParamChoice("volumePosition", cmd, tokens, positionChoices); + + case "botmargin": + case "botspace": + case "composerspace": + case "indent": + case "leftmargin": + case "linesep": + case "musicspace": + case "partsspace": + case "pageheight": + case "pagewidth": + case "rightmargin": + case "stafftopmargin": + case "staffsep": + case "staffwidth": + case "subtitlespace": + case "sysstaffsep": + case "systemsep": + case "textspace": + case "titlespace": + case "topmargin": + case "topspace": + case "vocalspace": + case "wordsspace": + return oneParameterMeasurement(cmd, tokens); + case "voicescale": + if (tokens.length !== 1 || tokens[0].type !== 'number') + return "voicescale requires one float as a parameter"; + var voiceScale = tokens.shift(); + if (multilineVars.currentVoice) { + multilineVars.currentVoice.scale = voiceScale.floatt; + tuneBuilder.changeVoiceScale(multilineVars.currentVoice.scale); + } + return null; + case "voicecolor": + if (tokens.length !== 1) // this could either be of type alpha or quote, but it's ok if it is a number + return "voicecolor requires one string as a parameter"; + var voiceColor = tokens.shift(); + if (multilineVars.currentVoice) { + multilineVars.currentVoice.color = voiceColor.token; + tuneBuilder.changeVoiceColor(multilineVars.currentVoice.color); + } + return null; + case "vskip": + var vskip = Math.round(getRequiredMeasurement(cmd, tokens)); + if (vskip.error) + return vskip.error; + tuneBuilder.addSpacing(vskip); + return null; + case "scale": + setScale(cmd, tokens); + break; + case "sep": + if (tokens.length === 0) + tuneBuilder.addSeparator(14,14,85, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+5}); // If no parameters are given, then there is a default size. + else { + var points = tokenizer.getMeasurement(tokens); + if (points.used === 0) + return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; + var spaceAbove = points.value; + + points = tokenizer.getMeasurement(tokens); + if (points.used === 0) + return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; + var spaceBelow = points.value; + + points = tokenizer.getMeasurement(tokens); + if (points.used === 0 || tokens.length !== 0) + return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line"; + var lenLine = points.value; + tuneBuilder.addSeparator(spaceAbove, spaceBelow, lenLine, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length}); + } + break; + case "barsperstaff": + scratch = addMultilineVar('barsperstaff', cmd, tokens); + if (scratch !== null) return scratch; + break; + case "staffnonote": + // The sense of the boolean is opposite here. "0" means true. + if (tokens.length !== 1) + return "Directive staffnonote requires one parameter: 0 or 1"; + if (tokens[0].token === '0') + multilineVars.staffnonote = true; + else if (tokens[0].token === '1') + multilineVars.staffnonote = false; + else + return "Directive staffnonote requires one parameter: 0 or 1 (received " + tokens[0].token + ')'; + break; + case "printtempo": + scratch = addMultilineVarBool('printTempo', cmd, tokens); + if (scratch !== null) return scratch; + break; + case "partsbox": + scratch = addMultilineVarBool('partsBox', cmd, tokens); + if (scratch !== null) return scratch; + multilineVars.partsfont.box = multilineVars.partsBox; + break; + case "freegchord": + scratch = addMultilineVarBool('freegchord', cmd, tokens); + if (scratch !== null) return scratch; + break; + case "measurenb": + case "barnumbers": + scratch = addMultilineVar('barNumbers', cmd, tokens); + if (scratch !== null) return scratch; + break; + case "setbarnb": + if (tokens.length !== 1 || tokens[0].type !== 'number') { + return 'Directive setbarnb requires a number as a parameter.'; + } + multilineVars.currBarNumber = tuneBuilder.setBarNumberImmediate(tokens[0].intt); + break; + case "begintext": + var textBlock = ''; + line = tokenizer.nextLine(); + while(line && line.indexOf('%%endtext') !== 0) { + if (parseCommon.startsWith(line, "%%")) + textBlock += line.substring(2) + "\n"; + else + textBlock += line + "\n"; + line = tokenizer.nextLine(); + } + tuneBuilder.addText(textBlock, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+textBlock.length+7}); + break; + case "continueall": + multilineVars.continueall = true; + break; + case "beginps": + line = tokenizer.nextLine(); + while(line && line.indexOf('%%endps') !== 0) { + tokenizer.nextLine(); + } + warn("Postscript ignored", str, 0); + break; + case "deco": + if (restOfString.length > 0) + multilineVars.ignoredDecorations.push(restOfString.substring(0, restOfString.indexOf(' '))); + warn("Decoration redefinition ignored", str, 0); + break; + case "text": + var textstr = tokenizer.translateString(restOfString); + tuneBuilder.addText(parseDirective.parseFontChangeLine(textstr), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+7}); + break; + case "center": + var centerstr = tokenizer.translateString(restOfString); + tuneBuilder.addCentered(parseDirective.parseFontChangeLine(centerstr)); + break; + case "font": + // don't need to do anything for this; it is a useless directive + break; + case "setfont": + var sfTokens = tokenizer.tokenize(restOfString, 0, restOfString.length); +// var sfDone = false; + if (sfTokens.length >= 4) { + if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') { + var sfNum = parseInt(sfTokens[1].token); + if (sfNum >= 1 && sfNum <= 9) { + if (!multilineVars.setfont) + multilineVars.setfont = []; + sfTokens.shift(); + sfTokens.shift(); + multilineVars.setfont[sfNum] = getFontParameter(sfTokens, multilineVars.setfont[sfNum], str, 0, 'setfont'); +// var sfSize = sfTokens.pop(); +// if (sfSize.type === 'number') { +// sfSize = parseInt(sfSize.token); +// var sfFontName = ''; +// for (var sfi = 2; sfi < sfTokens.length; sfi++) +// sfFontName += sfTokens[sfi].token; +// multilineVars.setfont[sfNum] = { face: sfFontName, size: sfSize }; +// sfDone = true; +// } + } + } + } +// if (!sfDone) +// return "Bad parameters: " + cmd; + break; + case "gchordfont": + case "partsfont": + case "tripletfont": + case "vocalfont": + case "textfont": + case "annotationfont": + case "historyfont": + case "infofont": + case "measurefont": + case "repeatfont": + case "wordsfont": + return getChangingFont(cmd, tokens, str); + case "composerfont": + case "subtitlefont": + case "tempofont": + case "titlefont": + case "voicefont": + case "footerfont": + case "headerfont": + return getGlobalFont(cmd, tokens, str); + case "barlabelfont": + case "barnumberfont": + case "barnumfont": + return getChangingFont("measurefont", tokens, str); + case "staves": + case "score": + multilineVars.score_is_present = true; + var addVoice = function(id, newStaff, bracket, brace, continueBar) { + if (newStaff || multilineVars.staves.length === 0) { + multilineVars.staves.push({index: multilineVars.staves.length, numVoices: 0}); + } + var staff = parseCommon.last(multilineVars.staves); + if (bracket !== undefined && staff.bracket === undefined) staff.bracket = bracket; + if (brace !== undefined && staff.brace === undefined) staff.brace = brace; + if (continueBar) staff.connectBarLines = 'end'; + if (multilineVars.voices[id] === undefined) { + multilineVars.voices[id] = {staffNum: staff.index, index: staff.numVoices}; + staff.numVoices++; + } + }; + + var openParen = false; + var openBracket = false; + var openBrace = false; + var justOpenParen = false; + var justOpenBracket = false; + var justOpenBrace = false; + var continueBar = false; + var lastVoice; + var addContinueBar = function() { + continueBar = true; + if (lastVoice) { + var ty = 'start'; + if (lastVoice.staffNum > 0) { + if (multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'start' || + multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'continue') + ty = 'continue'; + } + multilineVars.staves[lastVoice.staffNum].connectBarLines = ty; + } + }; + while (tokens.length) { + var t = tokens.shift(); + switch (t.token) { + case '(': + if (openParen) warn("Can't nest parenthesis in %%score", str, t.start); + else {openParen = true;justOpenParen = true;} + break; + case ')': + if (!openParen || justOpenParen) warn("Unexpected close parenthesis in %%score", str, t.start); + else openParen = false; + break; + case '[': + if (openBracket) warn("Can't nest brackets in %%score", str, t.start); + else {openBracket = true;justOpenBracket = true;} + break; + case ']': + if (!openBracket || justOpenBracket) warn("Unexpected close bracket in %%score", str, t.start); + else {openBracket = false;multilineVars.staves[lastVoice.staffNum].bracket = 'end';} + break; + case '{': + if (openBrace ) warn("Can't nest braces in %%score", str, t.start); + else {openBrace = true;justOpenBrace = true;} + break; + case '}': + if (!openBrace || justOpenBrace) warn("Unexpected close brace in %%score", str, t.start); + else {openBrace = false;multilineVars.staves[lastVoice.staffNum].brace = 'end';} + break; + case '|': + addContinueBar(); + break; + default: + var vc = ""; + while (t.type === 'alpha' || t.type === 'number') { + vc += t.token; + if (t.continueId) + t = tokens.shift(); + else + break; + } + var newStaff = !openParen || justOpenParen; + var bracket = justOpenBracket ? 'start' : openBracket ? 'continue' : undefined; + var brace = justOpenBrace ? 'start' : openBrace ? 'continue' : undefined; + addVoice(vc, newStaff, bracket, brace, continueBar); + justOpenParen = false; + justOpenBracket = false; + justOpenBrace = false; + continueBar = false; + lastVoice = multilineVars.voices[vc]; + if (cmd === 'staves') + addContinueBar(); + break; + } + } + break; + + case "newpage": + var pgNum = tokenizer.getInt(restOfString); + tuneBuilder.addNewPage(pgNum.digits === 0 ? -1 : pgNum.value); + break; + + case "abc": + var arr = restOfString.split(' '); + switch (arr[0]) { + case "-copyright": + case "-creator": + case "-edited-by": + case "-version": + case "-charset": + var subCmd = arr.shift(); + tuneBuilder.addMetaText(cmd+subCmd, arr.join(' '), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+restOfString.length+5}); + break; + default: + return "Unknown directive: " + cmd+arr[0]; + } + break; + case "header": + case "footer": + var footerStr = tokenizer.getMeat(restOfString, 0, restOfString.length); + footerStr = restOfString.substring(footerStr.start, footerStr.end); + if (footerStr[0] === '"' && footerStr[footerStr.length-1] === '"' ) + footerStr = footerStr.substring(1, footerStr.length-1); + var footerArr = footerStr.split('\t'); + var footer = {}; + if (footerArr.length === 1) + footer = { left: "", center: footerArr[0], right: "" }; + else if (footerArr.length === 2) + footer = { left: footerArr[0], center: footerArr[1], right: "" }; + else + footer = { left: footerArr[0], center: footerArr[1], right: footerArr[2] }; + if (footerArr.length > 3) + warn("Too many tabs in " + cmd + ": " + footerArr.length + " found.", restOfString, 0); + + tuneBuilder.addMetaTextObj(cmd, footer, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+str.length}); + break; + + case "midi": + var midi = tokenizer.tokenize(restOfString, 0, restOfString.length, true); + if (midi.length > 0 && midi[0].token === '=') + midi.shift(); + if (midi.length === 0) + warn("Expected midi command", restOfString, 0); + else + parseMidiCommand(midi, tune, restOfString); + break; + case "percmap": + var percmap = interpretPercMap(restOfString); + if (percmap.error) + warn(percmap.error, str, 8); + else { + if (!tune.formatting.percmap) + tune.formatting.percmap = {}; + tune.formatting.percmap[percmap.key] = percmap.value; + } + break; + + case "map": + case "playtempo": + case "auquality": + case "continuous": + case "nobarcheck": + // TODO-PER: Actually handle the parameters of these + tune.formatting[cmd] = restOfString; + break; + default: + return "Unknown directive: " + cmd; + } + return null; + }; + parseDirective.globalFormatting = function(formatHash) { + for (var cmd in formatHash) { + if (formatHash.hasOwnProperty(cmd)) { + var value = ''+formatHash[cmd]; + var tokens = tokenizer.tokenize(value, 0, value.length); + var scratch; + switch (cmd) { + case "titlefont": + case "gchordfont": + case "composerfont": + case "footerfont": + case "headerfont": + case "historyfont": + case "infofont": + case "measurefont": + case "partsfont": + case "repeatfont": + case "subtitlefont": + case "tempofont": + case "textfont": + case "voicefont": + case "tripletfont": + case "vocalfont": + case "wordsfont": + case "annotationfont": + case "tablabelfont": + case "tabnumberfont": + case "tabgracefont": + getChangingFont(cmd, tokens, value); + break; + case "scale": + setScale(cmd, tokens); + break; + case "partsbox": + scratch = addMultilineVarBool('partsBox', cmd, tokens); + if (scratch !== null) warn(scratch); + multilineVars.partsfont.box = multilineVars.partsBox; + break; + case "freegchord": + scratch = addMultilineVarBool('freegchord', cmd, tokens); + if (scratch !== null) warn(scratch); + break; + case "fontboxpadding": + if (tokens.length !== 1 || tokens[0].type !== 'number') + warn("Directive \"" + cmd + "\" requires a number as a parameter."); + tune.formatting.fontboxpadding = tokens[0].floatt; + break; + case "stafftopmargin": + if (tokens.length !== 1 || tokens[0].type !== 'number') + warn("Directive \"" + cmd + "\" requires a number as a parameter."); + tune.formatting.stafftopmargin = tokens[0].floatt; + break; + case "stretchlast": + var sl = parseStretchLast(tokens); + if (sl.value !== undefined) + tune.formatting.stretchlast = sl.value; + if (sl.error) + return sl.error; + break; + default: + warn("Formatting directive unrecognized: ", cmd, 0); + } + } + } + }; + + function parseStretchLast(tokens) { + if (tokens.length === 0) + return { value: 1 }; // if there is no value then the presence of this is the same as "true" + else if (tokens.length === 1) { + if (tokens[0].type === "number") { + if (tokens[0].floatt >= 0 || tokens[0].floatt <= 1) + return {value: tokens[0].floatt}; + } else if (tokens[0].token === 'false') { + return { value: 0 }; + } else if (tokens[0].token === 'true') { + return {value: 1}; + } + } + return { error: "Directive stretchlast requires zero or one parameter: false, true, or number between 0 and 1 (received " + tokens[0].token + ')' }; + } +})(); + +module.exports = parseDirective; diff --git a/src/parse/abc_parse_header.js b/src/parse/abc_parse_header.js new file mode 100644 index 0000000000000000000000000000000000000000..4294280a42fb84f7f2b52d19951a36eddd529f6e --- /dev/null +++ b/src/parse/abc_parse_header.js @@ -0,0 +1,569 @@ +// abc_parse_header.js: parses a the header fields from a string representing ABC Music Notation into a usable internal structure. + +var parseCommon = require('./abc_common'); +var parseDirective = require('./abc_parse_directive'); +var parseKeyVoice = require('./abc_parse_key_voice'); + +var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) { + this.reset = function(tokenizer, warn, multilineVars, tune) { + parseKeyVoice.initialize(tokenizer, warn, multilineVars, tune, tuneBuilder); + parseDirective.initialize(tokenizer, warn, multilineVars, tune, tuneBuilder); + }; + this.reset(tokenizer, warn, multilineVars, tune); + + this.setTitle = function(title, origSize) { + if (multilineVars.hasMainTitle) + tuneBuilder.addSubtitle(title, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+origSize+2}); // display secondary title + else + { + tuneBuilder.addMetaText("title", title, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+origSize+2}); + multilineVars.hasMainTitle = true; + } + }; + + this.setMeter = function(line) { + line = tokenizer.stripComment(line); + if (line === 'C') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'common_time'}; + } else if (line === 'C|') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'cut_time'}; + } else if (line === 'o') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'tempus_perfectum'}; + } else if (line === 'c') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'tempus_imperfectum'}; + } else if (line === 'o.') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'tempus_perfectum_prolatio'}; + } else if (line === 'c.') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return {type: 'tempus_imperfectum_prolatio'}; + } else if (line.length === 0 || line.toLowerCase() === 'none') { + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = 0.125; + multilineVars.havent_set_length = false; + } + return null; + } + else + { + var tokens = tokenizer.tokenize(line, 0, line.length); + // the form is [open_paren] decimal [ plus|dot decimal ]... [close_paren] slash decimal [plus same_as_before] + try { + var parseNum = function() { + // handles this much: [open_paren] decimal [ plus|dot decimal ]... [close_paren] + var ret = {value: 0, num: ""}; + + var tok = tokens.shift(); + if (tok.token === '(') + tok = tokens.shift(); + while (1) { + if (tok.type !== 'number') throw "Expected top number of meter"; + ret.value += parseInt(tok.token); + ret.num += tok.token; + if (tokens.length === 0 || tokens[0].token === '/') return ret; + tok = tokens.shift(); + if (tok.token === ')') { + if (tokens.length === 0 || tokens[0].token === '/') return ret; + throw "Unexpected paren in meter"; + } + if (tok.token !== '.' && tok.token !== '+') throw "Expected top number of meter"; + ret.num += tok.token; + if (tokens.length === 0) throw "Expected top number of meter"; + tok = tokens.shift(); + } + return ret; // just to suppress warning + }; + + var parseFraction = function() { + // handles this much: parseNum slash decimal + var ret = parseNum(); + if (tokens.length === 0) return ret; + var tok = tokens.shift(); + if (tok.token !== '/') throw "Expected slash in meter"; + tok = tokens.shift(); + if (tok.type !== 'number') throw "Expected bottom number of meter"; + ret.den = tok.token; + ret.value = ret.value / parseInt(ret.den); + return ret; + }; + + if (tokens.length === 0) throw "Expected meter definition in M: line"; + var meter = {type: 'specified', value: [ ]}; + var totalLength = 0; + while (1) { + var ret = parseFraction(); + totalLength += ret.value; + var mv = { num: ret.num }; + if (ret.den !== undefined) + mv.den = ret.den; + meter.value.push(mv); + if (tokens.length === 0) break; + //var tok = tokens.shift(); + //if (tok.token !== '+') throw "Extra characters in M: line"; + } + + if (multilineVars.havent_set_length === true) { + multilineVars.default_length = totalLength < 0.75 ? 0.0625 : 0.125; + multilineVars.havent_set_length = false; + } + return meter; + } catch (e) { + warn(e, line, 0); + } + } + return null; + }; + + this.calcTempo = function(relTempo) { + var dur = 1/4; + if (multilineVars.meter && multilineVars.meter.type === 'specified') { + dur = 1 / parseInt(multilineVars.meter.value[0].den); + } else if (multilineVars.origMeter && multilineVars.origMeter.type === 'specified') { + dur = 1 / parseInt(multilineVars.origMeter.value[0].den); + } + //var dur = multilineVars.default_length ? multilineVars.default_length : 1; + for (var i = 0; i < relTempo.duration; i++) + relTempo.duration[i] = dur * relTempo.duration[i]; + return relTempo; + }; + + this.resolveTempo = function() { + if (multilineVars.tempo) { // If there's a tempo waiting to be resolved + this.calcTempo(multilineVars.tempo); + tune.metaText.tempo = multilineVars.tempo; + delete multilineVars.tempo; + } + }; + + this.addUserDefinition = function(line, start, end) { + var equals = line.indexOf('=', start); + if (equals === -1) { + warn("Need an = in a macro definition", line, start); + return; + } + + var before = parseCommon.strip(line.substring(start, equals)); + var after = parseCommon.strip(line.substring(equals+1)); + + if (before.length !== 1) { + warn("Macro definitions can only be one character", line, start); + return; + } + var legalChars = "HIJKLMNOPQRSTUVWXYhijklmnopqrstuvw~"; + if (legalChars.indexOf(before) === -1) { + warn("Macro definitions must be H-Y, h-w, or tilde", line, start); + return; + } + if (after.length === 0) { + warn("Missing macro definition", line, start); + return; + } + if (multilineVars.macros === undefined) + multilineVars.macros = {}; + multilineVars.macros[before] = after; + }; + + this.setDefaultLength = function(line, start, end) { + var len = line.substring(start, end).replace(/ /g, ""); + var len_arr = len.split('/'); + if (len_arr.length === 2) { + var n = parseInt(len_arr[0]); + var d = parseInt(len_arr[1]); + if (d > 0) { + multilineVars.default_length = n / d; // a whole note is 1 + multilineVars.havent_set_length = false; + } + } else if (len_arr.length === 1 && len_arr[0] === '1') { + multilineVars.default_length = 1; + multilineVars.havent_set_length = false; + } + }; + + + var tempoString = { + + larghissimo: 20, + adagissimo: 24, + sostenuto: 28, + grave: 32, + largo: 40, + lento: 50, + larghetto: 60, + adagio: 68, + adagietto: 74, + andante: 80, + andantino: 88, + "marcia moderato": 84, + "andante moderato": 100, + moderato: 112, + allegretto: 116, + "allegro moderato": 120, + allegro: 126, + animato: 132, + agitato: 140, + veloce: 148, + "mosso vivo": 156, + vivace: 164, + vivacissimo: 172, + allegrissimo: 176, + presto: 184, + prestissimo: 210, + }; + + this.setTempo = function(line, start, end, iChar) { + //Q - tempo; can be used to specify the notes per minute, e.g. If + //the meter denominator is a 4 note then Q:120 or Q:C=120 + //is 120 quarter notes per minute. Similarly Q:C3=40 would be 40 + //dotted half notes per minute. An absolute tempo may also be + //set, e.g. Q:1/8=120 is 120 eighth notes per minute, + //irrespective of the meter's denominator. + // + // This is either a number, "C=number", "Cnumber=number", or fraction [fraction...]=number + // It depends on the M: field, which may either not be present, or may appear after this. + // If M: is not present, an eighth note is used. + // That means that this field can't be calculated until the end, if it is the first three types, since we don't know if we'll see an M: field. + // So, if it is the fourth type, set it here, otherwise, save the info in the multilineVars. + // The temporary variables we keep are the duration and the bpm. In the first two forms, the duration is 1. + // In addition, a quoted string may both precede and follow. If a quoted string is present, then the duration part is optional. + try { + var tokens = tokenizer.tokenize(line, start, end); + + if (tokens.length === 0) throw "Missing parameter in Q: field"; + + var tempo = { startChar: iChar+start-2, endChar: iChar+end }; + var delaySet = true; + var token = tokens.shift(); + if (token.type === 'quote') { + tempo.preString = token.token; + token = tokens.shift(); + if (tokens.length === 0) { // It's ok to just get a string for the tempo + // If the string is a well-known tempo, put in the bpm + if (tempoString[tempo.preString.toLowerCase()]) { + tempo.bpm = tempoString[tempo.preString.toLowerCase()]; + tempo.suppressBpm = true; + } + return {type: 'immediate', tempo: tempo}; + } + } + if (token.type === 'alpha' && token.token === 'C') { // either type 2 or type 3 + if (tokens.length === 0) throw "Missing tempo after C in Q: field"; + token = tokens.shift(); + if (token.type === 'punct' && token.token === '=') { + // This is a type 2 format. The duration is an implied 1 + if (tokens.length === 0) throw "Missing tempo after = in Q: field"; + token = tokens.shift(); + if (token.type !== 'number') throw "Expected number after = in Q: field"; + tempo.duration = [1]; + tempo.bpm = parseInt(token.token); + } else if (token.type === 'number') { + // This is a type 3 format. + tempo.duration = [parseInt(token.token)]; + if (tokens.length === 0) throw "Missing = after duration in Q: field"; + token = tokens.shift(); + if (token.type !== 'punct' || token.token !== '=') throw "Expected = after duration in Q: field"; + if (tokens.length === 0) throw "Missing tempo after = in Q: field"; + token = tokens.shift(); + if (token.type !== 'number') throw "Expected number after = in Q: field"; + tempo.bpm = parseInt(token.token); + } else throw "Expected number or equal after C in Q: field"; + + } else if (token.type === 'number') { // either type 1 or type 4 + var num = parseInt(token.token); + if (tokens.length === 0 || tokens[0].type === 'quote') { + // This is type 1 + tempo.duration = [1]; + tempo.bpm = num; + } else { // This is type 4 + delaySet = false; + token = tokens.shift(); + if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field"; + token = tokens.shift(); + if (token.type !== 'number') throw "Expected fraction in Q: field"; + var den = parseInt(token.token); + tempo.duration = [num/den]; + // We got the first fraction, keep getting more as long as we find them. + while (tokens.length > 0 && tokens[0].token !== '=' && tokens[0].type !== 'quote') { + token = tokens.shift(); + if (token.type !== 'number') throw "Expected fraction in Q: field"; + num = parseInt(token.token); + token = tokens.shift(); + if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field"; + token = tokens.shift(); + if (token.type !== 'number') throw "Expected fraction in Q: field"; + den = parseInt(token.token); + tempo.duration.push(num/den); + } + token = tokens.shift(); + if (token.type !== 'punct' && token.token !== '=') throw "Expected = in Q: field"; + token = tokens.shift(); + if (token.type !== 'number') throw "Expected tempo in Q: field"; + tempo.bpm = parseInt(token.token); + } + } else throw "Unknown value in Q: field"; + if (tokens.length !== 0) { + token = tokens.shift(); + if (token.type === 'quote') { + tempo.postString = token.token; + token = tokens.shift(); + } + if (tokens.length !== 0) throw "Unexpected string at end of Q: field"; + } + if (multilineVars.printTempo === false) + tempo.suppress = true; + return {type: delaySet?'delaySet':'immediate', tempo: tempo}; + } catch (msg) { + warn(msg, line, start); + return {type: 'none'}; + } + }; + + this.letter_to_inline_header = function(line, i, startLine) + { + var needsNewLine = false + var ws = tokenizer.eatWhiteSpace(line, i); + i +=ws; + if (line.length >= i+5 && line[i] === '[' && line[i+2] === ':') { + var e = line.indexOf(']', i); + var startChar = multilineVars.iChar + i; + var endChar = multilineVars.iChar + e + 1; + switch(line.substring(i, i+3)) + { + case "[I:": + var err = parseDirective.addDirective(line.substring(i+3, e)); + if (err) warn(err, line, i); + return [ e-i+1+ws ]; + case "[M:": + var meter = this.setMeter(line.substring(i+3, e)); + if (tuneBuilder.hasBeginMusic() && meter) + tuneBuilder.appendStartingElement('meter', startChar, endChar, meter); + else + multilineVars.meter = meter; + return [ e-i+1+ws ]; + case "[K:": + var result = parseKeyVoice.parseKey(line.substring(i+3, e), true); + if (result.foundClef && tuneBuilder.hasBeginMusic()) + tuneBuilder.appendStartingElement('clef', startChar, endChar, multilineVars.clef); + if (result.foundKey && tuneBuilder.hasBeginMusic()) + tuneBuilder.appendStartingElement('key', startChar, endChar, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key)); + return [ e-i+1+ws ]; + case "[P:": + var part = parseDirective.parseFontChangeLine(line.substring(i+3, e)) + if (startLine || tune.lines.length <= tune.lineNum) + multilineVars.partForNextLine = { title: part, startChar: startChar, endChar: endChar }; + else + tuneBuilder.appendElement('part', startChar, endChar, {title: part}); + return [ e-i+1+ws ]; + case "[L:": + this.setDefaultLength(line, i+3, e); + return [ e-i+1+ws ]; + case "[Q:": + if (e > 0) { + var tempo = this.setTempo(line, i+3, e, multilineVars.iChar); + if (tempo.type === 'delaySet') { + if (tuneBuilder.hasBeginMusic()) + tuneBuilder.appendElement('tempo', startChar, endChar, this.calcTempo(tempo.tempo)); + else + multilineVars.tempoForNextLine = ['tempo', startChar, endChar, this.calcTempo(tempo.tempo)] + } else if (tempo.type === 'immediate') { + if (!startLine && tuneBuilder.hasBeginMusic()) + tuneBuilder.appendElement('tempo', startChar, endChar, tempo.tempo); + else + multilineVars.tempoForNextLine = ['tempo', startChar, endChar, tempo.tempo] + } + return [ e-i+1+ws, line[i+1], line.substring(i+3, e)]; + } + break; + case "[V:": + if (e > 0) { + needsNewLine = parseKeyVoice.parseVoice(line, i+3, e); + //startNewLine(); + return [ e-i+1+ws, line[i+1], line.substring(i+3, e), needsNewLine]; + } + break; + case "[r:": + return [ e-i+1+ws ]; + + default: + // TODO: complain about unhandled header + } + } + return [ 0 ]; + }; + + this.letter_to_body_header = function(line, i) + { + var needsNewLine = false + if (line.length >= i+3) { + switch(line.substring(i, i+2)) + { + case "I:": + var err = parseDirective.addDirective(line.substring(i+2)); + if (err) warn(err, line, i); + return [ line.length ]; + case "M:": + var meter = this.setMeter(line.substring(i+2)); + if (tuneBuilder.hasBeginMusic() && meter) + tuneBuilder.appendStartingElement('meter', multilineVars.iChar + i, multilineVars.iChar + line.length, meter); + return [ line.length ]; + case "K:": + var result = parseKeyVoice.parseKey(line.substring(i+2), tuneBuilder.hasBeginMusic()); + if (result.foundClef && tuneBuilder.hasBeginMusic()) + tuneBuilder.appendStartingElement('clef', multilineVars.iChar + i, multilineVars.iChar + line.length, multilineVars.clef); + if (result.foundKey && tuneBuilder.hasBeginMusic()) + tuneBuilder.appendStartingElement('key', multilineVars.iChar + i, multilineVars.iChar + line.length, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key)); + return [ line.length ]; + case "P:": + if (tuneBuilder.hasBeginMusic()) + tuneBuilder.appendElement('part', multilineVars.iChar + i, multilineVars.iChar + line.length, {title: line.substring(i+2)}); + return [ line.length ]; + case "L:": + this.setDefaultLength(line, i+2, line.length); + return [ line.length ]; + case "Q:": + var e = line.indexOf('\x12', i+2); + if (e === -1) e = line.length; + var tempo = this.setTempo(line, i+2, e, multilineVars.iChar); + if (tempo.type === 'delaySet') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, this.calcTempo(tempo.tempo)); + else if (tempo.type === 'immediate') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, tempo.tempo); + return [ e, line[i], parseCommon.strip(line.substring(i+2))]; + case "V:": + needsNewLine = parseKeyVoice.parseVoice(line, i+2, line.length); +// startNewLine(); + return [ line.length, line[i], parseCommon.strip(line.substring(i+2)), needsNewLine]; + default: + // TODO: complain about unhandled header + } + } + return [ 0 ]; + }; + + var metaTextHeaders = { + A: 'author', + B: 'book', + C: 'composer', + D: 'discography', + F: 'url', + G: 'group', + I: 'instruction', + N: 'notes', + O: 'origin', + R: 'rhythm', + S: 'source', + W: 'unalignedWords', + Z: 'transcription' + }; + + this.parseHeader = function(line) { + var field = metaTextHeaders[line[0]]; + var origSize = line.length-2 + var restOfLine = tokenizer.translateString(tokenizer.stripComment(line.substring(2))) + if (field === 'unalignedWords' || field === 'notes') { + // These fields can be multi-line + tuneBuilder.addMetaTextArray(field, parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length}); + } else if (field !== undefined) { + // these fields are single line + tuneBuilder.addMetaText(field, parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length}); + } else { + var startChar = multilineVars.iChar; + var endChar = startChar + line.length; + switch(line[0]) + { + case 'H': + // History is a little different because once it starts it continues until another header field is encountered + tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length}); + line = tokenizer.peekLine() + while (line && line[1] !== ':') { + tokenizer.nextLine() + tuneBuilder.addMetaTextArray("history", parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line))), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length}); + line = tokenizer.peekLine() + } + break; + case 'K': + // since the key is the last thing that can happen in the header, we can resolve the tempo now + this.resolveTempo(); + var result = parseKeyVoice.parseKey(line.substring(2), false); + if (!multilineVars.is_in_header && tuneBuilder.hasBeginMusic()) { + if (result.foundClef) + tuneBuilder.appendStartingElement('clef', startChar, endChar, multilineVars.clef); + if (result.foundKey) + tuneBuilder.appendStartingElement('key', startChar, endChar, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key)); + } + multilineVars.is_in_header = false; // The first key signifies the end of the header. + break; + case 'L': + this.setDefaultLength(line, 2, line.length); + break; + case 'M': + multilineVars.origMeter = multilineVars.meter = this.setMeter(line.substring(2)); + break; + case 'P': + // TODO-PER: There is more to do with parts, but the writer doesn't care. + if (multilineVars.is_in_header) + tuneBuilder.addMetaText("partOrder", parseDirective.parseFontChangeLine(restOfLine), { startChar: multilineVars.iChar, endChar: multilineVars.iChar+line.length}); + else + multilineVars.partForNextLine = { title: restOfLine, startChar: startChar, endChar: endChar}; + break; + case 'Q': + var tempo = this.setTempo(line, 2, line.length, multilineVars.iChar); + if (tempo.type === 'delaySet') multilineVars.tempo = tempo.tempo; + else if (tempo.type === 'immediate') { + if (!tune.metaText.tempo) + tune.metaText.tempo = tempo.tempo; + else + multilineVars.tempoForNextLine = ['tempo', startChar, endChar, tempo.tempo] + } + break; + case 'T': + if (multilineVars.titlecaps) + restOfLine = restOfLine.toUpperCase(); + this.setTitle(parseDirective.parseFontChangeLine(tokenizer.theReverser(restOfLine)), origSize); + break; + case 'U': + this.addUserDefinition(line, 2, line.length); + break; + case 'V': + parseKeyVoice.parseVoice(line, 2, line.length); + if (!multilineVars.is_in_header) + return {newline: true}; + break; + case 's': + return {symbols: true}; + case 'w': + return {words: true}; + case 'X': + break; + case 'E': + case 'm': + warn("Ignored header", line, 0); + break; + default: + return {regular: true}; + } + } + return {}; + }; +}; + +module.exports = ParseHeader; diff --git a/src/parse/abc_parse_key_voice.js b/src/parse/abc_parse_key_voice.js new file mode 100644 index 0000000000000000000000000000000000000000..f7fa4b224b74696d037f1acd06126c35bd28d9d8 --- /dev/null +++ b/src/parse/abc_parse_key_voice.js @@ -0,0 +1,800 @@ +var parseDirective = require('./abc_parse_directive'); +var transpose = require('./abc_transpose'); + +var parseKeyVoice = {}; + +(function() { + var tokenizer; + var warn; + var multilineVars; + var tune; + var tuneBuilder; + parseKeyVoice.initialize = function(tokenizer_, warn_, multilineVars_, tune_, tuneBuilder_) { + tokenizer = tokenizer_; + warn = warn_; + multilineVars = multilineVars_; + tune = tune_; + tuneBuilder = tuneBuilder_; + }; + + parseKeyVoice.standardKey = function(keyName, root, acc, localTranspose) { + return transpose.keySignature(multilineVars, keyName, root, acc, localTranspose); + }; + + var clefLines = { + 'treble': { clef: 'treble', pitch: 4, mid: 0 }, + 'treble+8': { clef: 'treble+8', pitch: 4, mid: 0 }, + 'treble-8': { clef: 'treble-8', pitch: 4, mid: 0 }, + 'treble^8': { clef: 'treble+8', pitch: 4, mid: 0 }, + 'treble_8': { clef: 'treble-8', pitch: 4, mid: 0 }, + 'treble1': { clef: 'treble', pitch: 2, mid: 2 }, + 'treble2': { clef: 'treble', pitch: 4, mid: 0 }, + 'treble3': { clef: 'treble', pitch: 6, mid: -2 }, + 'treble4': { clef: 'treble', pitch: 8, mid: -4 }, + 'treble5': { clef: 'treble', pitch: 10, mid: -6 }, + 'perc': { clef: 'perc', pitch: 6, mid: 0 }, + 'none': { clef: 'none', mid: 0 }, + 'bass': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass+8': { clef: 'bass+8', pitch: 8, mid: -12 }, + 'bass-8': { clef: 'bass-8', pitch: 8, mid: -12 }, + 'bass^8': { clef: 'bass+8', pitch: 8, mid: -12 }, + 'bass_8': { clef: 'bass-8', pitch: 8, mid: -12 }, + 'bass+16': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass-16': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass^16': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass_16': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass1': { clef: 'bass', pitch: 2, mid: -6 }, + 'bass2': { clef: 'bass', pitch: 4, mid: -8 }, + 'bass3': { clef: 'bass', pitch: 6, mid: -10 }, + 'bass4': { clef: 'bass', pitch: 8, mid: -12 }, + 'bass5': { clef: 'bass', pitch: 10, mid: -14 }, + 'tenor': { clef: 'alto', pitch: 8, mid: -8 }, + 'tenor1': { clef: 'alto', pitch: 2, mid: -2 }, + 'tenor2': { clef: 'alto', pitch: 4, mid: -4 }, + 'tenor3': { clef: 'alto', pitch: 6, mid: -6 }, + 'tenor4': { clef: 'alto', pitch: 8, mid: -8 }, + 'tenor5': { clef: 'alto', pitch: 10, mid: -10 }, + 'alto': { clef: 'alto', pitch: 6, mid: -6 }, + 'alto1': { clef: 'alto', pitch: 2, mid: -2 }, + 'alto2': { clef: 'alto', pitch: 4, mid: -4 }, + 'alto3': { clef: 'alto', pitch: 6, mid: -6 }, + 'alto4': { clef: 'alto', pitch: 8, mid: -8 }, + 'alto5': { clef: 'alto', pitch: 10, mid: -10 }, + 'alto+8': { clef: 'alto+8', pitch: 6, mid: -6 }, + 'alto-8': { clef: 'alto-8', pitch: 6, mid: -6 }, + 'alto^8': { clef: 'alto+8', pitch: 6, mid: -6 }, + 'alto_8': { clef: 'alto-8', pitch: 6, mid: -6 } + }; + + var calcMiddle = function(clef, oct) { + var value = clefLines[clef]; + var mid = value ? value.mid : 0; + return mid+oct; + }; + + parseKeyVoice.fixClef = function(clef) { + var value = clefLines[clef.type]; + if (value) { + clef.clefPos = value.pitch; + clef.type = value.clef; + } + }; + + parseKeyVoice.deepCopyKey = function(key) { + var ret = { accidentals: [], root: key.root, acc: key.acc, mode: key.mode }; + key.accidentals.forEach(function(k) { + ret.accidentals.push(Object.assign({},k)); + }); + return ret; + }; + + var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11}; + + parseKeyVoice.addPosToKey = function(clef, key) { + // Shift the key signature from the treble positions to whatever position is needed for the clef. + // This may put the key signature unnaturally high or low, so if it does, then shift it. + var mid = clef.verticalPos; + key.accidentals.forEach(function(acc) { + var pitch = pitches[acc.note]; + pitch = pitch - mid; + acc.verticalPos = pitch; + }); + if (key.impliedNaturals) + key.impliedNaturals.forEach(function(acc) { + var pitch = pitches[acc.note]; + pitch = pitch - mid; + acc.verticalPos = pitch; + }); + + if (mid < -10) { + key.accidentals.forEach(function(acc) { + acc.verticalPos -= 7; + if (acc.verticalPos >= 11 || (acc.verticalPos === 10 && acc.acc === 'flat')) + acc.verticalPos -= 7; + if (acc.note === 'A' && acc.acc === 'sharp' ) + acc.verticalPos -=7; + if ((acc.note === 'G' || acc.note === 'F') && acc.acc === 'flat' ) + acc.verticalPos -=7; + }); + if (key.impliedNaturals) + key.impliedNaturals.forEach(function(acc) { + acc.verticalPos -= 7; + if (acc.verticalPos >= 11 || (acc.verticalPos === 10 && acc.acc === 'flat')) + acc.verticalPos -= 7; + if (acc.note === 'A' && acc.acc === 'sharp' ) + acc.verticalPos -=7; + if ((acc.note === 'G' || acc.note === 'F') && acc.acc === 'flat' ) + acc.verticalPos -=7; + }); + } else if (mid < -4) { + key.accidentals.forEach(function(acc) { + acc.verticalPos -= 7; + if (mid === -8 && (acc.note === 'f' || acc.note === 'g') && acc.acc === 'sharp' ) + acc.verticalPos -=7; + }); + if (key.impliedNaturals) + key.impliedNaturals.forEach(function(acc) { + acc.verticalPos -= 7; + if (mid === -8 && (acc.note === 'f' || acc.note === 'g') && acc.acc === 'sharp' ) + acc.verticalPos -=7; + }); + } else if (mid >= 7) { + key.accidentals.forEach(function(acc) { + acc.verticalPos += 7; + }); + if (key.impliedNaturals) + key.impliedNaturals.forEach(function(acc) { + acc.verticalPos += 7; + }); + } + }; + + parseKeyVoice.fixKey = function(clef, key) { + var fixedKey = Object.assign({},key); + parseKeyVoice.addPosToKey(clef, fixedKey); + return fixedKey; + }; + + var parseMiddle = function(str) { + var i = 0; + var p = str[i++]; + if (p === '^' || p === '_') + p = str[i++]; + var mid = pitches[p]; + if (mid === undefined) + mid = 6; // If a legal middle note wasn't received, just ignore it. + for ( ; i < str.length; i++) { + if (str[i] === ',') mid -= 7; + else if (str[i] === "'") mid += 7; + else break; + } + return { mid: mid - 6, str: str.substring(i) }; // We get the note in the middle of the staff. We want the note that appears as the first ledger line below the staff. + }; + + var normalizeAccidentals = function(accs) { + for (var i = 0; i < accs.length; i++) { + if (accs[i].note === 'b') + accs[i].note = 'B'; + else if (accs[i].note === 'a') + accs[i].note = 'A'; + else if (accs[i].note === 'F') + accs[i].note = 'f'; + else if (accs[i].note === 'E') + accs[i].note = 'e'; + else if (accs[i].note === 'D') + accs[i].note = 'd'; + else if (accs[i].note === 'C') + accs[i].note = 'c'; + else if (accs[i].note === 'G' && accs[i].acc === 'sharp') + accs[i].note = 'g'; + else if (accs[i].note === 'g' && accs[i].acc === 'flat') + accs[i].note = 'G'; + } + }; + + parseKeyVoice.parseKey = function(str, isInline) // (and clef) + { + // returns: + // { foundClef: true, foundKey: true } + // Side effects: + // calls warn() when there is a syntax error + // sets these members of multilineVars: + // clef + // key + // style + // + // The format is: + // K: [⟨key⟩] [⟨modifiers⟩*] + // modifiers are any of the following in any order: + // [⟨clef⟩] [middle=⟨pitch⟩] [transpose=[-]⟨number⟩] [stafflines=⟨number⟩] [staffscale=⟨number⟩][style=⟨style⟩] + // key is none|HP|Hp|⟨specified_key⟩ + // clef is [clef=] [⟨clef type⟩] [⟨line number⟩] [+8|-8] + // specified_key is ⟨pitch⟩[#|b][mode(first three chars are significant)][accidentals*] + if (str.length === 0) { + // an empty K: field is the same as K:none + str = 'none'; + } + var tokens = tokenizer.tokenize(str, 0, str.length); + var ret = {}; + + // Be sure that a key was passed in + if (tokens.length === 0) { + warn("Must pass in key signature.", str, 0); + return ret; + } + + // first the key + switch (tokens[0].token) { + case 'HP': + parseDirective.addDirective("bagpipes"); + multilineVars.key = { root: "HP", accidentals: [], acc: "", mode: "" }; + ret.foundKey = true; + tokens.shift(); + break; + case 'Hp': + parseDirective.addDirective("bagpipes"); + multilineVars.key = { root: "Hp", accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}], acc: "", mode: "" }; + ret.foundKey = true; + tokens.shift(); + break; + case 'none': + // we got the none key - that's the same as C to us + multilineVars.key = { root: "none", accidentals: [], acc: "", mode: "" }; + ret.foundKey = true; + tokens.shift(); + break; + default: + var retPitch = tokenizer.getKeyPitch(tokens[0].token); + if (retPitch.len > 0) { + ret.foundKey = true; + var acc = ""; + var mode = ""; + // The accidental and mode might be attached to the pitch, so we might want to just remove the first character. + if (tokens[0].token.length > 1) + tokens[0].token = tokens[0].token.substring(1); + else + tokens.shift(); + var key = retPitch.token; + // We got a pitch to start with, so we might also have an accidental and a mode + if (tokens.length > 0) { + var retAcc = tokenizer.getSharpFlat(tokens[0].token); + if (retAcc.len > 0) { + if (tokens[0].token.length > 1) + tokens[0].token = tokens[0].token.substring(1); + else + tokens.shift(); + key += retAcc.token; + acc = retAcc.token; + } + if (tokens.length > 0) { + var retMode = tokenizer.getMode(tokens[0].token); + if (retMode.len > 0) { + tokens.shift(); + key += retMode.token; + mode = retMode.token; + } + } + // Be sure that the key specified is in the list: not all keys are physically possible, like Cbmin. + if (parseKeyVoice.standardKey(key, retPitch.token, acc, 0) === undefined) { + warn("Unsupported key signature: " + key, str, 0); + return ret; + } + } + // We need to do a deep copy because we are going to modify it + var oldKey = parseKeyVoice.deepCopyKey(multilineVars.key); + //TODO-PER: HACK! To get the local transpose to work, the transposition is done for each line. This caused the global transposition variable to be factored in twice, so, instead of rewriting that right now, I'm just subtracting one of them here. + var keyCompensate = !isInline && multilineVars.globalTranspose ? -multilineVars.globalTranspose : 0; + //console.log("parse", JSON.stringify(multilineVars), isInline) + var savedOrigKey; + if (isInline) + savedOrigKey = multilineVars.globalTransposeOrigKeySig + multilineVars.key = parseKeyVoice.deepCopyKey(parseKeyVoice.standardKey(key, retPitch.token, acc, keyCompensate)); + if (isInline) + multilineVars.globalTransposeOrigKeySig = savedOrigKey + multilineVars.key.mode = mode; + if (oldKey) { + // Add natural in all places that the old key had an accidental. + var kk; + for (var k = 0; k < multilineVars.key.accidentals.length; k++) { + for (kk = 0; kk < oldKey.accidentals.length; kk++) { + if (oldKey.accidentals[kk].note && multilineVars.key.accidentals[k].note.toLowerCase() === oldKey.accidentals[kk].note.toLowerCase()) + oldKey.accidentals[kk].note = null; + } + } + for (kk = 0; kk < oldKey.accidentals.length; kk++) { + if (oldKey.accidentals[kk].note) { + if (!multilineVars.key.impliedNaturals) + multilineVars.key.impliedNaturals = []; + multilineVars.key.impliedNaturals.push({ acc: 'natural', note: oldKey.accidentals[kk].note }); + } + } + } + } + break; + } + + // There are two special cases of deprecated syntax. Ignore them if they occur + if (tokens.length === 0) return ret; + if (tokens[0].token === 'exp') tokens.shift(); + if (tokens.length === 0) return ret; + if (tokens[0].token === 'oct') tokens.shift(); + + // now see if there are extra accidentals + if (tokens.length === 0) return ret; + var accs = tokenizer.getKeyAccidentals2(tokens); + if (accs.warn) + warn(accs.warn, str, 0); + // If we have extra accidentals, first replace ones that are of the same pitch before adding them to the end. + if (accs.accs) { + if (!ret.foundKey) { // if there are only extra accidentals, make sure this is set. + ret.foundKey = true; + multilineVars.key = { root: "none", acc: "", mode: "", accidentals: [] }; + } + normalizeAccidentals(accs.accs); + for (var i = 0; i < accs.accs.length; i++) { + var found = false; + for (var j = 0; j < multilineVars.key.accidentals.length && !found; j++) { + if (multilineVars.key.accidentals[j].note === accs.accs[i].note) { + found = true; + if (multilineVars.key.accidentals[j].acc !== accs.accs[i].acc) { + // If the accidental is different, then replace it. If it is the same, then the declaration was redundant, so just ignore it. + multilineVars.key.accidentals[j].acc = accs.accs[i].acc; + if (!multilineVars.key.explicitAccidentals) + multilineVars.key.explicitAccidentals = []; + multilineVars.key.explicitAccidentals.push(accs.accs[i]); + } + } + } + if (!found) { + if (!multilineVars.key.explicitAccidentals) + multilineVars.key.explicitAccidentals = []; + multilineVars.key.explicitAccidentals.push(accs.accs[i]); + multilineVars.key.accidentals.push(accs.accs[i]); + if (multilineVars.key.impliedNaturals) { + for (var kkk = 0; kkk < multilineVars.key.impliedNaturals.length; kkk++) { + if (multilineVars.key.impliedNaturals[kkk].note === accs.accs[i].note) + multilineVars.key.impliedNaturals.splice(kkk, 1); + } + } + } + } + } + + // Now see if any optional parameters are present. They have the form "key=value", except that "clef=" is optional + var token; + while (tokens.length > 0) { + switch (tokens[0].token) { + case "m": + case "middle": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after middle", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after middle", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after middle=", str, 0); return ret; } + var pitch = tokenizer.getPitchFromTokens(tokens); + if (pitch.warn) + warn(pitch.warn, str, 0); + if (pitch.position) + multilineVars.clef.verticalPos = pitch.position - 6; // we get the position from the middle line, but want to offset it to the first ledger line. + break; + case "transpose": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after transpose", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after transpose", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after transpose=", str, 0); return ret; } + if (tokens[0].type !== 'number') { warn("Expected number after transpose", str, tokens[0].start); break; } + multilineVars.clef.transpose = tokens[0].intt; + tokens.shift(); + break; + case "stafflines": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after stafflines", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after stafflines", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after stafflines=", str, 0); return ret; } + if (tokens[0].type !== 'number') { warn("Expected number after stafflines", str, tokens[0].start); break; } + multilineVars.clef.stafflines = tokens[0].intt; + tokens.shift(); + break; + case "staffscale": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after staffscale", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after staffscale", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after staffscale=", str, 0); return ret; } + if (tokens[0].type !== 'number') { warn("Expected number after staffscale", str, tokens[0].start); break; } + multilineVars.clef.staffscale = tokens[0].floatt; + tokens.shift(); + break; + case "octave": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after octave", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after octave", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after octave=", str, 0); return ret; } + if (tokens[0].type !== 'number') { warn("Expected number after octave", str, tokens[0].start); break; } + multilineVars.octave = tokens[0].intt; + tokens.shift(); + break; + case "style": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after style", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after style", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after style=", str, 0); return ret; } + switch (tokens[0].token) { + case "normal": + case "harmonic": + case "rhythm": + case "x": + case "triangle": + multilineVars.style = tokens[0].token; + tokens.shift(); + break; + default: + warn("error parsing style element: " + tokens[0].token, str, tokens[0].start); + break; + } + break; + case "clef": + tokens.shift(); + if (tokens.length === 0) { warn("Expected = after clef", str, 0); return ret; } + token = tokens.shift(); + if (token.token !== "=") { warn("Expected = after clef", str, token.start); break; } + if (tokens.length === 0) { warn("Expected parameter after clef=", str, 0); return ret; } + //break; yes, we want to fall through. That allows "clef=" to be optional. + case "treble": + case "bass": + case "alto": + case "tenor": + case "perc": + case "none": + // clef is [clef=] [⟨clef type⟩] [⟨line number⟩] [+8|-8] + var clef = tokens.shift(); + switch (clef.token) { + case 'treble': + case 'tenor': + case 'alto': + case 'bass': + case 'perc': + case 'none': + break; + case 'C': clef.token = 'alto'; break; + case 'F': clef.token = 'bass'; break; + case 'G': clef.token = 'treble'; break; + case 'c': clef.token = 'alto'; break; + case 'f': clef.token = 'bass'; break; + case 'g': clef.token = 'treble'; break; + default: + warn("Expected clef name. Found " + clef.token, str, clef.start); + break; + } + if (tokens.length > 0 && tokens[0].type === 'number') { + clef.token += tokens[0].token; + tokens.shift(); + } + if (tokens.length > 1 && (tokens[0].token === '-' || tokens[0].token === '+' || tokens[0].token === '^' || tokens[0].token === '_') && tokens[1].token === '8') { + clef.token += tokens[0].token + tokens[1].token; + tokens.shift(); + tokens.shift(); + } + multilineVars.clef = {type: clef.token, verticalPos: calcMiddle(clef.token, 0)}; + if (multilineVars.currentVoice && multilineVars.currentVoice.transpose !== undefined) + multilineVars.clef.transpose = multilineVars.currentVoice.transpose; + ret.foundClef = true; + break; + default: + warn("Unknown parameter: " + tokens[0].token, str, tokens[0].start); + tokens.shift(); + } + } + return ret; + }; + + var setCurrentVoice = function(id) { + var currentVoice = multilineVars.voices[id] + if (multilineVars.currentVoice) { + if (multilineVars.currentVoice.index === currentVoice.index && multilineVars.currentVoice.staffNum === currentVoice.staffNum) + return // there was no change so don't reset it. + } + multilineVars.currentVoice = currentVoice; + return tuneBuilder.setCurrentVoice(currentVoice.staffNum, currentVoice.index, id); + }; + + parseKeyVoice.parseVoice = function(line, i, e) { + //First truncate the string to the first non-space character after V: through either the + //end of the line or a % character. Then remove trailing spaces, too. + var ret = tokenizer.getMeat(line, i, e); + var start = ret.start; + var end = ret.end; + //The first thing on the line is the ID. It can be any non-space string and terminates at the + //first space. + var id = tokenizer.getToken(line, start, end); + if (id.length === 0) { + warn("Expected a voice id", line, start); + return; + } + var isNew = false; + if (multilineVars.voices[id] === undefined) { + multilineVars.voices[id] = {}; + isNew = true; + if (multilineVars.score_is_present) + warn("Can't have an unknown V: id when the %score directive is present", line, start); + } + start += id.length; + start += tokenizer.eatWhiteSpace(line, start); + + var staffInfo = {startStaff: isNew}; + var addNextTokenToStaffInfo = function(name) { + var attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected value for " + name + " in voice: " + attr.warn, line, start); + else if (attr.err !== undefined) + warn("Expected value for " + name + " in voice: " + attr.err, line, start); + else if (attr.token.length === 0 && line[start] !== '"') + warn("Expected value for " + name + " in voice", line, start); + else + staffInfo[name] = attr.token; + start += attr.len; + }; + var addNextTokenToVoiceInfo = function(id, name, type) { + var attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected value for " + name + " in voice: " + attr.warn, line, start); + else if (attr.err !== undefined) + warn("Expected value for " + name + " in voice: " + attr.err, line, start); + else if (attr.token.length === 0 && line[start] !== '"') + warn("Expected value for " + name + " in voice", line, start); + else { + if (type === 'number') + attr.token = parseFloat(attr.token); + multilineVars.voices[id][name] = attr.token; + } + start += attr.len; + }; + var getNextToken = function(name, type) { + var attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected value for " + name + " in voice: " + attr.warn, line, start); + else if (attr.err !== undefined) + warn("Expected value for " + name + " in voice: " + attr.err, line, start); + else if (attr.token.length === 0 && line[start] !== '"') + warn("Expected value for " + name + " in voice", line, start); + else { + if (type === 'number') + attr.token = parseFloat(attr.token); + return attr.token; + } + start += attr.len; + }; + var addNextNoteTokenToVoiceInfo = function(id, name) { + var noteToTransposition = { + "_B": 2, + "_E": 9, + "_b": -10, + "_e": -3 + }; + var attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected one of (_B, _E, _b, _e) for " + name + " in voice: " + attr.warn, line, start); + else if (attr.token.length === 0 && line[start] !== '"') + warn("Expected one of (_B, _E, _b, _e) for " + name + " in voice", line, start); + else { + var t = noteToTransposition[attr.token]; + if (!t) + warn("Expected one of (_B, _E, _b, _e) for " + name + " in voice", line, start); + else + multilineVars.voices[id][name] = t; + } + start += attr.len; + }; + + //Then the following items can occur in any order: + while (start < end) { + var token = tokenizer.getVoiceToken(line, start, end); + start += token.len; + + if (token.warn) { + warn("Error parsing voice: " + token.warn, line, start); + } else { + var attr = null; + switch (token.token) { + case 'clef': + case 'cl': + addNextTokenToStaffInfo('clef'); + // TODO-PER: check for a legal clef; do octavizing + var oct = 0; + // for (var ii = 0; ii < staffInfo.clef.length; ii++) { + // if (staffInfo.clef[ii] === ',') oct -= 7; + // else if (staffInfo.clef[ii] === "'") oct += 7; + // } + if (staffInfo.clef !== undefined) { + staffInfo.clef = staffInfo.clef.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp + if (staffInfo.clef.indexOf('+16') !== -1) { + oct += 14; + staffInfo.clef = staffInfo.clef.replace('+16', ''); + } + staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct); + } + break; + case 'treble': + case 'bass': + case 'tenor': + case 'alto': + case 'perc': + case 'none': + case 'treble\'': + case 'bass\'': + case 'tenor\'': + case 'alto\'': + case 'none\'': + case 'treble\'\'': + case 'bass\'\'': + case 'tenor\'\'': + case 'alto\'\'': + case 'none\'\'': + case 'treble,': + case 'bass,': + case 'tenor,': + case 'alto,': + case 'none,': + case 'treble,,': + case 'bass,,': + case 'tenor,,': + case 'alto,,': + case 'none,,': + // TODO-PER: handle the octave indicators on the clef by changing the middle property + var oct2 = 0; + // for (var iii = 0; iii < token.token.length; iii++) { + // if (token.token[iii] === ',') oct2 -= 7; + // else if (token.token[iii] === "'") oct2 += 7; + // } + staffInfo.clef = token.token.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp + staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct2); + multilineVars.voices[id].clef = token.token; + break; + case 'staves': + case 'stave': + case 'stv': + addNextTokenToStaffInfo('staves'); + break; + case 'brace': + case 'brc': + addNextTokenToStaffInfo('brace'); + break; + case 'bracket': + case 'brk': + addNextTokenToStaffInfo('bracket'); + break; + case 'name': + case 'nm': + addNextTokenToStaffInfo('name'); + break; + case 'subname': + case 'sname': + case 'snm': + addNextTokenToStaffInfo('subname'); + break; + case 'merge': + staffInfo.startStaff = false; + break; + case 'stem': + case 'stems': + attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected value for stems in voice: " + attr.warn, line, start); + else if (attr.err !== undefined) + warn("Expected value for stems in voice: " + attr.err, line, start); + else if (attr.token === 'up' || attr.token === 'down') + multilineVars.voices[id].stem = attr.token; + else + warn("Expected up or down for voice stem", line, start); + start += attr.len; + break; + case 'up': + case 'down': + multilineVars.voices[id].stem = token.token; + break; + case 'middle': + case 'm': + addNextTokenToStaffInfo('verticalPos'); + staffInfo.verticalPos = parseMiddle(staffInfo.verticalPos).mid; + break; + case 'gchords': + case 'gch': + multilineVars.voices[id].suppressChords = true; + // gchords can stand on its own, or it could be gchords=0. + attr = tokenizer.getVoiceToken(line, start, end); + if (attr.token === "0") + start = start + attr.len; + break; + case 'space': + case 'spc': + addNextTokenToStaffInfo('spacing'); + break; + case 'scale': + addNextTokenToVoiceInfo(id, 'scale', 'number'); + break; + case 'score': + addNextNoteTokenToVoiceInfo(id, 'scoreTranspose'); + break; + case 'transpose': + addNextTokenToVoiceInfo(id, 'transpose', 'number'); + break; + case 'stafflines': + addNextTokenToVoiceInfo(id, 'stafflines', 'number'); + break; + case 'staffscale': + // TODO-PER: This is passed to the engraver, but the engraver ignores it. + addNextTokenToVoiceInfo(id, 'staffscale', 'number'); + break; + case 'octave': + addNextTokenToVoiceInfo(id, 'octave', 'number'); + break; + case 'volume': + // TODO-PER: This is accepted, but not implemented, yet. + addNextTokenToVoiceInfo(id, 'volume', 'number'); + break; + case 'cue': + // TODO-PER: This is accepted, but not implemented, yet. + var cue = getNextToken('cue', 'string'); + if (cue === 'on') + multilineVars.voices[id].scale = 0.6; + else multilineVars.voices[id].scale = 1; + break; + case "style": + attr = tokenizer.getVoiceToken(line, start, end); + if (attr.warn !== undefined) + warn("Expected value for style in voice: " + attr.warn, line, start); + else if (attr.err !== undefined) + warn("Expected value for style in voice: " + attr.err, line, start); + else if (attr.token === 'normal' || attr.token === 'harmonic' || attr.token === 'rhythm' || attr.token === 'x' || attr.token === 'triangle') + multilineVars.voices[id].style = attr.token; + else + warn("Expected one of [normal, harmonic, rhythm, x, triangle] for voice style", line, start); + start += attr.len; + break; + // default: + // Use this to find V: usages that aren't handled. + // console.log("parse voice", token, tune.metaText.title); + } + } + start += tokenizer.eatWhiteSpace(line, start); + } + + // now we've filled up staffInfo, figure out what to do with this voice + // TODO-PER: It is unclear from the standard and the examples what to do with brace, bracket, and staves, so they are ignored for now. + if (staffInfo.startStaff || multilineVars.staves.length === 0) { + multilineVars.staves.push({index: multilineVars.staves.length, meter: multilineVars.origMeter}); + if (!multilineVars.score_is_present) + multilineVars.staves[multilineVars.staves.length-1].numVoices = 0; + } + if (multilineVars.voices[id].staffNum === undefined) { + // store where to write this for quick access later. + multilineVars.voices[id].staffNum = multilineVars.staves.length-1; + var vi = 0; + for(var v in multilineVars.voices) { + if(multilineVars.voices.hasOwnProperty(v)) { + if (multilineVars.voices[v].staffNum === multilineVars.voices[id].staffNum) + vi++; + } + } + multilineVars.voices[id].index = vi-1; + } + var s = multilineVars.staves[multilineVars.voices[id].staffNum]; + if (!multilineVars.score_is_present) + s.numVoices++; + if (staffInfo.clef) s.clef = {type: staffInfo.clef, verticalPos: staffInfo.verticalPos}; + if (staffInfo.spacing) s.spacing_below_offset = staffInfo.spacing; + if (staffInfo.verticalPos) s.verticalPos = staffInfo.verticalPos; + + if (staffInfo.name) {if (s.name) s.name.push(staffInfo.name); else s.name = [ staffInfo.name ];} + if (staffInfo.subname) {if (s.subname) s.subname.push(staffInfo.subname); else s.subname = [ staffInfo.subname ];} + + return setCurrentVoice(id); + }; + +})(); + +module.exports = parseKeyVoice; diff --git a/src/parse/abc_parse_music.js b/src/parse/abc_parse_music.js new file mode 100644 index 0000000000000000000000000000000000000000..aad17148045bc280690faff386974c0f74e992a9 --- /dev/null +++ b/src/parse/abc_parse_music.js @@ -0,0 +1,1318 @@ +var parseKeyVoice = require('./abc_parse_key_voice'); +var transpose = require('./abc_transpose'); + +var tokenizer; +var warn; +var multilineVars; +var tune; +var tuneBuilder; +var header; + +var { + legalAccents, + volumeDecorations, + dynamicDecorations, + accentPseudonyms, + accentDynamicPseudonyms, + nonDecorations, + durations, + pitches, + rests, + accMap, + tripletQ +} = require('./abc_parse_settings') + +var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilder, _header) { + tokenizer = _tokenizer; + warn = _warn; + multilineVars = _multilineVars; + tune = _tune; + tuneBuilder = _tuneBuilder; + header = _header; + this.lineContinuation = false; +} + +// +// Parse line of music +// +// This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element +// core-note is with no spaces within that +// chord is with no spaces within that +// grace-notes is spaces are allowed +// note-group is spaces are allowed between items +// bar-marking is or spaces allowed +// header is spaces can occur between the colon, in the field, and before the close bracket +// header can also be the only thing on a line. This is true even if it is a continuation line. In this case the brackets are not required. +// a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam. + +// Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this) +// Then, all leading and trailing spaces are ignored. +// If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct +// of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ]. + +// TODO-PER: How to handle ! for line break? +// TODO-PER: dots before bar, dots before slur +// TODO-PER: U: redefinable symbols. + +// Ambiguous symbols: +// "[" can be the start of a chord, the start of a header element or part of a bar line. +// --- if it is immediately followed by "|", it is a bar line +// --- if it is immediately followed by K: L: M: V: it is a header (note: there are other headers mentioned in the standard, but I'm not sure how they would be used.) +// --- otherwise it is the beginning of a chord +// "(" can be the start of a slur or a triplet +// --- if it is followed by a number from 2-9, then it is a triplet +// --- otherwise it is a slur +// "]" +// --- if there is a chord open, then this is the close +// --- if it is after a [|, then it is an invisible bar line +// --- otherwise, it is par of a bar +// "." can be a bar modifier or a slur modifier, or a decoration +// --- if it comes immediately before a bar, it is a bar modifier +// --- if it comes immediately before a slur, it is a slur modifier +// --- otherwise it is a decoration for the next note. +// number: +// --- if it is after a bar, with no space, it is an ending marker +// --- if it is after a ( with no space, it is a triplet count +// --- if it is after a pitch or octave or slash, then it is a duration + +// Unambiguous symbols (except inside quoted strings): +// vertical-bar, colon: part of a bar +// ABCDEFGabcdefg: pitch +// xyzZ: rest +// comma, prime: octave +// close-paren: end-slur +// hyphen: tie +// tilde, v, u, bang, plus, THLMPSO: decoration +// carat, underscore, equal: accidental +// ampersand: time reset +// open-curly, close-curly: grace notes +// double-quote: chord symbol +// less-than, greater-than, slash: duration +// back-tick, space, tab: space + +var isInTie = function(multilineVars, overlayLevel, el) { + if (multilineVars.inTie[overlayLevel] === undefined) + return false; + // If this is single voice music then the voice index isn't set, so we use the first voice. + var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum * 100 + multilineVars.currentVoice.index : 0; + if (multilineVars.inTie[overlayLevel][voiceIndex]) { + if (el.pitches !== undefined || el.rest.type !== 'spacer') + return true; + } + return false; +}; + +var el = { }; +MusicParser.prototype.parseMusic = function(line) { + header.resolveTempo(); + //multilineVars.havent_set_length = false; // To late to set this now. + multilineVars.is_in_header = false; // We should have gotten a key header by now, but just in case, this is definitely out of the header. + var i = 0; + var startOfLine = multilineVars.iChar; + // see if there is nothing but a comment on this line. If so, just ignore it. A full line comment is optional white space followed by % + while (tokenizer.isWhiteSpace(line[i]) && i < line.length) + i++; + if (i === line.length || line[i] === '%') + return; + + // Start with the standard staff, clef and key symbols on each line + var delayStartNewLine = multilineVars.start_new_line; + if (multilineVars.continueall === undefined) + multilineVars.start_new_line = true; + else + multilineVars.start_new_line = false; + var tripletNotesLeft = 0; + + // See if the line starts with a header field + var retHeader = header.letter_to_body_header(line, i); + if (retHeader[0] > 0) { + i += retHeader[0]; + // fixes bug on this: c[V:2]d + if (retHeader[1] === 'V') + this.startNewLine(); + // delayStartNewLine = true; + // TODO-PER: Handle inline headers + } + + var overlayLevel = 0; + while (i < line.length) + { + var startI = i; + if (line[i] === '%') + break; + + var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine); + if (retInlineHeader[0] > 0) { + i += retInlineHeader[0]; + //console.log("inline header", retInlineHeader) + if (retInlineHeader[1] === 'V') + delayStartNewLine = true; // fixes bug on this: c[V:2]d + // TODO-PER: Handle inline headers + //multilineVars.start_new_line = false; + } else { + // Wait until here to actually start the line because we know we're past the inline statements. + if (!tuneBuilder.hasBeginMusic() || (delayStartNewLine && !this.lineContinuation)) { + this.startNewLine(); + delayStartNewLine = false; + } + + // We need to decide if the following characters are a bar-marking or a note-group. + // Unfortunately, that is ambiguous. Both can contain chord symbols and decorations. + // If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group. + // If there is a bar marker, it is definitely a bar-marking. + // If there is either a core-note or chord, it is definitely a note-group. + // So, loop while we find grace-notes, chords-symbols, or decorations. [It is an error to have more than one grace-note group in a row; the others can be multiple] + // Then, if there is a grace-note, we know where to go. + // Else see if we have a chord, core-note, slur, triplet, or bar. + + var ret; + while (1) { + ret = tokenizer.eatWhiteSpace(line, i); + if (ret > 0) { + i += ret; + } + if (i > 0 && line[i-1] === '\x12') { + // there is one case where a line continuation isn't the same as being on the same line, and that is if the next character after it is a header. + ret = header.letter_to_body_header(line, i); + if (ret[0] > 0) { + if (ret[1] === 'V') + this.startNewLine(); // fixes bug on this: c\\nV:2]\\nd + // TODO: insert header here + i = ret[0]; + multilineVars.start_new_line = false; + } + } + // gather all the grace notes, chord symbols and decorations + ret = letter_to_spacer(line, i); + if (ret[0] > 0) { + i += ret[0]; + } + + ret = letter_to_chord(line, i); + if (ret[0] > 0) { + // There could be more than one chord here if they have different positions. + // If two chords have the same position, then connect them with newline. + if (!el.chord) + el.chord = []; + var chordName = tokenizer.translateString(ret[1]); + chordName = chordName.replace(/;/g, "\n"); + var addedChord = false; + for (var ci = 0; ci < el.chord.length; ci++) { + if (el.chord[ci].position === ret[2]) { + addedChord = true; + el.chord[ci].name += "\n" + chordName; + } + } + if (addedChord === false) { + if (ret[2] === null && ret[3]) + el.chord.push({name: chordName, rel_position: ret[3]}); + else + el.chord.push({name: chordName, position: ret[2]}); + } + + i += ret[0]; + var ii = tokenizer.skipWhiteSpace(line.substring(i)); + if (ii > 0) + el.force_end_beam_last = true; + i += ii; + } else { + if (nonDecorations.indexOf(line[i]) === -1) + ret = letter_to_accent(line, i); + else ret = [ 0 ]; + if (ret[0] > 0) { + if (ret[1] === null) { + if (i + 1 < line.length) + this.startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it. + } else if (ret[1].length > 0) { + if (ret[1].indexOf("style=") === 0) { + el.style = ret[1].substr(6); + } else { + if (el.decoration === undefined) + el.decoration = []; + if (ret[1] === 'beambr1') + el.beambr = 1; + else if (ret[1] === "beambr2") + el.beambr = 2; + else el.decoration.push(ret[1]); + } + } + i += ret[0]; + } else { + ret = letter_to_grace(line, i); + // TODO-PER: Be sure there aren't already grace notes defined. That is an error. + if (ret[0] > 0) { + el.gracenotes = ret[1]; + i += ret[0]; + } else + break; + } + } + } + + ret = letter_to_bar(line, i); + if (ret[0] > 0) { + // This is definitely a bar + overlayLevel = 0; + if (el.gracenotes !== undefined) { + // Attach the grace note to an invisible note + el.rest = { type: 'spacer' }; + el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does. + multilineVars.addFormattingOptions(el, tune.formatting, 'note'); + tuneBuilder.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el); + multilineVars.measureNotEmpty = true; + el = {}; + } + var bar = {type: ret[1]}; + if (bar.type.length === 0) + warn("Unknown bar type", line, i); + else { + if (multilineVars.inEnding && bar.type !== 'bar_thin') { + bar.endEnding = true; + multilineVars.inEnding = false; + } + if (ret[2]) { + bar.startEnding = ret[2]; + if (multilineVars.inEnding) + bar.endEnding = true; + multilineVars.inEnding = true; + if (ret[1] === "bar_right_repeat") { + // restore the tie and slur state from the start repeat + multilineVars.restoreStartEndingHoldOvers(); + } else { + // save inTie, inTieChord + multilineVars.duplicateStartEndingHoldOvers(); + } + } + if (el.decoration !== undefined) + bar.decoration = el.decoration; + if (el.chord !== undefined) + bar.chord = el.chord; + if (bar.startEnding && multilineVars.barFirstEndingNum === undefined) + multilineVars.barFirstEndingNum = multilineVars.currBarNumber; + else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum) + multilineVars.currBarNumber = multilineVars.barFirstEndingNum; + else if (bar.endEnding) + multilineVars.barFirstEndingNum = undefined; + if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) { + var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0); + if (isFirstVoice) { + multilineVars.currBarNumber++; + if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0) + bar.barNumber = multilineVars.currBarNumber; + } + } + multilineVars.addFormattingOptions(el, tune.formatting, 'bar'); + tuneBuilder.appendElement('bar', startOfLine+startI, startOfLine+i+ret[0], bar); + multilineVars.measureNotEmpty = false; + el = {}; + } + i += ret[0]; + } else if (line[i] === '&') { // backtrack to beginning of measure + ret = letter_to_overlay(line, i); + if (ret[0] > 0) { + tuneBuilder.appendElement('overlay', startOfLine, startOfLine+1, {}); + i += 1; + overlayLevel++; + } + + } else { + // This is definitely a note group + // + // Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.) + ret = letter_to_open_slurs_and_triplets(line, i); + if (ret.consumed > 0) { + if (ret.startSlur !== undefined) + el.startSlur = ret.startSlur; + if (ret.dottedSlur) + el.dottedSlur = true; + if (ret.triplet !== undefined) { + if (tripletNotesLeft > 0) + warn("Can't nest triplets", line, i); + else { + el.startTriplet = ret.triplet; + el.tripletMultiplier = ret.tripletQ / ret.triplet; + el.tripletR = ret.num_notes; + tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes; + } + } + i += ret.consumed; + } + + // handle chords. + if (line[i] === '[') { + var chordStartChar = i; + i++; + var chordDuration = null; + var rememberEndBeam = false; + + var done = false; + while (!done) { + var accent = letter_to_accent(line, i); + if (accent[0] > 0) { + i += accent[0]; + } + + var chordNote = getCoreNote(line, i, {}, false); + if (chordNote !== null && chordNote.pitch !== undefined) { + if (accent[0] > 0) { // If we found a decoration above, it modifies the entire chord. "style" is handled below. + if (accent[1].indexOf("style=") !== 0) { + if (el.decoration === undefined) + el.decoration = []; + el.decoration.push(accent[1]); + } + } + if (chordNote.end_beam) { + el.end_beam = true; + delete chordNote.end_beam; + } + if (el.pitches === undefined) { + el.duration = chordNote.duration; + el.pitches = [ chordNote ]; + } else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing. + el.pitches.push(chordNote); + delete chordNote.duration; + if (accent[0] > 0) { // If we found a style above, it modifies the individual pitch, not the entire chord. + if (accent[1].indexOf("style=") === 0) { + el.pitches[el.pitches.length-1].style = accent[1].substr(6); + } + } + + if (multilineVars.inTieChord[el.pitches.length]) { + chordNote.endTie = true; + multilineVars.inTieChord[el.pitches.length] = undefined; + } + if (chordNote.startTie) + multilineVars.inTieChord[el.pitches.length] = true; + + i = chordNote.endChar; + delete chordNote.endChar; + } else if (line[i] === ' ') { + // Spaces are not allowed in chords, but we can recover from it by ignoring it. + warn("Spaces are not allowed in chords", line, i); + i++; + } else { + if (i < line.length && line[i] === ']') { + // consume the close bracket + i++; + + if (multilineVars.next_note_duration !== 0) { + el.duration = el.duration * multilineVars.next_note_duration; + multilineVars.next_note_duration = 0; + } + + if (isInTie(multilineVars, overlayLevel, el)) { + el.pitches.forEach(function(pitch) { pitch.endTie = true; }); + setIsInTie(multilineVars, overlayLevel, false); + } + + if (tripletNotesLeft > 0 && !(el.rest && el.rest.type === "spacer")) { + tripletNotesLeft--; + if (tripletNotesLeft === 0) { + el.endTriplet = true; + } + } + + var postChordDone = false; + while (i < line.length && !postChordDone) { + switch (line[i]) { + case ' ': + case '\t': + addEndBeam(el); + break; + case ')': + if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++; + break; + case '-': + el.pitches.forEach(function(pitch) { pitch.startTie = {}; }); + setIsInTie(multilineVars, overlayLevel, true); + break; + case '>': + case '<': + var br2 = getBrokenRhythm(line, i); + i += br2[0] - 1; // index gets incremented below, so we'll let that happen + multilineVars.next_note_duration = br2[2]; + if (chordDuration) + chordDuration = chordDuration * br2[1]; + else + chordDuration = br2[1]; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '/': + var fraction = tokenizer.getFraction(line, i); + chordDuration = fraction.value; + i = fraction.index; + var ch = line[i] + if (ch === ' ') + rememberEndBeam = true; + if (ch === '-' || ch === ')' || ch === ' ' || ch === '<' || ch === '>') + i--; // Subtracting one because one is automatically added below + else + postChordDone = true; + break; + case '0': + chordDuration = 0; + break; + default: + postChordDone = true; + break; + } + if (!postChordDone) { + i++; + } + } + } else + warn("Expected ']' to end the chords", line, i); + + if (el.pitches !== undefined) { + if (chordDuration !== null) { + el.duration = el.duration * chordDuration; + if (rememberEndBeam) + addEndBeam(el); + } + + multilineVars.addFormattingOptions(el, tune.formatting, 'note'); + tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); + multilineVars.measureNotEmpty = true; + el = {}; + } + done = true; + } + } + + } else { + // Single pitch + var el2 = {}; + var core = getCoreNote(line, i, el2, true); + if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true); + if (core !== null) { + if (core.pitch !== undefined) { + el.pitches = [ { } ]; + // TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e' + if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental; + el.pitches[0].pitch = core.pitch; + el.pitches[0].name = core.name; + if (core.midipitch || core.midipitch === 0) + el.pitches[0].midipitch = core.midipitch; + if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur; + if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie; + if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur; + if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur; + if (el.dottedSlur !== undefined) el.pitches[0].dottedSlur = true; + if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie; + if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie; + } else { + el.rest = core.rest; + if (core.endSlur !== undefined) el.endSlur = core.endSlur; + if (core.endTie !== undefined) el.rest.endTie = core.endTie; + if (core.startSlur !== undefined) el.startSlur = core.startSlur; + if (core.startTie !== undefined) el.rest.startTie = core.startTie; + if (el.startTie !== undefined) el.rest.startTie = el.startTie; + } + + if (core.chord !== undefined) el.chord = core.chord; + if (core.duration !== undefined) el.duration = core.duration; + if (core.decoration !== undefined) el.decoration = core.decoration; + if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes; + delete el.startSlur; + delete el.dottedSlur; + if (isInTie(multilineVars, overlayLevel, el)) { + if (el.pitches !== undefined) { + el.pitches[0].endTie = true; + } else if (el.rest.type !== 'spacer') { + el.rest.endTie = true; + } + setIsInTie(multilineVars, overlayLevel, false); + } + if (core.startTie || el.startTie) + setIsInTie(multilineVars, overlayLevel, true); + i = core.endChar; + + if (tripletNotesLeft > 0 && !(core.rest && core.rest.type === "spacer")) { + tripletNotesLeft--; + if (tripletNotesLeft === 0) { + el.endTriplet = true; + } + } + + if (core.end_beam) + addEndBeam(el); + + // If there is a whole rest, then it should be the duration of the measure, not it's own duration. We need to special case it. + // If the time signature length is greater than 4/4, though, then a whole rest has no special treatment. + if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) { + el.rest.type = 'whole'; + + el.duration = durationOfMeasure(multilineVars); + } + + // Create a warning if this is not a displayable duration. + // The first item on a line is a regular note value, each item after that represents a dot placed after the previous note. + // Only durations less than a whole note are tested because whole note durations have some tricky rules. + + if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) { + if (!el.rest || el.rest.type !== 'spacer') + warn("Duration not representable: " + line.substring(startI, i), line, i); + } + + multilineVars.addFormattingOptions(el, tune.formatting, 'note'); + var succeeded = tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); + if (!succeeded) { + this.startNewLine() + tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el); + } + multilineVars.measureNotEmpty = true; + el = {}; + } + } + + if (i === startI) { // don't know what this is, so ignore it. + if (line[i] !== ' ' && line[i] !== '`') + warn("Unknown character ignored", line, i); + i++; + } + } + } + } + this.lineContinuation = line.indexOf('\x12') >= 0 || (retHeader[0] > 0) + if (!this.lineContinuation) { el = { } } +}; + +var setIsInTie =function(multilineVars, overlayLevel, value) { + // If this is single voice music then the voice index isn't set, so we use the first voice. + var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum * 100 + multilineVars.currentVoice.index : 0; + if (multilineVars.inTie[overlayLevel] === undefined) + multilineVars.inTie[overlayLevel] = []; + multilineVars.inTie[overlayLevel][voiceIndex] = value; +}; + +var letter_to_chord = function(line, i) { + if (line[i] === '"') + { + var chord = tokenizer.getBrackettedSubstring(line, i, 5); + if (!chord[2]) + warn("Missing the closing quote while parsing the chord symbol", line , i); + // If it starts with ^, then the chord appears above. + // If it starts with _ then the chord appears below. + // (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.) + if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '^') { + chord[1] = chord[1].substring(1); + chord[2] = 'above'; + } else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '_') { + chord[1] = chord[1].substring(1); + chord[2] = 'below'; + } else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '<') { + chord[1] = chord[1].substring(1); + chord[2] = 'left'; + } else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '>') { + chord[1] = chord[1].substring(1); + chord[2] = 'right'; + } else if (chord[0] > 0 && chord[1].length > 0 && chord[1][0] === '@') { + // @-15,5.7 + chord[1] = chord[1].substring(1); + var x = tokenizer.getFloat(chord[1]); + if (x.digits === 0){ + warn("Missing first position in absolutely positioned annotation.", line, i); + chord[1] = chord[1].replace("@",""); + chord[2] = 'above'; + return chord; + } + chord[1] = chord[1].substring(x.digits); + if (chord[1][0] !== ','){ + warn("Missing comma absolutely positioned annotation.", line, i); + chord[1] = chord[1].replace("@",""); + chord[2] = 'above'; + return chord; + } + chord[1] = chord[1].substring(1); + var y = tokenizer.getFloat(chord[1]); + if (y.digits === 0){ + warn("Missing second position in absolutely positioned annotation.", line, i); + chord[1] = chord[1].replace("@",""); + chord[2] = 'above'; + return chord; + } + chord[1] = chord[1].substring(y.digits); + var ws = tokenizer.skipWhiteSpace(chord[1]); + chord[1] = chord[1].substring(ws); + chord[2] = null; + chord[3] = { + x: x.value, + y: y.value + }; + } else { + if (multilineVars.freegchord !== true) { + chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭"); + chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯"); + chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o([^A-Za-z])/g, "$1$2°$3"); + chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o$/g, "$1$2°"); + chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)0([^A-Za-z])/g, "$1$2ø$3"); + chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)\^([^A-Za-z])/g, "$1$2∆$3"); + } + chord[2] = 'default'; + chord[1] = transpose.chordName(multilineVars, chord[1]); + } + return chord; + } + return [0, ""]; +}; + +var letter_to_grace = function(line, i) { + // Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration + if (line[i] === '{') { + // fetch the gracenotes string and consume that into the array + var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}'); + if (!gra[2]) + warn("Missing the closing '}' while parsing grace note", line, i); + // If there is a slur after the grace construction, then move it to the last note inside the grace construction + if (line[i+gra[0]] === ')') { + gra[0]++; + gra[1] += ')'; + } + + var gracenotes = []; + var ii = 0; + var inTie = false; + while (ii < gra[1].length) { + var acciaccatura = false; + if (gra[1][ii] === '/') { + acciaccatura = true; + ii++; + } + var note = getCoreNote(gra[1], ii, {}, false); + if (note !== null) { + // The grace note durations should not be affected by the default length: they should be based on 1/16, so if that isn't the default, then multiply here. + note.duration = note.duration / (multilineVars.default_length * 8); + if (acciaccatura) + note.acciaccatura = true; + if (note.rest) { + // don't allow rests inside gracenotes + warn("Rests not allowed as grace notes '" + gra[1][ii] + "' while parsing grace note", line, i); + } else + gracenotes.push(note); + + if (inTie) { + note.endTie = true; + inTie = false; + } + if (note.startTie) + inTie = true; + + ii = note.endChar; + delete note.endChar; + + if (note.end_beam) { + note.endBeam = true; + delete note.end_beam; + } + } + else { + // We shouldn't get anything but notes or a space here, so report an error + if (gra[1][ii] === ' ') { + if (gracenotes.length > 0) + gracenotes[gracenotes.length-1].endBeam = true; + } else + warn("Unknown character '" + gra[1][ii] + "' while parsing grace note", line, i); + ii++; + } + } + if (gracenotes.length) + return [gra[0], gracenotes]; + } + return [ 0 ]; +}; + +function letter_to_overlay(line, i) { + if (line[i] === '&') { + var start = i; + while (line[i] && line[i] !== ':' && line[i] !== '|') + i++; + return [ i-start, line.substring(start+1, i) ]; + } + return [ 0 ]; +} + +function durationOfMeasure(multilineVars) { + // TODO-PER: This could be more complicated if one of the unusual measures is used. + var meter = multilineVars.origMeter; + if (!meter || meter.type !== 'specified') + return 1; + if (!meter.value || meter.value.length === 0) + return 1; + return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10); +} + + + + +var letter_to_accent = function(line, i) { + var macro = multilineVars.macros[line[i]]; + + if (macro !== undefined) { + if (macro[0] === '!' || macro[0] === '+') + macro = macro.substring(1); + if (macro[macro.length-1] === '!' || macro[macro.length-1] === '+') + macro = macro.substring(0, macro.length-1); + if (legalAccents.includes(macro)) + return [ 1, macro ]; + else if (volumeDecorations.includes(macro)) { + if (multilineVars.volumePosition === 'hidden') + macro = ""; + return [1, macro]; + } else if (dynamicDecorations.includes(macro)) { + if (multilineVars.dynamicPosition === 'hidden') + macro = ""; + return [1, macro]; + } else { + if (!multilineVars.ignoredDecorations.includes(macro)) + warn("Unknown macro: " + macro, line, i); + return [1, '' ]; + } + } + switch (line[i]) + { + case '.': + if (line[i+1] === '(' || line[i+1] === '-') // a dot then open paren is a dotted slur; likewise dot dash is dotted tie. + break; + return [1, 'staccato']; + case 'u':return [1, 'upbow']; + case 'v':return [1, 'downbow']; + case '~':return [1, 'irishroll']; + case '!': + case '+': + var ret = tokenizer.getBrackettedSubstring(line, i, 5); + // Be sure that the accent is recognizable. + if (ret[1].length > 1 && (ret[1][0] === '^' || ret[1][0] ==='_')) + ret[1] = ret[1].substring(1); // TODO-PER: The test files have indicators forcing the ornament to the top or bottom, but that isn't in the standard. We'll just ignore them. + if (legalAccents.includes(ret[1])) + return ret; + if (volumeDecorations.includes(ret[1])) { + if (multilineVars.volumePosition === 'hidden' ) + ret[1] = ''; + return ret; + } + if (dynamicDecorations.includes(ret[1])) { + if (multilineVars.dynamicPosition === 'hidden' ) + ret[1] = ''; + return ret; + } + + var ind = accentPseudonyms.findIndex(function (acc) { return ret[1] === acc[0]}) + if (ind >= 0) { + ret[1] = accentPseudonyms[ind][1]; + return ret; + } + + ind = accentDynamicPseudonyms.findIndex(function (acc) { return ret[1] === acc[0]}) + if (ind >= 0) { + ret[1] = accentDynamicPseudonyms[ind][1]; + if (multilineVars.dynamicPosition === 'hidden' ) + ret[1] = ''; + return ret; + } + + // We didn't find the accent in the list, so consume the space, but don't return an accent. + // Although it is possible that ! was used as a line break, so accept that. + if (line[i] === '!' && (ret[0] === 1 || line[i+ret[0]-1] !== '!')) + return [1, null ]; + warn("Unknown decoration: " + ret[1], line, i); + ret[1] = ""; + return ret; + case 'H':return [1, 'fermata']; + case 'J':return [1, 'slide']; + case 'L':return [1, 'accent']; + case 'M':return [1, 'mordent']; + case 'O':return[1, 'coda']; + case 'P':return[1, 'pralltriller']; + case 'R':return [1, 'roll']; + case 'S':return [1, 'segno']; + case 'T':return [1, 'trill']; + } + return [0, 0]; +}; + +var letter_to_spacer = function(line, i) { + var start = i; + while (tokenizer.isWhiteSpace(line[i])) + i++; + return [ i-start ]; +}; + +// returns the class of the bar line +// the number of the repeat +// and the number of characters used up +// if 0 is returned, then the next element was not a bar line +var letter_to_bar = function(line, curr_pos) { + var ret = tokenizer.getBarLine(line, curr_pos); + if (ret.len === 0) + return [0,""]; + if (ret.warn) { + warn(ret.warn, line, curr_pos); + return [ret.len,""]; + } + + // Now see if this is a repeated ending + // A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma + // It can also optionally start with '[', which is ignored. + // Also, it can have white space before the '['. + for (var ws = 0; ws < line.length; ws++) + if (line[curr_pos + ret.len + ws] !== ' ') + break; + var orig_bar_len = ret.len; + if (line[curr_pos+ret.len+ws] === '[') { + ret.len += ws + 1; + } + + // It can also be a quoted string. It is unclear whether that construct requires '[', but it seems like it would. otherwise it would be confused with a regular chord. + if (line[curr_pos+ret.len] === '"' && line[curr_pos+ret.len-1] === '[') { + var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5); + return [ret.len+ending[0], ret.token, ending[1]]; + } + var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,"); + if (retRep.len === 0 || retRep.token[0] === '-') + return [orig_bar_len, ret.token]; + + return [ret.len+retRep.len, ret.token, retRep.token]; +}; + +var letter_to_open_slurs_and_triplets = function(line, i) { + // consume spaces, and look for all the open parens. If there is a number after the open paren, + // that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet. + var ret = {}; + var start = i; + if (line[i] === '.' && line[i+1] === '(') { + ret.dottedSlur = true; + i++; + } + while (line[i] === '(' || tokenizer.isWhiteSpace(line[i])) { + if (line[i] === '(') { + if (i+1 < line.length && (line[i+1] >= '2' && line[i+1] <= '9')) { + if (ret.triplet !== undefined) + warn("Can't nest triplets", line, i); + else { + ret.triplet = line[i+1] - '0'; + ret.tripletQ = tripletQ[ret.triplet]; + ret.num_notes = ret.triplet; + if (i+2 < line.length && line[i+2] === ':') { + // We are expecting "(p:q:r" or "(p:q" or "(p::r" + // That is: "put p notes into the time of q for the next r notes" + // if r is missing, then it is equal to p. + // if q is missing, it is determined from this table: + // (2 notes in the time of 3 + // (3 notes in the time of 2 + // (4 notes in the time of 3 + // (5 notes in the time of n | if time sig is (6/8, 9/8, 12/8), n=3, else n=2 + // (6 notes in the time of 2 + // (7 notes in the time of n + // (8 notes in the time of 3 + // (9 notes in the time of n + if (i+3 < line.length && line[i+3] === ':') { + // The second number, 'q', is not present. + if (i+4 < line.length && (line[i+4] >= '1' && line[i+4] <= '9')) { + ret.num_notes = line[i+4] - '0'; + i += 3; + } else + warn("expected number after the two colons after the triplet to mark the duration", line, i); + } else if (i+3 < line.length && (line[i+3] >= '1' && line[i+3] <= '9')) { + ret.tripletQ = line[i+3] - '0'; + if (i+4 < line.length && line[i+4] === ':') { + if (i+5 < line.length && (line[i+5] >= '1' && line[i+5] <= '9')) { + ret.num_notes = line[i+5] - '0'; + i += 4; + } + } else { + i += 2; + } + } else + warn("expected number after the triplet to mark the duration", line, i); + } + } + i++; + } + else { + if (ret.startSlur === undefined) + ret.startSlur = 1; + else + ret.startSlur++; + } + } + i++; + } + ret.consumed = i-start; + return ret; +}; + +MusicParser.prototype.startNewLine = function() { + var params = { startChar: -1, endChar: -1}; + if (multilineVars.partForNextLine.title) + params.part = multilineVars.partForNextLine; + params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.assign({},multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.assign({},multilineVars.clef); + var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0; + params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose); + params.key.mode = multilineVars.key.mode; + if (multilineVars.key.impliedNaturals) + params.key.impliedNaturals = multilineVars.key.impliedNaturals; + if (multilineVars.key.explicitAccidentals) { + for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) { + var found = false; + for (var j = 0; j < params.key.accidentals.length; j++) { + if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) { + // If the note is already in the list, override it with the new value + params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc; + found = true; + } + } + if (!found) + params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]); + } + } + multilineVars.targetKey = params.key; + if (params.key.explicitAccidentals) + delete params.key.explicitAccidentals; + parseKeyVoice.addPosToKey(params.clef, params.key); + if (multilineVars.meter !== null) { + if (multilineVars.currentVoice) { + multilineVars.staves.forEach(function(st) { + st.meter = multilineVars.meter; + }); + params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter; + multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null; + } else + params.meter = multilineVars.meter; + multilineVars.meter = null; + } else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) { + // Make sure that each voice gets the meter marking. + params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter; + multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null; + } + if (multilineVars.currentVoice && multilineVars.currentVoice.name) + params.name = multilineVars.currentVoice.name; + if (multilineVars.vocalfont) + params.vocalfont = multilineVars.vocalfont; + if (multilineVars.tripletfont) + params.tripletfont = multilineVars.tripletfont; + if (multilineVars.gchordfont) + params.gchordfont = multilineVars.gchordfont; + if (multilineVars.style) + params.style = multilineVars.style; + if (multilineVars.currentVoice) { + var staff = multilineVars.staves[multilineVars.currentVoice.staffNum]; + if (staff.brace) params.brace = staff.brace; + if (staff.bracket) params.bracket = staff.bracket; + if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines; + if (staff.name) params.name = staff.name[multilineVars.currentVoice.index]; + if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index]; + if (multilineVars.currentVoice.stem) + params.stem = multilineVars.currentVoice.stem; + if (multilineVars.currentVoice.stafflines) + params.stafflines = multilineVars.currentVoice.stafflines; + if (multilineVars.currentVoice.staffscale) + params.staffscale = multilineVars.currentVoice.staffscale; + if (multilineVars.currentVoice.scale) + params.scale = multilineVars.currentVoice.scale; + if (multilineVars.currentVoice.color) + params.color = multilineVars.currentVoice.color; + if (multilineVars.currentVoice.style) + params.style = multilineVars.currentVoice.style; + if (multilineVars.currentVoice.transpose) + params.clef.transpose = multilineVars.currentVoice.transpose; + params.currentVoice = multilineVars.currentVoice + var voices = Object.keys(multilineVars.voices) + for (var mv = 0; mv < voices.length; mv++) { + if (params.currentVoice.staffNum === multilineVars.voices[voices[mv]].staffNum && params.currentVoice.index === multilineVars.voices[voices[mv]].index) + params.currentVoiceName = voices[mv] + } + } + var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0); + if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1) + params.barNumber = multilineVars.currBarNumber; + tuneBuilder.startNewLine(params); + if (multilineVars.key.impliedNaturals) + delete multilineVars.key.impliedNaturals; + + multilineVars.partForNextLine = {}; + if (multilineVars.tempoForNextLine.length === 4) + tuneBuilder.appendElement(multilineVars.tempoForNextLine[0],multilineVars.tempoForNextLine[1],multilineVars.tempoForNextLine[2],multilineVars.tempoForNextLine[3]); + multilineVars.tempoForNextLine = []; +} + +// TODO-PER: make this a method in el. +var addEndBeam = function(el) { + if (el.duration !== undefined && el.duration < 0.25) + el.end_beam = true; + return el; +}; + +var getCoreNote = function(line, index, el, canHaveBrokenRhythm) { + //var el = { startChar: index }; + var isComplete = function(state) { + return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur'); + }; + var dottedTie; + if (line[index] === '.' && line[index+1] === '-') { + dottedTie = true; + index++; + } + var state = 'startSlur'; + var durationSetByPreviousNote = false; + while (1) { + switch(line[index]) { + case '(': + if (state === 'startSlur') { + if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++; + } else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case ')': + if (isComplete(state)) { + if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++; + } else return null; + break; + case '^': + if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';} + else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';} + else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case '_': + if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';} + else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';} + else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case '=': + if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';} + else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') { + el.pitch = pitches[line[index]]; + el.pitch += 7 * (multilineVars.currentVoice && multilineVars.currentVoice.octave !== undefined ? multilineVars.currentVoice.octave : multilineVars.octave); + el.name = line[index]; + if (el.accidental) + el.name = accMap[el.accidental] + el.name; + transpose.note(multilineVars, el); + state = 'octave'; + // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below + if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) { + el.duration = multilineVars.default_length * multilineVars.next_note_duration; + multilineVars.next_note_duration = 0; + durationSetByPreviousNote = true; + } else + el.duration = multilineVars.default_length; + // If the clef is percussion, there is probably some translation of the pitch to a particular drum kit item. + if ((multilineVars.clef && multilineVars.clef.type === "perc") || + (multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) { + var key = line[index]; + if (el.accidental) { + key = accMap[el.accidental] + key; + } + if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap) + el.midipitch = tune.formatting.midi.drummap[key]; + } + } else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case ',': + if (state === 'octave') {el.pitch -= 7; el.name += ','; } + else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case '\'': + if (state === 'octave') {el.pitch += 7; el.name += "'"; } + else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case 'x': + case 'X': + case 'y': + case 'z': + case 'Z': + if (state === 'startSlur') { + el.rest = { type: rests[line[index]] }; + // There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file, + // just nix them here. + delete el.accidental; + delete el.startSlur; + delete el.startTie; + delete el.endSlur; + delete el.endTie; + delete el.end_beam; + delete el.grace_notes; + // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below + if (el.rest.type.indexOf('multimeasure') >= 0) { + el.duration = tune.getBarLength(); + el.rest.text = 1; + state = 'Zduration'; + } else { + if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) { + el.duration = multilineVars.default_length * multilineVars.next_note_duration; + multilineVars.next_note_duration = 0; + durationSetByPreviousNote = true; + } else + el.duration = multilineVars.default_length; + state = 'duration'; + } + } else if (isComplete(state)) {el.endChar = index;return el;} + else return null; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + case '/': + if (state === 'octave' || state === 'duration') { + var fraction = tokenizer.getFraction(line, index); + //if (!durationSetByPreviousNote) + el.duration = el.duration * fraction.value; + // TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected. + el.endChar = fraction.index; + while (fraction.index < line.length && (tokenizer.isWhiteSpace(line[fraction.index]) || line[fraction.index] === '-')) { + if (line[fraction.index] === '-') + el.startTie = {}; + else + el = addEndBeam(el); + fraction.index++; + } + index = fraction.index-1; + state = 'broken_rhythm'; + } else if (state === 'sharp2') { + el.accidental = 'quartersharp';state = 'pitch'; + } else if (state === 'flat2') { + el.accidental = 'quarterflat';state = 'pitch'; + } else if (state === 'Zduration') { + var num = tokenizer.getNumber(line, index); + el.duration = num.num * tune.getBarLength(); + el.rest.text = num.num; + el.endChar = num.index; + return el; + } else return null; + break; + case '-': + if (state === 'startSlur') { + // This is the first character, so it must have been meant for the previous note. Correct that here. + tuneBuilder.addTieToLastNote(dottedTie); + el.endTie = true; + } else if (state === 'octave' || state === 'duration' || state === 'end_slur') { + el.startTie = {}; + if (!durationSetByPreviousNote && canHaveBrokenRhythm) + state = 'broken_rhythm'; + else { + // Peek ahead to the next character. If it is a space, then we have an end beam. + if (tokenizer.isWhiteSpace(line[index + 1])) + addEndBeam(el); + el.endChar = index+1; + return el; + } + } else if (state === 'broken_rhythm') {el.endChar = index;return el;} + else return null; + break; + case ' ': + case '\t': + if (isComplete(state)) { + el.end_beam = true; + // look ahead to see if there is a tie + dottedTie = false; + do { + if (line[index] === '.' && line[index+1] === '-') { + dottedTie = true; + index++; + } + if (line[index] === '-') { + el.startTie = {}; + if (dottedTie) + el.startTie.style = "dotted"; + } + index++; + } while (index < line.length && + (tokenizer.isWhiteSpace(line[index]) || line[index] === '-') || + (line[index] === '.' && line[index+1] === '-')); + el.endChar = index; + if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line[index] === '<' || line[index] === '>')) { // TODO-PER: Don't need the test for < and >, but that makes the endChar work out for the regression test. + index--; + state = 'broken_rhythm'; + } else + return el; + } + else return null; + break; + case '>': + case '<': + if (isComplete(state)) { + if (canHaveBrokenRhythm) { + var br2 = getBrokenRhythm(line, index); + index += br2[0] - 1; // index gets incremented below, so we'll let that happen + multilineVars.next_note_duration = br2[2]; + el.duration = br2[1]*el.duration; + state = 'end_slur'; + } else { + el.endChar = index; + return el; + } + } else + return null; + break; + default: + if (isComplete(state)) { + el.endChar = index; + return el; + } + return null; + } + index++; + if (index === line.length) { + if (isComplete(state)) {el.endChar = index;return el;} + else return null; + } + } + return null; +}; + +var getBrokenRhythm = function(line, index) { + switch (line[index]) { + case '>': + if (index < line.length - 2 && line[index + 1] === '>' && line[index + 2] === '>') // triple >>> + return [3, 1.875, 0.125]; + else if (index < line.length - 1 && line[index + 1] === '>') // double >> + return [2, 1.75, 0.25]; + else + return [1, 1.5, 0.5]; + case '<': + if (index < line.length - 2 && line[index + 1] === '<' && line[index + 2] === '<') // triple <<< + return [3, 0.125, 1.875]; + else if (index < line.length - 1 && line[index + 1] === '<') // double << + return [2, 0.25, 1.75]; + else + return [1, 0.5, 1.5]; + } + return null; +}; + +module.exports = MusicParser; diff --git a/src/parse/abc_parse_settings.js b/src/parse/abc_parse_settings.js new file mode 100644 index 0000000000000000000000000000000000000000..2998dcee0ef66403b910961820f9f55a1cdd09aa --- /dev/null +++ b/src/parse/abc_parse_settings.js @@ -0,0 +1,165 @@ +module.exports.legalAccents = [ + 'trill', + 'lowermordent', + 'uppermordent', + 'mordent', + 'pralltriller', + 'accent', + 'fermata', + 'invertedfermata', + 'tenuto', + '0', + '1', + '2', + '3', + '4', + '5', + '+', + 'wedge', + 'open', + 'thumb', + 'snap', + 'turn', + 'roll', + 'breath', + 'shortphrase', + 'mediumphrase', + 'longphrase', + 'segno', + 'coda', + 'D.S.', + 'D.C.', + 'fine', + 'beambr1', + 'beambr2', + 'slide', + 'marcato', + 'upbow', + 'downbow', + '/', + '//', + '///', + '////', + 'trem1', + 'trem2', + 'trem3', + 'trem4', + 'turnx', + 'invertedturn', + 'invertedturnx', + 'trill(', + 'trill)', + 'arpeggio', + 'xstem', + 'mark', + 'umarcato', + 'style=normal', + 'style=harmonic', + 'style=rhythm', + 'style=x', + 'style=triangle', + 'D.C.alcoda', + 'D.C.alfine', + 'D.S.alcoda', + 'D.S.alfine', + 'editorial', + 'courtesy' +]; + +module.exports.volumeDecorations = [ + 'p', + 'pp', + 'f', + 'ff', + 'mf', + 'mp', + 'ppp', + 'pppp', + 'fff', + 'ffff', + 'sfz' +]; + +module.exports.dynamicDecorations = [ + 'crescendo(', + 'crescendo)', + 'diminuendo(', + 'diminuendo)', + 'glissando(', + 'glissando)', + '~(', + '~)' +]; + +module.exports.accentPseudonyms = [ + ['<', 'accent'], + ['>', 'accent'], + ['tr', 'trill'], + ['plus', '+'], + ['emphasis', 'accent'], + ['^', 'umarcato'], + ['marcato', 'umarcato'] +]; + +module.exports.accentDynamicPseudonyms = [ + ['<(', 'crescendo('], + ['<)', 'crescendo)'], + ['>(', 'diminuendo('], + ['>)', 'diminuendo)'] +]; + +module.exports.nonDecorations = 'ABCDEFGabcdefgxyzZ[]|^_{'; // use this to prescreen so we don't have to look for a decoration at every note. + +module.exports.durations = [ + 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375, 0.25, 0.375, 0.4375, 0.46875, + 0.484375, 0.4921875, 0.125, 0.1875, 0.21875, 0.234375, 0.2421875, 0.24609375, + 0.0625, 0.09375, 0.109375, 0.1171875, 0.12109375, 0.123046875, 0.03125, + 0.046875, 0.0546875, 0.05859375, 0.060546875, 0.0615234375, 0.015625, + 0.0234375, 0.02734375, 0.029296875, 0.0302734375, 0.03076171875 +]; + +module.exports.pitches = { + A: 5, + B: 6, + C: 0, + D: 1, + E: 2, + F: 3, + G: 4, + a: 12, + b: 13, + c: 7, + d: 8, + e: 9, + f: 10, + g: 11 +}; + +module.exports.rests = { + x: 'invisible', + X: 'invisible-multimeasure', + y: 'spacer', + z: 'rest', + Z: 'multimeasure' +}; + +module.exports.accMap = { + dblflat: '__', + flat: '_', + natural: '=', + sharp: '^', + dblsharp: '^^', + quarterflat: '_/', + quartersharp: '^/' +}; + +module.exports.tripletQ = { + 2: 3, + 3: 2, + 4: 3, + 5: 2, // TODO-PER: not handling 6/8 rhythm yet + 6: 2, + 7: 2, // TODO-PER: not handling 6/8 rhythm yet + 8: 3, + 9: 2 // TODO-PER: not handling 6/8 rhythm yet +}; diff --git a/src/parse/abc_tokenizer.js b/src/parse/abc_tokenizer.js new file mode 100644 index 0000000000000000000000000000000000000000..276ca18db54076984d2f438771b3ce943eb09db5 --- /dev/null +++ b/src/parse/abc_tokenizer.js @@ -0,0 +1,826 @@ +// abc_tokenizer.js: tokenizes an ABC Music Notation string to support abc_parse. + +var parseCommon = require('./abc_common'); + +// this is a series of functions that get a particular element out of the passed stream. +// the return is the number of characters consumed, so 0 means that the element wasn't found. +// also returned is the element found. This may be a different length because spaces may be consumed that aren't part of the string. +// The return structure for most calls is { len: num_chars_consumed, token: str } +var Tokenizer = function(lines, multilineVars) { + this.lineIndex = 0 + this.lines = lines + this.multilineVars = multilineVars; + + this.skipWhiteSpace = function(str) { + for (var i = 0; i < str.length; i++) { + if (!this.isWhiteSpace(str[i])) + return i; + } + return str.length; // It must have been all white space + }; + var finished = function(str, i) { + return i >= str.length; + }; + this.eatWhiteSpace = function(line, index) { + for (var i = index; i < line.length; i++) { + if (!this.isWhiteSpace(line[i])) + return i-index; + } + return i-index; + }; + + // This just gets the basic pitch letter, ignoring leading spaces, and normalizing it to a capital + this.getKeyPitch = function(str) { + var i = this.skipWhiteSpace(str); + if (finished(str, i)) + return {len: 0}; + switch (str[i]) { + case 'A':return {len: i+1, token: 'A'}; + case 'B':return {len: i+1, token: 'B'}; + case 'C':return {len: i+1, token: 'C'}; + case 'D':return {len: i+1, token: 'D'}; + case 'E':return {len: i+1, token: 'E'}; + case 'F':return {len: i+1, token: 'F'}; + case 'G':return {len: i+1, token: 'G'}; +// case 'a':return {len: i+1, token: 'A'}; +// case 'b':return {len: i+1, token: 'B'}; +// case 'c':return {len: i+1, token: 'C'}; +// case 'd':return {len: i+1, token: 'D'}; +// case 'e':return {len: i+1, token: 'E'}; +// case 'f':return {len: i+1, token: 'F'}; +// case 'g':return {len: i+1, token: 'G'}; + } + return {len: 0}; + }; + + // This just gets the basic accidental, ignoring leading spaces, and only the ones that appear in a key + this.getSharpFlat = function(str) { + if (str === 'bass') + return {len: 0}; + switch (str[0]) { + case '#':return {len: 1, token: '#'}; + case 'b':return {len: 1, token: 'b'}; + } + return {len: 0}; + }; + + this.getMode = function(str) { + var skipAlpha = function(str, start) { + // This returns the index of the next non-alphabetic char, or the entire length of the string if not found. + while (start < str.length && ((str[start] >= 'a' && str[start] <= 'z') || (str[start] >= 'A' && str[start] <= 'Z'))) + start++; + return start; + }; + + var i = this.skipWhiteSpace(str); + if (finished(str, i)) + return {len: 0}; + var firstThree = str.substring(i,i+3).toLowerCase(); + if (firstThree.length > 1 && firstThree[1] === ' ' || firstThree[1] === '^' || firstThree[1] === '_' || firstThree[1] === '=') firstThree = firstThree[0]; // This will handle the case of 'm' + switch (firstThree) { + case 'mix':return {len: skipAlpha(str, i), token: 'Mix'}; + case 'dor':return {len: skipAlpha(str, i), token: 'Dor'}; + case 'phr':return {len: skipAlpha(str, i), token: 'Phr'}; + case 'lyd':return {len: skipAlpha(str, i), token: 'Lyd'}; + case 'loc':return {len: skipAlpha(str, i), token: 'Loc'}; + case 'aeo':return {len: skipAlpha(str, i), token: 'm'}; + case 'maj':return {len: skipAlpha(str, i), token: ''}; + case 'ion':return {len: skipAlpha(str, i), token: ''}; + case 'min':return {len: skipAlpha(str, i), token: 'm'}; + case 'm':return {len: skipAlpha(str, i), token: 'm'}; + } + return {len: 0}; + }; + + this.getClef = function(str, bExplicitOnly) { + var strOrig = str; + var i = this.skipWhiteSpace(str); + if (finished(str, i)) + return {len: 0}; + // The word 'clef' is optional, but if it appears, a clef MUST appear + var needsClef = false; + var strClef = str.substring(i); + if (parseCommon.startsWith(strClef, 'clef=')) { + needsClef = true; + strClef = strClef.substring(5); + i += 5; + } + if (strClef.length === 0 && needsClef) + return {len: i+5, warn: "No clef specified: " + strOrig}; + + var j = this.skipWhiteSpace(strClef); + if (finished(strClef, j)) + return {len: 0}; + if (j > 0) { + i += j; + strClef = strClef.substring(j); + } + var name = null; + if (parseCommon.startsWith(strClef, 'treble')) + name = 'treble'; + else if (parseCommon.startsWith(strClef, 'bass3')) + name = 'bass3'; + else if (parseCommon.startsWith(strClef, 'bass')) + name = 'bass'; + else if (parseCommon.startsWith(strClef, 'tenor')) + name = 'tenor'; + else if (parseCommon.startsWith(strClef, 'alto2')) + name = 'alto2'; + else if (parseCommon.startsWith(strClef, 'alto1')) + name = 'alto1'; + else if (parseCommon.startsWith(strClef, 'alto')) + name = 'alto'; + else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'none'))) + name = 'none'; + else if (parseCommon.startsWith(strClef, 'perc')) + name = 'perc'; + else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'C'))) + name = 'tenor'; + else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'F'))) + name = 'bass'; + else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'G'))) + name = 'treble'; + else + return {len: i+5, warn: "Unknown clef specified: " + strOrig}; + + strClef = strClef.substring(name.length); + j = this.isMatch(strClef, '+8'); + if (j > 0) + name += "+8"; + else { + j = this.isMatch(strClef, '-8'); + if (j > 0) + name += "-8"; + } + return {len: i+name.length, token: name, explicit: needsClef}; + }; + + // This returns one of the legal bar lines + // This is called alot and there is no obvious tokenable items, so this is broken apart. + this.getBarLine = function(line, i) { + switch (line[i]) { + case ']': + ++i; + switch (line[i]) { + case '|': return {len: 2, token: "bar_thick_thin"}; + case '[': + ++i; + if ((line[i] >= '1' && line[i] <= '9') || line[i] === '"') + return {len: 2, token: "bar_invisible"}; + return {len: 1, warn: "Unknown bar symbol"}; + default: + return {len: 1, token: "bar_invisible"}; + } + break; + case ':': + ++i; + switch (line[i]) { + case ':': return {len: 2, token: "bar_dbl_repeat"}; + case '|': // :| + ++i; + switch (line[i]) { + case ']': // :|] + ++i; + switch (line[i]) { + case '|': // :|]| + ++i; + if (line[i] === ':') return {len: 5, token: "bar_dbl_repeat"}; + return {len: 3, token: "bar_right_repeat"}; + default: + return {len: 3, token: "bar_right_repeat"}; + } + break; + case '|': // :|| + ++i; + if (line[i] === ':') return {len: 4, token: "bar_dbl_repeat"}; + return {len: 3, token: "bar_right_repeat"}; + default: + return {len: 2, token: "bar_right_repeat"}; + } + break; + default: + return {len: 1, warn: "Unknown bar symbol"}; + } + break; + case '[': // [ + ++i; + if (line[i] === '|') { // [| + ++i; + switch (line[i]) { + case ':': return {len: 3, token: "bar_left_repeat"}; + case ']': return {len: 3, token: "bar_invisible"}; + default: return {len: 2, token: "bar_thick_thin"}; + } + } else { + if ((line[i] >= '1' && line[i] <= '9') || line[i] === '"') + return {len: 1, token: "bar_invisible"}; + return {len: 0}; + } + break; + case '|': // | + ++i; + switch (line[i]) { + case ']': return {len: 2, token: "bar_thin_thick"}; + case '|': // || + ++i; + if (line[i] === ':') return {len: 3, token: "bar_left_repeat"}; + return {len: 2, token: "bar_thin_thin"}; + case ':': // |: + var colons = 0; + while (line[i+colons] === ':') colons++; + return { len: 1+colons, token: "bar_left_repeat"}; + default: return {len: 1, token: "bar_thin"}; + } + break; + } + return {len: 0}; + }; + + // this returns all the characters in the string that match one of the characters in the legalChars string + this.getTokenOf = function(str, legalChars) { + for (var i = 0; i < str.length; i++) { + if (legalChars.indexOf(str[i]) < 0) + return {len: i, token: str.substring(0, i)}; + } + return {len: i, token: str}; + }; + + this.getToken = function(str, start, end) { + // This returns the next set of chars that doesn't contain spaces + var i = start; + while (i < end && !this.isWhiteSpace(str[i])) + i++; + return str.substring(start, i); + }; + + // This just sees if the next token is the word passed in, with possible leading spaces + this.isMatch = function(str, match) { + var i = this.skipWhiteSpace(str); + if (finished(str, i)) + return 0; + if (parseCommon.startsWith(str.substring(i), match)) + return i+match.length; + return 0; + }; + + this.getPitchFromTokens = function(tokens) { + var ret = { }; + var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11}; + ret.position = pitches[tokens[0].token]; + if (ret.position === undefined) + return { warn: "Pitch expected. Found: " + tokens[0].token }; + tokens.shift(); + while (tokens.length) { + switch (tokens[0].token) { + case ',': ret.position -= 7; tokens.shift(); break; + case '\'': ret.position += 7; tokens.shift(); break; + default: return ret; + } + } + return ret; + }; + + this.getKeyAccidentals2 = function(tokens) { + var accs; + // find and strip off all accidentals in the token list + while (tokens.length > 0) { + var acc; + if (tokens[0].token === '^') { + acc = 'sharp'; + tokens.shift(); + if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; + switch (tokens[0].token) { + case '^': acc = 'dblsharp'; tokens.shift(); break; + case '/': acc = 'quartersharp'; tokens.shift(); break; + } + } else if (tokens[0].token === '=') { + acc = 'natural'; + tokens.shift(); + } else if (tokens[0].token === '_') { + acc = 'flat'; + tokens.shift(); + if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; + switch (tokens[0].token) { + case '_': acc = 'dblflat'; tokens.shift(); break; + case '/': acc = 'quarterflat'; tokens.shift(); break; + } + } else { + // Not an accidental, we'll assume that a later parse will recognize it. + return { accs: accs }; + } + if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; + switch (tokens[0].token[0]) + { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + if (accs === undefined) + accs = []; + accs.push({ acc: acc, note: tokens[0].token[0] }); + if (tokens[0].token.length === 1) + tokens.shift(); + else + tokens[0].token = tokens[0].token.substring(1); + break; + default: + return {accs: accs, warn: 'Expected note name after ' + acc + ' Found: ' + tokens[0].token }; + } + } + return { accs: accs }; + }; + + // This gets an accidental marking for the key signature. It has the accidental then the pitch letter. + this.getKeyAccidental = function(str) { + var accTranslation = { + '^': 'sharp', + '^^': 'dblsharp', + '=': 'natural', + '_': 'flat', + '__': 'dblflat', + '_/': 'quarterflat', + '^/': 'quartersharp' + }; + var i = this.skipWhiteSpace(str); + if (finished(str, i)) + return {len: 0}; + var acc = null; + switch (str[i]) + { + case '^': + case '_': + case '=': + acc = str[i]; + break; + default:return {len: 0}; + } + i++; + if (finished(str, i)) + return {len: 1, warn: 'Expected note name after accidental'}; + switch (str[i]) + { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + return {len: i+1, token: {acc: accTranslation[acc], note: str[i]}}; + case '^': + case '_': + case '/': + acc += str[i]; + i++; + if (finished(str, i)) + return {len: 2, warn: 'Expected note name after accidental'}; + switch (str[i]) + { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + return {len: i+1, token: {acc: accTranslation[acc], note: str[i]}}; + default: + return {len: 2, warn: 'Expected note name after accidental'}; + } + break; + default: + return {len: 1, warn: 'Expected note name after accidental'}; + } + }; + + this.isWhiteSpace = function(ch) { + return ch === ' ' || ch === '\t' || ch === '\x12'; + }; + + this.getMeat = function(line, start, end) { + // This removes any comments starting with '%' and trims the ends of the string so that there are no leading or trailing spaces. + // it returns just the start and end characters that contain the meat. + var comment = line.indexOf('%', start); + if (comment >= 0 && comment < end) + end = comment; + while (start < end && (line[start] === ' ' || line[start] === '\t' || line[start] === '\x12')) + start++; + while (start < end && (line[end-1] === ' ' || line[end-1] === '\t' || line[end-1] === '\x12')) + end--; + return {start: start, end: end}; + }; + + var isLetter = function(ch) { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); + }; + + var isNumber = function(ch) { + return (ch >= '0' && ch <= '9'); + }; + + this.tokenize = function(line, start, end, alphaUntilWhiteSpace) { + // this returns all the tokens inside the passed string. A token is a punctuation mark, a string of digits, a string of letters. + // Quoted strings are one token. + // If there is a minus sign next to a number, then it is included in the number. + // If there is a period immediately after a number, with a number immediately following, then a float is returned. + // The type of token is returned: quote, alpha, number, punct + // If alphaUntilWhiteSpace is true, then the behavior of the alpha token changes. + + var ret = this.getMeat(line, start, end); + start = ret.start; + end = ret.end; + var tokens = []; + var i; + while (start < end) { + if (line[start] === '"') { + i = start+1; + while (i < end && line[i] !== '"') i++; + tokens.push({ type: 'quote', token: line.substring(start+1, i), start: start+1, end: i}); + i++; + } else if (isLetter(line[start])) { + i = start+1; + if (alphaUntilWhiteSpace) + while (i < end && !this.isWhiteSpace(line[i])) i++; + else + while (i < end && isLetter(line[i])) i++; + tokens.push({ type: 'alpha', token: line.substring(start, i), continueId: isNumber(line[i]), start: start, end: i}); + start = i + 1; + } else if (line[start] === '.' && isNumber(line[i+1])) { + i = start+1; + var int2 = null; + var float2 = null; + while (i < end && isNumber(line[i])) i++; + + float2 = parseFloat(line.substring(start, i)); + tokens.push({ type: 'number', token: line.substring(start, i), intt: int2, floatt: float2, continueId: isLetter(line[i]), start: start, end: i}); + start = i + 1; + } else if (isNumber(line[start]) || (line[start] === '-' && isNumber(line[i+1]))) { + i = start+1; + var intt = null; + var floatt = null; + while (i < end && isNumber(line[i])) i++; + if (line[i] === '.' && isNumber(line[i+1])) { + i++; + while (i < end && isNumber(line[i])) i++; + } else + intt = parseInt(line.substring(start, i)); + + floatt = parseFloat(line.substring(start, i)); + tokens.push({ type: 'number', token: line.substring(start, i), intt: intt, floatt: floatt, continueId: isLetter(line[i]), start: start, end: i}); + start = i + 1; + } else if (line[start] === ' ' || line[start] === '\t') { + i = start+1; + } else { + tokens.push({ type: 'punct', token: line[start], start: start, end: start+1}); + i = start+1; + } + start = i; + } + return tokens; + }; + + this.getVoiceToken = function(line, start, end) { + // This finds the next token. A token is delimited by a space or an equal sign. If it starts with a quote, then the portion between the quotes is returned. + var i = start; + while (i < end && this.isWhiteSpace(line[i]) || line[i] === '=') + i++; + + if (line[i] === '"') { + var close = line.indexOf('"', i+1); + if (close === -1 || close >= end) + return {len: 1, err: "Missing close quote"}; + return {len: close-start+1, token: this.translateString(line.substring(i+1, close))}; + } else { + var ii = i; + while (ii < end && !this.isWhiteSpace(line[ii]) && line[ii] !== '=') + ii++; + return {len: ii-start+1, token: line.substring(i, ii)}; + } + }; + + var charMap = { + "`a": 'à', "'a": "á", "^a": "â", "~a": "ã", "\"a": "ä", "oa": "å", "aa": "å", "=a": "ā", "ua": "ă", ";a": "ą", + "`e": 'è', "'e": "é", "^e": "ê", "\"e": "ë", "=e": "ē", "ue": "ĕ", ";e": "ę", ".e": "ė", + "`i": 'ì', "'i": "í", "^i": "î", "\"i": "ï", "=i": "ī", "ui": "ĭ", ";i": "į", + "`o": 'ò', "'o": "ó", "^o": "ô", "~o": "õ", "\"o": "ö", "=o": "ō", "uo": "ŏ", "/o": "ø", + "`u": 'ù', "'u": "ú", "^u": "û", "~u": "ũ", "\"u": "ü", "ou": "ů", "=u": "ū", "uu": "ŭ", ";u": "ų", + "`A": 'À', "'A": "Á", "^A": "Â", "~A": "Ã", "\"A": "Ä", "oA": "Å", "AA": "Å", "=A": "Ā", "uA": "Ă", ";A": "Ą", + "`E": 'È', "'E": "É", "^E": "Ê", "\"E": "Ë", "=E": "Ē", "uE": "Ĕ", ";E": "Ę", ".E": "Ė", + "`I": 'Ì', "'I": "Í", "^I": "Î", "~I": "Ĩ", "\"I": "Ï", "=I": "Ī", "uI": "Ĭ", ";I": "Į", ".I": "İ", + "`O": 'Ò', "'O": "Ó", "^O": "Ô", "~O": "Õ", "\"O": "Ö", "=O": "Ō", "uO": "Ŏ", "/O": "Ø", + "`U": 'Ù', "'U": "Ú", "^U": "Û", "~U": "Ũ", "\"U": "Ü", "oU": "Ů", "=U": "Ū", "uU": "Ŭ", ";U": "Ų", + "ae": "æ", "AE": "Æ", "oe": "œ", "OE": "Œ", "ss": "ß", + "'c": "ć", "^c": "ĉ", "uc": "č", "cc": "ç", ".c": "ċ", "cC": "Ç", "'C": "Ć", "^C": "Ĉ", "uC": "Č", ".C": "Ċ", + "~N": "Ñ", "~n": "ñ", + "=s": "š", "vs": "š", + "DH": "Ð", "dh": "ð", + "HO": "Ő", "Ho": "ő", "HU": "Ű", "Hu": "ű", + "'Y": "Ý", "'y": "ý", "^Y": "Ŷ", "^y": "ŷ", "\"Y": "Ÿ", "\"y": "ÿ", + "vS": "Š", "vZ": "Ž", "vz": 'ž' + +// More chars: IJ ij Ď ď Đ đ Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š Ţ ţ Ť ť Ŧ ŧ Ŵ ŵ Ź ź Ż ż Ž + }; + var charMap1 = { + "#": "♯", + "b": "♭", + "=": "♮" + }; + var charMap2 = { + "201": "♯", + "202": "♭", + "203": "♮", + "241": "¡", + "242": "¢", "252": "a", "262": "2", "272": "o", "302": "Â", "312": "Ê", "322": "Ò", "332": "Ú", "342": "â", "352": "ê", "362": "ò", "372": "ú", + "243": "£", "253": "«", "263": "3", "273": "»", "303": "Ã", "313": "Ë", "323": "Ó", "333": "Û", "343": "ã", "353": "ë", "363": "ó", "373": "û", + "244": "¤", "254": "¬", "264": " ́", "274": "1⁄4", "304": "Ä", "314": "Ì", "324": "Ô", "334": "Ü", "344": "ä", "354": "ì", "364": "ô", "374": "ü", + "245": "¥", "255": "-", "265": "μ", "275": "1⁄2", "305": "Å", "315": "Í", "325": "Õ", "335": "Ý", "345": "å", "355": "í", "365": "õ", "375": "ý", + "246": "¦", "256": "®", "266": "¶", "276": "3⁄4", "306": "Æ", "316": "Î", "326": "Ö", "336": "Þ", "346": "æ", "356": "î", "366": "ö", "376": "þ", + "247": "§", "257": " ̄", "267": "·", "277": "¿", "307": "Ç", "317": "Ï", "327": "×", "337": "ß", "347": "ç", "357": "ï", "367": "÷", "377": "ÿ", + "250": " ̈", "260": "°", "270": " ̧", "300": "À", "310": "È", "320": "Ð", "330": "Ø", "340": "à", "350": "è", "360": "ð", "370": "ø", + "251": "©", "261": "±", "271": "1", "301": "Á", "311": "É", "321": "Ñ", "331": "Ù", "341": "á", "351": "é", "361": "ñ", "371": "ù" }; + this.translateString = function(str) { + var arr = str.split('\\'); + if (arr.length === 1) return str; + var out = null; + arr.forEach(function(s) { + if (out === null) + out = s; + else { + var c = charMap[s.substring(0, 2)]; + if (c !== undefined) + out += c + s.substring(2); + else { + c = charMap2[s.substring(0, 3)]; + if (c !== undefined) + out += c + s.substring(3); + else { + c = charMap1[s.substring(0, 1)]; + if (c !== undefined) + out += c + s.substring(1); + else + out += "\\" + s; + } + } + } + }); + return out; + }; + this.getNumber = function(line, index) { + var num = 0; + while (index < line.length) { + switch (line[index]) { + case '0':num = num*10;index++;break; + case '1':num = num*10+1;index++;break; + case '2':num = num*10+2;index++;break; + case '3':num = num*10+3;index++;break; + case '4':num = num*10+4;index++;break; + case '5':num = num*10+5;index++;break; + case '6':num = num*10+6;index++;break; + case '7':num = num*10+7;index++;break; + case '8':num = num*10+8;index++;break; + case '9':num = num*10+9;index++;break; + default: + return {num: num, index: index}; + } + } + return {num: num, index: index}; + }; + + this.getFraction = function(line, index) { + var num = 1; + var den = 1; + if (line[index] !== '/') { + var ret = this.getNumber(line, index); + num = ret.num; + index = ret.index; + } + if (line[index] === '/') { + index++; + if (line[index] === '/') { + var div = 0.5; + while (line[index++] === '/') + div = div /2; + return {value: num * div, index: index-1}; + } else { + var iSave = index; + var ret2 = this.getNumber(line, index); + if (ret2.num === 0 && iSave === index) // If we didn't use any characters, it is an implied 2 + ret2.num = 2; + if (ret2.num !== 0) + den = ret2.num; + index = ret2.index; + } + } + + return {value: num/den, index: index}; + }; + +// +// MAE 10 Jan 2023 - For better handling of tunes that have tune numbers in front of them. +// +// Previous version would take: +// 21. Woman of the House, The +// and return: +// The 21. Woman of the House +// +// This fix results in: +// 21. The Woman of the House +// +// Also added additional checks and handlers for lower case ", the" and ", a" since I found several tune collections with those tune name constructs +// +// Find an optional title number at the start of a tune title +function getTitleNumber(str){ + + const regex = /^(\d+)\./; + + // Use the exec method to search for the pattern in the string + const match = regex.exec(str); + + // Check if a match is found + if (match) { + + // The matched number is captured in the first group (index 1) + const foundNumber = match[1]; + return foundNumber; + + } else { + + // Return null if no match is found + return null; + + } + +} + +var thePatterns = [ + { match: /,\s*[Tt]he$/, replace: "The " }, + { match: /,\s*[Aa]$/, replace: "A " }, + { match: /,\s*[Aa]n$/, replace: "An " }, +] + +this.theReverser = function (str) { + + for (var i = 0; i < thePatterns.length; i++) { + var thisPattern = thePatterns[i] + var match = str.match(thisPattern.match) + if (match) { + var theTitleNumber = getTitleNumber(str); + if (theTitleNumber){ + + //console.log("theReverser The titlenumber:"+theTitleNumber); + + str = str.replace(theTitleNumber+".",""); + str = str.trim(); + } + var len = match[0].length + var result = thisPattern.replace + str.substring(0, str.length - len); + + if (theTitleNumber){ + result = theTitleNumber+". "+result; + } + + return result; + + } + } + + return str; + + }; + + this.stripComment = function(str) { + var i = str.indexOf('%'); + if (i >= 0) + return parseCommon.strip(str.substring(0, i)); + return parseCommon.strip(str); + }; + + this.getInt = function(str) { + // This parses the beginning of the string for a number and returns { value: num, digits: num } + // If digits is 0, then the string didn't point to a number. + var x = parseInt(str); + if (isNaN(x)) + return {digits: 0}; + var s = "" + x; + var i = str.indexOf(s); // This is to account for leading spaces + return {value: x, digits: i+s.length}; + }; + + this.getFloat = function(str) { + // This parses the beginning of the string for a number and returns { value: num, digits: num } + // If digits is 0, then the string didn't point to a number. + var x = parseFloat(str); + if (isNaN(x)) + return {digits: 0}; + var s = "" + x; + var i = str.indexOf(s); // This is to account for leading spaces + return {value: x, digits: i+s.length}; + }; + + this.getMeasurement = function(tokens) { + if (tokens.length === 0) return { used: 0 }; + var used = 1; + var num = ''; + if (tokens[0].token === '-') { + tokens.shift(); + num = '-'; + used++; + } + else if (tokens[0].type !== 'number') return { used: 0 }; + num += tokens.shift().token; + if (tokens.length === 0) return { used: 1, value: parseInt(num) }; + var x = tokens.shift(); + if (x.token === '.') { + used++; + if (tokens.length === 0) return { used: used, value: parseInt(num) }; + if (tokens[0].type === 'number') { + x = tokens.shift(); + num = num + '.' + x.token; + used++; + if (tokens.length === 0) return { used: used, value: parseFloat(num) }; + } + x = tokens.shift(); + } + switch (x.token) { + case 'pt': return { used: used+1, value: parseFloat(num) }; + case 'px': return { used: used+1, value: parseFloat(num) }; + case 'cm': return { used: used+1, value: parseFloat(num)/2.54*72 }; + case 'in': return { used: used+1, value: parseFloat(num)*72 }; + default: tokens.unshift(x); return { used: used, value: parseFloat(num) }; + } + }; + var substInChord = function(str) { + str = str.replace(/\\n/g, "\n"); + str = str.replace(/\\"/g, '"'); + return str; + }; + this.getBrackettedSubstring = function(line, i, maxErrorChars, _matchChar) + { + // This extracts the sub string by looking at the first character and searching for that + // character later in the line (or search for the optional _matchChar). + // For instance, if the first character is a quote it will look for + // the end quote. If the end of the line is reached, then only up to the default number + // of characters are returned, so that a missing end quote won't eat up the entire line. + // It returns the substring and the number of characters consumed. + // The number of characters consumed is normally two more than the size of the substring, + // but in the error case it might not be. + var matchChar = _matchChar || line[i]; + var pos = i+1; + var esc = false; + while ((pos < line.length) && (esc || line[pos] !== matchChar)) { + esc = line[pos] === '\\'; + ++pos; + } + if (line[pos] === matchChar) + return [pos-i+1,substInChord(line.substring(i+1, pos)), true]; + else // we hit the end of line, so we'll just pick an arbitrary num of chars so the line doesn't disappear. + { + pos = i+maxErrorChars; + if (pos > line.length-1) + pos = line.length-1; + return [pos-i+1, substInChord(line.substring(i+1, pos)), false]; + } + }; +}; + +Tokenizer.prototype.peekLine = function() { + return this.lines[this.lineIndex] +} + +Tokenizer.prototype.nextLine = function() { + if (this.lineIndex > 0) { + this.multilineVars.iChar += this.lines[this.lineIndex-1].length + 1; + } + if (this.lineIndex < this.lines.length) { + var result = this.lines[this.lineIndex] + this.lineIndex++ + return result + } + return null +} + +module.exports = Tokenizer; diff --git a/src/parse/abc_transpose.js b/src/parse/abc_transpose.js new file mode 100644 index 0000000000000000000000000000000000000000..7091473686e51031e2268818cd7641b0e028a4b2 --- /dev/null +++ b/src/parse/abc_transpose.js @@ -0,0 +1,189 @@ +// abc_transpose.js: Handles the automatic transposition of key signatures, chord symbols, and notes. + +var allNotes = require("./all-notes"); +var transposeChordName = require("../parse/transpose-chord") +var keyAccidentals = require('../const/key-accidentals'); +var transpose = {}; + +var keyIndex = { + 'C': 0, + 'C#': 1, + 'Db': 1, + 'D': 2, + 'D#': 3, + 'Eb': 3, + 'E': 4, + 'F': 5, + 'F#': 6, + 'Gb': 6, + 'G': 7, + 'G#': 8, + 'Ab': 8, + 'A': 9, + 'A#': 10, + 'Bb': 10, + 'B': 11 +}; +var newKey = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B']; +var newKeyMinor = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'Bb', 'B']; + +transpose.keySignature = function(multilineVars, keyName, root, acc, localTranspose) { + if (multilineVars.clef.type === "perc" || multilineVars.clef.type === "none") + return { accidentals: keyAccidentals(keyName), root: root, acc: acc }; + if (!localTranspose) localTranspose = 0; + multilineVars.localTransposeVerticalMovement = 0; + multilineVars.localTransposePreferFlats = false; + var k = keyAccidentals(keyName); + if (!k) return multilineVars.key; // If the key isn't in the list, it is non-standard. We won't attempt to transpose it. + multilineVars.localTranspose = (multilineVars.globalTranspose ? multilineVars.globalTranspose : 0) + localTranspose; + + if (!multilineVars.localTranspose) + return { accidentals: k, root: root, acc: acc }; + multilineVars.globalTransposeOrigKeySig = k; + if (multilineVars.localTranspose % 12 === 0) { + multilineVars.localTransposeVerticalMovement = (multilineVars.localTranspose / 12) * 7; + return { accidentals: k, root: root, acc: acc }; + } + + var baseKey = keyName[0]; + if (keyName[1] === 'b' || keyName[1] === '#') { + baseKey += keyName[1]; + keyName = keyName.substr(2); + } else + keyName = keyName.substr(1); + var thisKeyIndex = keyIndex[baseKey] + var recognized = thisKeyIndex !== undefined + if (!recognized) { + // Either the key sig is "none" or we don't recognize it. Either way we don't change it, and we assume key of C for the purposes of this calculation. + thisKeyIndex = 0 + baseKey = "C" + keyName = "" + } + var index = thisKeyIndex + multilineVars.localTranspose; + while (index < 0) index += 12; + if (index > 11) index = index % 12; + var newKeyName = (keyName[0] === 'm' ? newKeyMinor[index] : newKey[index]); + var transposedKey = newKeyName + keyName; + var newKeySig = keyAccidentals(transposedKey); + if (newKeySig.length > 0 && newKeySig[0].acc === 'flat') + multilineVars.localTransposePreferFlats = true; + var distance = transposedKey.charCodeAt(0) - baseKey.charCodeAt(0); + if (multilineVars.localTranspose > 0) { + if (distance < 0) + distance += 7; + else if (distance === 0) { + // There's a funny thing that happens when the key changes only an accidental's distance, for instance, from Ab to A. + // If the distance is positive (we are raising pitch), and the change is higher (that is, Ab -> A), then raise an octave. + // This test is easier because we know the keys are not equal (or we wouldn't get this far), so if the base key is a flat key, then + // the transposed key must be higher. Likewise, if the transposed key is sharp, then the base key must be lower. And one + // of those two things must be true because they are not both natural. + if (baseKey[1] === '#' || transposedKey[1] === 'b') + distance += 7; + } + } else if (multilineVars.localTranspose < 0) { + if (distance > 0) + distance -= 7; + else if (distance === 0) { + // There's a funny thing that happens when the key changes only an accidental's distance, for instance, from Ab to A. + // If the distance is negative (we are dropping pitch), and the change is lower (that is, A -> Ab), then drop an octave. + if (baseKey[1] === 'b' || transposedKey[1] === '#') + distance -= 7; + } + } + + if (multilineVars.localTranspose > 0) + multilineVars.localTransposeVerticalMovement = distance + Math.floor(multilineVars.localTranspose / 12) * 7; + else + multilineVars.localTransposeVerticalMovement = distance + Math.ceil(multilineVars.localTranspose / 12) * 7; + if (recognized) + return { accidentals: newKeySig, root: newKeyName[0], acc: newKeyName.length > 1 ? newKeyName[1] : "" }; + else + return { accidentals: [], root: root, acc: acc }; +}; + +transpose.chordName = function(multilineVars, chord) { + return transposeChordName(chord, multilineVars.localTranspose, multilineVars.localTransposePreferFlats, multilineVars.freegchord) +}; + +var pitchToLetter = [ 'c', 'd', 'e', 'f', 'g', 'a', 'b' ]; +function accidentalChange(origPitch, newPitch, accidental, origKeySig, newKeySig) { + var origPitchLetter = pitchToLetter[(origPitch + 49) % 7]; // Make sure it is a positive pitch before normalizing. + var origAccidental = 0; + for (var i = 0; i < origKeySig.length; i++) { + if (origKeySig[i].note.toLowerCase() === origPitchLetter) + origAccidental = accidentals[origKeySig[i].acc]; + } + + var currentAccidental = accidentals[accidental]; + var delta = currentAccidental - origAccidental; + + var newPitchLetter = pitchToLetter[(newPitch + 49) % 7]; // Make sure it is a positive pitch before normalizing. + var newAccidental = 0; + for (var j = 0; j < newKeySig.accidentals.length; j++) { + if (newKeySig.accidentals[j].note.toLowerCase() === newPitchLetter) + newAccidental = accidentals[newKeySig.accidentals[j].acc]; + } + var calcAccidental = delta + newAccidental; + if (calcAccidental < -2) { + newPitch--; + calcAccidental += (newPitchLetter === 'c' || newPitchLetter === 'f') ? 1 : 2; + } + if (calcAccidental > 2) { + newPitch++; + calcAccidental -= (newPitchLetter === 'b' || newPitchLetter === 'e') ? 1 : 2; + } + return [newPitch, calcAccidental]; +} + +var accidentals = { + dblflat: -2, + flat: -1, + natural: 0, + sharp: 1, + dblsharp: 2 +}; +var accidentals2 = { + "-2": "dblflat", + "-1": "flat", + "0": "natural", + "1": "sharp", + "2": "dblsharp" +}; +var accidentals3 = { + "-2": "__", + "-1": "_", + "0": "=", + "1": "^", + "2": "^^" +}; +//var count = 0 +transpose.note = function(multilineVars, el) { + // the "el" that is passed in has el.name, el.accidental, and el.pitch. "pitch" is the vertical position (0=middle C) + // localTranspose is the number of half steps + // localTransposeVerticalMovement is the vertical distance to move. + //console.log(count++,multilineVars.localTranspose, el) + if (!multilineVars.localTranspose || multilineVars.clef.type === "perc") + return; + var origPitch = el.pitch; + if (multilineVars.localTransposeVerticalMovement) { + el.pitch = el.pitch + multilineVars.localTransposeVerticalMovement; + if (el.name) { + var actual = el.accidental ? el.name.substring(1) : el.name + var acc = el.accidental ? el.name[0] : '' + var p = allNotes.pitchIndex(actual) + el.name = acc + allNotes.noteName(p+multilineVars.localTransposeVerticalMovement) + } + } + + if (el.accidental) { + var ret = accidentalChange(origPitch, el.pitch, el.accidental, multilineVars.globalTransposeOrigKeySig, multilineVars.targetKey); + el.pitch = ret[0]; + el.accidental = accidentals2[ret[1]]; + if (el.name) { + el.name = accidentals3[ret[1]] + el.name.replace(/[_^=]/g,''); + } + } + +}; + +module.exports = transpose; diff --git a/src/parse/all-notes.js b/src/parse/all-notes.js new file mode 100644 index 0000000000000000000000000000000000000000..dbda257f1b773b38cb4c4ffda9c4c11ecf896fd7 --- /dev/null +++ b/src/parse/all-notes.js @@ -0,0 +1,22 @@ +var allNotes = {}; + +const allPitches = [ + 'C,,,', 'D,,,', 'E,,,', 'F,,,', 'G,,,', 'A,,,', 'B,,,', + 'C,,', 'D,,', 'E,,', 'F,,', 'G,,', 'A,,', 'B,,', + 'C,', 'D,', 'E,', 'F,', 'G,', 'A,', 'B,', + 'C', 'D', 'E', 'F', 'G', 'A', 'B', + 'c', 'd', 'e', 'f', 'g', 'a', 'b', + "c'", "d'", "e'", "f'", "g'", "a'", "b'", + "c''", "d''", "e''", "f''", "g''", "a''", "b''", + "c'''", "d'''", "e'''", "f'''", "g'''", "a'''", "b'''", +]; + +allNotes.pitchIndex = function(noteName) { + return allPitches.indexOf(noteName) +} + +allNotes.noteName = function(pitchIndex) { + return allPitches[pitchIndex] +} + +module.exports = allNotes; diff --git a/src/parse/transpose-chord.js b/src/parse/transpose-chord.js new file mode 100644 index 0000000000000000000000000000000000000000..263365e7b8daa8478598d8e8a745025eb7b091e1 --- /dev/null +++ b/src/parse/transpose-chord.js @@ -0,0 +1,80 @@ +var sharpChords = ['C', 'C♯', 'D', "D♯", 'E', 'F', "F♯", 'G', 'G♯', 'A', 'A♯', 'B']; +var flatChords = ['C', 'D♭', 'D', 'E♭', 'E', 'F', 'G♭', 'G', 'A♭', 'A', 'B♭', 'B']; +var sharpChordsFree = ['C', 'C#', 'D', "D#", 'E', 'F', "F#", 'G', 'G#', 'A', 'A#', 'B']; +var flatChordsFree = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; + +function transposeChordName(chord, steps, preferFlats, freeGCchord) { + if (!steps || (steps % 12 === 0)) // The chords are the same if it is an exact octave change. + return chord; + + // There are two things in the chord that might need to be transposed: + // The chord will start with a letter from A-G, and might have one accidental after it. + // That accidental might be an actual sharp or flat char, or it might be a pound sign or lower case "b". + // Then there is a bunch of stuff that isn't transposed and should just be copied. That is stuff like "7" and more complicated chords. + // But there is one other exception: right after a slash there will be a bass note and possibly an accidental. That should also be transposed. + + while (steps < 0) steps += 12; + if (steps > 11) steps = steps % 12; + + // (chord name w/accidental) (a bunch of stuff) (/) (bass note) (anything else) + var match = chord.match(/^([A-G][b#♭♯]?)([^\/]+)?\/?([A-G][b#♭♯]?)?(.+)?/) + if (!match) + return chord; // We don't recognize the format of the chord, so skip it. + var name = match[1] + var extra1 = match[2] + var bass = match[3] + var extra2 = match[4] + var index = sharpChords.indexOf(name) + if (index < 0) + index = flatChords.indexOf(name) + if (index < 0) + index = sharpChordsFree.indexOf(name) + if (index < 0) + index = flatChordsFree.indexOf(name) + if (index < 0) + return chord; // This should never happen, but if we can't find the chord just bail. + + index += steps + index = index % 12 + + if (preferFlats) { + if (freeGCchord) chord = flatChordsFree[index] + else chord = flatChords[index] + } else { + if (freeGCchord) chord = sharpChordsFree[index] + else chord = sharpChords[index] + } + + if (extra1) + chord += extra1 + + if (bass) { + var index = sharpChords.indexOf(bass) + if (index < 0) + index = flatChords.indexOf(bass) + if (index < 0) + index = sharpChordsFree.indexOf(bass) + if (index < 0) + index = flatChordsFree.indexOf(bass) + chord += '/' + if (index >= 0) { + index += steps + index = index % 12 + if (preferFlats) { + if (freeGCchord) chord += flatChordsFree[index] + else chord += flatChords[index] + } else { + if (freeGCchord) chord += sharpChordsFree[index] + else chord += sharpChords[index] + } + } else + chord += bass; // Don't know what to do so do nothing + } + + if (extra2) + chord += extra2 + + return chord; +} + +module.exports = transposeChordName \ No newline at end of file diff --git a/src/parse/tune-builder.js b/src/parse/tune-builder.js new file mode 100644 index 0000000000000000000000000000000000000000..a7bdb6f757f0b173188af805b511f8a94fe937cd --- /dev/null +++ b/src/parse/tune-builder.js @@ -0,0 +1,992 @@ +var parseKeyVoice = require('../parse/abc_parse_key_voice'); +//var parseCommon = require('../parse/abc_common'); +//var parseDirective = require('./abc_parse_directive'); + +var TuneBuilder = function (tune) { + var self = this; + var voiceDefs = {} + var currentVoiceName = '' + tune.reset(); + + this.setVisualTranspose = function (visualTranspose) { + if (visualTranspose) + tune.visualTranspose = visualTranspose; + }; + + this.cleanUp = function (barsperstaff, staffnonote, currSlur) { + closeLine(tune); // Close the last line. + delete tune.runningFonts; + + simplifyMetaText(tune) + //addRichTextToAnnotationsAndLyrics(tune) + + // If the tempo was created with a string like "Allegro", then the duration of a beat needs to be set at the last moment, when it is most likely known. + if (tune.metaText.tempo && tune.metaText.tempo.bpm && !tune.metaText.tempo.duration) + tune.metaText.tempo.duration = [tune.getBeatLength()]; + + // Remove any blank lines + var anyDeleted = false; + var i, s, v; + for (i = 0; i < tune.lines.length; i++) { + if (tune.lines[i].staff !== undefined) { + var hasAny = false; + for (s = 0; s < tune.lines[i].staff.length; s++) { + if (tune.lines[i].staff[s] === undefined) { + anyDeleted = true; + tune.lines[i].staff[s] = null; + //tune.lines[i].staff[s] = { voices: []}; // TODO-PER: There was a part missing in the abc music. How should we recover? + } else { + for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) { + if (tune.lines[i].staff[s].voices[v] === undefined) + tune.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover? + else + if (containsNotes(tune.lines[i].staff[s].voices[v])) hasAny = true; + } + } + } + if (!hasAny) { + tune.lines[i] = null; + anyDeleted = true; + } + } + } + if (anyDeleted) { + tune.lines = tune.lines.filter(function (line) { return !!line }); + tune.lines.forEach(function (line) { + if (line.staff) + line.staff = line.staff.filter(function (line) { return !!line }); + }); + } + + // if we exceeded the number of bars allowed on a line, then force a new line + if (barsperstaff) { + while (wrapMusicLines(tune.lines, barsperstaff)) { + // This will keep wrapping until the end of the piece. + } + } + + // If we were passed staffnonote, then we want to get rid of all staffs that contain only rests. + if (staffnonote) { + anyDeleted = false; + for (i = 0; i < tune.lines.length; i++) { + if (tune.lines[i].staff !== undefined) { + for (s = 0; s < tune.lines[i].staff.length; s++) { + var keepThis = false; + for (v = 0; v < tune.lines[i].staff[s].voices.length; v++) { + if (containsNotesStrict(tune.lines[i].staff[s].voices[v])) { + keepThis = true; + } + } + if (!keepThis) { + anyDeleted = true; + tune.lines[i].staff[s] = null; + } + } + } + } + if (anyDeleted) { + tune.lines.forEach(function (line) { + if (line.staff) + line.staff = line.staff.filter(function (staff) { return !!staff }); + }); + } + } + + fixTitles(tune.lines); + + // Remove the temporary working variables + for (i = 0; i < tune.lines.length; i++) { + if (tune.lines[i].staff) { + for (s = 0; s < tune.lines[i].staff.length; s++) + delete tune.lines[i].staff[s].workingClef; + } + } + + // If there are overlays, create new voices for them. + while (resolveOverlays(tune)) { + // keep resolving overlays as long as any are found. + } + + for (var i = 0; i < tune.lines.length; i++) { + var staff = tune.lines[i].staff; + if (staff) { + for (tune.staffNum = 0; tune.staffNum < staff.length; tune.staffNum++) { + if (staff[tune.staffNum].clef) + parseKeyVoice.fixClef(staff[tune.staffNum].clef); + for (tune.voiceNum = 0; tune.voiceNum < staff[tune.staffNum].voices.length; tune.voiceNum++) { + var voice = staff[tune.staffNum].voices[tune.voiceNum]; + cleanUpSlursInLine(voice, tune.staffNum, tune.voiceNum, currSlur); + for (var j = 0; j < voice.length; j++) { + if (voice[j].el_type === 'clef') + parseKeyVoice.fixClef(voice[j]); + } + if (voice.length > 0 && voice[voice.length - 1].barNumber) { + // Don't hang a bar number on the last bar line: it should go on the next line. + var nextLine = getNextMusicLine(tune.lines, i); + if (nextLine) + nextLine.staff[0].barNumber = voice[voice.length - 1].barNumber; + delete voice[voice.length - 1].barNumber; + } + } + } + } + } + + // Remove temporary variables that the outside doesn't need to know about + delete tune.staffNum; + delete tune.voiceNum; + delete tune.lineNum; + delete tune.potentialStartBeam; + delete tune.potentialEndBeam; + delete tune.vskipPending; + + return currSlur; + }; + + this.addTieToLastNote = function (dottedTie) { + // TODO-PER: if this is a chord, which note? + var el = getLastNote(tune); + if (el && el.pitches && el.pitches.length > 0) { + el.pitches[0].startTie = {}; + if (dottedTie) + el.pitches[0].startTie.style = 'dotted'; + return true; + } + return false; + }; + + this.appendElement = function (type, startChar, endChar, hashParams) { + hashParams.el_type = type; + if (startChar !== null) + hashParams.startChar = startChar; + if (endChar !== null) + hashParams.endChar = endChar; + if (type === 'note') { // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) { + // Now, add the startBeam and endBeam where it is needed. + // end_beam is already set on the places where there is a forced end_beam. We'll remove that here after using that info. + // this.potentialStartBeam either points to null or the start beam. + // this.potentialEndBeam either points to null or the start beam. + // If we have a beam break (note is longer than a quarter, or an end_beam is on this element), then set the beam if we have one. + // reset the variables for the next notes. + var dur = getDuration(hashParams); + if (dur >= 0.25) { // The beam ends on the note before this. + endBeamLast(tune); + } else if (hashParams.force_end_beam_last && tune.potentialStartBeam !== undefined) { + endBeamLast(tune); + } else if (hashParams.end_beam && tune.potentialStartBeam !== undefined) { // the beam is forced to end on this note, probably because of a space in the ABC + if (hashParams.rest === undefined) + endBeamHere(hashParams, tune); + else + endBeamLast(tune); + } else if (hashParams.rest === undefined) { // this a short note and we aren't about to end the beam + if (tune.potentialStartBeam === undefined) { // We aren't collecting notes for a beam, so start here. + if (!hashParams.end_beam) { + tune.potentialStartBeam = hashParams; + delete tune.potentialEndBeam; + } + } else { + tune.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note. + } + } + + // end_beam goes on rests and notes which precede rests _except_ when a rest (or set of adjacent rests) has normal notes on both sides (no spaces) + // if (hashParams.rest !== undefined) + // { + // hashParams.end_beam = true; + // var el2 = getLastNote(tune); + // if (el2) el2.end_beam = true; + // // TODO-PER: implement exception mentioned in the comment. + // } + } else { // It's not a note, so there definitely isn't beaming after it. + endBeamLast(tune); + } + delete hashParams.end_beam; // We don't want this temporary variable hanging around. + delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around. + if (hashParams.rest && hashParams.rest.type === 'invisible') { + delete hashParams.decoration // the decorations on invisible rests should be invisible, too. + } + if (tune.lines.length <= tune.lineNum || tune.lines[tune.lineNum].staff.length <= tune.staffNum) { + //console.log("pushNote IGNORED", tune.lines[tune.lineNum]) + // TODO-PER: This prevents a crash, but it drops the element. Need to figure out how to start a new line, or delay adding this. + return false; + } + + pushNote(self, tune, hashParams, voiceDefs, currentVoiceName); + return true + }; + + this.appendStartingElement = function (type, startChar, endChar, hashParams2) { + //console.log('appendStartingElement', hashParams2) + // If we're in the middle of beaming, then end the beam. + closeLine(tune); + + // We only ever want implied naturals the first time. + var impliedNaturals; + if (type === 'key') { + impliedNaturals = hashParams2.impliedNaturals; + delete hashParams2.impliedNaturals; + delete hashParams2.explicitAccidentals; + } + + // Clone the object because it will be sticking around for the next line and we don't want the extra fields in it. + var hashParams = Object.assign({}, hashParams2); + + // be sure that we are on a music type line before doing the following. + if (!tune.lines[tune.lineNum]) return + var staff = tune.lines[tune.lineNum].staff + if (!staff) return + + // If tune is the first item in tune staff, then we might have to initialize the staff, first. + if (staff.length <= tune.staffNum) { + staff[tune.staffNum] = {}; + staff[tune.staffNum].clef = Object.assign({}, staff[0].clef); + staff[tune.staffNum].key = Object.assign({}, staff[0].key); + if (staff[0].meter) + staff[tune.staffNum].meter = Object.assign({}, staff[0].meter); + staff[tune.staffNum].workingClef = Object.assign({}, staff[0].workingClef); + staff[tune.staffNum].voices = [[]]; + } + // If tune is a clef type, then we replace the working clef on the line. This is kept separate from + // the clef in case there is an inline clef field. We need to know what the current position for + // the note is. + if (type === 'clef') { + staff[tune.staffNum].workingClef = hashParams; + } + + // These elements should not be added twice, so if the element exists on tune line without a note or bar before it, just replace the staff version. + var voice = staff[tune.staffNum].voices[tune.voiceNum]; + for (var i = 0; i < voice.length; i++) { + if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') { + hashParams.el_type = type; + hashParams.startChar = startChar; + hashParams.endChar = endChar; + if (impliedNaturals) + hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals); + voice.push(hashParams); + return; + } + if (voice[i].el_type === type) { + hashParams.el_type = type; + hashParams.startChar = startChar; + hashParams.endChar = endChar; + if (impliedNaturals) + hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals); + voice[i] = hashParams; + return; + } + } + // We didn't see either that type or a note, so replace the element to the staff. + staff[tune.staffNum][type] = hashParams2; + }; + + this.addSubtitle = function (str, info) { + pushLine(tune, { subtitle: { text: str, startChar: info.startChar, endChar: info.endChar } }); + }; + + this.addSpacing = function (num) { + tune.vskipPending = num; + }; + + this.addNewPage = function (num) { + pushLine(tune, { newpage: num }); + }; + + this.addSeparator = function (spaceAbove, spaceBelow, lineLength, info) { + pushLine(tune, { separator: { spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength), startChar: info.startChar, endChar: info.endChar } }); + }; + + this.addText = function (str, info) { + pushLine(tune, { text: { text: str, startChar: info.startChar, endChar: info.endChar } }); + }; + + this.addCentered = function (str) { + pushLine(tune, { text: [{ text: str, center: true }] }); + }; + + // anyVoiceContainsNotes: function(line) { + // for (var i = 0; i < line.staff.voices.length; i++) { + // if (containsNotes(line.staff.voices[i])) + // return true; + // } + // return false; + // }, + this.changeVoiceScale = function (scale) { + self.appendElement('scale', null, null, { size: scale }); + }; + this.changeVoiceColor = function (color) { + self.appendElement('color', null, null, { color: color }); + }; + + this.startNewLine = function (params) { + //console.log("startNewLine", tune.lineNum, params, voiceDefs) + // If the pointed to line doesn't exist, just create that. If the line does exist, but doesn't have any music on it, just use it. + // If it does exist and has music, then increment the line number. If the new element doesn't exist, create it. + closeLine(tune); // Close the previous line. + if (params.currentVoiceName) { + currentVoiceName = params.currentVoiceName + voiceDefs[params.currentVoiceName] = params + } + + if (tune.lines[tune.lineNum] === undefined) createLine(self, tune, params); + else if (tune.lines[tune.lineNum].staff === undefined) { + tune.lineNum++; + this.startNewLine(params); + } else if (tune.lines[tune.lineNum].staff[tune.staffNum] === undefined) createStaff(self, tune, params); + else if (tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] === undefined) createVoice(self, tune, params); + else if (!containsNotes(tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum])) { + // We don't need a new line but we might need to update parts of it. + if (params.part) + self.appendElement('part', params.part.startChar, params.part.endChar, { title: params.part.title }); + } else { + tune.lineNum++; + this.startNewLine(params); + } + }; + + this.setRunningFont = function (type, font) { + // This is called at tune start to set the current default fonts so we know whether to record a change. + tune.runningFonts[type] = font; + }; + + this.setBarNumberImmediate = function (barNumber) { + // If tune is called right at the beginning of a line, then correct the measure number that is already written. + // If tune is called at the beginning of a measure, then correct the measure number that was just created. + // If tune is called in the middle of a measure, then subtract one from it, because it will be incremented before applied. + var currentVoice = this.getCurrentVoice(); + if (currentVoice && currentVoice.length > 0) { + var lastElement = currentVoice[currentVoice.length - 1]; + if (lastElement.el_type === 'bar') { + if (lastElement.barNumber !== undefined) // the measure number might not be written for tune bar, don't override that. + lastElement.barNumber = barNumber; + } else + return barNumber - 1; + } + return barNumber; + }; + + this.hasBeginMusic = function () { + // return true if there exists at least one line that contains "staff" + for (var i = 0; i < tune.lines.length; i++) { + if (tune.lines[i].staff) + return true; + } + return false; + }; + + this.isFirstLine = function (index) { + for (var i = index - 1; i >= 0; i--) { + if (tune.lines[i].staff !== undefined) return false; + } + return true; + }; + + this.getCurrentVoice = function () { + //console.log("getCurrentVoice", tune.lineNum) + var currLine = tune.lines[tune.lineNum]; + if (!currLine) + return null; + var currStaff = currLine.staff[tune.staffNum]; + if (!currStaff) + return null; + if (currStaff.voices[tune.voiceNum] !== undefined) + return currStaff.voices[tune.voiceNum]; + else return null; + }; + + this.setCurrentVoice = function (staffNum, voiceNum, name) { + //console.log("setCurrentVoice", tune.lineNum, staffNum, voiceNum, name, voiceDefs) + tune.staffNum = staffNum; + tune.voiceNum = voiceNum; + currentVoiceName = name + for (var i = 0; i < tune.lines.length; i++) { + if (tune.lines[i].staff) { + if (tune.lines[i].staff[staffNum] === undefined || tune.lines[i].staff[staffNum].voices[voiceNum] === undefined || + !containsNotes(tune.lines[i].staff[staffNum].voices[voiceNum])) { + //console.log("cv2", i, tune.lines[i].staff[staffNum]) + tune.lineNum = i; + if (!tune.lines[i].staff[staffNum] || !!tune.lines[i].staff[staffNum].voices[voiceNum]) return true + return false; + } + } + } + //console.log("cv3", i, tune.lineNum, tune.lines[tune.lineNum]) + tune.lineNum = i; + return false + }; + + this.addMetaText = function (key, value, info) { + if (tune.metaText[key] === undefined) { + tune.metaText[key] = value; + tune.metaTextInfo[key] = info; + } else { + if (typeof tune.metaText[key] === 'string' && typeof value === 'string') + tune.metaText[key] += "\n" + value; + else { + if (tune.metaText[key] === 'string') + tune.metaText[key] = [{ text: tune.metaText[key] }] + if (typeof value === 'string') + value = [{ text: value }] + tune.metaText[key] = tune.metaText[key].concat(value) + } + tune.metaTextInfo[key].endChar = info.endChar; + } + }; + + this.addMetaTextArray = function (key, value, info) { + if (tune.metaText[key] === undefined) { + tune.metaText[key] = [value]; + tune.metaTextInfo[key] = info; + } else { + tune.metaText[key].push(value); + tune.metaTextInfo[key].endChar = info.endChar; + } + }; + this.addMetaTextObj = function (key, value, info) { + tune.metaText[key] = value; + tune.metaTextInfo[key] = info; + }; +}; + +function isArrayOfStrings(arr) { + if (!arr) return false + if (typeof arr === "string") return false + //var str = '' + for (var i = 0; i < arr.length; i++) { + if (typeof arr[i] !== 'string') + return false + } + return true +} + +function simplifyMetaText(tune) { + if (isArrayOfStrings(tune.metaText.notes)) + tune.metaText.notes = tune.metaText.notes.join("\n") + if (isArrayOfStrings(tune.metaText.history)) + tune.metaText.history = tune.metaText.history.join("\n") +} + +// function addRichTextToAnnotationsAndLyrics(tune) { +// var lines = tune.lines +// for (var i = 0; i < lines.length; i++) { +// if (lines[i].staff !== undefined) { +// for (var s = 0; s < lines[i].staff.length; s++) { +// for (var v = 0; v < lines[i].staff[s].voices.length; v++) { +// var voice = lines[i].staff[s].voices[v]; +// for (var n = 0; n < voice.length; n++) { +// var element = voice[n] +// if (element.chord) { +// for (var c = 0; c < element.chord.length; c++) { +// element.chord[c].name = parseDirective.parseFontChangeLine(element.chord[c].name) +// console.log(element.chord[c].name) +// } +// } +// if (element.lyric) { +// for (var l = 0; l < element.lyric.length; l++) { +// element.lyric[l].syllable = parseDirective.parseFontChangeLine(element.lyric[l].syllable) +// console.log(element.lyric[l].syllable) +// } +// } +// } +// } +// } +// } +// } + +// } + +function resolveOverlays(tune) { + var madeChanges = false; + var durationsPerLines = []; + for (var i = 0; i < tune.lines.length; i++) { + var line = tune.lines[i]; + if (line.staff) { + for (var j = 0; j < line.staff.length; j++) { + var staff = line.staff[j]; + var overlayVoice = []; + for (var k = 0; k < staff.voices.length; k++) { + var voice = staff.voices[k]; + overlayVoice.push({ hasOverlay: false, voice: [], snip: [] }); + durationsPerLines[i] = 0; + var durationThisBar = 0; + var inOverlay = false; + var overlayDuration = 0; + var snipStart = -1; + for (var kk = 0; kk < voice.length; kk++) { + var event = voice[kk]; + if (event.el_type === "overlay" && !inOverlay) { + madeChanges = true; + inOverlay = true; + snipStart = kk; + overlayVoice[k].hasOverlay = true; + if (overlayDuration === 0) + overlayDuration = durationsPerLines[i]; + // If this isn't the first line, we also need invisible rests on the previous lines. + // So, if the next voice doesn't appear in a previous line, create it + for (var ii = 0; ii < i; ii++) { + if (durationsPerLines[ii] && tune.lines[ii].staff && staff.voices.length >= tune.lines[ii].staff[0].voices.length) { + tune.lines[ii].staff[0].voices.push([{ + el_type: "note", + duration: durationsPerLines[ii], + rest: { type: "invisible" }, + startChar: event.startChar, + endChar: event.endChar + }]); + } + } + } else if (event.el_type === "bar") { + if (inOverlay) { + // delete the overlay events from this array without messing up this loop. + inOverlay = false; + overlayVoice[k].snip.push({ start: snipStart, len: kk - snipStart }); + overlayVoice[k].voice.push(event); // Also end the overlay with the barline. + } else { + // This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests. + if (durationThisBar > 0) + overlayVoice[k].voice.push({ el_type: "note", duration: durationThisBar, rest: { type: "invisible" }, startChar: event.startChar, endChar: event.endChar }); + overlayVoice[k].voice.push(event); + } + durationThisBar = 0; + } else if (event.el_type === "note") { + if (inOverlay) { + overlayVoice[k].voice.push(event); + } else { + durationThisBar += event.duration; + durationsPerLines[i] += event.duration; + } + } else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose" || event.el_type === "color") { + // These types of events are duplicated on the overlay layer. + overlayVoice[k].voice.push(event); + } + } + if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) { + // there was no closing bar, so we didn't set the snip amount. + overlayVoice[k].snip.push({ start: snipStart, len: voice.length - snipStart }); + } + } + for (k = 0; k < overlayVoice.length; k++) { + var ov = overlayVoice[k]; + if (ov.hasOverlay) { + ov.voice.splice(0, 0, { el_type: "stem", direction: "down" }) + staff.voices.push(ov.voice); + for (var kkk = ov.snip.length - 1; kkk >= 0; kkk--) { + var snip = ov.snip[kkk]; + staff.voices[k].splice(snip.start, snip.len); + staff.voices[k].splice(snip.start + 1, 0, { el_type: "stem", direction: "auto" }); + var indexOfLastBar = findLastBar(staff.voices[k], snip.start); + staff.voices[k].splice(indexOfLastBar, 0, { el_type: "stem", direction: "up" }); + } + // remove ending marks from the overlay voice so they are not repeated + for (kkk = 0; kkk < staff.voices[staff.voices.length - 1].length; kkk++) { + staff.voices[staff.voices.length - 1][kkk] = Object.assign({}, staff.voices[staff.voices.length - 1][kkk]); + var el = staff.voices[staff.voices.length - 1][kkk]; + if (el.el_type === 'bar' && el.startEnding) { + delete el.startEnding; + } + if (el.el_type === 'bar' && el.endEnding) + delete el.endEnding; + } + } + } + } + } + } + return madeChanges; +}; + +function findLastBar(voice, start) { + for (var i = start - 1; i > 0 && voice[i].el_type !== "bar"; i--) { + + } + return i; +} + +function fixTitles(lines) { + // We might have name and subname defined. We now know what line everything is on, so we can determine which to use. + var firstMusicLine = true; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.staff) { + for (var j = 0; j < line.staff.length; j++) { + var staff = line.staff[j]; + if (staff.title) { + var hasATitle = false; + for (var k = 0; k < staff.title.length; k++) { + if (staff.title[k]) { + staff.title[k] = (firstMusicLine) ? staff.title[k].name : staff.title[k].subname; + if (staff.title[k]) + hasATitle = true; + else + staff.title[k] = ''; + } else + staff.title[k] = ''; + } + if (!hasATitle) + delete staff.title; + } + } + firstMusicLine = false; + } + } +} + +function cleanUpSlursInLine(line, staffNum, voiceNum, currSlur) { + if (!currSlur[staffNum]) + currSlur[staffNum] = []; + if (!currSlur[staffNum][voiceNum]) + currSlur[staffNum][voiceNum] = []; + var x; + // var lyr = null; // TODO-PER: debugging. + + var addEndSlur = function (obj, num, chordPos) { + if (currSlur[staffNum][voiceNum][chordPos] === undefined) { + // There isn't an exact match for note position, but we'll take any other open slur. + for (x = 0; x < currSlur[staffNum][voiceNum].length; x++) { + if (currSlur[staffNum][voiceNum][x] !== undefined) { + chordPos = x; + break; + } + } + if (currSlur[staffNum][voiceNum][chordPos] === undefined) { + var offNum = chordPos * 100 + 1; + obj.endSlur.forEach(function (x) { if (offNum === x) --offNum; }); + currSlur[staffNum][voiceNum][chordPos] = [offNum]; + } + } + var slurNum; + for (var i = 0; i < num; i++) { + slurNum = currSlur[staffNum][voiceNum][chordPos].pop(); + obj.endSlur.push(slurNum); + // lyr.syllable += '<' + slurNum; // TODO-PER: debugging + } + if (currSlur[staffNum][voiceNum][chordPos].length === 0) + delete currSlur[staffNum][voiceNum][chordPos]; + return slurNum; + }; + + var addStartSlur = function (obj, num, chordPos, usedNums) { + obj.startSlur = []; + if (currSlur[staffNum][voiceNum][chordPos] === undefined) { + currSlur[staffNum][voiceNum][chordPos] = []; + } + var nextNum = chordPos * 100 + 1; + for (var i = 0; i < num; i++) { + if (usedNums) { + usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); + usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); + usedNums.forEach(function (x) { if (nextNum === x) ++nextNum; }); + } + currSlur[staffNum][voiceNum][chordPos].forEach(function (x) { if (nextNum === x) ++nextNum; }); + currSlur[staffNum][voiceNum][chordPos].forEach(function (x) { if (nextNum === x) ++nextNum; }); + + currSlur[staffNum][voiceNum][chordPos].push(nextNum); + obj.startSlur.push({ label: nextNum }); + if (obj.dottedSlur) { + obj.startSlur[obj.startSlur.length - 1].style = 'dotted'; + delete obj.dottedSlur; + } + // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging + nextNum++; + } + }; + + for (var i = 0; i < line.length; i++) { + var el = line[i]; + // if (el.lyric === undefined) // TODO-PER: debugging + // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging + // lyr = el.lyric[0]; // TODO-PER: debugging + // lyr.syllable = ''; // TODO-PER: debugging + if (el.el_type === 'note') { + if (el.gracenotes) { + for (var g = 0; g < el.gracenotes.length; g++) { + if (el.gracenotes[g].endSlur) { + var gg = el.gracenotes[g].endSlur; + el.gracenotes[g].endSlur = []; + for (var ggg = 0; ggg < gg; ggg++) + addEndSlur(el.gracenotes[g], 1, 20); + } + if (el.gracenotes[g].startSlur) { + x = el.gracenotes[g].startSlur; + addStartSlur(el.gracenotes[g], x, 20); + } + } + } + if (el.endSlur) { + x = el.endSlur; + el.endSlur = []; + addEndSlur(el, x, 0); + } + if (el.startSlur) { + x = el.startSlur; + addStartSlur(el, x, 0); + } + if (el.pitches) { + var usedNums = []; + for (var p = 0; p < el.pitches.length; p++) { + if (el.pitches[p].endSlur) { + var k = el.pitches[p].endSlur; + el.pitches[p].endSlur = []; + for (var j = 0; j < k; j++) { + var slurNum = addEndSlur(el.pitches[p], 1, p + 1); + usedNums.push(slurNum); + } + } + } + for (p = 0; p < el.pitches.length; p++) { + if (el.pitches[p].startSlur) { + x = el.pitches[p].startSlur; + addStartSlur(el.pitches[p], x, p + 1, usedNums); + } + } + // Correct for the weird gracenote case where ({g}a) should match. + // The end slur was already assigned to the note, and needs to be moved to the first note of the graces. + if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) { + if (el.gracenotes[0].endSlur) + el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label); + else + el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label]; + if (el.pitches[0].endSlur.length === 1) + delete el.pitches[0].endSlur; + else if (el.pitches[0].endSlur[0] === 100) + el.pitches[0].endSlur.shift(); + else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length - 1] === 100) + el.pitches[0].endSlur.pop(); + if (currSlur[staffNum][voiceNum][1].length === 1) + delete currSlur[staffNum][voiceNum][1]; + else + currSlur[staffNum][voiceNum][1].pop(); + } + } + } + } +} + +function wrapMusicLines(lines, barsperstaff) { + for (i = 0; i < lines.length; i++) { + if (lines[i].staff !== undefined) { + for (s = 0; s < lines[i].staff.length; s++) { + var permanentItems = []; + for (v = 0; v < lines[i].staff[s].voices.length; v++) { + var voice = lines[i].staff[s].voices[v]; + var barNumThisLine = 0; + for (var n = 0; n < voice.length; n++) { + if (voice[n].el_type === 'bar') { + barNumThisLine++; + if (barNumThisLine >= barsperstaff) { + // push everything else to the next line, if there is anything else, + // and there is a next line. If there isn't a next line, create one. + if (n < voice.length - 1) { + var nextLine = getNextMusicLine(lines, i); + if (!nextLine) { + var cp = JSON.parse(JSON.stringify(lines[i])); + lines.push(Object.assign({}, cp)); + nextLine = lines[lines.length - 1]; + for (var ss = 0; ss < nextLine.staff.length; ss++) { + for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++) + nextLine.staff[ss].voices[vv] = []; + } + } + var startElement = n + 1; + var section = lines[i].staff[s].voices[v].slice(startElement); + lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement); + nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v])); + return true; + } + } + } else if (!voice[n].duration) { + permanentItems.push(voice[n]); + } + } + } + } + } + } + return false; +} + +function getNextMusicLine(lines, currentLine) { + currentLine++; + while (lines.length > currentLine) { + if (lines[currentLine].staff) + return lines[currentLine]; + currentLine++; + } + return null; +} + +function getLastNote(tune) { + if (!tune.lines[tune.lineNum]) return null + if (!tune.lines[tune.lineNum].staff) return null + if (!tune.lines[tune.lineNum].staff[tune.staffNum]) return null + var voice = tune.lines[tune.lineNum].staff[tune.staffNum].voices[tune.voiceNum] + if (!voice) return null + for (var i = voice.length - 1; i >= 0; i--) { + var el = voice[i]; + if (el.el_type === 'note') { + return el; + } + } + return null; +}; + +function getDuration(el) { + if (el.duration) return el.duration; + return 0; +}; + +function closeLine(tune) { + if (tune.potentialStartBeam && tune.potentialEndBeam) { + tune.potentialStartBeam.startBeam = true; + tune.potentialEndBeam.endBeam = true; + } + delete tune.potentialStartBeam; + delete tune.potentialEndBeam; +}; + +function containsNotes(voice) { + for (var i = 0; i < voice.length; i++) { + if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') + return true; + } + return false; +}; + +function containsNotesStrict(voice) { + for (var i = 0; i < voice.length; i++) { + if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined)) + return true; + } + return false; +}; + +function pushLine(tune, hash) { + if (tune.vskipPending) { + hash.vskip = tune.vskipPending; + delete tune.vskipPending; + } + tune.lines.push(hash); +}; + +function pushNote(self, tune, hp, voiceDefs, currentVoiceName) { + //console.log("pushNote", tune.lineNum, tune.staffNum, hp.pitches ? JSON.stringify(hp.pitches) : hp.pitches) + var currStaff = tune.lines[tune.lineNum].staff[tune.staffNum]; + + if (hp.pitches !== undefined) { + var mid = currStaff.workingClef.verticalPos; + hp.pitches.forEach(function (p) { p.verticalPos = p.pitch - mid; }); + } + if (hp.gracenotes !== undefined) { + var mid2 = currStaff.workingClef.verticalPos; + hp.gracenotes.forEach(function (p) { p.verticalPos = p.pitch - mid2; }); + } + if (currStaff.voices.length <= tune.voiceNum) { + //console.log("should create?", currentVoiceName, voiceDefs) + if (!voiceDefs[currentVoiceName]) + voiceDefs[currentVoiceName] = {} + createVoice(self, tune, voiceDefs[currentVoiceName]) + } + currStaff.voices[tune.voiceNum].push(hp); +} + +function endBeamHere(hashParams, tune) { + tune.potentialStartBeam.startBeam = true; + hashParams.endBeam = true; + delete tune.potentialStartBeam; + delete tune.potentialEndBeam; +} +function endBeamLast(tune) { + if (tune.potentialStartBeam !== undefined && tune.potentialEndBeam !== undefined) { // Do we have a set of notes to beam? + tune.potentialStartBeam.startBeam = true; + tune.potentialEndBeam.endBeam = true; + } + delete tune.potentialStartBeam; + delete tune.potentialEndBeam; +} + +function setLineFont(tune, type, font) { + // If we haven't encountered the font type yet then we are using the default font so it doesn't + // need to be noted. If we have encountered it, then only record it if it is different from the last time. + if (tune.runningFonts[type]) { + var isDifferent = false; + var keys = Object.keys(font); + for (var i = 0; i < keys.length; i++) { + if (tune.runningFonts[type][keys[i]] !== font[keys[i]]) + isDifferent = true; + } + if (isDifferent) { + tune.lines[tune.lineNum].staff[tune.staffNum][type] = font; + } + } + tune.runningFonts[type] = font; +} + +function createVoice(self, tune, params) { + //console.log("createVoice", params) + var thisStaff = tune.lines[tune.lineNum].staff[tune.staffNum]; + thisStaff.voices[tune.voiceNum] = []; + if (!thisStaff.title) + thisStaff.title = []; + thisStaff.title[tune.voiceNum] = { name: params.name, subname: params.subname }; + if (params.style) + self.appendElement('style', null, null, { head: params.style }); + if (params.stem) + self.appendElement('stem', null, null, { direction: params.stem }); + else if (tune.voiceNum > 0) { + if (thisStaff.voices[0] !== undefined) { + var found = false; + for (var i = 0; i < thisStaff.voices[0].length; i++) { + if (thisStaff.voices[0].el_type === 'stem') + found = true; + } + if (!found) { + var stem = { el_type: 'stem', direction: 'up' }; + thisStaff.voices[0].splice(0, 0, stem); + } + } + self.appendElement('stem', null, null, { direction: 'down' }); + } + if (params.scale) + self.appendElement('scale', null, null, { size: params.scale }); + if (params.color) + self.appendElement('color', null, null, { color: params.color }); +} + +function createStaff(self, tune, params) { + if (params.key && params.key.impliedNaturals) { + params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals); + delete params.key.impliedNaturals; + } + + tune.lines[tune.lineNum].staff[tune.staffNum] = { voices: [], clef: params.clef, key: params.key, workingClef: params.clef }; + var staff = tune.lines[tune.lineNum].staff[tune.staffNum] + if (params.stafflines !== undefined) { + staff.clef.stafflines = params.stafflines; + staff.workingClef.stafflines = params.stafflines; + } + if (params.staffscale) { + staff.staffscale = params.staffscale; + } + if (params.annotationfont) setLineFont(tune, "annotationfont", params.annotationfont); + if (params.gchordfont) setLineFont(tune, "gchordfont", params.gchordfont); + if (params.tripletfont) setLineFont(tune, "tripletfont", params.tripletfont); + if (params.vocalfont) setLineFont(tune, "vocalfont", params.vocalfont); + if (params.bracket) staff.bracket = params.bracket; + if (params.brace) staff.brace = params.brace; + if (params.connectBarLines) staff.connectBarLines = params.connectBarLines; + if (params.barNumber) staff.barNumber = params.barNumber; + createVoice(self, tune, params); + // Some stuff just happens for the first voice + if (params.part) + self.appendElement('part', params.part.startChar, params.part.endChar, { title: params.part.title }); + if (params.meter !== undefined) staff.meter = params.meter; + if (tune.vskipPending) { + tune.lines[tune.lineNum].vskip = tune.vskipPending; + delete tune.vskipPending; + } +} + +function createLine(self, tune, params) { + tune.lines[tune.lineNum] = { staff: [] }; + createStaff(self, tune, params); +} + +module.exports = TuneBuilder; diff --git a/src/parse/wrap_lines.js b/src/parse/wrap_lines.js new file mode 100644 index 0000000000000000000000000000000000000000..ea47397b41c09bec165d114c093894c3625edd75 --- /dev/null +++ b/src/parse/wrap_lines.js @@ -0,0 +1,442 @@ +// wrap_lines.js: does line wrap on an already parsed tune. + +function wrapLines(tune, lineBreaks, barNumbers) { + if (!lineBreaks || tune.lines.length === 0) + return; + + // tune.lines contains nested arrays: there is an array of lines (that's the part this function rewrites), + // there is an array of staffs per line (for instance, piano will have 2, orchestra will have many) + // there is an array of voices per staff (for instance, 4-part harmony might have bass and tenor on a single staff) + var lines = tune.deline({lineBreaks: false}); + var linesBreakElements = findLineBreaks(lines, lineBreaks); + //console.log(JSON.stringify(linesBreakElements)) + tune.lines = addLineBreaks(lines, linesBreakElements, barNumbers); + tune.lineBreaks = linesBreakElements; +} + +function addLineBreaks(lines, linesBreakElements, barNumbers) { + // linesBreakElements is an array of all of the elements that break for a new line + // The objects in the array look like: + // {"ogLine":0,"line":0,"staff":0,"voice":0,"start":0, "end":21} + // ogLine is the original line that it came from, + // line is the target line. + // then copy all the elements from start to end for the staff and voice specified. + // If the item doesn't contain "staff" then it is a non music line and should just be copied. + var outputLines = []; + var lastKeySig = []; // This is per staff - if the key changed then this will be populated. + var lastStem = []; + var currentBarNumber = 1; + for (var i = 0; i < linesBreakElements.length; i++) { + var action = linesBreakElements[i]; + if (lines[action.ogLine].staff) { + var inputStaff = lines[action.ogLine].staff[action.staff]; + if (!outputLines[action.line]) { + outputLines[action.line] = {staff: []} + } + if (!outputLines[action.line].staff[action.staff]) { + outputLines[action.line].staff[action.staff] = {voices: []}; + if (barNumbers !== undefined && action.staff === 0 && action.line > 0) { + outputLines[action.line].staff[action.staff].barNumber = currentBarNumber; + } + var keys = Object.keys(inputStaff) + for (var k = 0; k < keys.length; k++) { + var skip = keys[k] === "voices"; + if (keys[k] === "meter" && action.line !== 0) + skip = true; + if (!skip) + outputLines[action.line].staff[action.staff][keys[k]] = inputStaff[keys[k]]; + } + if (lastKeySig[action.staff]) + outputLines[action.line].staff[action.staff].key = lastKeySig[action.staff]; + + } + if (!outputLines[action.line].staff[action.staff].voices[action.voice]) { + outputLines[action.line].staff[action.staff].voices[action.voice] = []; + } + outputLines[action.line].staff[action.staff].voices[action.voice] = + lines[action.ogLine].staff[action.staff].voices[action.voice].slice(action.start, action.end+1); + if (lastStem[action.staff*10+action.voice]) + outputLines[action.line].staff[action.staff].voices[action.voice].unshift({el_type: "stem", direction: lastStem[action.staff*10+action.voice].direction}) + var currVoice = outputLines[action.line].staff[action.staff].voices[action.voice]; + for (var kk = currVoice.length-1; kk >= 0; kk--) { + if (currVoice[kk].el_type === "key") { + lastKeySig[action.staff] = { + root: currVoice[kk].root, + acc: currVoice[kk].acc, + mode: currVoice[kk].mode, + accidentals: currVoice[kk].accidentals.filter(function (acc) { return acc.acc !== 'natural' }) + }; + break; + } + } + for (kk = currVoice.length-1; kk >= 0; kk--) { + if (currVoice[kk].el_type === "stem") { + lastStem[action.staff*10+action.voice] = { + direction: currVoice[kk].direction, + }; + break; + } + } + if (barNumbers !== undefined && action.staff === 0 && action.voice === 0) { + for (kk = 0; kk < currVoice.length; kk++) { + if (currVoice[kk].el_type === 'bar') { + currentBarNumber++ + if (kk === currVoice.length-1) + delete currVoice[kk].barNumber + else + currVoice[kk].barNumber = currentBarNumber + } + } + } + } else { + outputLines[action.line] = lines[action.ogLine]; + } + } + // There could be some missing info - if the tune passed in was incomplete or had different lengths for different voices or was missing a voice altogether - just fill in the gaps. + for (var ii = 0; ii < outputLines.length; ii++) { + if (outputLines[ii].staff) { + outputLines[ii].staff = outputLines[ii].staff.filter(function (el) { + return el != null; + }); + } + } + return outputLines; +} + + +function findLineBreaks(lines, lineBreakArray) { + // lineBreakArray is an array of all of the sections of the tune - often there will just be one + // section unless there is a subtitle or other non-music lines. Each of the elements of + // Each element of lineBreakArray is an array of the zero-based last measure of the line. + var lineBreakIndexes = []; + var lbai = 0; + var lineCounter = 0; + var outputLine = 0; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.staff) { + var lineStart = lineCounter; + var lineBreaks = lineBreakArray[lbai]; + lbai++; + for (var j = 0; j < line.staff.length; j++) { + var staff = line.staff[j]; + for (var k = 0; k < staff.voices.length; k++) { + outputLine = lineStart; + var measureNumber = 0; + var lbi = 0; + var voice = staff.voices[k]; + var start = 0; + for (var e = 0; e < voice.length; e++) { + var el = voice[e]; + + if (el.el_type === 'bar') { + if (lineBreaks[lbi] === measureNumber) { + lineBreakIndexes.push({ ogLine: i, line: outputLine, staff: j, voice: k, start: start, end: e}) + start = e + 1; + outputLine++; + lineCounter = Math.max(lineCounter, outputLine) + lbi++; + } + measureNumber++; + + } + } + lineBreakIndexes.push({ ogLine: i, line: outputLine, staff: j, voice: k, start: start, end: voice.length}) + outputLine++; + lineCounter = Math.max(lineCounter, outputLine) + } + } + } else { + lineBreakIndexes.push({ ogLine: i, line: outputLine }) + outputLine++; + lineCounter = Math.max(lineCounter, outputLine) + } + } + return lineBreakIndexes; +} + + +function freeFormLineBreaks(widths, lineBreakPoint) { + var lineBreaks = []; + var totals = []; + var totalThisLine = 0; + // run through each measure and see if the accumulation is less than the ideal. + // if it passes the ideal, then see whether the last or this one is closer to the ideal. + for (var i = 0; i < widths.length; i++) { + var width = widths[i]; + var attemptedWidth = totalThisLine + width; + if (attemptedWidth < lineBreakPoint) + totalThisLine = attemptedWidth; + else { + // This just passed the ideal, so see whether the previous or the current number of measures is closer. + var oldDistance = lineBreakPoint - totalThisLine; + var newDistance = attemptedWidth - lineBreakPoint; + if (oldDistance < newDistance && totalThisLine > 0) { + lineBreaks.push(i - 1); + totals.push(Math.round(totalThisLine - width)); + totalThisLine = width; + } else { + if (i < widths.length-1) { + lineBreaks.push(i); + totals.push(Math.round(totalThisLine)); + totalThisLine = 0; + } + } + } + } + totals.push(Math.round(totalThisLine)); + return { lineBreaks: lineBreaks, totals: totals }; +} + +function clone(arr) { + var newArr = []; + for (var i = 0; i < arr.length; i++) + newArr.push(arr[i]); + return newArr; +} + +function oneTry(measureWidths, idealWidths, accumulator, lineAccumulator, lineWidths, lastVariance, highestVariance, currLine, lineBreaks, startIndex, otherTries) { + for (var i = startIndex; i < measureWidths.length; i++) { + var measureWidth = measureWidths[i]; + accumulator += measureWidth; + lineAccumulator += measureWidth; + var thisVariance = Math.abs(accumulator - idealWidths[currLine]); + var varianceIsClose = Math.abs(thisVariance - lastVariance) < idealWidths[0] / 10; // see if the difference is less than 10%, if so, run the test both ways. + if (varianceIsClose) { + if (thisVariance < lastVariance) { + // Also attempt one less measure on the current line - sometimes that works out better. + var newWidths = clone(lineWidths); + var newBreaks = clone(lineBreaks); + newBreaks.push(i-1); + newWidths.push(lineAccumulator - measureWidth); + otherTries.push({ + accumulator: accumulator, + lineAccumulator: measureWidth, + lineWidths: newWidths, + lastVariance: Math.abs(accumulator - idealWidths[currLine+1]), + highestVariance: Math.max(highestVariance, lastVariance), + currLine: currLine+1, + lineBreaks: newBreaks, + startIndex: i+1}); + } else if (thisVariance > lastVariance && i < measureWidths.length-1) { + // Also attempt one extra measure on this line. + newWidths = clone(lineWidths); + newBreaks = clone(lineBreaks); + // newBreaks[newBreaks.length-1] = i; + // newWidths[newWidths.length-1] = lineAccumulator; + otherTries.push({ + accumulator: accumulator, + lineAccumulator: lineAccumulator, + lineWidths: newWidths, + lastVariance: thisVariance, + highestVariance: Math.max(highestVariance, thisVariance), + currLine: currLine, + lineBreaks: newBreaks, + startIndex: i+1}); + } + } + if (thisVariance > lastVariance) { + lineBreaks.push(i - 1); + currLine++; + highestVariance = Math.max(highestVariance, lastVariance); + lastVariance = Math.abs(accumulator - idealWidths[currLine]); + lineWidths.push(lineAccumulator - measureWidth); + lineAccumulator = measureWidth; + } else { + lastVariance = thisVariance; + } + } + lineWidths.push(lineAccumulator); +} + +function optimizeLineWidths(widths, lineBreakPoint, lineBreaks, explanation) { + // figure out how many lines + var numLines = Math.ceil(widths.total / lineBreakPoint); // + 1 TODO-PER: this used to be plus one - not sure why + + // get the ideal width for a line (cumulative width / num lines) - approx the same as lineBreakPoint except for rounding + var idealWidth = Math.floor(widths.total / numLines); + + // get each ideal line width (1*ideal, 2*ideal, 3*ideal, etc) + var idealWidths = []; + for (var i = 0; i < numLines; i++) + idealWidths.push(idealWidth*(i+1)); + + // from first measure, step through accum. Widths until the abs of the ideal is greater than the last one. + // This can sometimes look funny in edge cases, so when the length is within 10%, try one more or one less to see which is better. + // This is better than trying all the possibilities because that would get to be a huge number for even a medium size piece. + // This method seems to never generate more than about 16 tries and it is usually 4 or less. + var otherTries = []; + otherTries.push({ + accumulator: 0, + lineAccumulator: 0, + lineWidths: [], + lastVariance: 999999, + highestVariance: 0, + currLine: 0, + lineBreaks: [], // These are the zero-based last measure on each line + startIndex: 0}); + var index = 0; + while (index < otherTries.length) { + oneTry(widths.measureWidths, + idealWidths, + otherTries[index].accumulator, + otherTries[index].lineAccumulator, + otherTries[index].lineWidths, + otherTries[index].lastVariance, + otherTries[index].highestVariance, + otherTries[index].currLine, + otherTries[index].lineBreaks, + otherTries[index].startIndex, + otherTries); + index++; + } + for (i = 0; i < otherTries.length; i++) { + var otherTry = otherTries[i]; + otherTry.variances = []; + otherTry.aveVariance = 0; + for (var j = 0; j < otherTry.lineWidths.length; j++) { + var lineWidth = otherTry.lineWidths[j]; + otherTry.variances.push(lineWidth - idealWidths[0]); + otherTry.aveVariance += Math.abs(lineWidth - idealWidths[0]); + } + otherTry.aveVariance = otherTry.aveVariance / otherTry.lineWidths.length; + explanation.attempts.push({ type: "optimizeLineWidths", lineBreaks: otherTry.lineBreaks, variances: otherTry.variances, aveVariance: otherTry.aveVariance, widths: widths.measureWidths }); + } + var smallest = 9999999; + var smallestIndex = -1; + for (i = 0; i < otherTries.length; i++) { + otherTry = otherTries[i]; + if (otherTry.aveVariance < smallest) { + smallest = otherTry.aveVariance; + smallestIndex = i; + } + } + return { failed: false, lineBreaks: otherTries[smallestIndex].lineBreaks, variance: otherTries[smallestIndex].highestVariance }; +} + +function fixedMeasureLineBreaks(widths, lineBreakPoint, preferredMeasuresPerLine) { + var lineBreaks = []; + var totals = []; + var thisWidth = 0; + var failed = false; + for (var i = 0; i < widths.length; i++) { + thisWidth += widths[i]; + if (thisWidth > lineBreakPoint) { + failed = true; + } + if (i % preferredMeasuresPerLine === (preferredMeasuresPerLine-1)) { + if (i !== widths.length-1) // Don't bother putting a line break for the last line - it's already a break. + lineBreaks.push(i); + totals.push(Math.round(thisWidth)); + thisWidth = 0; + } + } + return { failed: failed, totals: totals, lineBreaks: lineBreaks }; +} + +function getRevisedTuneParams(lineBreaks, staffWidth, params) { + + var revisedParams = { + lineBreaks: lineBreaks, + staffwidth: staffWidth + }; + for (var key in params) { + if (params.hasOwnProperty(key) && key !== 'wrap' && key !== 'staffwidth') { + revisedParams[key] = params[key]; + } + } + + return { revisedParams: revisedParams }; +} + +function calcLineWraps(tune, widths, params) { + // For calculating how much can go on the line, it depends on the width of the line. It is a convenience to just divide it here + // by the minimum spacing instead of multiplying the min spacing later. + // The scaling works differently: this is done by changing the scaling of the outer SVG, so the scaling needs to be compensated + // for here, because the actual width will be different from the calculated numbers. + + // If the desired width is less than the margin, just punt and return the original tune + //console.log(widths) + if (widths.length === 0 || params.staffwidth < widths[0].left) { + return { + reParse: false, + explanation: "Staff width is narrower than the margin", + revisedParams: params + }; + } + var scale = params.scale ? Math.max(params.scale, 0.1) : 1; + var minSpacing = params.wrap.minSpacing ? Math.max(parseFloat(params.wrap.minSpacing), 1) : 1; + var minSpacingLimit = params.wrap.minSpacingLimit ? Math.max(parseFloat(params.wrap.minSpacingLimit), 1) : minSpacing - 0.1; + var maxSpacing = params.wrap.maxSpacing ? Math.max(parseFloat(params.wrap.maxSpacing), 1) : undefined; + if (params.wrap.lastLineLimit && !maxSpacing) + maxSpacing = Math.max(parseFloat(params.wrap.lastLineLimit), 1); + // var targetHeight = params.wrap.targetHeight ? Math.max(parseInt(params.wrap.targetHeight, 10), 100) : undefined; + var preferredMeasuresPerLine = params.wrap.preferredMeasuresPerLine ? Math.max(parseInt(params.wrap.preferredMeasuresPerLine, 10), 0) : undefined; + + var accumulatedLineBreaks = []; + var explanations = []; + for (var s = 0; s < widths.length; s++) { + var section = widths[s]; + var usableWidth = params.staffwidth - section.left; + var lineBreakPoint = usableWidth / minSpacing / scale; + var minLineSize = usableWidth / maxSpacing / scale; + var allowableVariance = usableWidth / minSpacingLimit / scale; + var explanation = { + widths: section, + lineBreakPoint: lineBreakPoint, + minLineSize: minLineSize, + attempts: [], + staffWidth: params.staffwidth, + minWidth: Math.round(allowableVariance) + }; + + // If there is a preferred number of measures per line, test that first. If none of the lines is too long, then we're finished. + var lineBreaks = null; + if (preferredMeasuresPerLine) { + var f = fixedMeasureLineBreaks(section.measureWidths, lineBreakPoint, preferredMeasuresPerLine); + explanation.attempts.push({ + type: "Fixed Measures Per Line", + preferredMeasuresPerLine: preferredMeasuresPerLine, + lineBreaks: f.lineBreaks, + failed: f.failed, + totals: f.totals + }); + if (!f.failed) + lineBreaks = f.lineBreaks; + } + + // If we don't have lineBreaks yet, use the free form method of line breaks. + // This will be called either if Preferred Measures is not used, or if the music is just weird - like a single measure is way too crowded. + if (!lineBreaks) { + var ff = freeFormLineBreaks(section.measureWidths, lineBreakPoint); + explanation.attempts.push({type: "Free Form", lineBreaks: ff.lineBreaks, totals: ff.totals}); + lineBreaks = ff.lineBreaks; + + // We now have an acceptable number of lines, but the measures may not be optimally distributed. See if there is a better distribution. + if (lineBreaks.length > 0 && section.measureWidths.length < 25) { + // Only do this if everything doesn't fit on one line. + // This is an intensive operation and it is optional so just do it for shorter music. + ff = optimizeLineWidths(section, lineBreakPoint, lineBreaks, explanation); + explanation.attempts.push({ + type: "Optimize", + failed: ff.failed, + reason: ff.reason, + lineBreaks: ff.lineBreaks, + totals: ff.totals + }); + if (!ff.failed) + lineBreaks = ff.lineBreaks; + } + } + accumulatedLineBreaks.push(lineBreaks); + explanations.push(explanation); + } + // If the vertical space exceeds targetHeight, remove a line and try again. If that is too crowded, then don't use it. + var staffWidth = params.staffwidth; + var ret = getRevisedTuneParams(accumulatedLineBreaks, staffWidth, params); + ret.explanation = explanations; + ret.reParse = true; + return ret; +} + +module.exports = { wrapLines: wrapLines, calcLineWraps: calcLineWraps }; diff --git a/src/plugin/greasemonkey.js b/src/plugin/greasemonkey.js new file mode 100644 index 0000000000000000000000000000000000000000..78ef31d755e94ff527587b09772873402b667749 --- /dev/null +++ b/src/plugin/greasemonkey.js @@ -0,0 +1,16 @@ +// ==UserScript== +// @name abcjs +// @namespace http://code.google.com/p/abcjs +// @description This searches any page you load for ABC-formatted music and inserts the standard notation for it. +// ==/UserScript== + +// Because the js files are concatenated, these variables will be visible to the ones in abcjs_plugin user script. +// However, this file is not included in the regular abcjs_plugin, so that won't be affected. +var abcjs_is_user_script = true; +var scripts = document.getElementsByTagName('script'); +var abcjs_plugin_autostart = true; +for (var i = 0; i < scripts.length; i++) { + var src = scripts[i].src; + if (src.indexOf('abcjs') > 0) + abcjs_plugin_autostart = false; +} diff --git a/src/str/output.js b/src/str/output.js new file mode 100644 index 0000000000000000000000000000000000000000..ce3e929e5dc9b6dcc69351c2075d29ad43773da7 --- /dev/null +++ b/src/str/output.js @@ -0,0 +1,433 @@ +var keyAccidentals = require("../const/key-accidentals"); +var { relativeMajor, transposeKey, relativeMode } = require("../const/relative-major"); +var transposeChordName = require("../parse/transpose-chord") + +var strTranspose; + +(function () { + "use strict"; + strTranspose = function (abc, abcTune, steps) { + if (abcTune === "TEST") // Backdoor way to get entry points for unit tests + return { keyAccidentals: keyAccidentals, relativeMajor: relativeMajor, transposeKey: transposeKey, relativeMode: relativeMode, transposeChordName: transposeChordName} + steps = parseInt(steps, 10) + var changes = []; + var i; + for (i = 0; i < abcTune.length; i++) + changes = changes.concat(transposeOneTune(abc, abcTune[i], steps)) + + // Reverse sort so that we are replacing strings from the end to the beginning so that the indexes aren't invalidated as we go. + // (Because voices can be written in different ways we can't count on the notes being encountered in the order they appear in the string.) + changes = changes.sort(function (a, b) { + return b.start - a.start + }) + var output = abc.split('') + for (i = 0; i < changes.length; i++) { + var ch = changes[i] + output.splice(ch.start, ch.end - ch.start, ch.note) + } + return output.join('') + } + + function transposeOneTune(abc, abcTune, steps) { + var changes = [] + + // Don't transpose bagpipe music - that is a special case and is always a particular key + var key = abcTune.getKeySignature() + if (key.root === 'Hp' || key.root === "HP") + return changes; + + changes = changes.concat(changeAllKeySigs(abc, steps)) + + for (var i = 0; i < abcTune.lines.length; i++) { + var staves = abcTune.lines[i].staff + if (staves) { + for (var j = 0; j < staves.length; j++) { + var staff = staves[j] + if (staff.clef.type !== "perc") + changes = changes.concat(transposeVoices(abc, staff.voices, staff.key, steps)) + } + } + } + return changes + } + + function changeAllKeySigs(abc, steps) { + var changes = []; + var arr = abc.split("K:") + // now each line except the first one will start with whatever is right after "K:" + var count = arr[0].length + for (var i = 1; i < arr.length; i++) { + var segment = arr[i] + var match = segment.match(/^( *)([A-G])([#b]?)(\w*)/) + if (match) { + var start = count + 2 + match[1].length // move past the 'K:' and optional white space + var key = match[2] + match[3] + match[4] // key name, accidental, and mode + var destinationKey = newKey({ root: match[2], acc: match[3], mode: match[4] }, steps) + var dest = destinationKey.root + destinationKey.acc + destinationKey.mode + changes.push({ start: start, end: start + key.length, note: dest }) + } + count += segment.length + 2 + } + return changes + } + + function transposeVoices(abc, voices, key, steps) { + var changes = []; + var destinationKey = newKey(key, steps) + for (var i = 0; i < voices.length; i++) { + changes = changes.concat(transposeVoice(abc, voices[i], key.root, createKeyAccidentals(key), destinationKey, steps)) + } + return changes + } + + function createKeyAccidentals(key) { + var ret = {} + for (var i = 0; i < key.accidentals.length; i++) { + var acc = key.accidentals[i]; + if (acc.acc === 'flat') + ret[acc.note.toUpperCase()] = '_' + else if (acc.acc === 'sharp') + ret[acc.note.toUpperCase()] = '^' + } + return ret + } + + function setLetterDistance(destinationKey, keyRoot, steps) { + var letterDistance = letters.indexOf(destinationKey.root) - letters.indexOf(keyRoot) + if (keyRoot === "none") + letterDistance = letters.indexOf(destinationKey.root) + if (letterDistance === 0) { + // This could either be a half step (like Eb => E) or almost an octave (like E => Eb) + if (steps > 2) // If it is a large leap, then we are going up an octave + letterDistance += 7 + else if (steps === -12) // If it is a large leap, then we are going down an octave + letterDistance -= 7 + } else if (steps > 0 && letterDistance < 0) + letterDistance += 7 + else if (steps < 0 && letterDistance > 0) + letterDistance -= 7 + + if (steps > 12) + letterDistance += 7 + else if (steps < -12) + letterDistance -= 7 + + return letterDistance + } + + function transposeVoice(abc, voice, keyRoot, keyAccidentals, destinationKey, steps) { + var changes = [] + var letterDistance = setLetterDistance(destinationKey, keyRoot, steps) + + var measureAccidentals = {} + var transposedMeasureAccidentals = {} + for (var i = 0; i < voice.length; i++) { + var el = voice[i]; + if (el.chord) { + for (var c = 0; c < el.chord.length; c++) { + var ch = el.chord[c] + if (ch.position === 'default') { + var prefersFlats = destinationKey.accidentals.length && destinationKey.accidentals[0].acc === 'flat' + var newChord = transposeChordName(ch.name, steps, prefersFlats, true) + newChord = newChord.replace(/♭/g, "b").replace(/♯/g, "#") + if (newChord !== ch.name) // If we didn't recognize the chord the input is returned unchanged and there is nothing to replace + changes.push(replaceChord(abc, el.startChar, el.endChar, newChord)) + } + } + } + if (el.el_type === 'note' && el.pitches) { + for (var j = 0; j < el.pitches.length; j++) { + var note = parseNote(el.pitches[j].name, keyRoot, keyAccidentals, measureAccidentals) + if (note.acc) + measureAccidentals[note.name.toUpperCase()] = note.acc + var newPitch = transposePitch(note, destinationKey, letterDistance, transposedMeasureAccidentals) + if (newPitch.acc) + transposedMeasureAccidentals[newPitch.upper] = newPitch.acc + changes.push(replaceNote(abc, el.startChar, el.endChar, newPitch.acc + newPitch.name, j)) + } + if (el.gracenotes) { + for (var g = 0; g < el.gracenotes.length; g++) { + var grace = parseNote(el.gracenotes[g].name, keyRoot, keyAccidentals, measureAccidentals) + if (grace.acc) + measureAccidentals[grace.name.toUpperCase()] = grace.acc + var newGrace = transposePitch(grace, destinationKey, letterDistance, measureAccidentals) + if (newGrace.acc) + transposedMeasureAccidentals[newGrace.upper] = newGrace.acc + changes.push(replaceGrace(abc, el.startChar, el.endChar, newGrace.acc + newGrace.name, g)) + } + } + } else if (el.el_type === "bar") { + measureAccidentals = {} + transposedMeasureAccidentals = {} + } else if (el.el_type === "keySignature") { + keyRoot = el.root + keyAccidentals = createKeyAccidentals(el) + destinationKey = newKey(el, steps) + letterDistance = setLetterDistance(destinationKey, keyRoot, steps) + } + } + return changes + } + + var letters = "CDEFGAB" + var octaves = [",,,,", ",,,", ",,", ",", "", "'", "''", "'''", "''''"] + + function newKey(key, steps) { + if (key.root === "none") { + return { root: transposeKey("C", steps), mode: "", acc: "", accidentals: [] } + } + var major = relativeMajor(key.root + key.acc + key.mode) + var newMajor = transposeKey(major, steps) + var newMode = relativeMode(newMajor, key.mode) + var acc = keyAccidentals(newMajor) + return { root: newMode[0], mode: key.mode, acc: newMode.length > 1 ? newMode[1] : '', accidentals: acc } + } + + function transposePitch(note, key, letterDistance, measureAccidentals) { + // Depending on what the current note and new note are, the octave might have changed + // The letterDistance is how far the change is to see if we passed "C" when transposing. + + var pitch = note.pitch + var origDistFromC = letters.indexOf(note.name) + var root = letters.indexOf(key.root) + var index = (root + pitch) % 7 + // if the note crosses "c" then the octave changes, so that is true of "B" when going up one step, "A" and "B" when going up two steps, etc., and reverse when going down. + var newDistFromC = origDistFromC + letterDistance + var oct = note.oct + while (newDistFromC > 6) { + oct++ + newDistFromC -= 7 + } + while (newDistFromC < 0) { + oct-- + newDistFromC += 7 + } + + var name = letters[index] + + var acc = ''; + var adj = note.adj + // the amount of adjustment depends on the key - if there is a sharp in the key sig, then -1 is a natural, if there isn't, then -1 is a flat. + var keyAcc = '='; + for (var i = 0; i < key.accidentals.length; i++) { + if (key.accidentals[i].note.toLowerCase() === name.toLowerCase()) { + adj = adj + (key.accidentals[i].acc === 'flat' ? -1 : 1) + keyAcc = (key.accidentals[i].acc === 'flat' ? '_' : '^') + break; + } + } + switch (adj) { + case -2: acc = "__"; break; + case -1: acc = "_"; break; + case 0: acc = "="; break; + case 1: acc = "^"; break; + case 2: acc = "^^"; break; + case -3: + // This requires a triple flat, so bump down the pitch and try again + var newNote = {} + newNote.pitch = note.pitch - 1 + newNote.oct = note.oct + newNote.name = letters[letters.indexOf(note.name) - 1] + if (!newNote.name) { + newNote.name = "B" + newNote.oct-- + } + if (newNote.name === "B" || newNote.name === "E") + newNote.adj = note.adj + 1; + else + newNote.adj = note.adj + 2; + return transposePitch(newNote, key, letterDistance + 1, measureAccidentals) + case 3: + // This requires a triple sharp, so bump up the pitch and try again + var newNote = {} + newNote.pitch = note.pitch + 1 + newNote.oct = note.oct + newNote.name = letters[letters.indexOf(note.name) + 1] + if (!newNote.name) { + newNote.name = "C" + newNote.oct++ + } + if (newNote.name === "C" || newNote.name === "F") + newNote.adj = note.adj - 1; + else + newNote.adj = note.adj - 2; + return transposePitch(newNote, key, letterDistance + 1, measureAccidentals) + } + if ((measureAccidentals[name] === acc || (!measureAccidentals[name] && acc === keyAcc)) && !note.courtesy) + acc = "" + + switch (oct) { + case 0: name = name + ",,,"; break; + case 1: name = name + ",,"; break; + case 2: name = name + ","; break; + // case 3: it is already correct + case 4: name = name.toLowerCase(); break; + case 5: name = name.toLowerCase() + "'"; break; + case 6: name = name.toLowerCase() + "''"; break; + case 7: name = name.toLowerCase() + "'''"; break; + case 8: name = name.toLowerCase() + "''''"; break; + } + if (oct > 4) + name = name.toLowerCase(); + + return { acc: acc, name: name, upper: name.toUpperCase() } + } + + var regPitch = /([_^=]*)([A-Ga-g])([,']*)/ + var regNote = /([_^=]*[A-Ga-g][,']*)(\d*\/*\d*)([\>\<\-\)\.\s\\]*)/ + var regOptionalNote = /([_^=]*[A-Ga-g][,']*)?(\d*\/*\d*)?([\>\<\-\)]*)?/ + var regSpace = /(\s*)$/ + + // This the relationship of the note to the tonic and an octave. So what is returned is a distance in steps from the tonic and the amount of adjustment from + // a normal scale. That is - in the key of D an F# is two steps from the tonic and no adjustment. A G# is three steps from the tonic and one half-step higher. + // I don't think there is any adjustment needed for minor keys since the adjustment is based on the key signature and the accidentals. + function parseNote(note, keyRoot, keyAccidentals, measureAccidentals) { + var root = keyRoot === "none" ? 0 : letters.indexOf(keyRoot) + var reg = note.match(regPitch) + // reg[1] : "__", "_", "", "=", "^", or "^^" + // reg[2] : A-G a-g + // reg[3] : commas or apostrophes + var name = reg[2].toUpperCase() + var pos = letters.indexOf(name) - root; + if (pos < 0) pos += 7 + var oct = octaves.indexOf(reg[3]) + if (name === reg[2]) // See if it is a capital letter and subtract an octave if so. + oct--; + var currentAcc = measureAccidentals[name] || keyAccidentals[name] || "=" // use the key accidentals if they exist, but override with the measure accidentals, and if neither of them exist, use a natural. + return { acc: reg[1], name: name, pitch: pos, oct: oct, adj: calcAdjustment(reg[1], keyAccidentals[name], measureAccidentals[name]), courtesy: reg[1] === currentAcc } + } + + function replaceNote(abc, start, end, newPitch, index) { + // There may be more than just the note between the start and end - there could be spaces, there could be a chord symbol, there could be a decoration. + // This could also be a part of a chord. If so, then the particular note needs to be teased out. + var note = abc.substring(start, end) + var match = note.match(new RegExp(regNote.source + regSpace.source), '') + if (match) { + // This will match a single note + var noteLen = match[1].length + var trailingLen = match[2].length + match[3].length + match[4].length + var leadingLen = end - start - noteLen - trailingLen + start += leadingLen + end -= trailingLen + } else { + // I don't know how to capture more than one note, so I'm separating them. There is a limit of the number of notes in a chord depending on the repeats I have here, but it is unlikely to happen in real music. + var regPreBracket = /([^\[]*)/ + var regOpenBracket = /\[/ + var regCloseBracket = /\-?](\d*\/*\d*)?([\>\<\-\)]*)/ + match = note.match(new RegExp(regPreBracket.source + regOpenBracket.source + regOptionalNote.source + + regOptionalNote.source + regOptionalNote.source + regOptionalNote.source + + regOptionalNote.source + regOptionalNote.source + regOptionalNote.source + + regOptionalNote.source + regCloseBracket.source + regSpace.source)) + + if (match) { + // This will match a chord + // Get the number of chars used by the previous notes in this chord + var count = 1 + match[1].length // one character for the open bracket + for (var i = 0; i < index; i++) { // index is the iteration through the chord. This function gets called for each one. + if (match[i * 3 + 2]) + count += match[i * 3 + 2].length + if (match[i * 3 + 3]) + count += match[i * 3 + 3].length + if (match[i * 3 + 4]) + count += match[i * 3 + 4].length + } + start += count + var endLen = match[index * 3 + 2] ? match[index * 3 + 2].length : 0 + // endLen += match[index * 3 + 3] ? match[index * 3 + 3].length : 0 + // endLen += match[index * 3 + 4] ? match[index * 3 + 4].length : 0 + + end = start + endLen + } + } + return { start: start, end: end, note: newPitch } + } + + function replaceGrace(abc, start, end, newGrace, index) { + var note = abc.substring(start, end) + // I don't know how to capture more than one note, so I'm separating them. There is a limit of the number of notes in a chord depending on the repeats I have here, but it is unlikely to happen in real music. + var regOpenBrace = /\{/ + var regCloseBrace = /\}/ + var regPreBrace = /([^\{]*)/ + var regPreNote = /(\/*)/ + var match = note.match(new RegExp(regPreBrace.source + regOpenBrace.source + regPreNote.source + regOptionalNote.source + + regPreNote.source + regOptionalNote.source + regPreNote.source + regOptionalNote.source + regPreNote.source + regOptionalNote.source + + regPreNote.source + regOptionalNote.source + regPreNote.source + regOptionalNote.source + regPreNote.source + regOptionalNote.source + + regPreNote.source + regOptionalNote.source + regCloseBrace.source)) + if (match) { + // This will match all notes inside a grace symbol + // Get the number of chars used by the previous graces + var count = 1 + match[1].length // one character for the open brace, and whatever comes before the brace + for (var i = 0; i < index; i++) { // index is the iteration through the chord. This function gets called for each one. + if (match[i * 3 + 2]) + count += match[i * 3 + 2].length + if (match[i * 3 + 3]) + count += match[i * 3 + 3].length + if (match[i * 3 + 4]) + count += match[i * 3 + 4].length + if (match[i * 3 + 5]) + count += match[i * 3 + 5].length + } + if (match[index * 3 + 2]) + count += match[i * 3 + 2].length + start += count + var endLen = match[index * 3 + 3] ? match[index * 3 + 3].length : 0 + endLen += match[index * 3 + 4] ? match[index * 3 + 4].length : 0 + endLen += match[index * 3 + 5] ? match[index * 3 + 5].length : 0 + + end = start + endLen + } + return { start: start, end: end, note: newGrace } + } + + function replaceChord(abc, start, end, newChord) { + // Isolate the chord and just replace that + var match = abc.substring(start, end).match(/([^"]+)?(".+")+/) + if (match[1]) + start += match[1].length + end = start + match[2].length + // leave the quote in, so skip one more + return { start: start + 1, end: end - 1, note: newChord } + } + + function calcAdjustment(thisAccidental, keyAccidental, measureAccidental) { + if (!thisAccidental && measureAccidental) { + // There was no accidental on this note, but there was earlier in the measure, so we'll use that + thisAccidental = measureAccidental + } + if (!thisAccidental) + return 0; // there is no deviation from the key. + + switch (keyAccidental) { + case undefined: + switch (thisAccidental) { + case '__': return -2; + case '_': return -1; + case '=': return 0; + case '^': return 1; + case '^^': return 2; + default: return 0; // this should never happen + } + case '_': + switch (thisAccidental) { + case '__': return -1; + case '_': return 0; + case '=': return 1; + case '^': return 2; + case '^^': return 3; + default: return 0; // this should never happen + } + case '^': + switch (thisAccidental) { + case '__': return -3; + case '_': return -2; + case '=': return -1; + case '^': return 0; + case '^^': return 1; + default: return 0; // this should never happen + } + } + return 0// this should never happen + } +})(); + +module.exports = strTranspose; diff --git a/src/synth/abc_midi_flattener.js b/src/synth/abc_midi_flattener.js new file mode 100644 index 0000000000000000000000000000000000000000..651c09cb85a14722a2cff6ae52a42750e1833dd9 --- /dev/null +++ b/src/synth/abc_midi_flattener.js @@ -0,0 +1,888 @@ +// abc_midi_flattener.js: Turn a linear series of events into a series of MIDI commands. + +// We input a set of voices, but the notes are still complex. This pass changes the logical definitions +// of the grace notes, decorations, ties, triplets, rests, transpositions, keys, and accidentals into actual note durations. +// It also extracts guitar chords to a separate voice and resolves their rhythm. + +var flatten; +var ChordTrack = require("./chord-track"); +var pitchesToPerc = require('./pitches-to-perc'); + +(function() { + "use strict"; + + var barAccidentals; + var accidentals; + var transpose; + var bagpipes; + var tracks; + var startingTempo; + var startingMeter; + var tempoChangeFactor = 1; + var instrument; + var currentInstrument; + // var channel; + var currentTrack; + var lastNoteDurationPosition; + var currentTrackName; + var lastEventTime; + var chordTrack; + + var meter = { num: 4, den: 4 }; + var drumInstrument = 128; + var lastBarTime; + var doBeatAccents = true; + var stressBeat1 = 105; + var stressBeatDown = 95; + var stressBeatUp = 85; + var beatFraction = 0.25; + var nextVolume; + var nextVolumeDelta; + var slurCount = 0; + + var drumTrack; + var drumTrackFinished; + var drumDefinition = {}; + var drumBars; + + var pickupLength = 0; + var percmap; + + // The gaps per beat. The first two are in seconds, the third is in fraction of a duration. + var normalBreakBetweenNotes = 0; //0.000520833333325*1.5; // for articulation (matches muse score value) + var slurredBreakBetweenNotes = -0.001; // make the slurred notes actually overlap + var staccatoBreakBetweenNotes = 0.4; // some people say staccato is half duration, some say 3/4 so this splits it + + flatten = function(voices, options, percmap_, midiOptions) { + if (!options) options = {}; + if (!midiOptions) midiOptions = {}; + barAccidentals = []; + accidentals = [0,0,0,0,0,0,0]; + bagpipes = false; + tracks = []; + startingTempo = options.qpm; + startingMeter = undefined; + tempoChangeFactor = 1; + instrument = undefined; + currentInstrument = undefined; + // channel = undefined; + currentTrack = undefined; + currentTrackName = undefined; + lastEventTime = 0; + percmap = percmap_; + + meter = { num: 4, den: 4 }; + + doBeatAccents = true; + stressBeat1 = 105; + stressBeatDown = 95; + stressBeatUp = 85; + beatFraction = 0.25; + nextVolume = undefined; + nextVolumeDelta = undefined; + slurCount = 0; + + // For the drum/metronome track. + drumTrack = []; + drumTrackFinished = false; + drumDefinition = {}; + drumBars = 1; + + if (voices.length > 0 && voices[0].length > 0) + pickupLength = voices[0][0].pickupLength; + + // For resolving chords. + if (options.bassprog !== undefined && !midiOptions.bassprog) + midiOptions.bassprog = [options.bassprog] + if (options.bassvol !== undefined && !midiOptions.bassvol) + midiOptions.bassvol = [options.bassvol] + if (options.chordprog !== undefined && !midiOptions.chordprog) + midiOptions.chordprog = [options.chordprog] + if (options.chordvol !== undefined && !midiOptions.chordvol) + midiOptions.chordvol = [options.chordvol] + if (options.gchord !== undefined && !midiOptions.gchord) + midiOptions.gchord = [options.gchord] + chordTrack = new ChordTrack(voices.length, options.chordsOff, midiOptions, meter) + + // First adjust the input to resolve ties, set the starting time for each note, etc. That will make the rest of the logic easier + preProcess(voices, options); + + for (var i = 0; i < voices.length; i++) { + transpose = 0; + chordTrack.setTranspose(transpose) + lastNoteDurationPosition = -1; + var voice = voices[i]; + currentTrack = [{ cmd: 'program', channel: i, instrument: instrument }]; + currentTrackName = undefined; + lastBarTime = 0; + chordTrack.setLastBarTime(0) + var voiceOff = false; + if (options.voicesOff === true) + voiceOff = true; + else if (options.voicesOff && options.voicesOff.length && options.voicesOff.indexOf(i) >= 0) + voiceOff = true; + for (var j = 0; j < voice.length; j++) { + var element = voice[j]; + switch (element.el_type) { + case "name": + currentTrackName = {cmd: 'text', type: "name", text: element.trackName }; + break; + case "note": + writeNote(element, voiceOff); + break; + case "key": + accidentals = setKeySignature(element); + break; + case "meter": + if (!startingMeter) + startingMeter = element; + meter = element; + chordTrack.setMeter(meter) + beatFraction = getBeatFraction(meter); + alignDrumToMeter(); + break; + case "tempo": + if (!startingTempo) + startingTempo = element.qpm; + else + tempoChangeFactor = element.qpm ? startingTempo / element.qpm : 1; + chordTrack.setTempoChangeFactor(tempoChangeFactor) + break; + case "transpose": + transpose = element.transpose; + chordTrack.setTranspose(transpose) + break; + case "bar": + chordTrack.barEnd(element) + + barAccidentals = []; + if (i === 0) // Only write the drum part on the first voice so that it is not duplicated. + writeDrum(voices.length+1); + chordTrack.setRhythmHead(false) // decide whether there are rhythm heads each measure. + lastBarTime = timeToRealTime(element.time); + chordTrack.setLastBarTime(lastBarTime) + break; + case "bagpipes": + bagpipes = true; + break; + case "instrument": + if (instrument === undefined) + instrument = element.program; + currentInstrument = element.program; + if (currentTrack.length > 0 && currentTrack[currentTrack.length-1].cmd === 'program') + currentTrack[currentTrack.length-1].instrument = element.program; + else { + var ii; + for (ii = currentTrack.length-1; ii >= 0 && currentTrack[ii].cmd !== 'program'; ii--) + ; + if (ii < 0 || currentTrack[ii].instrument !== element.program) + currentTrack.push({cmd: 'program', channel: 0, instrument: element.program}); + } + break; + case "channel": + setChannel(element.channel); + break; + case "drum": + drumDefinition = normalizeDrumDefinition(element.params); + alignDrumToMeter(); + break; + case "gchordOn": + chordTrack.gChordOn(element) + break; + case "beat": + stressBeat1 = element.beats[0]; + stressBeatDown = element.beats[1]; + stressBeatUp = element.beats[2]; + // TODO-PER: also use the last parameter - which changes which beats are strong. + break; + case "vol": + nextVolume = element.volume; + break; + case "volinc": + nextVolumeDelta = element.volume; + break; + case "beataccents": + doBeatAccents = element.value; + break; + case "gchord": + case "bassprog": + case "chordprog": + case "bassvol": + case "chordvol": + case "gchordbars": + chordTrack.paramChange(element) + break + default: + // This should never happen + console.log("MIDI creation. Unknown el_type: " + element.el_type + "\n");// jshint ignore:line + break; + } + } + if (currentTrack[0].instrument === undefined) + currentTrack[0].instrument = instrument ? instrument : 0; + if (currentTrackName) + currentTrack.unshift(currentTrackName); + tracks.push(currentTrack); + chordTrack.finish() + if (drumTrack.length > 0) // Don't do drums on more than one track, so turn off drum after we create it. + drumTrackFinished = true; + } + // See if any notes are octaves played at the same time. If so, raise the pitch of the higher one. + if (options.detuneOctave) + findOctaves(tracks, parseInt(options.detuneOctave, 10)); + + chordTrack.addTrack(tracks) + if (drumTrack.length > 0) + tracks.push(drumTrack); + + return { tempo: startingTempo, instrument: instrument, tracks: tracks, totalDuration: lastEventTime }; + }; + + function setChannel(channel) { + for (var i = currentTrack.length-1; i>=0; i--) { + if (currentTrack[i].cmd === "program") { + currentTrack[i].channel = channel; + return; + } + } + } + + function timeToRealTime(time) { + return time/1000000; + } + + function durationRounded(duration) { + return Math.round(duration*tempoChangeFactor*1000000)/1000000; + } + + function preProcess(voices, options) { + for (var i = 0; i < voices.length; i++) { + var voice = voices[i]; + var ties = {}; + var startingTempo = options.qpm; + var timeCounter = 0; + var tempoMultiplier = 1; + for (var j = 0; j < voice.length; j++) { + var element = voice[j]; + + if (element.el_type === 'tempo') { + if (!startingTempo) + startingTempo = element.qpm; + else + tempoMultiplier = element.qpm ? startingTempo / element.qpm : 1; + continue; + } + + // For convenience, put the current time in each event so that it doesn't have to be calculated in the complicated stuff that follows. + element.time = timeCounter; + var thisDuration = element.duration ? element.duration : 0; + timeCounter += Math.round(thisDuration*tempoMultiplier*1000000); // To compensate for JS rounding problems, do all intermediate calcs on integers. + + // If there are pitches then put the duration in the pitch object and if there are ties then change the duration of the first note in the tie. + if (element.pitches) { + for (var k = 0; k < element.pitches.length; k++) { + var pitch = element.pitches[k]; + if (pitch) { + pitch.duration = element.duration; + if (pitch.startTie) { + //console.log(element) + if (ties[pitch.pitch] === undefined) // We might have three notes tied together - if so just add this duration. + ties[pitch.pitch] = {el: j, pitch: k}; + else { + voice[ties[pitch.pitch].el].pitches[ties[pitch.pitch].pitch].duration += pitch.duration; + element.pitches[k] = null; + } + //console.log(">>> START", JSON.stringify(ties)); + } else if (pitch.endTie) { + //console.log(element) + var tie = ties[pitch.pitch]; + //console.log(">>> END", pitch.pitch, tie, JSON.stringify(ties)); + if (tie) { + var dur = pitch.duration; + delete voice[tie.el].pitches[tie.pitch].startTie; + voice[tie.el].pitches[tie.pitch].duration += dur; + element.pitches[k] = null; + delete ties[pitch.pitch]; + } else { + delete pitch.endTie; + } + } + } + } + delete element.duration; + } + } + for (var key in ties) { + if (ties.hasOwnProperty(key)) { + var item = ties[key]; + delete voice[item.el].pitches[item.pitch].startTie; + } + } + // voices[0].forEach(v => delete v.elem) + // voices[1].forEach(v => delete v.elem) + // console.log(JSON.stringify(voices)) + } + } + + function getBeatFraction(meter) { + switch (parseInt(meter.den,10)) { + case 2: return 0.5; + case 4: return 0.25; + case 8: + if (meter.num % 3 === 0) + return 0.375; + else + return 0.125; + case 16: return 0.125; + } + return 0.25; + } + + function calcBeat(measureStart, beatLength, currTime) { + var distanceFromStart = currTime - measureStart; + return distanceFromStart / beatLength; + } + + function processVolume(beat, voiceOff) { + if (voiceOff) + return 0; + + var volume; + // MAE 21 Jun 2024 - This previously wasn't allowing zero volume to be applied + if (nextVolume != undefined) { + volume = nextVolume; + nextVolume = undefined; + } else if (!doBeatAccents) { + volume = stressBeatDown; + } else if (pickupLength > beat) { + volume = stressBeatUp; + } else { + //var barLength = meter.num / meter.den; + var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), beat); + if (barBeat === 0) + volume = stressBeat1; + else if (parseInt(barBeat,10) === barBeat) + volume = stressBeatDown; + else + volume = stressBeatUp; + } + if (nextVolumeDelta) { + volume += nextVolumeDelta; + nextVolumeDelta = undefined; + } + if (volume < 0) + volume = 0; + if (volume > 127) + volume = 127; + return voiceOff ? 0 : volume; + } + + + function findNoteModifications(elem, velocity) { + var ret = { }; + if (elem.decoration) { + for (var d = 0; d < elem.decoration.length; d++) { + if (elem.decoration[d] === 'staccato') + ret.thisBreakBetweenNotes = 'staccato'; + else if (elem.decoration[d] === 'tenuto') + ret.thisBreakBetweenNotes = 'tenuto'; + else if (elem.decoration[d] === 'accent') + ret.velocity = Math.min(127, velocity * 1.5); + else if (elem.decoration[d] === 'trill') + ret.noteModification = "trill"; + else if (elem.decoration[d] === 'lowermordent') + ret.noteModification = "lowermordent"; + else if (elem.decoration[d] === 'uppermordent') + ret.noteModification = "mordent"; + else if (elem.decoration[d] === 'mordent') + ret.noteModification = "mordent"; + else if (elem.decoration[d] === 'turn') + ret.noteModification = "turn"; + else if (elem.decoration[d] === 'roll') + ret.noteModification = "roll"; + } + } + return ret; + } + + function doModifiedNotes(noteModification, p) { + var noteTime; + var numNotes; + var start = p.start; + var pp; + var runningDuration = p.duration; + var shortestNote = durationRounded(1.0 / 32); + + switch (noteModification) { + case "trill": + var note = 1; + while (runningDuration > 0) { + currentTrack.push({ cmd: 'note', pitch: p.pitch+note, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + note = (note === 1) ? 0 : 1; + runningDuration -= shortestNote; + start += shortestNote; + } + break; + case "mordent": + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + runningDuration -= shortestNote; + start += shortestNote; + currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + runningDuration -= shortestNote; + start += shortestNote; + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument }); + break; + case "lowermordent": + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + runningDuration -= shortestNote; + start += shortestNote; + currentTrack.push({ cmd: 'note', pitch: p.pitch-1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + runningDuration -= shortestNote; + start += shortestNote; + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument }); + break; + case "turn": + shortestNote = p.duration / 5; + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*2, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote*3, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*4, duration: shortestNote, gap: 0, instrument: currentInstrument }); + break; + case "roll": + while (runningDuration > 0) { + currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' }); + runningDuration -= shortestNote*2; + start += shortestNote*2; + } + break; + } + } + + function writeNote(elem, voiceOff) { + // + // Create a series of note events to append to the current track. + // The output event is one of: { pitchStart: pitch_in_abc_units, volume: from_1_to_64 } + // { pitchStop: pitch_in_abc_units } + // { moveTime: duration_in_abc_units } + // If there are guitar chords, then they are put in a separate track, but they have the same format. + // + + //var trackStartingIndex = currentTrack.length; + + var velocity = processVolume(timeToRealTime(elem.time), voiceOff); + chordTrack.processChord(elem) + + // if there are grace notes, then also play them. + // I'm not sure there is an exact rule for the length of the notes. My rule, unless I find + // a better one is: the grace notes cannot take more than 1/2 of the main note's value. + // A grace note (of 1/8 note duration) takes 1/8 of the main note's value. + var graces; + if (elem.gracenotes && elem.pitches && elem.pitches.length > 0 && elem.pitches[0]) { + graces = processGraceNotes(elem.gracenotes, elem.pitches[0].duration); + if (elem.elem) + elem.elem.midiGraceNotePitches = writeGraceNotes(graces, timeToRealTime(elem.time), velocity*2/3, currentInstrument); // make the graces a little quieter. + } + + // The beat fraction is the note that gets a beat (.25 is a quarter note) + // The tempo is in minutes and we want to get to milliseconds. + // If the element is inside a repeat, there may be more than one value. If there is one value, + // then just store that as a number. If there are more than one value, then change that to + // an array and return all of them. + if (elem.elem) { + var rt = timeToRealTime(elem.time); + var ms = rt / beatFraction / startingTempo * 60 * 1000; + if (elem.elem.currentTrackMilliseconds === undefined) { + elem.elem.currentTrackMilliseconds = ms; + elem.elem.currentTrackWholeNotes = rt; + } else { + if (elem.elem.currentTrackMilliseconds.length === undefined) { + if (elem.elem.currentTrackMilliseconds !== ms) { + elem.elem.currentTrackMilliseconds = [elem.elem.currentTrackMilliseconds, ms]; + elem.elem.currentTrackWholeNotes = [elem.elem.currentTrackWholeNotes, rt]; + } + } else { + // There can be duplicates if there are multiple voices + var found = false; + for (var j = 0; j < elem.elem.currentTrackMilliseconds.length; j++) { + if (elem.elem.currentTrackMilliseconds[j] === ms) + found = true; + } + if (!found) { + elem.elem.currentTrackMilliseconds.push(ms); + elem.elem.currentTrackWholeNotes.push(rt); + } + } + } + } + //var tieAdjustment = 0; + if (elem.pitches) { + var thisBreakBetweenNotes = ''; + var ret = findNoteModifications(elem, velocity); + if (ret.thisBreakBetweenNotes) + thisBreakBetweenNotes = ret.thisBreakBetweenNotes; + if (ret.velocity) + velocity = ret.velocity; + + // TODO-PER: Can also make a different sound on style=x and style=harmonic + var ePitches = elem.pitches; + if (elem.style === "rhythm") { + ePitches = chordTrack.setRhythmHead(true, elem) + } + + if (elem.elem) + elem.elem.midiPitches = []; + for (var i=0; i 0) + p.endType = 'tenuto'; + else if (thisBreakBetweenNotes) + p.endType = thisBreakBetweenNotes; + + switch (p.endType) { + case "tenuto": + p.gap = slurredBreakBetweenNotes; + break; + case "staccato": + var d = p.duration * staccatoBreakBetweenNotes; + p.gap = startingTempo / 60 * d; + break; + default: + p.gap = normalBreakBetweenNotes; + break; + } + currentTrack.push(p); + } + } + lastNoteDurationPosition = currentTrack.length-1; + + } + var realDur = getRealDuration(elem); + lastEventTime = Math.max(lastEventTime, timeToRealTime(elem.time)+durationRounded(realDur)); + } + function getRealDuration(elem) { + if (elem.pitches && elem.pitches.length > 0 && elem.pitches[0]) + return elem.pitches[0].duration; + if (elem.elem) + return elem.elem.duration; + return elem.duration; + } + + var scale = [0,2,4,5,7,9,11]; + function adjustPitch(note) { + if (note.midipitch !== undefined) + return note.midipitch; // The pitch might already be known, for instance if there is a drummap. + var pitch = note.pitch; + if (note.accidental) { + switch(note.accidental) { // change that pitch (not other octaves) for the rest of the bar + case "sharp": + barAccidentals[pitch]=1; break; + case "flat": + barAccidentals[pitch]=-1; break; + case "natural": + barAccidentals[pitch]=0; break; + case "dblsharp": + barAccidentals[pitch]=2; break; + case "dblflat": + barAccidentals[pitch]=-2; break; + case "quartersharp": + barAccidentals[pitch]=0.25; break; + case "quarterflat": + barAccidentals[pitch]=-0.25; break; + } + } + + var actualPitch = extractOctave(pitch) *12 + scale[extractNote(pitch)] + 60; + + if ( barAccidentals[pitch]!==undefined) { + // An accidental is always taken at face value and supersedes the key signature. + actualPitch += barAccidentals[pitch]; + } else { // use normal accidentals + actualPitch += accidentals[extractNote(pitch)]; + } + actualPitch += transpose; + return actualPitch; + } + + function setKeySignature(elem) { + var accidentals = [0,0,0,0,0,0,0]; + if (!elem.accidentals) return accidentals; + for (var i = 0; i < elem.accidentals.length; i++) { + var acc = elem.accidentals[i]; + var d; + switch (acc.acc) { + case "flat": d = -1; break; + case "quarterflat": d = -0.25; break; + case "sharp": d = 1; break; + case "quartersharp": d = 0.25; break; + default: d = 0; break; + } + + var lowercase = acc.note.toLowerCase(); + var note = extractNote(lowercase.charCodeAt(0)-'c'.charCodeAt(0)); + accidentals[note]+=d; + } + return accidentals; + } + + function processGraceNotes(graces, companionDuration) { + // Grace notes take up half of the note value. So if there are many of them they are all real short. + var graceDuration = 0; + var ret = []; + var grace; + for (var g = 0; g < graces.length; g++) { + grace = graces[g]; + graceDuration += grace.duration; + } + var multiplier = companionDuration/2 / graceDuration; + + for (g = 0; g < graces.length; g++) { + grace = graces[g]; + var actualPitch = adjustPitch(grace); + if (currentInstrument === drumInstrument && percmap) { + var name = pitchesToPerc(grace) + if (name && percmap[name]) + actualPitch = percmap[name].sound; + } + var pitch = { pitch: actualPitch, duration: grace.duration*multiplier }; + pitch = adjustForMicroTone(pitch); + ret.push(pitch); + } + return ret; + } + + function writeGraceNotes(graces, start, velocity, currentInstrument) { + var midiGrace = []; + velocity = Math.round(velocity) + for (var g = 0; g < graces.length; g++) { + var gp = graces[g]; + currentTrack.push({cmd: 'note', pitch: gp.pitch, volume: velocity, start: start, duration: gp.duration, gap: 0, instrument:currentInstrument, style: 'grace'}); + midiGrace.push({ + pitch: gp.pitch, + durationInMeasures: gp.duration, + volume: velocity, + instrument: currentInstrument + }); + start += gp.duration; + } + return midiGrace; + } + + var quarterToneFactor = 0.02930223664349; + function adjustForMicroTone(description) { + // if the pitch is not a whole number then make it a whole number and add a tuning factor + var pitch = ''+description.pitch; + if (pitch.indexOf(".75") >= 0) { + description.pitch = Math.round(description.pitch); + description.cents = -50; + } else if (pitch.indexOf(".25") >= 0) { + description.pitch = Math.round(description.pitch); + description.cents = 50; + } + + return description; + } + + function extractOctave(pitch) { + return Math.floor(pitch/7); + } + + function extractNote(pitch) { + pitch = pitch%7; + if (pitch<0) pitch+=7; + return pitch; + } + + + function normalizeDrumDefinition(params) { + // Be very strict with the drum definition. If anything is not perfect, + // just turn the drums off. + // Perhaps all of this logic belongs in the parser instead. + if (params.pattern.length === 0 || params.on === false) + return { on: false }; + + var str = params.pattern[0]; + var events = []; + var event = ""; + var totalPlay = 0; + for (var i = 0; i < str.length; i++) { + if (str[i] === 'd') + totalPlay++; + if (str[i] === 'd' || str[i] === 'z') { + if (event.length !== 0) { + events.push(event); + event = str[i]; + } else + event = event + str[i]; + } else { + if (event.length === 0) { + // there was an error: the string should have started with d or z + return {on: false}; + } + event = event + str[i]; + } + } + + if (event.length !== 0) + events.push(event); + + // Now the events array should have one item per event. + // There should be two more params for each event: the volume and the pitch. + if (params.pattern.length !== totalPlay*2 + 1) + return { on: false }; + + var ret = { on: true, bars: params.bars, pattern: []}; + var beatLength = getBeatFraction(meter); + var playCount = 0; + for (var j = 0; j < events.length; j++) { + event = events[j]; + var len = 1; + var div = false; + var num = 0; + for (var k = 1; k < event.length; k++) { + switch(event[k]) { + case "/": + if (num !== 0) + len *= num; + num = 0; + div = true; + break; + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + num = num*10 +event[k]; + break; + default: + return { on: false }; + } + } + if (div) { + if (num === 0) num = 2; // a slash by itself is interpreted as "/2" + len /= num; + } else if (num) + len *= num; + if (event[0] === 'd') { + ret.pattern.push({ len: len * beatLength, pitch: params.pattern[1 + playCount], velocity: params.pattern[1 + playCount + totalPlay]}); + playCount++; + } else + ret.pattern.push({ len: len * beatLength, pitch: null}); + } + drumBars = params.bars ? params.bars : 1; + return ret; + } + + function alignDrumToMeter() { + if (!drumDefinition ||!drumDefinition.pattern) { + return; + } + var ret = drumDefinition; + // Now normalize the pattern to cover the correct number of measures. The note lengths passed are relative to each other and need to be scaled to fit a measure. + var totalTime = 0; + var measuresPerBeat = meter.num/meter.den; + for (var ii = 0; ii < ret.pattern.length; ii++) + totalTime += ret.pattern[ii].len; + var factor = totalTime / drumBars / measuresPerBeat; + for (ii = 0; ii < ret.pattern.length; ii++) + ret.pattern[ii].len = ret.pattern[ii].len / factor; + drumDefinition = ret; + } + + function writeDrum(channel) { + if (drumTrack.length === 0 && !drumDefinition.on) + return; + + var measureLen = meter.num/meter.den; + if (drumTrack.length === 0) { + if (lastEventTime < measureLen) + return; // This is true if there are pickup notes. The drum doesn't start until the first full measure. + drumTrack.push({cmd: 'program', channel: channel, instrument: drumInstrument}); + } + + if (!drumDefinition.on) { + // this is the case where there has been a drum track, but it was specifically turned off. + return; + } + var start = lastBarTime; + for (var i = 0; i < drumDefinition.pattern.length; i++) { + var len = durationRounded(drumDefinition.pattern[i].len); + if (drumDefinition.pattern[i].pitch) { + drumTrack.push({ + cmd: 'note', + pitch: drumDefinition.pattern[i].pitch, + volume: drumDefinition.pattern[i].velocity, + start: start, + duration: len, + gap: 0, + instrument: drumInstrument}); + } + start += len; + } + } + + function findOctaves(tracks, detuneCents) { + var timing = {}; + for (var i = 0; i < tracks.length; i++) { + for (var j = 0; j < tracks[i].length; j++) { + var note = tracks[i][j]; + if (note.cmd === "note") { + if (timing[note.start] === undefined) + timing[note.start] = []; + timing[note.start].push({track: i, event: j, pitch: note.pitch}); + } + } + } + var keys = Object.keys(timing); + for (i = 0; i < keys.length; i++) { + var arr = timing[keys[i]]; + if (arr.length > 1) { + arr = arr.sort(function(a,b) { + return a.pitch - b.pitch; + }); + var topEvent = arr[arr.length-1]; + var topNote = topEvent.pitch % 12; + var found = false; + for (j = 0; !found && j < arr.length-1; j++) { + if (arr[j].pitch % 12 === topNote) + found = true; + } + if (found) { + var event = tracks[topEvent.track][topEvent.event]; + if (!event.cents) + event.cents = 0; + event.cents += detuneCents; + } + } + } + } +})(); + +module.exports = flatten; diff --git a/src/synth/abc_midi_renderer.js b/src/synth/abc_midi_renderer.js new file mode 100644 index 0000000000000000000000000000000000000000..e8e8c47a1a274d762eed1105936d85b053dc43de --- /dev/null +++ b/src/synth/abc_midi_renderer.js @@ -0,0 +1,297 @@ +// abc_midi_renderer.js: Create the actual format for the midi. + +var centsToFactor = require("./cents-to-factor"); +var rendererFactory; + +(function() { + "use strict"; + function setAttributes(elm, attrs) { + for (var attr in attrs) + if (attrs.hasOwnProperty(attr)) + elm.setAttribute(attr, attrs[attr]); + return elm; + } + + function Midi() { + this.trackstrings = ""; + this.trackcount = 0; + this.noteOnAndChannel = "%90"; + this.noteOffAndChannel = "%80"; + } + + Midi.prototype.setTempo = function(qpm) { + if (this.trackcount === 0) { + this.startTrack(); + this.track += "%00%FF%51%03" + toHex(Math.round(60000000 / qpm), 6); + this.endTrack(); + } + }; + + Midi.prototype.setGlobalInfo = function(qpm, name, key, time) { + if (this.trackcount === 0) { + this.startTrack(); + var divisions = Math.round(60000000 / qpm); + // Add the tempo + this.track += "%00%FF%51%03" + toHex(divisions, 6); + + if (key) + this.track += keySignature(key); + if (time) + this.track += timeSignature(time); + if (name) { + this.track += encodeString(name, "%01"); + } + this.endTrack(); + } + }; + + Midi.prototype.startTrack = function() { + this.noteWarped = {}; + this.track = ""; + this.trackName = ""; + this.trackInstrument = ""; + this.silencelength = 0; + this.trackcount++; + if (this.instrument) { + this.setInstrument(this.instrument); + } + }; + + Midi.prototype.endTrack = function() { + this.track = this.trackName + this.trackInstrument + this.track; + var tracklength = toHex(this.track.length / 3 + 4, 8); + this.track = "MTrk" + tracklength + // track header + this.track + + '%00%FF%2F%00'; // track end + this.trackstrings += this.track; + }; + + Midi.prototype.setText = function(type, text) { + // MIDI defines the following types of events: + //FF 01 len text Text Event + //FF 02 len text Copyright Notice + //FF 03 len text Sequence/Track Name + //FF 04 len text Instrument Name + //FF 05 len text Lyric + //FF 06 len text Marker + //FF 07 len text Cue Point + switch(type) { + case 'name': + this.trackName = encodeString(text, "%03"); + break; + } + }; + + Midi.prototype.setInstrument = function(number) { + this.trackInstrument = "%00%C0" + toHex(number, 2); + this.instrument = number; + }; + + Midi.prototype.setChannel = function(number, pan) { + this.channel = number; + var ccPrefix = "%00%B" + this.channel.toString(16); + // Reset midi, in case it was set previously. + this.track += ccPrefix + "%79%00"; // Reset All Controllers + this.track += ccPrefix + "%40%00"; // Damper pedal + this.track += ccPrefix + "%5B%30"; // Effect 1 Depth (reverb) + // Translate pan as -1 to 1 to 0 to 127 + if (!pan) + pan = 0; + pan = Math.round((pan + 1) * 64); + this.track += ccPrefix + "%0A" + toHex(pan, 2); // Pan + this.track += ccPrefix + "%07%64"; // Channel Volume + + this.noteOnAndChannel = "%9" + this.channel.toString(16); + this.noteOffAndChannel = "%8" + this.channel.toString(16); + }; + + var HALF_STEP = 4096; // For the pitch wheel - (i.e. the distance from C to C#) + Midi.prototype.startNote = function(pitch, loudness, cents) { + this.track += toDurationHex(this.silencelength); // only need to shift by amount of silence (if there is any) + this.silencelength = 0; + if (cents) { + // the pitch is altered so send a midi pitch wheel event + this.track += "%e" + this.channel.toString(16); + var bend = Math.round(centsToFactor(cents)*HALF_STEP); + this.track += to7BitHex(0x2000 + bend); + this.track += toDurationHex(0); // this all happens at once so there is a zero length here + this.noteWarped[pitch] = true; + } + this.track += this.noteOnAndChannel; + this.track += "%" + pitch.toString(16) + toHex(loudness, 2); //note + }; + + Midi.prototype.endNote = function(pitch) { + this.track += toDurationHex(this.silencelength); // only need to shift by amount of silence (if there is any) + this.silencelength = 0; + if (this.noteWarped[pitch]) { + // the pitch was altered so alter it back. + this.track += "%e" + this.channel.toString(16); + this.track += to7BitHex(0x2000); + this.track += toDurationHex(0); // this all happens at once so there is a zero length here + this.noteWarped[pitch] = false; + } + this.track += this.noteOffAndChannel; + this.track += "%" + pitch.toString(16) + "%00";//end note + }; + + Midi.prototype.addRest = function(length) { + this.silencelength += length; + if (this.silencelength < 0) + this.silencelength = 0; + }; + + Midi.prototype.getData = function() { + return "data:audio/midi," + + "MThd%00%00%00%06%00%01" + toHex(this.trackcount, 4) + "%01%e0" + // header + this.trackstrings; + }; + + Midi.prototype.embed = function(parent, noplayer) { + + var data = this.getData(); + + var link = setAttributes(document.createElement('a'), { + href: data + }); + link.innerHTML = "download midi"; + parent.insertBefore(link, parent.firstChild); + + if (noplayer) return; + + var embed = setAttributes(document.createElement('embed'), { + src: data, + type: 'video/quicktime', + controller: 'true', + autoplay: 'false', + loop: 'false', + enablejavascript: 'true', + style: 'display:block; height: 20px;' + }); + parent.insertBefore(embed, parent.firstChild); + }; + + function encodeString(str, cmdType) { + // If there are multi-byte chars, we don't know how long the string will be until we create it. + var nameArray = ""; + for (var i = 0; i < str.length; i++) + nameArray += toHex(str.charCodeAt(i), 2); + return "%00%FF" + cmdType + toHex(nameArray.length/3, 2) + nameArray; // Each byte is represented by three chars "%XX", so divide by 3 to get the length. + } + + function keySignature(key) { + //00 FF 5902 03 00 - key signature + if (!key || !key.accidentals) + return ""; + var hex = "%00%FF%59%02"; + var sharpCount = 0; + var flatCount = 256; + for (var i = 0; i < key.accidentals.length; i++) { + if (key.accidentals[i].acc === "sharp") sharpCount++; + else if (key.accidentals[i].acc === "flat") flatCount--; + } + var sig = flatCount !== 256 ? toHex(flatCount, 2) : toHex(sharpCount, 2); + var mode = (key.mode === "m") ? "%01" : "%00"; + return hex + sig + mode; + } + + function timeSignature(time) { + //00 FF 58 04 04 02 30 08 - time signature + var hex = "%00%FF%58%04" + toHex(time.num,2); + var dens = { 1: 0, 2: 1, 4: 2, 8: 3, 16: 4, 32: 5 }; + var den = dens[time.den]; + if (!den) + return ""; // the denominator is not supported, so just don't include this. + hex += toHex(den, 2); + + var clocks; + switch (time.num+"/"+time.den) { + case "2/4": + case "3/4": + case "4/4": + case "5/4": + clocks = 24; + break; + case "6/4": + clocks = 72; + break; + case "2/2": + case "3/2": + case "4/2": + clocks = 48; + break; + case "3/8": + case "6/8": + case "9/8": + case "12/8": + clocks = 36; + break; + } + if (!clocks) + return ""; // time sig is not supported. + hex += toHex(clocks, 2); + return hex + "%08"; + } + + // s is assumed to be of even length + function encodeHex(s) { + var ret = ""; + for (var i = 0; i < s.length; i += 2) { + ret += "%"; + ret += s.substr(i, 2); + } + return ret; + } + + function toHex(n, padding) { + var s = n.toString(16); + s = s.split(".")[0]; + while (s.length < padding) { + s = "0" + s; + } + if (s.length > padding) + s = s.substring(0,padding) + return encodeHex(s); + } + + function to7BitHex(n) { + // this takes a number and shifts all digits from the 7th one to the left. + n = Math.round(n); + var lower = n % 128; + var higher = n - lower; + return toHex(higher*2+lower, 4); + } + + function toDurationHex(n) { + var res = 0; + var a = []; + + // cut up into 7 bit chunks; + n = Math.round(n); + while (n !== 0) { + a.push(n & 0x7F); + n = n >> 7; + } + + // join the 7 bit chunks together, all but last chunk get leading 1 + for (var i = a.length - 1; i >= 0; i--) { + res = res << 8; + var bits = a[i]; + if (i !== 0) { + bits = bits | 0x80; + } + res = res | bits; + } + + var padding = res.toString(16).length; + padding += padding % 2; + + return toHex(res, padding); + } + + rendererFactory = function() { + return new Midi(); + }; +})(); + +module.exports = rendererFactory; diff --git a/src/synth/abc_midi_sequencer.js b/src/synth/abc_midi_sequencer.js new file mode 100644 index 0000000000000000000000000000000000000000..8eae4cd5926c16528b3c030cf2d4a520bf60efa8 --- /dev/null +++ b/src/synth/abc_midi_sequencer.js @@ -0,0 +1,678 @@ +// abc_midi_sequencer.js: Turn parsed abc into a linear series of events. + +var sequence; +var parseCommon = require("../parse/abc_common"); + +(function() { + "use strict"; + + var measureLength = 1; // This should be set by the meter, but just in case that is missing, we'll take a guess. + // The abc is provided to us line by line. It might have repeats in it. We want to re arrange the elements to + // be an array of voices with all the repeats embedded, and no lines. Then it is trivial to go through the events + // one at a time and turn it into midi. + + var PERCUSSION_PROGRAM = 128; + + sequence = function(abctune, options) { + // Global options + options = options || {}; + var qpm; + var program = options.program || 0; // The program if there isn't a program specified. + var transpose = options.midiTranspose || 0; + // If the tune has a visual transpose then that needs to be subtracted out because we are getting the visual object. + if (abctune.visualTranspose) + transpose -= abctune.visualTranspose; + var channel = options.channel || 0; + var channelExplicitlySet = false; + var drumPattern = options.drum || ""; + var drumBars = options.drumBars || 1; + var drumIntro = options.drumIntro || 0; + var drumOn = drumPattern !== ""; + var drumOffAfterIntro = !!options.drumOff + var style = []; // The note head style for each voice. + var rhythmHeadThisBar = false; // Rhythm notation was detected. + var crescendoSize = 50; // how much to increase or decrease volume when crescendo/diminuendo is encountered. + + // All of the above overrides need to be integers + program = parseInt(program, 10); + transpose = parseInt(transpose, 10); + channel = parseInt(channel, 10); + if (channel === 10) + program = PERCUSSION_PROGRAM; + drumPattern = drumPattern.split(" "); + drumBars = parseInt(drumBars, 10); + drumIntro = parseInt(drumIntro, 10); + + var bagpipes = abctune.formatting.bagpipes; // If it is bagpipes, then the gracenotes are played on top of the main note. + if (bagpipes) + program = 71; + + // %%MIDI fermatafixed + // %%MIDI fermataproportional + // %%MIDI deltaloudness n + // %%MIDI gracedivider b + // %%MIDI ratio n m + // %%MIDI beat a b c n + // %%MIDI grace a/b + // %%MIDI trim x/y + + // %MIDI gchordon + // %MIDI gchordoff + // %%MIDI bassprog 45 + // %%MIDI chordprog 24 + // %%MIDI chordname name n1 n2 n3 n4 n5 n6 + + //%%MIDI beat ⟨int1⟩ ⟨int2⟩ ⟨int3⟩ ⟨int4⟩: controls the volumes of the notes in a measure. The first note in a bar has volume ⟨int1⟩; other ‘strong’ notes have volume ⟨int2⟩ and all the rest have volume ⟨int3⟩. These values must be in the range 0–127. The parameter ⟨int4⟩ determines which notes are ‘strong’. If the time signature is x/y, then each note is given a position number k = 0, 1, 2. . . x-1 within each bar. If k is a multiple of ⟨int4⟩, then the note is ‘strong’. + + var startingMidi = []; + if (abctune.formatting.midi) { + //console.log("MIDI Formatting:", abctune.formatting.midi); + var globals = abctune.formatting.midi; + if (globals.program && globals.program.length > 0) { + program = globals.program[0]; + if (globals.program.length > 1) { + program = globals.program[1]; + channel = globals.program[0]; + } + channelExplicitlySet = true; + } + if (globals.transpose) + transpose = globals.transpose[0]; + if (globals.channel) { + channel = globals.channel[0]; + channelExplicitlySet = true; + } + if (globals.drum) + drumPattern = globals.drum; + if (globals.drumbars) + drumBars = globals.drumbars[0]; + if (globals.drumon) + drumOn = true; + if (channel === 10) + program = PERCUSSION_PROGRAM; + if (globals.beat) + startingMidi.push({ el_type: 'beat', beats: globals.beat }) + if (globals.nobeataccents) + startingMidi.push({ el_type: 'beataccents', value: false }); + + } + + // Specified options in abc string. + + // If the tempo was passed in, use that. + // If the tempo is specified, use that. + // If there is a default, use that. + // Otherwise, use the default. + if (options.qpm) + qpm = parseInt(options.qpm, 10); + else if (abctune.metaText.tempo) + qpm = interpretTempo(abctune.metaText.tempo, abctune.getBeatLength()); + else if (options.defaultQpm) + qpm = options.defaultQpm; + else + qpm = 180; // The tempo if there isn't a tempo specified. + + var startVoice = []; + if (bagpipes) + startVoice.push({ el_type: 'bagpipes' }); + startVoice.push({ el_type: 'instrument', program: program }); + if (channel) + startVoice.push({ el_type: 'channel', channel: channel }); + if (transpose) + startVoice.push({ el_type: 'transpose', transpose: transpose }); + startVoice.push({ el_type: 'tempo', qpm: qpm }); + for (var ss = 0; ss < startingMidi.length;ss++) + startVoice.push(startingMidi[ss]); + + // the relevant part of the input structure is: + // abctune + // array lines + // array staff + // object key + // object meter + // array voices + // array abcelem + + // visit each voice completely in turn + var voices = []; + var inCrescendo = []; + var inDiminuendo = []; + var durationCounter = [0]; + var tempoChanges = {}; + tempoChanges["0"] = { el_type: 'tempo', qpm: qpm, timing: 0 }; + var currentVolume; + var startRepeatPlaceholder = []; // There is a place holder for each voice. + var skipEndingPlaceholder = []; // This is the place where the first ending starts. + var startingDrumSet = false; + var lines = abctune.lines; //abctune.deline(); TODO-PER: can switch to this, then simplify the loops below. + for (var i = 0; i < lines.length; i++) { + // For each group of staff lines in the tune. + var line = lines[i]; + if (line.staff) { + var staves = line.staff; + var voiceNumber = 0; + for (var j = 0; j < staves.length; j++) { + var staff = staves[j]; + if (staff.clef && staff.clef.type === "TAB") + continue; + + // For each staff line + for (var k = 0; k < staff.voices.length; k++) { + // For each voice in a staff line + var voice = staff.voices[k]; + if (!voices[voiceNumber]) { + voices[voiceNumber] = [].concat(JSON.parse(JSON.stringify(startVoice))); + var voiceName = getTrackTitle(line.staff, voiceNumber); + if (voiceName) + voices[voiceNumber].unshift({el_type: "name", trackName: voiceName}); + } + // Negate any transposition for the percussion staff. + if (transpose && staff.clef.type === "perc") + voices[voiceNumber].push({ el_type: 'transpose', transpose: 0 }); + + if (staff.clef && staff.clef.type === 'perc' && !channelExplicitlySet) { + for (var cl = 0; cl < voices[voiceNumber].length; cl++) { + if (voices[voiceNumber][cl].el_type === 'instrument') + voices[voiceNumber][cl].program = PERCUSSION_PROGRAM; + } + } else if (staff.key) { + addKey(voices[voiceNumber], staff.key); + } + if (staff.meter) { + addMeter(voices[voiceNumber], staff.meter); + } + if (!startingDrumSet && drumOn) { // drum information is only needed once, so use the first line and track 0. + voices[voiceNumber].push({el_type: 'drum', params: {pattern: drumPattern, bars: drumBars, on: drumOn, intro: drumIntro}}); + startingDrumSet = true; + } + if (staff.clef && staff.clef.type !== "perc" && staff.clef.transpose) { + staff.clef.el_type = 'clef'; + voices[voiceNumber].push({ el_type: 'transpose', transpose: staff.clef.transpose }); + } + if (staff.clef && staff.clef.type) { + if (staff.clef.type.indexOf("-8") >= 0) + voices[voiceNumber].push({ el_type: 'transpose', transpose: -12 }); + else if (staff.clef.type.indexOf("+8") >= 0) + voices[voiceNumber].push({ el_type: 'transpose', transpose: 12 }); + } + + if (abctune.formatting.midi && abctune.formatting.midi.drumoff) { + // If there is a drum off command right at the beginning it is put in the metaText instead of the stream, + // so we will just insert it here. + voices[voiceNumber].push({ el_type: 'bar' }); + voices[voiceNumber].push({el_type: 'drum', params: {pattern: "", on: false }}); + } + var noteEventsInBar = 0; + var tripletMultiplier = 0; + var tripletDurationTotal = 0; // try to mitigate the js rounding problems. + var tripletDurationCount = 0; + currentVolume = [105, 95, 85, 1]; + + for (var v = 0; v < voice.length; v++) { + // For each element in a voice + var elem = voice[v]; + switch (elem.el_type) { + case "note": + if (inCrescendo[k]) { + currentVolume[0] += inCrescendo[k]; + currentVolume[1] += inCrescendo[k]; + currentVolume[2] += inCrescendo[k]; + voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); + } + + if (inDiminuendo[k]) { + currentVolume[0] += inDiminuendo[k]; + currentVolume[1] += inDiminuendo[k]; + currentVolume[2] += inDiminuendo[k]; + voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); + } + setDynamics(elem); + + // regular items are just pushed. + if (!elem.rest || elem.rest.type !== 'spacer') { + var noteElem = { elem: elem, el_type: "note", timing: durationCounter[voiceNumber] }; // Make a copy so that modifications aren't kept except for adding the midiPitches + if (elem.style) + noteElem.style = elem.style; + else if (style[voiceNumber]) + noteElem.style = style[voiceNumber]; + noteElem.duration = (elem.duration === 0) ? 0.25 : elem.duration; + if (elem.startTriplet) { + tripletMultiplier = elem.tripletMultiplier; + tripletDurationTotal = elem.startTriplet * tripletMultiplier * elem.duration; + if (elem.startTriplet !== elem.tripletR) { // most commonly (3:2:2 + if (v + elem.tripletR <= voice.length) { + var durationTotal = 0; + for (var w = v; w < v + elem.tripletR; w++) { + durationTotal += voice[w].duration; + } + tripletDurationTotal = tripletMultiplier * durationTotal; + } + } + noteElem.duration = noteElem.duration * tripletMultiplier; + noteElem.duration = Math.round(noteElem.duration*1000000)/1000000; + tripletDurationCount = noteElem.duration; + } else if (tripletMultiplier) { + if (elem.endTriplet) { + tripletMultiplier = 0; + noteElem.duration = Math.round((tripletDurationTotal - tripletDurationCount)*1000000)/1000000; + } else { + noteElem.duration = noteElem.duration * tripletMultiplier; + noteElem.duration = Math.round(noteElem.duration*1000000)/1000000; + tripletDurationCount += noteElem.duration; + } + } + if (elem.rest) noteElem.rest = elem.rest; + if (elem.decoration) noteElem.decoration = elem.decoration.slice(0); + if (elem.pitches) noteElem.pitches = parseCommon.cloneArray(elem.pitches); + if (elem.gracenotes) noteElem.gracenotes = parseCommon.cloneArray(elem.gracenotes); + if (elem.chord) noteElem.chord = parseCommon.cloneArray(elem.chord); + + voices[voiceNumber].push(noteElem); + if (elem.style === "rhythm") { + rhythmHeadThisBar = true; + chordVoiceOffThisBar(voices) + } + noteEventsInBar++; + durationCounter[voiceNumber] += noteElem.duration; + } + break; + case "key": + case "keySignature": + addKey(voices[voiceNumber], elem); + break; + case "meter": + addMeter(voices[voiceNumber], elem); + break; + case "clef": // need to keep this to catch the "transpose" element. + if (elem.transpose) + voices[voiceNumber].push({ el_type: 'transpose', transpose: elem.transpose }); + if (elem.type) { + if (elem.type.indexOf("-8") >= 0) + voices[voiceNumber].push({ el_type: 'transpose', transpose: -12 }); + else if (elem.type.indexOf("+8") >= 0) + voices[voiceNumber].push({ el_type: 'transpose', transpose: 12 }); + } + break; + case "tempo": + qpm = interpretTempo(elem, abctune.getBeatLength()); + voices[voiceNumber].push({ el_type: 'tempo', qpm: qpm, timing: durationCounter[voiceNumber] }); + tempoChanges[''+durationCounter[voiceNumber]] = { el_type: 'tempo', qpm: qpm, timing: durationCounter[voiceNumber] }; + break; + case "bar": + if (noteEventsInBar > 0) // don't add two bars in a row. + voices[voiceNumber].push({ el_type: 'bar' }); // We need the bar marking to reset the accidentals. + setDynamics(elem); + noteEventsInBar = 0; + // figure out repeats and endings -- + // The important part is where there is a start repeat, and end repeat, or a first ending. + var endRepeat = (elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat"); + var startEnding = (elem.startEnding === '1'); + var startRepeat = (elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat" || elem.type === "bar_right_repeat"); + if (endRepeat) { + var s = startRepeatPlaceholder[voiceNumber]; + if (!s) s = 0; // If there wasn't a left repeat, then we repeat from the beginning. + var e = skipEndingPlaceholder[voiceNumber]; + if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything. + // duplicate each of the elements - this has to be a deep copy. + for (var z = s; z < e; z++) { + var item = Object.assign({},voices[voiceNumber][z]); + if (item.pitches) + item.pitches = parseCommon.cloneArray(item.pitches); + voices[voiceNumber].push(item); + } + // reset these in case there is a second repeat later on. + skipEndingPlaceholder[voiceNumber] = undefined; + startRepeatPlaceholder[voiceNumber] = undefined; + } + if (startEnding) + skipEndingPlaceholder[voiceNumber] = voices[voiceNumber].length; + if (startRepeat) + startRepeatPlaceholder[voiceNumber] = voices[voiceNumber].length; + rhythmHeadThisBar = false; + break; + case 'style': + style[voiceNumber] = elem.head; + break; + case 'timeSignature': + voices[voiceNumber].push(interpretMeter(elem)); + break; + case 'part': + // TODO-PER: If there is a part section in the header, then this should probably affect the repeats. + break; + case 'stem': + case 'scale': + case 'break': + case 'font': + // These elements don't affect sound + break; + case 'midi': + //console.log("MIDI inline", elem); // TODO-PER: for debugging. Remove this. + var drumChange = false; + switch (elem.cmd) { + case "drumon": drumOn = true; drumChange = true; break; + case "drumoff": drumOn = false; drumChange = true; break; + case "drum": drumPattern = elem.params; drumChange = true; break; + case "drumbars": drumBars = elem.params[0]; drumChange = true; break; + case "drummap": + // This is handled before getting here so it can be ignored. + break; + case "channel": + // There's not much needed for the channel except to look out for the percussion channel + if (elem.params[0] === 10) + voices[voiceNumber].push({ el_type: 'instrument', program: PERCUSSION_PROGRAM }); + break; + case "program": + addIfDifferent(voices[voiceNumber], { el_type: 'instrument', program: elem.params[0] }); + channelExplicitlySet = true; + break; + case "transpose": + voices[voiceNumber].push({ el_type: 'transpose', transpose: elem.params[0] }); + break; + case "gchordoff": + voices[voiceNumber].push({ el_type: 'gchordOn', tacet: true }); + break; + case "gchordon": + voices[voiceNumber].push({ el_type: 'gchordOn', tacet: false }); + break; + case "beat": + voices[voiceNumber].push({ el_type: 'beat', beats: elem.params }); + break; + case "nobeataccents": + voices[voiceNumber].push({ el_type: 'beataccents', value: false }); + break; + case "beataccents": + voices[voiceNumber].push({ el_type: 'beataccents', value: true }); + break; + case "vol": + case "volinc": + voices[voiceNumber].push({ el_type: elem.cmd, volume: elem.params[0] }); + break; + case "swing": + case "gchord": + case "bassvol": + case "chordvol": + voices[voiceNumber].push({ el_type: elem.cmd, param: elem.params[0] }); + break; + + case "bassprog": // MAE 22 May 2024 + case "chordprog": // MAE 22 May 2024 + voices[voiceNumber].push({ + el_type: elem.cmd, + value: elem.params[0], + octaveShift: elem.params[1] + }); + break; + + // MAE 23 Jun 2024 + case "gchordbars": + voices[voiceNumber].push({ + el_type: elem.cmd, + param: elem.params[0] + }); + break; + default: + console.log("MIDI seq: midi cmd not handled: ", elem.cmd, elem); + } + if (drumChange) { + voices[0].push({el_type: 'drum', params: { pattern: drumPattern, bars: drumBars, intro: drumIntro, on: drumOn}}); + startingDrumSet = true; + } + break; + default: + console.log("MIDI: element type " + elem.el_type + " not handled."); + } + } + voiceNumber++; + if (!durationCounter[voiceNumber]) + durationCounter[voiceNumber] = 0; + } + } + + function setDynamics(elem) { + var volumes = { + 'pppp': [15, 10, 5, 1], + 'ppp': [30, 20, 10, 1], + 'pp': [45, 35, 20, 1], + 'p': [60, 50, 35, 1], + 'mp': [75, 65, 50, 1], + 'mf': [90, 80, 65, 1], + 'f': [105, 95, 80, 1], + 'ff': [120, 110, 95, 1], + 'fff': [127, 125, 110, 1], + 'ffff': [127, 125, 110, 1] + }; + + var dynamicType; + if (elem.decoration) { + if (elem.decoration.indexOf('pppp') >= 0) + dynamicType = 'pppp'; + else if (elem.decoration.indexOf('ppp') >= 0) + dynamicType = 'ppp'; + else if (elem.decoration.indexOf('pp') >= 0) + dynamicType = 'pp'; + else if (elem.decoration.indexOf('p') >= 0) + dynamicType = 'p'; + else if (elem.decoration.indexOf('mp') >= 0) + dynamicType = 'mp'; + else if (elem.decoration.indexOf('mf') >= 0) + dynamicType = 'mf'; + else if (elem.decoration.indexOf('f') >= 0) + dynamicType = 'f'; + else if (elem.decoration.indexOf('ff') >= 0) + dynamicType = 'ff'; + else if (elem.decoration.indexOf('fff') >= 0) + dynamicType = 'fff'; + else if (elem.decoration.indexOf('ffff') >= 0) + dynamicType = 'ffff'; + + if (dynamicType) { + currentVolume = volumes[dynamicType].slice(0); + voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) }); + inCrescendo[k] = false; + inDiminuendo[k] = false; + } + + if (elem.decoration.indexOf("crescendo(") >= 0) { + var n = numNotesToDecoration(voice, v, "crescendo)"); + var top = Math.min(127, currentVolume[0] + crescendoSize); + var endDec = endingVolume(voice, v+n+1, Object.keys(volumes)); + if (endDec) + top = volumes[endDec][0]; + if (n > 0) + inCrescendo[k] = Math.floor((top - currentVolume[0]) / n); + else + inCrescendo[k] = false; + inDiminuendo[k] = false; + } else if (elem.decoration.indexOf("crescendo)") >= 0) { + inCrescendo[k] = false; + } else if (elem.decoration.indexOf("diminuendo(") >= 0) { + var n2 = numNotesToDecoration(voice, v, "diminuendo)"); + var bottom = Math.max(15, currentVolume[0] - crescendoSize); + var endDec2 = endingVolume(voice, v+n2+1, Object.keys(volumes)); + if (endDec2) + bottom = volumes[endDec2][0]; + inCrescendo[k] = false; + if (n2 > 0) + inDiminuendo[k] = Math.floor((bottom - currentVolume[0]) / n2); + else + inDiminuendo[k] = false; + } else if (elem.decoration.indexOf("diminuendo)") >= 0) { + inDiminuendo[k] = false; + } + } + } + } + } + // If there are tempo changes, make sure they are in all the voices. This must be done post process because all the elements in all the voices need to be created first. + insertTempoChanges(voices, tempoChanges); + + if (drumIntro) { + var pickups = abctune.getPickupLength(); + // add some measures of rests to the start of each track. + for (var vv = 0; vv < voices.length; vv++) { + var insertPoint = 0; + while (voices[vv][insertPoint].el_type !== "note" && voices[vv].length > insertPoint) + insertPoint++; + if (voices[vv].length > insertPoint) { + for (var w = 0; w < drumIntro; w++) { + // If it is the last measure of intro, subtract the pickups. + if (pickups === 0 || w < drumIntro-1) { + voices[vv].splice(insertPoint, 0, + {el_type: "note", rest: {type: "rest"}, duration: measureLength}, + { el_type: "bar" } + ); + insertPoint += 2 + } else { + voices[vv].splice(insertPoint++, 0, {el_type: "note", rest: {type: "rest"}, duration: measureLength-pickups}); + } + } + if (drumOffAfterIntro) { + drumOn = false + voices[vv].splice(insertPoint++, 0, {el_type: 'drum', params: { pattern: drumPattern, bars: drumBars, intro: drumIntro, on: drumOn}}); + drumOffAfterIntro = false + } + } + } + } + if (voices.length > 0 && voices[0].length > 0) { + voices[0][0].pickupLength = abctune.getPickupLength(); + } + return voices; + }; + + function numNotesToDecoration(voice, start, decoration) { + var counter = 0; + for (var i = start+1; i < voice.length; i++) { + if (voice[i].el_type === "note") + counter++; + if (voice[i].decoration && voice[i].decoration.indexOf(decoration) >= 0) + return counter; + } + return counter; + } + function endingVolume(voice, start, volumeDecorations) { + var end = Math.min(voice.length, start + 3); // If we have a volume within a couple notes of the end then assume that is the destination. + for (var i = start; i < end; i++) { + if (voice[i].el_type === "note") { + if (voice[i].decoration) { + for (var j = 0; j < voice[i].decoration.length; j++) { + if (volumeDecorations.indexOf(voice[i].decoration[j]) >= 0) + return voice[i].decoration[j]; + } + } + } + } + return null; + } + + function insertTempoChanges(voices, tempoChanges) { + if (!tempoChanges || tempoChanges.length === 0) + return; + var changePositions = Object.keys(tempoChanges); + for (var i = 0; i < voices.length; i++) { + var voice = voices[i]; + var lastTempo = tempoChanges['0'] ? tempoChanges['0'].qpm : 0; // Don't insert redundant changes. This happens normally when repeating from the beginning, but could happen anywhere that there is a tempo marking that is the same as the last one. + for (var j = 0; j < voice.length; j++) { + var el = voice[j]; + if (el.el_type === "tempo") + lastTempo = el.qpm; + if (changePositions.indexOf(''+el.timing) >= 0 && lastTempo !== tempoChanges[''+el.timing].qpm) { + lastTempo = tempoChanges[''+el.timing].qpm; + if (el.el_type === "tempo") { + el.qpm = tempoChanges[''+el.timing].qpm; + j++; // when there is a tempo element the next element has the same timing and we don't want it to match the second time. + } else { + //console.log("tempo position", i, j, el); + voices[i].splice(j, 0, {el_type: "tempo", qpm: tempoChanges[''+el.timing].qpm, timing: el.timing}); + j +=2; // skip the element we just inserted. + } + } + } + } + } + + function chordVoiceOffThisBar(voices) { + for (var i = 0; i < voices.length; i++) { + var voice = voices[i]; + var j = voice.length-1; + while (j >= 0 && voice[j].el_type !== 'bar') { + voice[j].noChordVoice = true; + j--; + } + } + } + + function getTrackTitle(staff, voiceNumber) { + if (!staff || staff.length <= voiceNumber || !staff[voiceNumber].title) + return undefined; + return staff[voiceNumber].title.join(" "); + } + + function interpretTempo(element, beatLength) { + var duration = 1/4; + if (element.duration) { + duration = element.duration[0]; + } + var bpm = 60; + if (element.bpm) { + bpm = element.bpm; + } + // The tempo is defined with a beat length of "duration". If that isn't the natural beat length then there is a translation. + return duration * bpm / beatLength; + } + + function interpretMeter(element) { + var meter; + switch (element.type) { + case "common_time": + meter = { el_type: 'meter', num: 4, den: 4 }; + break; + case "cut_time": + meter = { el_type: 'meter', num: 2, den: 2 }; + break; + case "specified": + // TODO-PER: only taking the first meter, so the complex meters are not handled. + meter = { el_type: 'meter', num: element.value[0].num, den: element.value[0].den }; + break; + default: + // This should never happen. + meter = { el_type: 'meter' }; + } + measureLength = meter.num/meter.den; + return meter; + } + + function removeNaturals(accidentals) { + var acc = []; + for (var i = 0; i < accidentals.length; i++) { + if (accidentals[i].acc !== "natural") + acc.push(accidentals[i]) + } + return acc; + } + function addKey(arr, key) { + var newKey; + if (key.root === 'HP') + newKey = {el_type: 'key', accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}]}; + else + newKey = {el_type: 'key', accidentals: removeNaturals(key.accidentals) }; + addIfDifferent(arr, newKey); + } + function addMeter(arr, meter) { + var newMeter = interpretMeter(meter); + addIfDifferent(arr, newMeter); + } + function addIfDifferent(arr, item) { + for (var i = arr.length-1; i >= 0; i--) { + if (arr[i].el_type === item.el_type) { + if (JSON.stringify(arr[i]) !== JSON.stringify(item)) + arr.push(item); + return; + } + } + arr.push(item); + } + +})(); + +module.exports = sequence; diff --git a/src/synth/active-audio-context.js b/src/synth/active-audio-context.js new file mode 100644 index 0000000000000000000000000000000000000000..15245ba55de93b6dcf90fdeff838c73bb10e8a38 --- /dev/null +++ b/src/synth/active-audio-context.js @@ -0,0 +1,9 @@ +var registerAudioContext = require('./register-audio-context.js'); + +function activeAudioContext() { + if (!window.abcjsAudioContext) + registerAudioContext(); + return window.abcjsAudioContext; +} + +module.exports = activeAudioContext; diff --git a/src/synth/cents-to-factor.js b/src/synth/cents-to-factor.js new file mode 100644 index 0000000000000000000000000000000000000000..e97dd012f1ab1e96eda329eebd1cfb83571e03f2 --- /dev/null +++ b/src/synth/cents-to-factor.js @@ -0,0 +1,10 @@ +// This turns the number of cents to detune into a value that is convenient to use in pitch calculations +// A cent is 1/100 of a musical half step and is calculated exponentially over the course of an octave. +// The equation is: +// Two to the power of cents divided by 1200 + +function centsToFactor(cents) { + return Math.pow(2, cents/1200); +} + +module.exports = centsToFactor; diff --git a/src/synth/chord-track.js b/src/synth/chord-track.js new file mode 100644 index 0000000000000000000000000000000000000000..063654a52fc4bd4e515f913cc7fa118559adaf01 --- /dev/null +++ b/src/synth/chord-track.js @@ -0,0 +1,637 @@ +// +// The algorithm for chords is: +// - The chords are done in a separate track. +// - If there are notes before the first chord, then put that much silence to start the track. +// - The pattern of chord expression depends on the meter, and how many chords are in a measure. +// - There is a possibility that a measure will have an incorrect number of beats, if that is the case, then +// start the pattern anew on the next measure number. +// - If a chord root is not A-G, then ignore it as if the chord wasn't there at all. +// - If a chord modification isn't in our supported list, change it to a major triad. +// +// - There is a standard pattern of boom-chick for each time sig, or it can be overridden. +// - For any unrecognized meter, play the full chord on each beat. +// +// - If there is a chord specified that is not on a beat, move it earlier to the previous beat, unless there is already a chord on that beat. +// - Otherwise, move it later, unless there is already a chord on that beat. +// - Otherwise, ignore it. (TODO-PER: expand this as more support is added.) +// +// If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so there is no chord added for that entire measure. + +var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) { + this.chordTrack = []; + this.chordTrackFinished = false; + this.chordChannel = numVoices; // first free channel for chords + this.currentChords = []; + this.lastChord; + this.chordLastBar; + this.chordsOff = !!chordsOff + this.gChordTacet = this.chordsOff; + this.hasRhythmHead = false; + this.transpose = 0; + this.lastBarTime = 0; + this.meter = meter; + this.tempoChangeFactor = 1; + + // MAE 17 Jun 2024 - To allow for bass and chord instrument octave shifts + this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length >= 1 ? midiOptions.bassprog[0] : 0; + this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length >= 1 ? midiOptions.chordprog[0] : 0; + + // MAE For octave shifted bass and chords + this.bassOctaveShift = midiOptions.bassprog && midiOptions.bassprog.length === 2 ? midiOptions.bassprog[1] : 0; + this.chordOctaveShift = midiOptions.chordprog && midiOptions.chordprog.length === 2 ? midiOptions.chordprog[1] : 0; + + this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64; + this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48; + + // This allows for an initial %%MIDI gchord with no string + if (midiOptions.gchord && (midiOptions.gchord.length > 0)) { + this.overridePattern = parseGChord(midiOptions.gchord[0]) + } + else { + this.overridePattern = undefined; + } +}; + +ChordTrack.prototype.setMeter = function (meter) { + this.meter = meter +}; + +ChordTrack.prototype.setTempoChangeFactor = function (tempoChangeFactor) { + this.tempoChangeFactor = tempoChangeFactor +}; + +ChordTrack.prototype.setLastBarTime = function (lastBarTime) { + this.lastBarTime = lastBarTime +}; + +ChordTrack.prototype.setTranspose = function (transpose) { + this.transpose = transpose +}; + +ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) { + this.hasRhythmHead = isRhythmHead + var ePitches = []; + if (isRhythmHead) { + if (this.lastChord && this.lastChord.chick) { + for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) { + var note2 = Object.assign({},elem.pitches[0]); + note2.actualPitch = this.lastChord.chick[i2]; + ePitches.push(note2); + } + } + } + return ePitches +}; + +ChordTrack.prototype.barEnd = function (element) { + if (this.chordTrack.length > 0 && !this.chordTrackFinished) { + this.resolveChords(this.lastBarTime, timeToRealTime(element.time)); + this.currentChords = []; + } + this.chordLastBar = this.lastChord; +}; + +ChordTrack.prototype.gChordOn = function (element) { + if (!this.chordsOff) + this.gChordTacet = element.tacet; +}; + +ChordTrack.prototype.paramChange = function (element) { + switch (element.el_type) { + case "gchord": + // Skips gchord elements that don't have pattern strings + if (element.param && element.param.length > 0) { + this.overridePattern = parseGChord(element.param); + + // Generate a default duration scale based on the pattern + //this.gchordduration = generateDefaultDurationScale(element.param); + } else + this.overridePattern = undefined; + break; + case "bassprog": + this.bassInstrument = element.value; + if ((element.octaveShift != undefined) && (element.octaveShift != null)) { + this.bassOctaveShift = element.octaveShift; + } + else { + this.bassOctaveShift = 0; + } + break; + case "chordprog": + this.chordInstrument = element.value; + if ((element.octaveShift != undefined) && (element.octaveShift != null)) { + this.chordOctaveShift = element.octaveShift; + } + else { + this.chordOctaveShift = 0; + } + break; + case "bassvol": + this.boomVolume = element.param; + break; + case "chordvol": + this.chickVolume = element.param; + break; + default: + console.log("unhandled midi param", element) + } +}; + +ChordTrack.prototype.finish = function () { + if (!this.chordTrackEmpty()) // Don't do chords on more than one track, so turn off chord detection after we create it. + this.chordTrackFinished = true; +}; + +ChordTrack.prototype.addTrack = function (tracks) { + if (!this.chordTrackEmpty()) + tracks.push(this.chordTrack); +}; + +ChordTrack.prototype.findChord = function (elem) { + if (this.gChordTacet) + return 'break'; + + // TODO-PER: Just using the first chord if there are more than one. + if (this.chordTrackFinished || !elem.chord || elem.chord.length === 0) + return null; + + // Return the first annotation that is a regular chord: that is, it is in the default place or is a recognized "tacet" phrase. + for (var i = 0; i < elem.chord.length; i++) { + var ch = elem.chord[i]; + if (ch.position === 'default') + return ch.name; + if (this.breakSynonyms.indexOf(ch.name.toLowerCase()) >= 0) + return 'break'; + } + return null; +} + +ChordTrack.prototype.interpretChord = function (name) { + // chords have the format: + // [root][acc][modifier][/][bass][acc] + // (The chord might be surrounded by parens. Just ignore them.) + // root must be present and must be from A-G. + // acc is optional and can be # or b + // The modifier can be a wide variety of things, like "maj7". As they are discovered, more are supported here. + // If there is a slash, then there is a bass note, which can be from A-G, with an optional acc. + // If the root is unrecognized, then "undefined" is returned and there is no chord. + // If the modifier is unrecognized, a major triad is returned. + // If the bass notes is unrecognized, it is ignored. + if (name.length === 0) + return undefined; + if (name === 'break') + return { chick: [] }; + var root = name.substring(0, 1); + if (root === '(') { + name = name.substring(1, name.length - 2); + if (name.length === 0) + return undefined; + root = name.substring(0, 1); + } + var bass = this.basses[root]; + if (!bass) // If the bass note isn't listed, then this was an unknown root. Only A-G are accepted. + return undefined; + // Don't transpose the chords more than an octave. + var chordTranspose = this.transpose; + while (chordTranspose < -8) + chordTranspose += 12; + while (chordTranspose > 8) + chordTranspose -= 12; + bass += chordTranspose; + + // MAE 31 Aug 2024 - For visual transpose backup range issue + // If transposed below A or above G, bring it back in the normal backup range + if (bass < 33){ + bass += 12; + } + else if (bass > 44){ + bass -= 12; + } + + // MAE 17 Jun 2024 - Supporting octave shifted bass and chords + var unshiftedBass = bass; + + bass += this.bassOctaveShift * 12; + + var bass2 = bass - 5; // The alternating bass is a 4th below + var chick; + if (name.length === 1) + chick = this.chordNotes(bass, ''); + var remaining = name.substring(1); + var acc = remaining.substring(0, 1); + if (acc === 'b' || acc === '♭') { + unshiftedBass--; + bass--; + bass2--; + remaining = remaining.substring(1); + } else if (acc === '#' || acc === '♯') { + unshiftedBass++; + bass++; + bass2++; + remaining = remaining.substring(1); + } + var arr = remaining.split('/'); + chick = this.chordNotes(unshiftedBass, arr[0]); + // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't. + if (chick.length >= 3) { + var fifth = chick[2] - chick[0]; + bass2 = bass2 + fifth - 7; + } + + if (arr.length === 2) { + var explicitBass = this.basses[arr[1].substring(0, 1)]; + if (explicitBass) { + var bassAcc = arr[1].substring(1); + var bassShift = { '#': 1, '♯': 1, 'b': -1, '♭': -1 }[bassAcc] || 0; + bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose; + + // MAE 22 May 2024 - Supporting octave shifted bass and chords + bass += this.bassOctaveShift * 12; + + bass2 = bass; + } + } + return { boom: bass, boom2: bass2, chick: chick }; +} + +ChordTrack.prototype.chordNotes = function (bass, modifier) { + var intervals = this.chordIntervals[modifier]; + if (!intervals) { + if (modifier.slice(0, 2).toLowerCase() === 'ma' || modifier[0] === 'M') + intervals = this.chordIntervals.M; + else if (modifier[0] === 'm' || modifier[0] === '-') + intervals = this.chordIntervals.m; + else + intervals = this.chordIntervals.M; + } + bass += 12; // the chord is an octave above the bass note. + + // MAE 22 May 2024 - For chick octave shift + bass += (this.chordOctaveShift * 12); + + var notes = []; + for (var i = 0; i < intervals.length; i++) { + notes.push(bass + intervals[i]); + } + return notes; +} + +ChordTrack.prototype.writeNote = function (note, beatLength, volume, beat, noteLength, instrument) { + // undefined means there is a stop time. + if (note !== undefined) + this.chordTrack.push({ cmd: 'note', pitch: note, volume: volume, start: this.lastBarTime + beat * durationRounded(beatLength, this.tempoChangeFactor), duration: durationRounded(noteLength, this.tempoChangeFactor), gap: 0, instrument: instrument }); +} + +ChordTrack.prototype.chordTrackEmpty = function () { + var isEmpty = true; + for (var i = 0; i < this.chordTrack.length && isEmpty; i++) { + if (this.chordTrack[i].cmd === 'note') + isEmpty = false + } + return isEmpty; +} + +ChordTrack.prototype.resolveChords = function (startTime, endTime) { + // If there is a rhythm head anywhere in the measure then don't add a separate rhythm track + if (this.hasRhythmHead) + return + + var num = this.meter.num; + var den = this.meter.den; + var beatLength = 1 / den; + var noteLength = beatLength / 2; + var thisMeasureLength = parseInt(num, 10) / parseInt(den, 10); + var portionOfAMeasure = thisMeasureLength - (endTime - startTime) / this.tempoChangeFactor; + if (Math.abs(portionOfAMeasure) < 0.00001) + portionOfAMeasure = 0; + + // there wasn't a new chord this measure, so use the last chord declared. + // also the case where there is a chord declared in the measure, but not on its first beat. + if (this.currentChords.length === 0 || this.currentChords[0].beat !== 0) { + this.currentChords.unshift({ beat: 0, chord: this.chordLastBar }); + } + + //console.log(this.currentChords) + var currentChordsExpanded = expandCurrentChords(this.currentChords, 8*num/den, beatLength) + //console.log(currentChordsExpanded) + var thisPattern = this.overridePattern ? this.overridePattern : this.rhythmPatterns[num + '/' + den] + if (portionOfAMeasure) { + thisPattern = []; + var beatsPresent = ((endTime - startTime) / this.tempoChangeFactor) * 8; + for (var p = 0; p < beatsPresent/2; p += 2) { + thisPattern.push("chick"); + thisPattern.push(""); + } + } + if (!thisPattern) { + thisPattern = [] + for (var p = 0; p < (8*num/den)/2; p++) { + thisPattern.push('chick') + thisPattern.push(""); + } + } + var firstBoom = true + // If the pattern is overridden, it might be longer than the length of a measure. If so, then ignore the rest of it + var minLength = Math.min(thisPattern.length, currentChordsExpanded.length) + for (var p = 0; p < minLength; p++) { + if (p > 0 && currentChordsExpanded[p-1] && currentChordsExpanded[p] && currentChordsExpanded[p-1].boom !== currentChordsExpanded[p].boom) + firstBoom = true + var type = thisPattern[p] + var isBoom = type.indexOf('boom') >= 0 + // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in if the first thing in the pattern is a bass note. + var newBass = !isBoom && + p !== 0 && + thisPattern[0].indexOf('boom') >= 0 && + (!currentChordsExpanded[p-1] || currentChordsExpanded[p-1].boom !== currentChordsExpanded[p].boom) + var pitches = resolvePitch(currentChordsExpanded[p], type, firstBoom, newBass) + if (isBoom) + firstBoom = false + for (var oo = 0; oo < pitches.length; oo++) { + this.writeNote(pitches[oo], + 0.125, + isBoom || newBass ? this.boomVolume : this.chickVolume, + p, + noteLength, + isBoom || newBass ? this.bassInstrument : this.chordInstrument + ) + if (newBass) + newBass = false + else + isBoom = false // only the first note in a chord is a bass note. This handles the case where bass and chord are played at the same time. + } + } + return +} + +ChordTrack.prototype.processChord = function (elem) { + if (this.chordTrackFinished) + return + var chord = this.findChord(elem); + if (chord) { + var c = this.interpretChord(chord); + // If this isn't a recognized chord, just completely ignore it. + if (c) { + // If we ever have a chord in this voice, then we add the chord track. + // However, if there are chords on more than one voice, then just use the first voice. + if (this.chordTrack.length === 0) { + this.chordTrack.push({ cmd: 'program', channel: this.chordChannel, instrument: this.chordInstrument }); + } + + this.lastChord = c; + var barBeat = calcBeat(this.lastBarTime, timeToRealTime(elem.time)); + this.currentChords.push({ chord: this.lastChord, beat: barBeat, start: timeToRealTime(elem.time) }); + } + } +} + +function resolvePitch(currentChord, type, firstBoom, newBass) { + var ret = [] + if (!currentChord) + return ret + if (type.indexOf('boom') >= 0) + ret.push(firstBoom ? currentChord.boom : currentChord.boom2) + else if (newBass) + ret.push(currentChord.boom) + var numChordNotes = currentChord.chick.length + if (type.indexOf('chick') >= 0) { + for (var i = 0; i < numChordNotes; i++) + ret.push(currentChord.chick[i]) + } + switch (type) { + case 'DO': ret.push(currentChord.chick[0]); break; + case 'MI': ret.push(currentChord.chick[1]); break; + case 'SOL': ret.push(extractNote(currentChord,2)); break; + case 'TI': ret.push(extractNote(currentChord,3)); break; + case 'TOP': ret.push(extractNote(currentChord,4)); break; + case 'do': ret.push(currentChord.chick[0]+12); break; + case 'mi': ret.push(currentChord.chick[1]+12); break; + case 'sol': ret.push(extractNote(currentChord,2)+12); break; + case 'ti': ret.push(extractNote(currentChord,3)+12); break; + case 'top': ret.push(extractNote(currentChord,4)+12); break; + } + return ret +} + +function extractNote(chord, index) { + // This creates an arpeggio note no matter how many notes are in the chord - if it runs out of notes it continues in the next octave + var octave = Math.floor(index / chord.chick.length) + var note = chord.chick[index % chord.chick.length] + //console.log(chord.chick, {index, octave, note}, index % chord.chick.length) + return note + octave * 12 +} + +function parseGChord(gchord) { + // TODO-PER: The spec is more complicated than this but for now this will not try to do anything with error cases like the wrong number of beats. + var pattern = [] + for (var i = 0; i < gchord.length; i++) { + var ch = gchord[i] + switch(ch) { + case 'z' : pattern.push(''); break; + case '2' : pattern.push(''); break; // TODO-PER: This should extend the last note, but that's a small effect + case 'c' : pattern.push('chick'); break; + case 'b' : pattern.push('boom&chick'); break; + case 'f' : pattern.push('boom'); break; + case 'G' : pattern.push('DO'); break; + case 'H' : pattern.push('MI'); break; + case 'I' : pattern.push('SOL'); break; + case 'J' : pattern.push('TI'); break; + case 'K' : pattern.push('TOP'); break; + case 'g' : pattern.push('do'); break; + case 'h' : pattern.push('mi'); break; + case 'i' : pattern.push('sol'); break; + case 'j' : pattern.push('ti'); break; + case 'k' : pattern.push('top'); break; + } + } + return pattern +} + +// This returns an array that has a chord for each 1/8th note position in the current measure +function expandCurrentChords(currentChords, num8thNotes, beatLength) { + beatLength = beatLength * 8 // this is expressed as a fraction, so that 0.25 is a quarter notes. We want it to be the number of 8th notes + var chords = [] + if (currentChords.length === 0) + return chords + + var currentChord = currentChords[0].chord + for (var i = 1; i < currentChords.length; i++) { + var current = currentChords[i] + while (chords.length < current.beat) { + chords.push(currentChord) + } + currentChord = current.chord + } + while (chords.length < num8thNotes) + chords.push(currentChord) + return chords +} + +function calcBeat(measureStart, currTime) { + var distanceFromStart = currTime - measureStart; + return distanceFromStart * 8; +} + +ChordTrack.prototype.breakSynonyms = ['break', '(break)', 'no chord', 'n.c.', 'tacet']; + +ChordTrack.prototype.basses = { + 'A': 33, 'B': 35, 'C': 36, 'D': 38, 'E': 40, 'F': 41, 'G': 43 +}; + +ChordTrack.prototype.chordIntervals = { + // diminished (all flat 5 chords) + 'dim': [0, 3, 6], + '°': [0, 3, 6], + '˚': [0, 3, 6], + + 'dim7': [0, 3, 6, 9], + '°7': [0, 3, 6, 9], + '˚7': [0, 3, 6, 9], + + 'ø7': [0, 3, 6, 10], + 'm7(b5)': [0, 3, 6, 10], + 'm7b5': [0, 3, 6, 10], + 'm7♭5': [0, 3, 6, 10], + '-7(b5)': [0, 3, 6, 10], + '-7b5': [0, 3, 6, 10], + + '7b5': [0, 4, 6, 10], + '7(b5)': [0, 4, 6, 10], + '7♭5': [0, 4, 6, 10], + + '7(b9,b5)': [0, 4, 6, 10, 13], + '7b9,b5': [0, 4, 6, 10, 13], + '7(#9,b5)': [0, 4, 6, 10, 15], + '7#9b5': [0, 4, 6, 10, 15], + 'maj7(b5)': [0, 4, 6, 11], + 'maj7b5': [0, 4, 6, 11], + '13(b5)': [0, 4, 6, 10, 14, 21], + '13b5': [0, 4, 6, 10, 14, 21], + + // minor (all normal 5, minor 3 chords) + 'm': [0, 3, 7], + '-': [0, 3, 7], + 'm6': [0, 3, 7, 9], + '-6': [0, 3, 7, 9], + 'm7': [0, 3, 7, 10], + '-7': [0, 3, 7, 10], + + '-(b6)': [0, 3, 7, 8], + '-b6': [0, 3, 7, 8], + '-6/9': [0, 3, 7, 9, 14], + '-7(b9)': [0, 3, 7, 10, 13], + '-7b9': [0, 3, 7, 10, 13], + '-maj7': [0, 3, 7, 11], + '-9+7': [0, 3, 7, 11, 13], + '-11': [0, 3, 7, 11, 14, 17], + 'm11': [0, 3, 7, 11, 14, 17], + '-maj9': [0, 3, 7, 11, 14], + '-∆9': [0, 3, 7, 11, 14], + 'mM9': [0, 3, 7, 11, 14], + + // major (all normal 5, major 3 chords) + 'M': [0, 4, 7], + '6': [0, 4, 7, 9], + '6/9': [0, 4, 7, 9, 14], + '6add9': [0, 4, 7, 9, 14], + '69': [0, 4, 7, 9, 14], + + '7': [0, 4, 7, 10], + '9': [0, 4, 7, 10, 14], + '11': [0, 7, 10, 14, 17], + '13': [0, 4, 7, 10, 14, 21], + '7b9': [0, 4, 7, 10, 13], + '7♭9': [0, 4, 7, 10, 13], + '7(b9)': [0, 4, 7, 10, 13], + '7(#9)': [0, 4, 7, 10, 15], + '7#9': [0, 4, 7, 10, 15], + '(13)': [0, 4, 7, 10, 14, 21], + '7(9,13)': [0, 4, 7, 10, 14, 21], + '7(#9,b13)': [0, 4, 7, 10, 15, 20], + '7(#11)': [0, 4, 7, 10, 14, 18], + '7#11': [0, 4, 7, 10, 14, 18], + '7(b13)': [0, 4, 7, 10, 20], + '7b13': [0, 4, 7, 10, 20], + '9(#11)': [0, 4, 7, 10, 14, 18], + '9#11': [0, 4, 7, 10, 14, 18], + '13(#11)': [0, 4, 7, 10, 18, 21], + '13#11': [0, 4, 7, 10, 18, 21], + + 'maj7': [0, 4, 7, 11], + '∆7': [0, 4, 7, 11], + 'Δ7': [0, 4, 7, 11], + 'maj9': [0, 4, 7, 11, 14], + 'maj7(9)': [0, 4, 7, 11, 14], + 'maj7(11)': [0, 4, 7, 11, 17], + 'maj7(#11)': [0, 4, 7, 11, 18], + 'maj7(13)': [0, 4, 7, 14, 21], + 'maj7(9,13)': [0, 4, 7, 11, 14, 21], + + '7sus4': [0, 5, 7, 10], + 'm7sus4': [0, 3, 7, 10, 17], + 'sus4': [0, 5, 7], + 'sus2': [0, 2, 7], + '7sus2': [0, 2, 7, 10], + '9sus4': [0, 5, 7, 10, 14], + '13sus4': [0, 5, 7, 10, 14, 21], + + // augmented (all sharp 5 chords) + 'aug7': [0, 4, 8, 10], + '+7': [0, 4, 8, 10], + '+': [0, 4, 8], + '7#5': [0, 4, 8, 10], + '7♯5': [0, 4, 8, 10], + '7+5': [0, 4, 8, 10], + '9#5': [0, 4, 8, 10, 14], + '9♯5': [0, 4, 8, 10, 14], + '9+5': [0, 4, 8, 10, 14], + '-7(#5)': [0, 3, 8, 10], + '-7#5': [0, 3, 8, 10], + '7(#5)': [0, 4, 8, 10], + '7(b9,#5)': [0, 4, 8, 10, 13], + '7b9#5': [0, 4, 8, 10, 13], + 'maj7(#5)': [0, 4, 8, 11], + 'maj7#5': [0, 4, 8, 11], + 'maj7(#5,#11)': [0, 4, 8, 11, 18], + 'maj7#5#11': [0, 4, 8, 11, 18], + '9(#5)': [0, 4, 8, 10, 14], + '13(#5)': [0, 4, 8, 10, 14, 21], + '13#5': [0, 4, 8, 10, 14, 21], + // MAE Power chords added 10 April 2024 + '5': [0, 7], + '5(8)': [0, 7, 12], + '5add8': [0, 7, 12] + +}; + +ChordTrack.prototype.rhythmPatterns = { + "2/2": ['boom', '', '', '', 'chick', '', '', ''], + "3/2": ['boom', '', '', '', 'chick', '', '', '', 'chick', '', '', ''], + "4/2": ['boom', '', '', '', 'chick', '', '', '', 'boom', '', '', '', 'chick', '', '', ''], + + "2/4": ['boom', '', 'chick', ''], + "3/4": ['boom', '', 'chick', '', 'chick', ''], + "4/4": ['boom', '', 'chick', '', 'boom', '', 'chick', ''], + "5/4": ['boom', '', 'chick', '', 'chick', '', 'boom', '', 'chick', ''], + "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''], + + "3/8": ['boom', '', 'chick'], + "5/8": ['boom', 'chick', 'chick', 'boom', 'chick'], + "6/8": ['boom', '', 'chick', 'boom', '', 'chick'], + "7/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'], + "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'], + "10/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'], + "11/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick', 'chick'], + "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'], +}; + +// TODO-PER: these are repeated in flattener. Can it be shared? + +function timeToRealTime(time) { + return time / 1000000; +} + +function durationRounded(duration, tempoChangeFactor) { + return Math.round(duration * tempoChangeFactor * 1000000) / 1000000; +} + +module.exports = ChordTrack; diff --git a/src/synth/create-note-map.js b/src/synth/create-note-map.js new file mode 100644 index 0000000000000000000000000000000000000000..2af1d47b3b614b05917a831f5f0434154762d8b7 --- /dev/null +++ b/src/synth/create-note-map.js @@ -0,0 +1,59 @@ +// Convert the input structure to a more useful structure where each item has a length of its own. + +var instrumentIndexToName = require('./instrument-index-to-name'); + +var createNoteMap = function(sequence) { + var map = []; + for (var i = 0; i < sequence.tracks.length; i++) + map.push([]); + + // TODO-PER: handle more than one note in a track + var nextNote = {}; + var currentInstrument = instrumentIndexToName[0]; + // ev.start and ev.duration are in whole notes. Need to turn them into + sequence.tracks.forEach(function(track, i) { + track.forEach(function(ev) { + switch (ev.cmd) { + case "note": + // ev contains: + // {"cmd":"note","pitch":72,"volume":95,"start":0.125,"duration":0.25,"instrument":0,"gap":0} + // where start and duration are in whole notes, gap is in 1/1920 of a second (i.e. MIDI ticks) + var inst = ev.instrument !== undefined ? instrumentIndexToName[ev.instrument] : currentInstrument + if (ev.duration > 0) { + var gap = ev.gap ? ev.gap : 0; + var len = ev.duration; + gap = Math.min(gap, len * 2 / 3); + var obj = { + pitch: ev.pitch, + instrument: inst, + start: Math.round((ev.start) * 1000000)/1000000, + end: Math.round((ev.start + len - gap) * 1000000)/1000000, + volume: ev.volume + }; + if (ev.startChar) + obj.startChar = ev.startChar; + if (ev.endChar) + obj.endChar = ev.endChar; + if (ev.style) + obj.style = ev.style; + if (ev.cents) + obj.cents = ev.cents; + map[i].push(obj); + } + break; + case "program": + currentInstrument = instrumentIndexToName[ev.instrument]; + break; + case "text": + // Ignore the track names - that is just for midi files. + break; + default: + // TODO-PER: handle other event types + console.log("Unhandled midi event", ev); + } + }); + }); + return map; +}; + +module.exports = createNoteMap; diff --git a/src/synth/create-synth-control.js b/src/synth/create-synth-control.js new file mode 100644 index 0000000000000000000000000000000000000000..36735b0d2d42fe74d020252c678fb9d6990a5532 --- /dev/null +++ b/src/synth/create-synth-control.js @@ -0,0 +1,226 @@ +var supportsAudio = require('./supports-audio'); +var registerAudioContext = require('./register-audio-context'); +var activeAudioContext = require('./active-audio-context'); + +var loopImage = require('./images/loop.svg.js'); +var playImage = require('./images/play.svg.js'); +var pauseImage = require('./images/pause.svg.js'); +var loadingImage = require('./images/loading.svg.js'); +var resetImage = require('./images/reset.svg.js'); + +function CreateSynthControl(parent, options) { + var self = this; + // parent is either an element or a selector. + if (typeof parent === "string") { + var selector = parent; + parent = document.querySelector(selector); + if (!parent) + throw new Error("Cannot find element \"" + selector + "\" in the DOM."); + } else if (!(parent instanceof HTMLElement)) + throw new Error("The first parameter must be a valid element or selector in the DOM."); + + self.parent = parent; + self.options = {}; + if (options) + self.options = Object.assign({},options); + + // This can be called in the following cases: + // AC already registered and not suspended + // AC already registered and suspended + // AC not registered and not passed in + // AC not registered but passed in (but suspended) + // AC not registered but passed in (not suspended) + // If the AC is already registered, then just use it - ignore what is passed in + // Create the AC if necessary if there isn't one already. + // We don't care right now if the AC is suspended - whenever a button is clicked then we check it. + if (self.options.ac) + registerAudioContext(self.options.ac); + buildDom(self.parent, self.options); + attachListeners(self); + + self.disable = function(isDisabled) { + var el = self.parent.querySelector(".abcjs-inline-audio"); + if (isDisabled) + el.classList.add("abcjs-disabled"); + else + el.classList.remove("abcjs-disabled"); + }; + self.setWarp = function(tempo, warp) { + var el = self.parent.querySelector(".abcjs-midi-tempo"); + el.value = Math.round(warp); + self.setTempo(tempo) + }; + self.setTempo = function(tempo) { + var el = self.parent.querySelector(".abcjs-midi-current-tempo"); + if (el) + el.innerHTML = Math.round(tempo); + }; + self.resetAll = function() { + var pushedButtons = self.parent.querySelectorAll(".abcjs-pushed"); + for (var i = 0; i < pushedButtons.length; i++) { + var button = pushedButtons[i]; + button.classList.remove("abcjs-pushed"); + } + }; + self.pushPlay = function(push) { + var startButton = self.parent.querySelector(".abcjs-midi-start"); + if (!startButton) + return; + if (push) + startButton.classList.add("abcjs-pushed"); + else + startButton.classList.remove("abcjs-pushed"); + }; + self.pushLoop = function(push) { + var loopButton = self.parent.querySelector(".abcjs-midi-loop"); + if (!loopButton) + return; + if (push) + loopButton.classList.add("abcjs-pushed"); + else + loopButton.classList.remove("abcjs-pushed"); + }; + + self.setProgress = function (percent, totalTime) { + var progressBackground = self.parent.querySelector(".abcjs-midi-progress-background"); + var progressThumb = self.parent.querySelector(".abcjs-midi-progress-indicator"); + if (!progressBackground || !progressThumb) + return; + var width = progressBackground.clientWidth; + var left = width * percent; + progressThumb.style.left = left + "px"; + + var clock = self.parent.querySelector(".abcjs-midi-clock"); + if (clock) { + var totalSeconds = (totalTime * percent) / 1000; + var minutes = Math.floor(totalSeconds / 60); + var seconds = Math.floor(totalSeconds % 60); + var secondsFormatted = seconds < 10 ? "0" + seconds : seconds; + clock.innerHTML = minutes + ":" + secondsFormatted; + } + }; + + if (self.options.afterResume) { + var isResumed = false; + if (self.options.ac) { + isResumed = self.options.ac.state !== "suspended"; + } else if (activeAudioContext()) { + isResumed = activeAudioContext().state !== "suspended"; + } + if (isResumed) + self.options.afterResume(); + } +} + +function buildDom(parent, options) { + var hasLoop = !!options.loopHandler; + var hasRestart = !!options.restartHandler; + var hasPlay = !!options.playHandler || !!options.playPromiseHandler; + var hasProgress = !!options.progressHandler; + var hasWarp = !!options.warpHandler; + var hasClock = options.hasClock !== false; + + var html = '
\n'; + if (hasLoop) { + var repeatTitle = options.repeatTitle ? options.repeatTitle : "Click to toggle play once/repeat."; + var repeatAria = options.repeatAria ? options.repeatAria : repeatTitle; + html += '\n'; + } + if (hasRestart) { + var restartTitle = options.restartTitle ? options.restartTitle : "Click to go to beginning."; + var restartAria = options.restartAria ? options.restartAria : restartTitle; + html += '\n'; + } + if (hasPlay) { + var playTitle = options.playTitle ? options.playTitle : "Click to play/pause."; + var playAria = options.playAria ? options.playAria : playTitle; + html += '\n'; + } + if (hasProgress) { + var randomTitle = options.randomTitle ? options.randomTitle : "Click to change the playback position."; + var randomAria = options.randomAria ? options.randomAria : randomTitle; + html += '\n'; + } + if (hasClock) { + html += '\n'; + } + if (hasWarp) { + var warpTitle = options.warpTitle ? options.warpTitle : "Change the playback speed."; + var warpAria = options.warpAria ? options.warpAria : warpTitle; + var bpm = options.bpm ? options.bpm : "BPM"; + html += ' ( ' + bpm + ')\n'; + } + html += '
CSS required: load abcjs-audio.css
'; + html += '
\n'; + parent.innerHTML = html; +} + +function acResumerMiddleWare(next, ev, playBtn, afterResume, isPromise) { + var needsInit = true; + if (!activeAudioContext()) { + registerAudioContext(); + } else { + needsInit = activeAudioContext().state === "suspended"; + } + if (!supportsAudio()) { + throw { status: "NotSupported", message: "This browser does not support audio."}; + } + + if ((needsInit || isPromise) && playBtn) + playBtn.classList.add("abcjs-loading"); + + if (needsInit) { + activeAudioContext().resume().then(function () { + if (afterResume) { + afterResume().then(function (response) { + doNext(next, ev, playBtn, isPromise); + }); + } else { + doNext(next, ev, playBtn, isPromise); + } + }); + } else { + doNext(next, ev, playBtn, isPromise); + } +} + +function doNext(next, ev, playBtn, isPromise) { + if (isPromise) { + next(ev).then(function() { + if (playBtn) + playBtn.classList.remove("abcjs-loading"); + }); + } else { + next(ev); + if (playBtn) + playBtn.classList.remove("abcjs-loading"); + } +} + +function attachListeners(self) { + var hasLoop = !!self.options.loopHandler; + var hasRestart = !!self.options.restartHandler; + var hasPlay = !!self.options.playHandler || !!self.options.playPromiseHandler; + var hasProgress = !!self.options.progressHandler; + var hasWarp = !!self.options.warpHandler; + var playBtn = self.parent.querySelector(".abcjs-midi-start"); + + if (hasLoop) + self.parent.querySelector(".abcjs-midi-loop").addEventListener("click", function(ev){acResumerMiddleWare(self.options.loopHandler, ev, playBtn, self.options.afterResume)}); + if (hasRestart) + self.parent.querySelector(".abcjs-midi-reset").addEventListener("click", function(ev){acResumerMiddleWare(self.options.restartHandler, ev, playBtn, self.options.afterResume)}); + if (hasPlay) + playBtn.addEventListener("click", function(ev){ + acResumerMiddleWare( + self.options.playPromiseHandler || self.options.playHandler, + ev, + playBtn, + self.options.afterResume, + !!self.options.playPromiseHandler) + }); + if (hasProgress) + self.parent.querySelector(".abcjs-midi-progress-background").addEventListener("click", function(ev){acResumerMiddleWare(self.options.progressHandler, ev, playBtn, self.options.afterResume)}); + if (hasWarp) + self.parent.querySelector(".abcjs-midi-tempo").addEventListener("change", function(ev){acResumerMiddleWare(self.options.warpHandler, ev, playBtn, self.options.afterResume)}); +} +module.exports = CreateSynthControl; diff --git a/src/synth/create-synth.js b/src/synth/create-synth.js new file mode 100644 index 0000000000000000000000000000000000000000..0c028d1a745ade4d8ef9bc9e033c8479bc591923 --- /dev/null +++ b/src/synth/create-synth.js @@ -0,0 +1,631 @@ +var getNote = require('./load-note'); +var createNoteMap = require('./create-note-map'); +var registerAudioContext = require('./register-audio-context'); +var activeAudioContext = require('./active-audio-context'); +var supportsAudio = require('./supports-audio'); +var pitchToNoteName = require('./pitch-to-note-name'); +var instrumentIndexToName = require('./instrument-index-to-name'); +var downloadBuffer = require('./download-buffer'); +var placeNote = require('./place-note'); +var soundsCache = require('./sounds-cache'); + +// TODO-PER: remove the midi tests from here: I don't think the object can be constructed unless it passes. +var notSupportedMessage = "MIDI is not supported in this browser."; + +var originalSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/"; +// These are the original soundfonts supplied. They will need a volume boost: +var defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/"; +var alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/"; + +function CreateSynth() { + var self = this; + self.audioBufferPossible = undefined; + self.directSource = []; // type: AudioBufferSourceNode + self.startTimeSec = undefined; // the time (in seconds) that the audio started: used for pause to get the pausedTimeSec. + self.pausedTimeSec = undefined; // the position (in seconds) that the audio was paused: used for resume. + self.audioBuffers = []; // cache of the buffers so starting play can be fast. + self.duration = undefined; // the duration of the tune in seconds. + self.isRunning = false; // whether there is currently a sound buffer running. + self.options = undefined + self.pickupLength = 0 + + // Load and cache all needed sounds + self.init = function(options) { + if (!options) + options = {}; + if (options.options) + self.options = options.options + registerAudioContext(options.audioContext); // This works no matter what - if there is already an ac it is a nop; if the context is not passed in, then it creates one. + var startTime = activeAudioContext().currentTime; + self.debugCallback = options.debugCallback; + if (self.debugCallback) + self.debugCallback("init called"); + self.audioBufferPossible = self._deviceCapable(); + if (!self.audioBufferPossible) + return Promise.reject({ status: "NotSupported", message: notSupportedMessage}); + var params = options.options ? options.options : {}; + self.soundFontUrl = params.soundFontUrl ? params.soundFontUrl : defaultSoundFontUrl; + if (self.soundFontUrl[self.soundFontUrl.length-1] !== '/') + self.soundFontUrl += '/'; + if (params.soundFontVolumeMultiplier || params.soundFontVolumeMultiplier === 0) + self.soundFontVolumeMultiplier = params.soundFontVolumeMultiplier; + else if (self.soundFontUrl === defaultSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl) + self.soundFontVolumeMultiplier = 3.0; + else if (self.soundFontUrl === originalSoundFontUrl) + self.soundFontVolumeMultiplier = 0.4; + else + self.soundFontVolumeMultiplier = 1.0; + if (params.programOffsets) + self.programOffsets = params.programOffsets; + else if (self.soundFontUrl === originalSoundFontUrl) + self.programOffsets = { + "bright_acoustic_piano": 20, + "honkytonk_piano": 20, + "electric_piano_1": 30, + "electric_piano_2": 30, + "harpsichord": 40, + "clavinet": 20, + "celesta": 20, + "glockenspiel": 40, + "vibraphone": 30, + "marimba": 35, + "xylophone": 30, + "tubular_bells": 35, + "dulcimer": 30, + "drawbar_organ": 20, + "percussive_organ": 25, + "rock_organ": 20, + "church_organ": 40, + "reed_organ": 40, + "accordion": 40, + "harmonica": 40, + "acoustic_guitar_nylon": 20, + "acoustic_guitar_steel": 30, + "electric_guitar_jazz": 25, + "electric_guitar_clean": 15, + "electric_guitar_muted": 35, + "overdriven_guitar": 25, + "distortion_guitar": 20, + "guitar_harmonics": 30, + "electric_bass_finger": 15, + "electric_bass_pick": 30, + "fretless_bass": 40, + "violin": 105, + "viola": 50, + "cello": 40, + "contrabass": 60, + "trumpet": 10, + "trombone": 90, + "alto_sax": 20, + "tenor_sax": 20, + "clarinet": 20, + "flute": 50, + "banjo": 50, + "woodblock": 20, + }; + else + self.programOffsets = {}; + var p = params.fadeLength !== undefined ? parseInt(params.fadeLength,10) : NaN; + self.fadeLength = isNaN(p) ? 200 : p; + p = params.noteEnd !== undefined ? parseInt(params.noteEnd,10) : NaN; + self.noteEnd = isNaN(p) ? 0 : p; + + self.pan = params.pan; + self.meterSize = 1; + if (options.visualObj) { + self.flattened = options.visualObj.setUpAudio(params); + var meter = options.visualObj.getMeterFraction(); + if (meter.den) + self.meterSize = options.visualObj.getMeterFraction().num / options.visualObj.getMeterFraction().den; + self.pickupLength = options.visualObj.getPickupLength() + } else if (options.sequence) + self.flattened = options.sequence; + else + return Promise.reject(new Error("Must pass in either a visualObj or a sequence")); + self.millisecondsPerMeasure = options.millisecondsPerMeasure ? options.millisecondsPerMeasure : (options.visualObj ? options.visualObj.millisecondsPerMeasure(self.flattened.tempo) : 1000); + self.beatsPerMeasure = options.visualObj ? options.visualObj.getBeatsPerMeasure() : 4; + self.sequenceCallback = params.sequenceCallback; + self.callbackContext = params.callbackContext; + self.onEnded = params.onEnded; + self.meterFraction = options.visualObj ? options.visualObj.getMeterFraction() : {den: 1} // If we are given a sequence instead of a regular visual obj, then don't do the swing + + var allNotes = {}; + var cached = []; + var errorNotes = []; + var currentInstrument = instrumentIndexToName[0]; + self.flattened.tracks.forEach(function(track) { + track.forEach(function(event) { + if (event.cmd === "program" && instrumentIndexToName[event.instrument]) + currentInstrument = instrumentIndexToName[event.instrument]; + if (event.pitch !== undefined) { + var pitchNumber = event.pitch; + var noteName = pitchToNoteName[pitchNumber]; + var inst = event.instrument !== undefined ? instrumentIndexToName[event.instrument] : currentInstrument + if (noteName) { + if (!allNotes[inst]) + allNotes[inst] = {}; + if (!soundsCache[inst] || !soundsCache[inst][noteName]) + allNotes[inst][noteName] = true; + else { + var label2 = inst+":"+noteName + if (cached.indexOf(label2) < 0) + cached.push(label2); + } + } else { + var label = inst+":"+noteName + console.log("Can't find note: ", pitchNumber, label); + if (errorNotes.indexOf(label) < 0) + errorNotes.push(label) + } + } + }); + }); + if (self.debugCallback) + self.debugCallback("note gathering time = " + Math.floor((activeAudioContext().currentTime - startTime)*1000)+"ms"); + startTime = activeAudioContext().currentTime; + + var notes = []; + Object.keys(allNotes).forEach(function(instrument) { + Object.keys(allNotes[instrument]).forEach(function(note) { + notes.push({ instrument: instrument, note: note }); + }); + }); + if (self.debugCallback) + self.debugCallback("notes "+JSON.stringify(notes)); + + // If there are lots of notes, load them in batches + var batches = []; + var CHUNK = 256; + for (var i=0; i < notes.length; i += CHUNK) { + batches.push(notes.slice(i, i + CHUNK)); + } + + return new Promise(function(resolve, reject) { + var results = { + cached: cached, + error: errorNotes, + loaded: [] + }; + + var index = 0; + var next = function() { + if (self.debugCallback) + self.debugCallback("loadBatch idx="+index+ " len="+batches.length); + + if (index < batches.length) { + self._loadBatch(batches[index], self.soundFontUrl, startTime).then(function(data) { + if (self.debugCallback) + self.debugCallback("loadBatch then"); + startTime = activeAudioContext().currentTime; + if (data) { + if (data.error) + results.error = results.error.concat(data.error); + if (data.loaded) + results.loaded = results.loaded.concat(data.loaded); + } + index++; + next(); + }, reject); + } else { + if (self.debugCallback) + self.debugCallback("resolve init"); + + resolve(results); + } + }; + next(); + }); + }; + + self._loadBatch = (function(batch, soundFontUrl, startTime, delay) { + // This is called recursively to see if the sounds have loaded. The "delay" parameter is how long it has been since the original call. + var promises = []; + batch.forEach(function(item) { + if (self.debugCallback) + self.debugCallback("getNote " + item.instrument+':'+item.note); + promises.push(getNote(soundFontUrl, item.instrument, item.note, activeAudioContext())); + }); + return Promise.all(promises).then(function(response) { + if (self.debugCallback) + self.debugCallback("mp3 load time = " + Math.floor((activeAudioContext().currentTime - startTime)*1000)+"ms"); + var loaded = []; + var cached = []; + var pending = []; + var error = []; + for (var i = 0; i < response.length; i++) { + var oneResponse = response[i]; + var which = oneResponse.instrument + ":" + oneResponse.name; + if (oneResponse.status === "loaded") + loaded.push(which); + else if (oneResponse.status === "pending") + pending.push(which); + else if (oneResponse.status === "cached") + cached.push(which); + else + error.push(which + ' ' + oneResponse.message); + } + if (pending.length > 0) { + if (self.debugCallback) + self.debugCallback("pending " + JSON.stringify(pending)); + // There was probably a second call for notes before the first one finished, so just retry a few times to see if they stop being pending. + // Retry quickly at first so that there isn't an unnecessary delay, but increase the delay each time. + if (!delay) + delay = 50; + else + delay = delay * 2; + if (delay < 90000) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + var newBatch = []; + for (i = 0; i < pending.length; i++) { + which = pending[i].split(":"); + newBatch.push({instrument: which[0], note: which[1]}); + } + if (self.debugCallback) + self.debugCallback("retry " + JSON.stringify(newBatch)); + self._loadBatch(newBatch, soundFontUrl, startTime, delay).then(function (response) { + resolve(response); + }).catch(function (error) { + reject(error); + }); + }, delay); + }); + } else { + var list = []; + for (var j = 0; j < batch.length; j++) + list.push(batch[j].instrument+'/'+batch[j].note) + if (self.debugCallback) + self.debugCallback("loadBatch timeout") + return Promise.reject(new Error("timeout attempting to load: " + list.join(", "))); + } + } else { + if (self.debugCallback) + self.debugCallback("loadBatch resolve") + return Promise.resolve({loaded: loaded, cached: cached, error: error}); + } + }).catch(function (error) { + if (self.debugCallback) + self.debugCallback("loadBatch catch "+error.message) + }); + }); + + self.prime = function() { + // At this point all of the notes are loaded. This function writes them into the output buffer. + // Most music has a lot of repeating notes. If a note is the same pitch, volume, length, etc. as another one, + // It saves a lot of time to just create it once and place it repeatedly where ever it needs to be. + var fadeTimeSec = self.fadeLength/1000; + self.isRunning = false; + if (!self.audioBufferPossible) + return Promise.reject(new Error(notSupportedMessage)); + if (self.debugCallback) + self.debugCallback("prime called"); + return new Promise(function(resolve) { + var startTime = activeAudioContext().currentTime; + var tempoMultiplier = self.millisecondsPerMeasure / 1000 / self.meterSize; + self.duration = self.flattened.totalDuration * tempoMultiplier; + if(self.duration <= 0) { + self.audioBuffers = []; + return resolve({ status: "empty", seconds: 0}); + } + self.duration += fadeTimeSec; + var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration); + + // There might be a previous run that needs to be turned off. + self.stop(); + + var noteMapTracks = createNoteMap(self.flattened); + + if (self.options.swing) + addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength) + + if (self.sequenceCallback) + self.sequenceCallback(noteMapTracks, self.callbackContext); + + var panDistances = setPan(noteMapTracks.length, self.pan); + + // Create a simple list of all the unique sounds in this music and where they should be placed. + // There appears to be a limit on how many audio buffers can be created at once so this technique limits the number needed. + var uniqueSounds = {}; + noteMapTracks.forEach(function(noteMap, trackNumber) { + var panDistance = panDistances && panDistances.length > trackNumber ? panDistances[trackNumber] : 0; + noteMap.forEach(function(note) { + var key = note.instrument + ':' + note.pitch + ':' +note.volume + ':' + Math.round((note.end-note.start)*1000)/1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + (note.cents ? note.cents : 0); + if (self.debugCallback) + self.debugCallback("noteMapTrack "+key) + if (!uniqueSounds[key]) + uniqueSounds[key] = []; + uniqueSounds[key].push(note.start); + }); + }); + + // Now that we know what we are trying to create, construct the audio buffer by creating each sound and placing it. + var allPromises = []; + var audioBuffer = activeAudioContext().createBuffer(2, totalSamples, activeAudioContext().sampleRate); + for (var key2 = 0; key2 < Object.keys(uniqueSounds).length; key2++) { + var k = Object.keys(uniqueSounds)[key2]; + var parts = k.split(":"); + var cents = parts[6] !== undefined ? parseFloat(parts[6]) : 0; + parts = {instrument: parts[0], pitch: parseInt(parts[1], 10), volume: parseInt(parts[2], 10), len: parseFloat(parts[3]), pan: parseFloat(parts[4]), tempoMultiplier: parseFloat(parts[5]), cents: cents}; + allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k], self.soundFontVolumeMultiplier, self.programOffsets[parts.instrument], fadeTimeSec, self.noteEnd/1000, self.debugCallback)); + } + self.audioBuffers = [audioBuffer]; + + if (self.debugCallback) { + self.debugCallback("sampleRate = " + activeAudioContext().sampleRate); + self.debugCallback("totalSamples = " + totalSamples); + self.debugCallback("creationTime = " + Math.floor((activeAudioContext().currentTime - startTime)*1000) + "ms"); + } + function resolveData(me) { + var duration = me && me.audioBuffers && me.audioBuffers.length > 0 ? me.audioBuffers[0].duration : 0; + return { status: activeAudioContext().state, duration: duration} + } + Promise.all(allPromises).then(function() { + // Safari iOS can mess with the audioContext state, so resume if needed. + if (activeAudioContext().state === "suspended") { + activeAudioContext().resume().then(function () { + resolve(resolveData(self)); + }) + } else if (activeAudioContext().state === "interrupted") { + activeAudioContext().suspend().then(function () { + activeAudioContext().resume().then(function () { + resolve(resolveData(self)); + }) + }) + } else { + resolve(resolveData(self)); + } + }); + }); + }; + + function setPan(numTracks, panParam) { + // panParam, if it is set, can be either a number representing the separation between each track, + // or an array, which is the absolute pan position for each track. + if (panParam === null || panParam === undefined) + return null; + + var panDistances = []; + if (panParam.length) { + // We received an array. If there are the same number of items in the pan array as the number of tracks, + // it all lines up perfectly. If there are more items in the pan array than the tracks then the excess items are ignored. + // If there are more tracks than items in the pan array then the remaining tracks are placed in the middle. + // If any of the pan numbers are out of range then they are adjusted. + for (var pp = 0; pp < numTracks; pp++) { + if (pp < panParam.length) { + var x = parseFloat(panParam[pp]); + if (x < -1) + x = -1; + else if (x > 1) + x = 1; + panDistances.push(x); + } else + panDistances.push(0) + } + return panDistances; + } else { + var panNumber = parseFloat(panParam); + // the separation needs to be no further than 2 (i.e. -1 to 1) so test to see if there are too many tracks for the passed in distance + if (panNumber*(numTracks-1) > 2) + return null; + + // If there are an even number of tracks, then offset so that the first two are centered around the middle + var even = numTracks % 2 === 0; + var currLow = even ? 0 - panNumber/2 : 0; + var currHigh = currLow+panNumber; + // Now add the tracks to either side + for (var p = 0; p < numTracks; p++) { + even = p % 2 === 0; + if (even) { + panDistances.push(currLow); + currLow -= panNumber; + } else { + panDistances.push(currHigh); + currHigh += panNumber; + } + } + return panDistances; + } + // There was either no panning, or the parameters were illegal + return null; + } + + // This is called after everything is set up, so it can quickly make sound + self.start = function() { + if (!self.audioBufferPossible) + throw new Error(notSupportedMessage); + if (self.debugCallback) + self.debugCallback("start called"); + + var resumePosition = self.pausedTimeSec ? self.pausedTimeSec : 0; + self._kickOffSound(resumePosition); + self.startTimeSec = activeAudioContext().currentTime - resumePosition; + self.pausedTimeSec = undefined; + + if (self.debugCallback) + self.debugCallback("MIDI STARTED", self.startTimeSec); + }; + + self.pause = function() { + if (!self.audioBufferPossible) + throw new Error(notSupportedMessage); + if (self.debugCallback) + self.debugCallback("pause called"); + + self.pausedTimeSec = self.stop(); + return self.pausedTimeSec; + }; + + self.resume = function() { + self.start(); + }; + + self.seek = function(position, units) { + var offset; + switch (units) { + case "seconds": + offset = position; + break; + case "beats": + offset = position * self.millisecondsPerMeasure / self.beatsPerMeasure / 1000; + break; + default: + // this is "percent" or any illegal value + offset = (self.duration-self.fadeLength/1000) * position; + break; + } + + // TODO-PER: can seek when paused or when playing + if (!self.audioBufferPossible) + throw new Error(notSupportedMessage); + if (self.debugCallback) + self.debugCallback("seek called sec=" + offset); + + if (self.isRunning) { + self.stop(); + self._kickOffSound(offset); + } else { + self.pausedTimeSec = offset; + } + self.pausedTimeSec = offset; + }; + + self.stop = function() { + self.isRunning = false; + self.pausedTimeSec = undefined; + self.directSource.forEach(function(source) { + try { + source.stop(); + } catch (error) { + // We don't care if self succeeds: it might fail if something else turned off the sound or it ended for some reason. + console.log("direct source didn't stop:", error) + } + }); + self.directSource = []; + var elapsed = activeAudioContext().currentTime - self.startTimeSec; + return elapsed; + }; + self.finished = function() { + self.startTimeSec = undefined; + self.pausedTimeSec = undefined; + self.isRunning = false; + }; + + self.download = function() { + return downloadBuffer(self); + }; + + self.getAudioBuffer = function() { + return self.audioBuffers[0]; + }; + + self.getIsRunning = function() { + return self.isRunning; + } + + /////////////// Private functions ////////////// + + self._deviceCapable = function() { + if (!supportsAudio()) { + console.warn(notSupportedMessage); + if (self.debugCallback) + self.debugCallback(notSupportedMessage); + return false; + } + return true; + }; + + self._kickOffSound = function(seconds) { + self.isRunning = true; + self.directSource = []; + self.audioBuffers.forEach(function(audioBuffer, trackNum) { + self.directSource[trackNum] = activeAudioContext().createBufferSource(); // creates a sound source + self.directSource[trackNum].buffer = audioBuffer; // tell the source which sound to play + self.directSource[trackNum].connect(activeAudioContext().destination); // connect the source to the context's destination (the speakers) + }); + self.directSource.forEach(function(source) { + source.start(0, seconds); + }); + if (self.onEnded) { + self.directSource[0].onended = function () { + self.onEnded(self.callbackContext); + }; + } + }; + + function addSwing(noteMapTracks, swing, meterFraction, pickupLength) { + + // we can only swing in X/4 and X/8 meters. + if (meterFraction.den != 4 && meterFraction.den != 8) + return; + + swing = parseFloat(swing); + + // 50 (or less) is no swing, + if (isNaN(swing) || swing <= 50) + return; + + // 66 is triplet swing 2:1, and + // 60 is swing with a ratio of 3:2. + // 75 is the maximum swing where the first eight is played as a dotted eight and the second as a sixteenth. + if (swing > 75) + swing = 75; + + // convert the swing percentage to a percentage of increase for the first half of the beat + swing = swing/50 - 1; + + // The volume of the swung notes is increased by this factor + // could be also in the settings. Try out values such 0.1, 0.2 + var volumeIncrease = 0.0; + + // the beatLength in X/8 meters + var beatLength = 0.25; + + // in X/8 meters the 16s swing so the beatLength is halved + if (meterFraction.den === 8) + beatLength = beatLength/2; + + // duration of a half beat + var halfbeatLength = beatLength/2; + + // the extra duration of the first swung notes and the delay of the second notes + var swingDuration = halfbeatLength * swing; + + for (var t = 0; t < noteMapTracks.length; t++) { + var track = noteMapTracks[t]; + for (var i = 0; i < track.length; i++) { + var event = track[i]; + if ( + // is halfbeat + (event.start-pickupLength) % halfbeatLength == 0 && (event.start-pickupLength) % beatLength != 0 + && ( + // the previous note is on the beat or before OR there is no previous note + i == 0 + || track[i-1].start <= track[i].start - halfbeatLength + ) + && ( + // the next note is on the beat or after OR there is no next note + i == track.length - 1 + || track[i+1].start >= track[i].start + halfbeatLength + ) + ) { + var oldEventStart = event.start; + + event.start += swingDuration; + + // Increase volume of swung notes + event.volume *= 1 + volumeIncrease; + + // if there is a previous note ending at the start of this note, extend its end + // and decrease its volume + if (i > 0 && track[i-1].end == oldEventStart) { + track[i-1].end = event.start; + track[i-1].volume *= 1 - volumeIncrease; + } + } + } + } + } + +} + +module.exports = CreateSynth; diff --git a/src/synth/download-buffer.js b/src/synth/download-buffer.js new file mode 100644 index 0000000000000000000000000000000000000000..e21165f86563cc3fc947fc5bd27404ca01bf285c --- /dev/null +++ b/src/synth/download-buffer.js @@ -0,0 +1,63 @@ +var downloadBuffer = function(buffer) { + return window.URL.createObjectURL(bufferToWave(buffer.audioBuffers)); +}; + +// Convert an AudioBuffer to a Blob using WAVE representation +function bufferToWave(audioBuffers) { + var audioBuffer = audioBuffers[0]; + var numOfChan = audioBuffer.numberOfChannels; + var length = audioBuffer.length * numOfChan * 2 + 44; + var buffer = new ArrayBuffer(length); + var view = new DataView(buffer); + var channels = []; + var i; + var sample; + var offset = 0; + var pos = 0; + + // write WAVE header + setUint32(0x46464952); // "RIFF" + setUint32(length - 8); // file length - 8 + setUint32(0x45564157); // "WAVE" + + setUint32(0x20746d66); // "fmt " chunk + setUint32(16); // length = 16 + setUint16(1); // PCM (uncompressed) + setUint16(numOfChan); + setUint32(audioBuffer.sampleRate); + setUint32(audioBuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec + setUint16(numOfChan * 2); // block-align + setUint16(16); // 16-bit (hardcoded in this demo) + + setUint32(0x61746164); // "data" - chunk + setUint32(length - pos - 4); // chunk length + + // write interleaved data + for(i = 0; i < numOfChan; i++) + channels.push(audioBuffer.getChannelData(i)); + + while(pos < length) { + for(i = 0; i < channels.length; i++) { // interleave channels + sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp + sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // scale to 16-bit signed int + view.setInt16(pos, sample, true); // write 16-bit sample + pos += 2; + } + offset++; // next source sample + } + + // create Blob + return new Blob([buffer], {type: "audio/wav"}); + + function setUint16(data) { + view.setUint16(pos, data, true); + pos += 2; + } + + function setUint32(data) { + view.setUint32(pos, data, true); + pos += 4; + } +} + +module.exports = downloadBuffer; diff --git a/src/synth/get-midi-file.js b/src/synth/get-midi-file.js new file mode 100644 index 0000000000000000000000000000000000000000..ba82fbff2c9f4a23c2a994dc856ad4f5c47684c1 --- /dev/null +++ b/src/synth/get-midi-file.js @@ -0,0 +1,73 @@ +var tunebook = require('../api/abc_tunebook'); +var midiCreate = require('../midi/abc_midi_create'); + +var getMidiFile = function(source, options) { + var params = {}; + if (options) { + for (var key in options) { + if (options.hasOwnProperty(key)) { + params[key] = options[key]; + } + } + } + params.generateInline = false; + + function callback(div, tune, index) { + var downloadMidi = midiCreate(tune, params); + switch (params.midiOutputType) { + case "encoded": + return downloadMidi; + case "binary": + var decoded = downloadMidi.replace("data:audio/midi,", ""); + decoded = decoded.replace(/MThd/g,"%4d%54%68%64"); + decoded = decoded.replace(/MTrk/g,"%4d%54%72%6b"); + var buffer = new ArrayBuffer(decoded.length/3); + var output = new Uint8Array(buffer); + for (var i = 0; i < decoded.length/3; i++) { + var p = i*3+1; + var d = parseInt(decoded.substring(p, p+2), 16); + output[i] = d; + } + return output; + case "link": + default: + return generateMidiDownloadLink(tune, params, downloadMidi, index); + } + } + + if (typeof source === "string") + return tunebook.renderEngine(callback, "*", source, params); + else + return callback(null, source, 0); +}; + +function isFunction(functionToCheck) { + var getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; +} + +var generateMidiDownloadLink = function(tune, midiParams, midi, index) { + var divClasses = ['abcjs-download-midi', 'abcjs-midi-' + index] + if (midiParams.downloadClass) + divClasses.push(midiParams.downloadClass) + var html = '
'; + if (midiParams.preTextDownload) + html += midiParams.preTextDownload; + var title = tune.metaText && tune.metaText.title ? tune.metaText.title : 'Untitled'; + var label; + if (midiParams.downloadLabel && isFunction(midiParams.downloadLabel)) + label = midiParams.downloadLabel(tune, index); + else if (midiParams.downloadLabel) + label = midiParams.downloadLabel.replace(/%T/, title); + else + label = "Download MIDI for \"" + title + "\""; + title = title.toLowerCase().replace(/'/g, '').replace(/\W/g, '_').replace(/__/g, '_'); + var filename = (midiParams.fileName) ? midiParams.fileName : title + '.midi'; + html += '' + label + ''; + if (midiParams.postTextDownload) + html += midiParams.postTextDownload; + return html + "
"; +}; + + +module.exports = getMidiFile; diff --git a/src/synth/images/loading.svg.js b/src/synth/images/loading.svg.js new file mode 100644 index 0000000000000000000000000000000000000000..02f061891038918964c93f2ac13140b950fc88d8 --- /dev/null +++ b/src/synth/images/loading.svg.js @@ -0,0 +1,7 @@ +var svg = ` + + + +` + +module.exports = svg diff --git a/src/synth/images/loop.svg.js b/src/synth/images/loop.svg.js new file mode 100644 index 0000000000000000000000000000000000000000..96cbcb6dac908829ded961bec453692e134e9115 --- /dev/null +++ b/src/synth/images/loop.svg.js @@ -0,0 +1,65 @@ +var svg = ` + + + + + + +` + +module.exports = svg diff --git a/src/synth/images/pause.svg.js b/src/synth/images/pause.svg.js new file mode 100644 index 0000000000000000000000000000000000000000..1b4143fc49430ac878bf6fb9450dd481d687d8f9 --- /dev/null +++ b/src/synth/images/pause.svg.js @@ -0,0 +1,10 @@ +var svg = ` + + + + + + +` + +module.exports = svg diff --git a/src/synth/images/play.svg.js b/src/synth/images/play.svg.js new file mode 100644 index 0000000000000000000000000000000000000000..17fe85aa1996729e70018439c056517e4cdf7084 --- /dev/null +++ b/src/synth/images/play.svg.js @@ -0,0 +1,9 @@ +var svg = ` + + + + + +` + +module.exports = svg diff --git a/src/synth/images/reset.svg.js b/src/synth/images/reset.svg.js new file mode 100644 index 0000000000000000000000000000000000000000..cb05aa01704ea78b076cd262351262f8e5d7c9bb --- /dev/null +++ b/src/synth/images/reset.svg.js @@ -0,0 +1,10 @@ +var svg = ` + + + + + + +` + +module.exports = svg diff --git a/src/synth/instrument-index-to-name.js b/src/synth/instrument-index-to-name.js new file mode 100644 index 0000000000000000000000000000000000000000..50a3c343b02581a68a3d8560e62db0c51a1a92a9 --- /dev/null +++ b/src/synth/instrument-index-to-name.js @@ -0,0 +1,149 @@ +var instrumentIndexToName = [ + "acoustic_grand_piano", + "bright_acoustic_piano", + "electric_grand_piano", + "honkytonk_piano", + "electric_piano_1", + "electric_piano_2", + "harpsichord", + "clavinet", + + "celesta", + "glockenspiel", + "music_box", + "vibraphone", + "marimba", + "xylophone", + "tubular_bells", + "dulcimer", + + "drawbar_organ", + "percussive_organ", + "rock_organ", + "church_organ", + "reed_organ", + "accordion", + "harmonica", + "tango_accordion", + + "acoustic_guitar_nylon", + "acoustic_guitar_steel", + "electric_guitar_jazz", + "electric_guitar_clean", + "electric_guitar_muted", + "overdriven_guitar", + "distortion_guitar", + "guitar_harmonics", + + "acoustic_bass", + "electric_bass_finger", + "electric_bass_pick", + "fretless_bass", + "slap_bass_1", + "slap_bass_2", + "synth_bass_1", + "synth_bass_2", + + "violin", + "viola", + "cello", + "contrabass", + "tremolo_strings", + "pizzicato_strings", + "orchestral_harp", + "timpani", + + "string_ensemble_1", + "string_ensemble_2", + "synth_strings_1", + "synth_strings_2", + "choir_aahs", + "voice_oohs", + "synth_choir", + "orchestra_hit", + + "trumpet", + "trombone", + "tuba", + "muted_trumpet", + "french_horn", + "brass_section", + "synth_brass_1", + "synth_brass_2", + + "soprano_sax", + "alto_sax", + "tenor_sax", + "baritone_sax", + "oboe", + "english_horn", + "bassoon", + "clarinet", + + "piccolo", + "flute", + "recorder", + "pan_flute", + "blown_bottle", + "shakuhachi", + "whistle", + "ocarina", + + "lead_1_square", + "lead_2_sawtooth", + "lead_3_calliope", + "lead_4_chiff", + "lead_5_charang", + "lead_6_voice", + "lead_7_fifths", + "lead_8_bass_lead", + + "pad_1_new_age", + "pad_2_warm", + "pad_3_polysynth", + "pad_4_choir", + "pad_5_bowed", + "pad_6_metallic", + "pad_7_halo", + "pad_8_sweep", + + "fx_1_rain", + "fx_2_soundtrack", + "fx_3_crystal", + "fx_4_atmosphere", + "fx_5_brightness", + "fx_6_goblins", + "fx_7_echoes", + "fx_8_scifi", + + "sitar", + "banjo", + "shamisen", + "koto", + "kalimba", + "bagpipe", + "fiddle", + "shanai", + + "tinkle_bell", + "agogo", + "steel_drums", + "woodblock", + "taiko_drum", + "melodic_tom", + "synth_drum", + "reverse_cymbal", + + "guitar_fret_noise", + "breath_noise", + "seashore", + "bird_tweet", + "telephone_ring", + "helicopter", + "applause", + "gunshot", + + "percussion" +]; + +module.exports = instrumentIndexToName; diff --git a/src/synth/load-note.js b/src/synth/load-note.js new file mode 100644 index 0000000000000000000000000000000000000000..7b45ed8b006a917e6a73139e7c6b8dd23247c01c --- /dev/null +++ b/src/synth/load-note.js @@ -0,0 +1,44 @@ +// Load one mp3 file for one note. +// url = the base url for the soundfont +// instrument = the instrument name (e.g. "acoustic_grand_piano") +// name = the pitch name (e.g. "A3") +var soundsCache = require("./sounds-cache"); + +var getNote = function (url, instrument, name, audioContext) { + if (!soundsCache[instrument]) soundsCache[instrument] = {}; + var instrumentCache = soundsCache[instrument]; + + if (!instrumentCache[name]) + instrumentCache[name] = new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + let noteUrl = url + instrument + "-mp3/" + name + ".mp3"; + xhr.open("GET", noteUrl, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function () { + if (xhr.status !== 200) { + reject(Error("Can't load sound at " + noteUrl + ' status=' + xhr.status)); + return + } + var noteDecoded = function(audioBuffer) { + resolve({instrument: instrument, name: name, status: "loaded", audioBuffer: audioBuffer}) + } + var maybePromise = audioContext.decodeAudioData(xhr.response, noteDecoded, function () { + reject(Error("Can't decode sound at " + noteUrl)); + }); + // In older browsers `BaseAudioContext.decodeAudio()` did not return a promise + if (maybePromise && typeof maybePromise.catch === "function") maybePromise.catch(reject); + }; + xhr.onerror = function () { + reject(Error("Can't load sound at " + noteUrl)); + }; + xhr.send(); + }) + .catch(err => { + console.error("Didn't load note", instrument, name, ":", err.message); + throw err; + }); + + return instrumentCache[name]; +}; + +module.exports = getNote; diff --git a/src/synth/note-to-midi.js b/src/synth/note-to-midi.js new file mode 100644 index 0000000000000000000000000000000000000000..f678b743a8c0c3b4be1754f529d3f677238d867a --- /dev/null +++ b/src/synth/note-to-midi.js @@ -0,0 +1,50 @@ +var accidentals = { + "__": -2, + "_": -1, + "_/": -0.5, + "=": 0, + "": 0, + "^/": 0.5, + "^": 1, + "^^": 2 +} + +var notesInOrder = ['C', '-', 'D', '-', 'E', 'F', '-', 'G', '-', 'A', '-', 'B', 'c', '-', 'd', '-', 'e', 'f', '-', 'g', '-', 'a', '-', 'b'] + +function noteToMidi(note) { + var reg = note.match(/([_^\/]*)([ABCDEFGabcdefg])(,*)('*)/) + if (reg && reg.length === 5) { + var acc = accidentals[reg[1]] + var pitch = notesInOrder.indexOf(reg[2]) + var octave = reg[4].length - reg[3].length + return 48 + pitch + acc + octave * 12; + } + return 0; +} + +function midiToNote(midi) { + midi = parseInt(midi, 10) // TODO-PER: not sure how to handle quarter sharps and flats, so strip them for now. + var octave = Math.floor(midi / 12) + var pitch = midi % 12 + var name = notesInOrder[pitch] + if (name === '-') { + name = '^' + notesInOrder[pitch-1] + } + + if (octave > 4) { + name = name.toLowerCase() + octave -= 5 + while (octave > 0) { + name += "'" + octave-- + } + } else { + while (octave < 4) { + name += ',' + octave++ + } + } + return name +} + +module.exports = {noteToMidi: noteToMidi, midiToNote: midiToNote}; diff --git a/src/synth/pitch-to-note-name.js b/src/synth/pitch-to-note-name.js new file mode 100644 index 0000000000000000000000000000000000000000..7c301d1f7e75a5559ba48a5ec2456c2f1577a71f --- /dev/null +++ b/src/synth/pitch-to-note-name.js @@ -0,0 +1,105 @@ +var pitchToNoteName = { + 21: 'A0', + 22: 'Bb0', + 23: 'B0', + 24: 'C1', + 25: 'Db1', + 26: 'D1', + 27: 'Eb1', + 28: 'E1', + 29: 'F1', + 30: 'Gb1', + 31: 'G1', + 32: 'Ab1', + 33: 'A1', + 34: 'Bb1', + 35: 'B1', + 36: 'C2', + 37: 'Db2', + 38: 'D2', + 39: 'Eb2', + 40: 'E2', + 41: 'F2', + 42: 'Gb2', + 43: 'G2', + 44: 'Ab2', + 45: 'A2', + 46: 'Bb2', + 47: 'B2', + 48: 'C3', + 49: 'Db3', + 50: 'D3', + 51: 'Eb3', + 52: 'E3', + 53: 'F3', + 54: 'Gb3', + 55: 'G3', + 56: 'Ab3', + 57: 'A3', + 58: 'Bb3', + 59: 'B3', + 60: 'C4', + 61: 'Db4', + 62: 'D4', + 63: 'Eb4', + 64: 'E4', + 65: 'F4', + 66: 'Gb4', + 67: 'G4', + 68: 'Ab4', + 69: 'A4', + 70: 'Bb4', + 71: 'B4', + 72: 'C5', + 73: 'Db5', + 74: 'D5', + 75: 'Eb5', + 76: 'E5', + 77: 'F5', + 78: 'Gb5', + 79: 'G5', + 80: 'Ab5', + 81: 'A5', + 82: 'Bb5', + 83: 'B5', + 84: 'C6', + 85: 'Db6', + 86: 'D6', + 87: 'Eb6', + 88: 'E6', + 89: 'F6', + 90: 'Gb6', + 91: 'G6', + 92: 'Ab6', + 93: 'A6', + 94: 'Bb6', + 95: 'B6', + 96: 'C7', + 97: 'Db7', + 98: 'D7', + 99: 'Eb7', + 100: 'E7', + 101: 'F7', + 102: 'Gb7', + 103: 'G7', + 104: 'Ab7', + 105: 'A7', + 106: 'Bb7', + 107: 'B7', + 108: 'C8', + 109: 'Db8', + 110: 'D8', + 111: 'Eb8', + 112: 'E8', + 113: 'F8', + 114: 'Gb8', + 115: 'G8', + 116: 'Ab8', + 117: 'A8', + 118: 'Bb8', + 119: 'B8', + 120: 'C9', + 121: 'Db9' +}; + +module.exports = pitchToNoteName; diff --git a/src/synth/pitches-to-perc.js b/src/synth/pitches-to-perc.js new file mode 100644 index 0000000000000000000000000000000000000000..cb6cadb84095aa799845dcb968b7109efcca33e9 --- /dev/null +++ b/src/synth/pitches-to-perc.js @@ -0,0 +1,76 @@ +var pitchMap = { + f0: "_C", + n0: "=C", + s0: "^C", + x0: "C", + f1: "_D", + n1: "=D", + s1: "^D", + x1: "D", + f2: "_E", + n2: "=E", + s2: "^E", + x2: "E", + f3: "_F", + n3: "=F", + s3: "^F", + x3: "F", + f4: "_G", + n4: "=G", + s4: "^G", + x4: "G", + f5: "_A", + n5: "=A", + s5: "^A", + x5: "A", + f6: "_B", + n6: "=B", + s6: "^B", + x6: "B", + f7: "_c", + n7: "=c", + s7: "^c", + x7: "c", + f8: "_d", + n8: "=d", + s8: "^d", + x8: "d", + f9: "_e", + n9: "=e", + s9: "^e", + x9: "e", + f10: "_f", + n10: "=f", + s10: "^f", + x10: "f", + f11: "_g", + n11: "=g", + s11: "^g", + x11: "g", + f12: "_a", + n12: "=a", + s12: "^a", + x12: "a", + f13: "_b", + n13: "=b", + s13: "^b", + x13: "b", + f14: "_c'", + n14: "=c'", + s14: "^c'", + x14: "c'", + f15: "_d'", + n15: "=d'", + s15: "^d'", + x15: "d'", + f16: "_e'", + n16: "=e'", + s16: "^e'", + x16: "e'", +} + +function pitchesToPerc(pitchObj) { + var pitch = (pitchObj.accidental ? pitchObj.accidental[0] : 'x') + pitchObj.verticalPos; + return pitchMap[pitch]; +} +module.exports = pitchesToPerc; diff --git a/src/synth/place-note.js b/src/synth/place-note.js new file mode 100644 index 0000000000000000000000000000000000000000..a35eaeb214a3d4d094e0fc279394ea1cca601aa8 --- /dev/null +++ b/src/synth/place-note.js @@ -0,0 +1,120 @@ +var soundsCache = require('./sounds-cache'); +var pitchToNoteName = require('./pitch-to-note-name'); +var centsToFactor = require("./cents-to-factor"); + +function placeNote(outputAudioBuffer, sampleRate, sound, startArray, volumeMultiplier, ofsMs, fadeTimeSec, noteEndSec, debugCallback) { + // sound contains { instrument, pitch, volume, len, pan, tempoMultiplier + // len is in whole notes. Multiply by tempoMultiplier to get seconds. + // ofsMs is an offset to subtract from the note to line up programs that have different length onsets. + var OfflineAC = window.OfflineAudioContext || + window.webkitOfflineAudioContext; + + var len = sound.len * sound.tempoMultiplier; + if (ofsMs) + len +=ofsMs/1000; + len -= noteEndSec; + if (len < 0) + len = 0.005; // Have some small audible length no matter how short the note is. + var offlineCtx = new OfflineAC(2,Math.floor((len+fadeTimeSec)*sampleRate),sampleRate); + var noteName = pitchToNoteName[sound.pitch]; + if (!soundsCache[sound.instrument]) { + // It shouldn't happen that the entire instrument cache wasn't created, but this has been seen in practice, so guard against it. + if (debugCallback) + debugCallback('placeNote skipped (instrument empty): '+sound.instrument+':'+noteName) + return Promise.resolve(); + } + var noteBufferPromise = soundsCache[sound.instrument][noteName]; + + if (!noteBufferPromise) { + // if the note isn't present then just skip it - it will leave a blank spot in the audio. + if (debugCallback) + debugCallback('placeNote skipped: '+sound.instrument+':'+noteName) + return Promise.resolve(); + } + + return noteBufferPromise + .then(function (response) { + // create audio buffer + var source = offlineCtx.createBufferSource(); + source.buffer = response.audioBuffer; + + // add gain + // volume can be between 1 to 127. This translation to gain is just trial and error. + // The smaller the first number, the more dynamic range between the quietest to loudest. + // The larger the second number, the louder it will be in general. + var volume = (sound.volume / 96) * volumeMultiplier; + source.gainNode = offlineCtx.createGain(); + + // add pan if supported and present + if (sound.pan && offlineCtx.createStereoPanner) { + source.panNode = offlineCtx.createStereoPanner(); + source.panNode.pan.setValueAtTime(sound.pan, 0); + } + source.gainNode.gain.value = volume; // Math.min(2, Math.max(0, volume)); + source.gainNode.gain.linearRampToValueAtTime(source.gainNode.gain.value, len); + source.gainNode.gain.linearRampToValueAtTime(0.0, len + fadeTimeSec); + + if (sound.cents) { + source.playbackRate.value = centsToFactor(sound.cents); + } + + // connect all the nodes + if (source.panNode) { + source.panNode.connect(offlineCtx.destination); + source.gainNode.connect(source.panNode); + } else { + source.gainNode.connect(offlineCtx.destination); + } + source.connect(source.gainNode); + + // Do the process of creating the sound and placing it in the buffer + source.start(0); + + if (source.noteOff) { + source.noteOff(len + fadeTimeSec); + } else { + source.stop(len + fadeTimeSec); + } + var fnResolve; + offlineCtx.oncomplete = function(e) { + if (e.renderedBuffer && e.renderedBuffer.getChannelData) { // If the system gets overloaded or there are network problems then this can start failing. Just drop the note if so. + for (var i = 0; i < startArray.length; i++) { + //Math.floor(startArray[i] * sound.tempoMultiplier * sampleRate) + var start = startArray[i] * sound.tempoMultiplier; + if (ofsMs) + start -=ofsMs/1000; + if (start < 0) + start = 0; // If the item that is moved back is at the very beginning of the buffer then don't move it back. To do that would be to push everything else forward. TODO-PER: this should probably be done at some point but then it would change timing in existing apps. + start = Math.floor(start*sampleRate); + copyToChannel(outputAudioBuffer, e.renderedBuffer, start); + } + } + if (debugCallback) + debugCallback('placeNote: '+sound.instrument+':'+noteName) + fnResolve(); + }; + offlineCtx.startRendering(); + return new Promise(function(resolve) { + fnResolve = resolve; + }); + }) + .catch(function (error) { + if (debugCallback) + debugCallback('placeNote catch: '+error.message) + return Promise.resolve() + }); +} + +var copyToChannel = function(toBuffer, fromBuffer, start) { + for (var ch = 0; ch < 2; ch++) { + var fromData = fromBuffer.getChannelData(ch); + var toData = toBuffer.getChannelData(ch); + + // Mix the current note into the existing track + for (var n = 0; n < fromData.length; n++) { + toData[n + start] += fromData[n]; + } + } +}; + +module.exports = placeNote; diff --git a/src/synth/play-event.js b/src/synth/play-event.js new file mode 100644 index 0000000000000000000000000000000000000000..2f8f1bd5385bf38ec6336a2a4f74d3f7d312da0e --- /dev/null +++ b/src/synth/play-event.js @@ -0,0 +1,46 @@ +var SynthSequence = require('./synth-sequence'); +var CreateSynth = require('./create-synth'); +var activeAudioContext = require("./active-audio-context"); + +function playEvent(midiPitches, midiGracePitches, millisecondsPerMeasure, soundFontUrl, debugCallback) { + var sequence = new SynthSequence(); + + for (var i = 0; i < midiPitches.length; i++) { + var note = midiPitches[i]; + var trackNum = sequence.addTrack(); + sequence.setInstrument(trackNum, note.instrument); + if (i === 0 && midiGracePitches) { + for (var j = 0; j < midiGracePitches.length; j++) { + var grace = midiGracePitches[j]; + sequence.appendNote(trackNum, grace.pitch, 1 / 64, grace.volume, grace.cents); + } + } + sequence.appendNote(trackNum, note.pitch, note.duration, note.volume, note.cents); + } + + var ac = activeAudioContext(); + if (ac.state === "suspended") { + return ac.resume().then(function () { + return doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback); + }); + } else { + return doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback); + } +} + +function doPlay(sequence, millisecondsPerMeasure, soundFontUrl, debugCallback) { + var buffer = new CreateSynth(); + return buffer.init({ + sequence: sequence, + millisecondsPerMeasure: millisecondsPerMeasure, + options: { soundFontUrl: soundFontUrl }, + debugCallback: debugCallback, + }).then(function () { + return buffer.prime(); + }).then(function () { + buffer.start(); + return Promise.resolve(); + }); +} + +module.exports = playEvent; diff --git a/src/synth/register-audio-context.js b/src/synth/register-audio-context.js new file mode 100644 index 0000000000000000000000000000000000000000..de4b082f185f89d243ee9ea6543193c50dc2d911 --- /dev/null +++ b/src/synth/register-audio-context.js @@ -0,0 +1,22 @@ +// Call this when it is safe for the abcjs to produce sound. This is after the first user gesture on the page. +// If you call it with no parameters, then an AudioContext is created and stored. +// If you call it with a parameter, that is used as an already created AudioContext. + +function registerAudioContext(ac) { + // If one is passed in, that is the one to use even if there was already one created. + if (ac) + window.abcjsAudioContext = ac; + else { + // no audio context passed in, so create it unless there is already one from before. + if (!window.abcjsAudioContext) { + var AudioContext = window.AudioContext || window.webkitAudioContext; + if (AudioContext) + window.abcjsAudioContext = new AudioContext(); + else + return false; + } + } + return window.abcjsAudioContext.state !== "suspended"; +} + +module.exports = registerAudioContext; diff --git a/src/synth/sounds-cache.js b/src/synth/sounds-cache.js new file mode 100644 index 0000000000000000000000000000000000000000..ee6dd8a2c57f518bfe9be34be2556263acc2fbe4 --- /dev/null +++ b/src/synth/sounds-cache.js @@ -0,0 +1,4 @@ +var soundsCache = { +}; + +module.exports = soundsCache; diff --git a/src/synth/supports-audio.js b/src/synth/supports-audio.js new file mode 100644 index 0000000000000000000000000000000000000000..600739c75870f313bf1dd5dcde131cfba80e4fba --- /dev/null +++ b/src/synth/supports-audio.js @@ -0,0 +1,28 @@ +var activeAudioContext = require('./active-audio-context'); + +// +// Support for audio depends on three things: support for Promise, support for AudioContext, and support for AudioContext.resume. +// Unfortunately, AudioContext.resume cannot be detected unless an AudioContext is created, and creating an AudioContext can't +// be done until a user click, so there is no way to know for sure if audio is supported until the user tries. +// We can get close, though - we can test for Promises and AudioContext - there are just a few evergreen browsers that supported +// that before supporting resume, so we'll test what we can. + +// The best use of this routine is to call it before doing any audio related stuff to decide whether to bother. +// But then, call it again after a user interaction to test for resume. + +function supportsAudio() { + if (!window.Promise) + return false; + + if (!window.AudioContext && + !window.webkitAudioContext && + !navigator.mozAudioContext && + !navigator.msAudioContext) + return false; + + var aac = activeAudioContext(); + if (aac) + return aac.resume !== undefined; +} + +module.exports = supportsAudio; diff --git a/src/synth/synth-controller.js b/src/synth/synth-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..449050ba0e0576f8dd6663a20453cabeae6452e2 --- /dev/null +++ b/src/synth/synth-controller.js @@ -0,0 +1,312 @@ +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 = {}; + if (visualOptions.displayPlay === undefined) + visualOptions.displayPlay = true + if (visualOptions.displayProgress === undefined) + visualOptions.displayProgress = true + 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 ? 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.getBoundingClientRect().left) / 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; diff --git a/src/synth/synth-sequence.js b/src/synth/synth-sequence.js new file mode 100644 index 0000000000000000000000000000000000000000..6b5d44a0a339398c7629052390f6b06c673ea739 --- /dev/null +++ b/src/synth/synth-sequence.js @@ -0,0 +1,43 @@ +var SynthSequence = function() { + var self = this; + self.tracks = []; + self.totalDuration = 0; + self.currentInstrument = []; + self.starts = []; + + self.addTrack = function() { + self.tracks.push([]); + self.currentInstrument.push(0); + self.starts.push(0); + return self.tracks.length - 1; + }; + + self.setInstrument = function(trackNumber, instrumentNumber) { + self.tracks[trackNumber].push({ + channel: 0, + cmd: "program", + instrument: instrumentNumber + }); + self.currentInstrument[trackNumber] = instrumentNumber; + }; + + self.appendNote = function(trackNumber, pitch, durationInMeasures, volume, cents) { + var note = { + cmd: "note", + duration: durationInMeasures, + gap: 0, + instrument: self.currentInstrument[trackNumber], + pitch: pitch, + start: self.starts[trackNumber], + volume: volume + }; + if (cents) + note.cents = cents; + self.tracks[trackNumber].push(note); + self.starts[trackNumber] += durationInMeasures; + + self.totalDuration = Math.max(self.totalDuration, self.starts[trackNumber]); + }; +}; + +module.exports = SynthSequence; diff --git a/src/tablatures/abc_tablatures.js b/src/tablatures/abc_tablatures.js new file mode 100644 index 0000000000000000000000000000000000000000..1d247ea11f9343bca48441116ced8654346f8a4b --- /dev/null +++ b/src/tablatures/abc_tablatures.js @@ -0,0 +1,184 @@ +/* + * Tablature Plugins + * tablature are defined dynamically and registered inside abcjs + * by calling abcTablatures.register(plugin) + * where plugin represents a plugin instance + * + */ + +// This is the only entry point to the tablatures. It is called both after parsing a tune and just before engraving + +var TabString = require('./instruments/tab-string'); + +/* extend the table below when adding a new instrument plugin */ + +// Existing tab classes +var pluginTab = { + 'violin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 }, + 'fiddle': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 }, + 'mandolin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 }, + 'guitar': { name: 'StringTab', defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'], isTabBig: true, tabSymbolOffset: 0 }, + 'fiveString': { name: 'StringTab', defaultTuning: ['C,', 'G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: -.95 }, +}; + +var abcTablatures = { + + inited: false, + plugins: {}, + + + /** + * to be called once per plugin for registration + * @param {*} plugin + */ + register: function (plugin) { + var name = plugin.name; + var tablature = plugin.tablature; + this.plugins[name] = tablature; + }, + + setError: function (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 (tune, tuneNumber, params) { + // Called after parsing a tune and before engraving it + if (!this.inited) { + // TODO-PER: I don't think this is needed - the plugin array can be hard coded, right? + this.register(new TabString()); + this.inited = true; + } + 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.name]; + } + 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, + tabType: tabName, + }; + // proceed with tab plugin init + // plugin.init(tune, tuneNumber, args, ii); + returned.push(pluginInstance); + nbPlugins++; + } else if (instrument === '') { + // create a placeholder - there is no tab for this staff + returned.push(null) + } 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 + var staffLineCount = 0; + + // Clear the suppression flag + if (tabs && (tabs.length > 0)) { + var nTabs = tabs.length; + for (var kk = 0; kk < nTabs; ++kk) { + if (tabs[kk] && tabs[kk].params.firstStaffOnly) { + tabs[kk].params.suppress = false; + } + } + } + + for (var ii = 0; ii < abcTune.lines.length; ii++) { + var line = abcTune.lines[ii]; + + if (line.staff) { + staffLineCount++; + } + + // MAE 27Nov2023 + // If tab param "firstStaffOnly", remove the tab label after the first staff + if (staffLineCount > 1) { + if (tabs && (tabs.length > 0)) { + var nTabs = tabs.length; + for (var kk = 0; kk < nTabs; ++kk) { + if (tabs[kk].params.firstStaffOnly) { + // Set the staff draw suppression flag + tabs[kk].params.suppress = true; + } + } + } + } + + var curStaff = line.staff; + if (curStaff) { + var maxStaves = curStaff.length + for (var jj = 0; jj < curStaff.length; jj++) { + + if (tabs[jj] && jj < maxStaves) { + // tablature requested for staff + var tabPlugin = tabs[jj]; + if (tabPlugin.instance == null) { + //console.log("★★★★ Tab Init line: " + ii + " staff: " + jj) + tabPlugin.instance = new tabPlugin.classz(); + // plugin.init(tune, tuneNumber, args, ii); + // call initer first + tabPlugin.instance.init(abcTune, + tabPlugin.tuneNumber, + tabPlugin.params, + tabPlugin.tabType + ); + } + // render next + //console.log("★★★★ Tab Render line: " + ii + " staff: " + jj) + tabPlugin.instance.render(renderer, line, jj); + } + } + } + } + }, + +}; + + +module.exports = abcTablatures; diff --git a/src/tablatures/instruments/string-patterns.js b/src/tablatures/instruments/string-patterns.js new file mode 100644 index 0000000000000000000000000000000000000000..dccbf6eeeda36df3fb1783693d41c938491d297a --- /dev/null +++ b/src/tablatures/instruments/string-patterns.js @@ -0,0 +1,321 @@ +const { noteToMidi } = require('../../synth/note-to-midi'); +var TabNote = require('./tab-note'); +var tabNotes = require('./tab-notes'); + + +function buildCapo(self) { + var capoTuning = null; + var tuning = self.tuning; + if (self.capo > 0) { + capoTuning = []; + for (var iii = 0; iii < tuning.length; iii++) { + var curNote = new TabNote(tuning[iii]); + for (var jjj = 0; jjj < self.capo; jjj++) { + curNote = curNote.nextNote(); + } + capoTuning[iii] = curNote.emit(); + } + } + return capoTuning; +} + +function buildPatterns(self) { + var strings = []; + var tuning = self.tuning; + if (self.capo > 0) { + tuning = self.capoTuning; + } + var pos = tuning.length - 1; + for (var iii = 0; iii < tuning.length; iii++) { + var nextNote = self.highestNote; // highest handled note + if (iii != tuning.length - 1) { + nextNote = tuning[iii + 1]; + } + var stringNotes = tabNotes(tuning[iii], nextNote); + if (stringNotes.error) { + return stringNotes; + } + strings[pos--] = stringNotes; + } + return strings; +} + + +function buildSecond(first) { + var seconds = []; + seconds[0] = []; + var strings = first.strings; + for (var iii = 1; iii < strings.length; iii++) { + seconds[iii] = strings[iii - 1]; + } + return seconds; +} + +function sameString(self, chord) { + for (var jjjj = 0; jjjj < chord.length - 1; jjjj++) { + var curPos = chord[jjjj]; + var nextPos = chord[jjjj + 1]; + if (curPos.str == nextPos.str) { + // same String + // => change lower pos + if (curPos.str == self.strings.length - 1) { + // Invalid tab Chord position for instrument + curPos.num = "?"; + nextPos.num = "?"; + return; + } + // change lower pitch on lowest string + if (nextPos.num < curPos.num) { + nextPos.str++; + nextPos = noteToNumber(self, + nextPos.note, + nextPos.str, + self.secondPos, + self.strings[nextPos.str].length + ); + } else { + curPos.str++; + curPos = noteToNumber(self, + curPos.note, + curPos.str, + self.secondPos, + self.strings[curPos.str].length + ); + } + // update table + chord[jjjj] = curPos; + chord[jjjj + 1] = nextPos; + } + } + return null; +} + +function handleChordNotes(self, notes) { + var retNotes = []; + for (var iiii = 0; iiii < notes.length; iiii++) { + if (notes[iiii].endTie) + continue; + var note = new TabNote(notes[iiii].name, self.clefTranspose); + note.checkKeyAccidentals(self.accidentals, self.measureAccidentals) + var curPos = toNumber(self, note); + retNotes.push(curPos); + } + sameString(self, retNotes); + return retNotes; +} + +function noteToNumber(self, note, stringNumber, secondPosition, firstSize) { + var strings = self.strings; + note.checkKeyAccidentals(self.accidentals, self.measureAccidentals); + if (secondPosition) { + strings = secondPosition; + } + var noteName = note.emitNoAccidentals(); + var num = strings[stringNumber].indexOf(noteName); + var acc = note.acc; + if (num != -1) { + if (secondPosition) { + num += firstSize; + } + if ((note.isFlat || note.acc == -1) && (num == 0)) { + // flat on 0 pos => previous string 7th position + var noteEquiv = note.getAccidentalEquiv(); + stringNumber++; + num = strings[stringNumber].indexOf(noteEquiv.emit()); + acc = 0; + } + return { + num: (num + acc), + str: stringNumber, + note: note + }; + } + return null; +} + +function toNumber(self, note) { + if (note.isAltered || note.natural) { + var acc; + if (note.isFlat) { + if (note.isDouble) + acc = "__" + else + acc = "_" + } else if (note.isSharp) { + if (note.isDouble) + acc = "^^" + else + acc = "^" + } else if (note.natural) + acc = "=" + self.measureAccidentals[note.name.toUpperCase()] = acc + } + for (var i = self.stringPitches.length - 1; i >= 0; i--) { + if (note.pitch + note.pitchAltered >= self.stringPitches[i]) { + var num = note.pitch + note.pitchAltered - self.stringPitches[i] + if (note.quarter === '^') num -= 0.5 + else if (note.quarter === "v") num += 0.5 + return { + num: Math.round(num), + str: self.stringPitches.length - 1 - i, // reverse the strings because string 0 is on the bottom + note: note + } + } + } + return { + num: "?", + str: self.stringPitches.length - 1, + note: note, + }; +} + +StringPatterns.prototype.stringToPitch = function (stringNumber) { + var startingPitch = 5.3; + var bottom = this.strings.length - 1; + return startingPitch + ((bottom - stringNumber) * this.linePitch); +}; + +function invalidNumber(retNotes, note) { + var number = { + num: "?", + str: 0, + note: note + }; + retNotes.push(number); + retNotes.error = note.emit() + ': unexpected note for instrument'; +} + +StringPatterns.prototype.notesToNumber = function (notes, graces) { + var note; + var number; + var error = null; + var retNotes = null; + if (notes) { + retNotes = []; + if (notes.length > 1) { + retNotes = handleChordNotes(this, notes); + if (retNotes.error) { + error = retNotes.error; + } + } else { + if (!notes[0].endTie) { + note = new TabNote(notes[0].name, this.clefTranspose); + note.checkKeyAccidentals(this.accidentals, this.measureAccidentals) + number = toNumber(this, note); + if (number) { + retNotes.push(number); + } else { + invalidNumber(retNotes, note); + error = retNotes.error; + } + } + } + } + if (error) return retNotes; + var retGraces = null; + if (graces) { + retGraces = []; + for (var iiii = 0; iiii < graces.length; iiii++) { + note = new TabNote(graces[iiii].name, this.clefTranspose); + note.checkKeyAccidentals(this.accidentals, this.measureAccidentals) + number = toNumber(this, note); + if (number) { + retGraces.push(number); + } else { + invalidNumber(retGraces, note); + error = retNotes.error; + } + } + } + + return { + notes: retNotes, + graces: retGraces, + error: error + }; +}; + +StringPatterns.prototype.toString = function () { + var arr = [] + for (var i = 0; i < this.tuning.length; i++) { + var str = this.tuning[i].replaceAll(',', '').replaceAll("'", '').toUpperCase(); + if (str[0] === '_') str = str[1] + 'b ' + else if (str[0] === '^') str = str[1] + "# " + arr.push(str) + } + return arr.join(''); +}; + +StringPatterns.prototype.tabInfos = function (plugin) { + var name = plugin.params.label; + if (name) { + var tunePos = name.indexOf('%T'); + var tuning = ""; + if (tunePos != -1) { + tuning = this.toString(); + if (plugin.capo > 0) { + tuning += ' capo:' + plugin.capo; + } + name = name.replace('%T', tuning); + } + return name; + } + return ''; +}; + +// MAE 27 Nov 2023 +StringPatterns.prototype.suppress = function (plugin) { + var suppress = plugin.params.suppress; + if (suppress) { + return true; + } + return false; +}; +// MAE 27 Nov 2023 End + +/** + * Common patterns for all string instruments + * @param {} plugin + * @param {} tuning + * @param {*} capo + * @param {*} highestNote + */ +function StringPatterns(plugin) { + //console.log("INIT StringPatterns constructor") + var tuning = plugin.tuning; + var capo = plugin.capo; + var highestNote = plugin.params.highestNote; + this.linePitch = plugin.linePitch; + this.highestNote = "a'"; + if (highestNote) { + // override default + this.highestNote = highestNote; + } + this.measureAccidentals = {} + this.capo = 0; + if (capo) { + this.capo = parseInt(capo, 10); + } + this.transpose = plugin.transpose ? plugin.transpose : 0 + this.tuning = tuning; + this.stringPitches = [] + for (var i = 0; i < this.tuning.length; i++) { + var pitch = noteToMidi(this.tuning[i]) + this.capo + this.stringPitches.push(pitch) + } + if (this.capo > 0) { + this.capoTuning = buildCapo(this); + } + this.strings = buildPatterns(this); + if (this.strings.error) { + plugin.setError(this.strings.error); + plugin.inError = true; + return; + } + // second position pattern per string + this.secondPos = buildSecond(this); +} + + + +module.exports = StringPatterns; \ No newline at end of file diff --git a/src/tablatures/instruments/string-tablature.js b/src/tablatures/instruments/string-tablature.js new file mode 100644 index 0000000000000000000000000000000000000000..42f52bae3f9a06f2da6e8c67fe746a5215785159 --- /dev/null +++ b/src/tablatures/instruments/string-tablature.js @@ -0,0 +1,59 @@ + +/** + * Layout tablature informations for draw + * @param {*} numLines + * @param {*} lineSpace + */ + +function StringTablature(numLines, lineSpace) { + //console.log("INIT StringTablature constructor") + this.numLines = numLines; + this.lineSpace = lineSpace; + this.verticalSize = this.numLines * this.lineSpace; + var pitch = 3; + this.bar = { + pitch: pitch, + pitch2: lineSpace * numLines, + height: 5, + }; +} + +/** + * return true if current line should not produce a tab + * @param {} line + */ +StringTablature.prototype.bypass = function (line) { + //console.log("RENDER StringTablature bypass") + var voices = line.staffGroup.voices; + if (voices.length > 0) { + if (voices[0].isPercussion) return true; + } + return false; +}; + + +StringTablature.prototype.setRelative = function (child, relative, first) { + //console.log("RENDER StringTablature setRelative") + switch (child.type) { + case 'bar': + relative.pitch = this.bar.pitch; + relative.pitch2 = this.bar.pitch2; + relative.height = this.height; + break; + case 'symbol': + var top = this.bar.pitch2 / 2; + if (child.name == 'dots.dot') { + if (first) { + relative.pitch = top; + return false; + } else { + relative.pitch = top + this.lineSpace; + return true; + } + } + break; + } + return first; +}; + +module.exports = StringTablature; \ No newline at end of file diff --git a/src/tablatures/instruments/tab-note.js b/src/tablatures/instruments/tab-note.js new file mode 100644 index 0000000000000000000000000000000000000000..44ee9c64aa591865d6c914cf99dea18631c10b1a --- /dev/null +++ b/src/tablatures/instruments/tab-note.js @@ -0,0 +1,221 @@ +var { noteToMidi, midiToNote } = require('../../synth/note-to-midi'); + +/** + * + * Note structure for Tabs + * + */ + + +function TabNote(note, clefTranspose) { + //console.log("INIT/RENDER TabNote constructor") + var pitch = noteToMidi(note) + if (clefTranspose) + pitch += clefTranspose + var newNote = midiToNote(pitch); + var isFlat = false; + var isSharp = false; + var isAltered = false; + var natural = null; + var quarter = null; + var isDouble = false; + var acc = 0; + + if (note.startsWith('_')) { + isFlat = true; + acc = -1; + // check quarter flat + if (note[1] == '/') { + isFlat = false; + quarter = "v"; + acc = 0; + } else if (note[1] == '_') { + // double flat + isDouble = true; + acc -= 1; + } + } else if (note.startsWith('^')) { + isSharp = true; + acc = +1; + // check quarter sharp + if (note[1] == '/') { + isSharp = false; + quarter = "^"; + acc = 0; + } else if (note[1] == '^') { + // double sharp + isDouble = true; + acc += 1; + } + } else if (note.startsWith('=')) { + natural = true; + acc = 0; + } + isAltered = isFlat || isSharp || (quarter != null); + if (isAltered || natural) { + if ((quarter != null) || (isDouble)) { + newNote = note.slice(2); + } else { + newNote = note.slice(1); + } + } + var hasComma = (newNote.match(/,/g) || []).length; + var hasQuote = (newNote.match(/'/g) || []).length; + + this.pitch = pitch + this.pitchAltered = 0 + this.name = newNote; + this.acc = acc; + this.isSharp = isSharp; + this.isKeySharp = false; + this.isDouble = isDouble; + this.isAltered = isAltered; + this.isFlat = isFlat; + this.isKeyFlat = false; + this.natural = natural; + this.quarter = quarter; + this.isLower = (this.name == this.name.toLowerCase()); + this.name = this.name[0].toUpperCase(); + this.hasComma = hasComma; + this.isQuoted = hasQuote; +} + +function cloneNote(self) { + var newNote = self.name; + var newTabNote = new TabNote(newNote); + newTabNote.pitch = self.pitch; + newTabNote.hasComma = self.hasComma; + newTabNote.isLower = self.isLower; + newTabNote.isQuoted = self.isQuoted; + newTabNote.isSharp = self.isSharp; + newTabNote.isKeySharp = self.isKeySharp; + newTabNote.isFlat = self.isFlat; + newTabNote.isKeyFlat = self.isKeyFlat; + return newTabNote; +} +TabNote.prototype.sameNoteAs = function (note) { + //console.log("INIT TabNote sameNoteAs") + return note.pitch === this.pitch +}; + +TabNote.prototype.isLowerThan = function (note) { + //console.log("INIT TabNote isLowerThan") + return note.pitch > this.pitch +}; + +TabNote.prototype.checkKeyAccidentals = function (accidentals, measureAccidentals) { + //console.log("RENDER TabNote checkKeyAccidentals") + if (this.isAltered || this.natural) + return + if (measureAccidentals[this.name.toUpperCase()]) { + switch (measureAccidentals[this.name.toUpperCase()]) { + case "__": this.acc = -2; this.pitchAltered = -2; return; + case "_": this.acc = -1; this.pitchAltered = -1; return; + case "=": this.acc = 0; this.pitchAltered = 0; return; + case "^": this.acc = 1; this.pitchAltered = 1; return; + case "^^": this.acc = 2; this.pitchAltered = 2; return; + } + } else if (accidentals) { + var curNote = this.name; + for (var iii = 0; iii < accidentals.length; iii++) { + var curAccidentals = accidentals[iii]; + if (curNote == curAccidentals.note.toUpperCase()) { + if (curAccidentals.acc == 'flat') { + this.acc = -1; + this.isKeyFlat = true; + this.pitchAltered = -1 + } + if (curAccidentals.acc == 'sharp') { + this.acc = +1; + this.isKeySharp = true; + this.pitchAltered = 1 + } + } + } + } +}; + +TabNote.prototype.getAccidentalEquiv = function () { + //console.log("TabNote getAccidentalEquiv") + var cloned = cloneNote(this); + if (cloned.isSharp || cloned.isKeySharp) { + cloned = cloned.nextNote(); + cloned.isFlat = true; + cloned.isSharp = false; + cloned.isKeySharp = false; + } else if (cloned.isFlat || cloned.isKeyFlat) { + cloned = cloned.prevNote(); + cloned.isSharp = true; + cloned.isFlat = false; + cloned.isKeyFlat = false; + } + return cloned; +}; + + +TabNote.prototype.nextNote = function () { + //console.log("INIT TabNote nextNote") + var note = midiToNote(this.pitch + 1 + this.pitchAltered) + return new TabNote(note) +}; + +TabNote.prototype.prevNote = function () { + //console.log("TabNote prevNote") + var note = midiToNote(this.pitch - 1 + this.pitchAltered) + return new TabNote(note) +}; + +TabNote.prototype.emitNoAccidentals = function () { + //console.log("TabNote emitNoAccidentals") + var returned = this.name; + if (this.isLower) { + returned = returned.toLowerCase(); + } + for (var ii = 0; ii < this.isQuoted; ii++) { + returned += "'"; + } + for (var jj = 0; jj < this.hasComma; jj++) { + returned += ","; + } + return returned; +}; + +TabNote.prototype.emit = function () { + //console.log("INIT/RENDER TabNote emit") + var returned = this.name; + if (this.isSharp || this.isKeySharp) { + returned = '^' + returned; + if (this.isDouble) { + returned = '^' + returned; + } + } + if (this.isFlat || this.isKeyFlat) { + returned = '_' + returned; + if (this.isDouble) { + returned = '_' + returned; + } + } + if (this.quarter) { + if (this.quarter == "^") { + returned = "^/" + returned; + } else { + returned = "_/" + returned; + } + } + if (this.natural) { + returned = '=' + returned; + } + for (var ii = 1; ii <= this.hasComma; ii++) { + returned += ','; + } + + if (this.isLower) { + returned = returned.toLowerCase(); + for (var jj = 1; jj <= this.isQuoted; jj++) { + returned += "'"; + } + } + return returned; +}; + +module.exports = TabNote diff --git a/src/tablatures/instruments/tab-notes.js b/src/tablatures/instruments/tab-notes.js new file mode 100644 index 0000000000000000000000000000000000000000..322d5ebac3d39dc5f01b949becb8c8a923d2d22e --- /dev/null +++ b/src/tablatures/instruments/tab-notes.js @@ -0,0 +1,36 @@ + +var TabNote = require('./tab-note'); + +var notes = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; + +function tabNotes(fromNote, toNote) { + //console.log("INIT TabNotes") + var fromN = new TabNote(fromNote); + var toN = new TabNote(toNote); + // check that toN is not lower than fromN + if (toN.isLowerThan(fromN)) { + var from = fromN.emit(); + var tn = toN.emit(); + return { + error: 'Invalid string Instrument tuning : ' + + tn + ' string lower than ' + from + ' string' + }; + } + var buildReturned = []; + var startIndex = notes.indexOf(fromN.name); + var toIndex = notes.indexOf(toN.name); + if ((startIndex == -1) || (toIndex == -1)) { + return buildReturned; + } + var finished = false; + while (!finished) { + buildReturned.push(fromN.emit()); + fromN = fromN.nextNote(); + if (fromN.sameNoteAs(toN)) { + finished = true; + } + } + return buildReturned; +} + +module.exports = tabNotes; diff --git a/src/tablatures/instruments/tab-string.js b/src/tablatures/instruments/tab-string.js new file mode 100644 index 0000000000000000000000000000000000000000..b816bb5057f9ee9ec8ad7fdc60abce9e25b9d56b --- /dev/null +++ b/src/tablatures/instruments/tab-string.js @@ -0,0 +1,65 @@ + +var StringTablature = require('./string-tablature'); +var tabRenderer = require('../render/tab-renderer'); +var StringPatterns = require('./string-patterns'); + + +/** + * upon init mainly store provided instances for later usage + * @param {*} abcTune the parsed tune AST tree + * @param {*} tuneNumber the parsed tune AST tree + * @param {*} params complementary args provided to Tablature Plugin + */ +Plugin.prototype.init = function (abcTune, tuneNumber, params, tabSettings) { + //console.log("INIT AbcStringTab Plugin.init") + this.tune = abcTune; + this.params = params; + this.tuneNumber = tuneNumber; + this.inError = false; + this.abcTune = abcTune; + this.linePitch = 3; + this.nbLines = tabSettings.defaultTuning.length; + this.isTabBig = tabSettings.isTabBig; + this.tabSymbolOffset = tabSettings.tabSymbolOffset; + this.capo = params.capo; + this.transpose = params.visualTranspose; + this.hideTabSymbol = params.hideTabSymbol; + this.tablature = new StringTablature(this.nbLines, this.linePitch); + var tuning = params.tuning; + if (!tuning) { + tuning = tabSettings.defaultTuning; + } + this.tuning = tuning; + this.semantics = new StringPatterns(this); +}; + +Plugin.prototype.setError = function (error) { + //console.log("Plugin setError") + if (error) { + this.error = error; + this.inError = true; + if (this.tune.warnings) { + this.tune.warnings.push(error); + } else { + this.tune.warnings = [error]; + } + } +}; + +Plugin.prototype.render = function (renderer, line, staffIndex) { + //console.log("RENDER AbcStringTab Plugin.render") + if (this.inError) return; + if (this.tablature.bypass(line)) return; + tabRenderer(this, renderer, line, staffIndex); +}; + +function Plugin() { } + +// +// Tablature plugin definition +// +var AbcStringTab = function () { + return { name: 'StringTab', tablature: Plugin }; +}; + +module.exports = AbcStringTab; diff --git a/src/tablatures/render/tab-absolute-elements.js b/src/tablatures/render/tab-absolute-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..82e1086094243668cb58d1b293d2134ac9ac6250 --- /dev/null +++ b/src/tablatures/render/tab-absolute-elements.js @@ -0,0 +1,303 @@ +/** + * Tablature Absolute elements factory + */ +var AbsoluteElement = require('../../write/creation/elements/absolute-element'); +var RelativeElement = require('../../write/creation/elements/relative-element'); + +function isObject(a) { return a != null && a.constructor === Object; } +function cloneObject(dest, src) { + for (var prop in src) { + if (src.hasOwnProperty(prop)) { + if (!(Array.isArray(src[prop]) || isObject(src[prop]))) { + dest[prop] = src[prop]; + } + } + } +} + +function cloneAbsolute(absSrc) { + var returned = new AbsoluteElement('', 0, 0, '', 0); + cloneObject(returned, absSrc); + returned.top = 0; + returned.bottom = -1; + if (absSrc.abcelem) { + returned.abcelem = {}; + cloneObject(returned.abcelem, absSrc.abcelem); + if (returned.abcelem.el_type === "note") + returned.abcelem.el_type = 'tabNumber'; + } + // TODO-PER: This fixes the classes because the element isn't created at the right time. + absSrc.cloned = returned + return returned; +} + +function cloneAbsoluteAndRelatives(absSrc, plugin) { + var returned = cloneAbsolute(absSrc); + if (plugin) { + var children = absSrc.children; + // proceed with relative as well + var first = true; + for (var ii = 0; ii < children.length; ii++) { + var child = children[ii]; + var relative = new RelativeElement('', 0, 0, 0, ''); + cloneObject(relative, child); + first = plugin.tablature.setRelative(child, relative, first); + returned.children.push(relative); + } + } + return returned; +} + +function buildTabAbsolute(plugin, absX, relX) { + var tabIcon = 'tab.tiny'; + var tabYPos = 7.5; + if (plugin.isTabBig) { + tabIcon = 'tab.big'; + tabYPos = 10; + } + var element = { + el_type: "tab", + icon: tabIcon, + Ypos: tabYPos + }; + + // Offset the TAB symbol position if specified in the tab description + tabYPos += plugin.tabSymbolOffset; + + // For tablature like whistle tab where you want the TAB symbol hidden + if (!plugin.hideTabSymbol) { + + var tabAbsolute = new AbsoluteElement(element, 0, 0, "symbol", 0); + tabAbsolute.x = absX; + var tabRelative = new RelativeElement(tabIcon, 0, 0, 7.5, "tab"); + tabRelative.x = relX; + tabAbsolute.children.push(tabRelative); + if (tabAbsolute.abcelem.el_type == 'tab') { + tabRelative.pitch = tabYPos; + } + + } + return tabAbsolute; +} + +function lyricsDim(abs) { + if (abs.extra) { + for (var ii = 0; ii < abs.extra.length; ii++) { + var extra = abs.extra[ii]; + if (extra.type == 'lyric') { + return { + bottom: extra.bottom, + height: extra.height + }; + } + } + } + return null; +} +function TabAbsoluteElements() { + //console.log("RENDER TabAbsoluteElements constructor") + this.accidentals = null; +} + +function getInitialStaffSize(staffGroup) { + var returned = 0; + for (var ii = 0; ii < staffGroup.length; ii++) { + if (!staffGroup[ii].tabNameInfos) returned++; + } + return returned; +} + +function buildRelativeTabNote(plugin, relX, def, curNote, isGrace) { + var strNote = curNote.num; + if (curNote.note.quarter != null) { + // add tab quarter => needs to string conversion then + strNote = strNote.toString(); + strNote += curNote.note.quarter; + } + var pitch = plugin.semantics.stringToPitch(curNote.str); + def.notes.push({ num: strNote, str: curNote.str, pitch: curNote.note.emit() }); + var opt = { + type: 'tabNumber' + }; + var tabNoteRelative = new RelativeElement( + strNote, 0, 0, pitch + 0.3, opt); + tabNoteRelative.x = relX; + tabNoteRelative.isGrace = isGrace; + tabNoteRelative.isAltered = curNote.note.isAltered; + return tabNoteRelative; +} + +function getXGrace(abs, index) { + var found = 0; + if (abs.extra) { + for (var ii = 0; ii < abs.extra.length; ii++) { + if (abs.extra[ii].c.indexOf('noteheads') >= 0) { + if (found === index) { + return abs.extra[ii].x + abs.extra[ii].w / 2; + } else { + found++; + } + } + } + } + return -1; +} + +function graceInRest(absElem) { + if (absElem.abcelem) { + var elem = absElem.abcelem; + if (elem.rest) { + return elem.gracenotes; + } + } + return null; +} + +function convertToNumber(plugin, pitches, graceNotes) { + var tabPos = plugin.semantics.notesToNumber(pitches, graceNotes); + if (tabPos.error) { + plugin.setError(tabPos.error); + return tabPos; // give up on error here + } + if (tabPos.graces && tabPos.notes) { + // add graces to last note in notes + var posNote = tabPos.notes.length - 1; + tabPos.notes[posNote].graces = tabPos.graces; + } + return tabPos; +} + +function buildGraceRelativesForRest(plugin, abs, absChild, graceNotes, tabVoice) { + for (var mm = 0; mm < graceNotes.length; mm++) { + var defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true }; + var graceX = getXGrace(absChild, mm); + var curGrace = graceNotes[mm]; + var tabGraceRelative = buildRelativeTabNote(plugin, graceX, defGrace, curGrace, true); + abs.children.push(tabGraceRelative); + tabVoice.push(defGrace); + } +} + +/** + * Build tab absolutes by scanning current staff line absolute array + * @param {*} staffAbsolute + */ +TabAbsoluteElements.prototype.build = function (plugin, + staffAbsolute, + tabVoice, + voiceIndex, + staffIndex, + keySig, + tabVoiceIndex) { + //console.log("RENDER TabAbsoluteElements build") + var staffSize = getInitialStaffSize(staffAbsolute); + var source = staffAbsolute[staffIndex + voiceIndex]; + var dest = staffAbsolute[tabVoiceIndex]; + var tabPos = null; + var defNote = null; + if (source.children[0].abcelem.el_type != 'clef') { + // keysig missing => provide one for tabs + if (keySig != 'none') { + source.children.splice(0, 0, keySig); + } + } + for (var ii = 0; ii < source.children.length; ii++) { + var absChild = source.children[ii]; + var absX = absChild.x; + var relX = absX; + // if (absChild.children.length > 0) { + // relX = absChild.children[0].x; + // } + if ((absChild.isClef)) { + dest.children.push(buildTabAbsolute(plugin, absX, relX)); + if (absChild.abcelem.type.indexOf('-8') >= 0) plugin.semantics.clefTranspose = -12 + if (absChild.abcelem.type.indexOf('+8') >= 0) plugin.semantics.clefTranspose = 12 + } + switch (absChild.type) { + case 'staff-extra key-signature': + // refresh key accidentals + this.accidentals = absChild.abcelem.accidentals; + plugin.semantics.accidentals = this.accidentals; + break; + case 'bar': + plugin.semantics.measureAccidentals = {} + var lastBar = false; + if (ii === source.children.length - 1) { + // used for final line bar drawing + // for multi tabs / multi staves + lastBar = true; + } + var cloned = cloneAbsoluteAndRelatives(absChild, plugin); + if (cloned.abcelem.barNumber) { + delete cloned.abcelem.barNumber; + for (var bn = 0; bn < cloned.children.length; bn++) { + if (cloned.children[bn].type === "barNumber") { + cloned.children.splice(bn, 1); + break; + } + } + } + cloned.abcelem.lastBar = lastBar; + dest.children.push(cloned); + tabVoice.push({ + el_type: absChild.abcelem.el_type, + type: absChild.abcelem.type, + endChar: absChild.abcelem.endChar, + startChar: absChild.abcelem.startChar, + abselem: cloned + }); + break; + case 'rest': + var restGraces = graceInRest(absChild); + if (restGraces) { + // to number conversion + tabPos = convertToNumber(plugin, null, restGraces); + if (tabPos.error) return; + // build relative for grace + defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true }; + buildGraceRelativesForRest(plugin, abs, absChild, tabPos.graces, tabVoice); + } + break; + case 'note': + var abs = cloneAbsolute(absChild); + abs.x = absChild.heads[0].x + absChild.heads[0].w / 2; // center the number + abs.lyricDim = lyricsDim(absChild); + var pitches = absChild.abcelem.pitches; + var graceNotes = absChild.abcelem.gracenotes; + abs.type = 'tabNumber'; + // to number conversion + tabPos = convertToNumber(plugin, pitches, graceNotes); + if (tabPos.error) return; + if (tabPos.graces) { + // add graces to last note in notes + var posNote = tabPos.notes.length - 1; + tabPos.notes[posNote].graces = tabPos.graces; + } + // build relative + defNote = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [] }; + for (var ll = 0; ll < tabPos.notes.length; ll++) { + var curNote = tabPos.notes[ll]; + if (curNote.graces) { + for (var mm = 0; mm < curNote.graces.length; mm++) { + var defGrace = { el_type: "note", startChar: absChild.abcelem.startChar, endChar: absChild.abcelem.endChar, notes: [], grace: true }; + var graceX = getXGrace(absChild, mm); + var curGrace = curNote.graces[mm]; + var tabGraceRelative = buildRelativeTabNote(plugin, graceX, defGrace, curGrace, true); + abs.children.push(tabGraceRelative); + tabVoice.push(defGrace); + } + } + var tabNoteRelative = buildRelativeTabNote(plugin, abs.x + absChild.heads[ll].dx, defNote, curNote, false); + abs.children.push(tabNoteRelative); + } + if (defNote.notes.length > 0) { + defNote.abselem = abs; + tabVoice.push(defNote); + dest.children.push(abs); + } + break; + } + } +}; + +module.exports = TabAbsoluteElements; diff --git a/src/tablatures/render/tab-renderer.js b/src/tablatures/render/tab-renderer.js new file mode 100644 index 0000000000000000000000000000000000000000..2d7acdc91df7e261eb9c14b3bcf10a5b66068790 --- /dev/null +++ b/src/tablatures/render/tab-renderer.js @@ -0,0 +1,244 @@ +/* eslint-disable no-debugger */ +var VoiceElement = require('../../write/creation/elements/voice-element'); +var TabAbsoluteElements = require('./tab-absolute-elements'); +var spacing = require('../../write/helpers/spacing'); + +function initSpecialY() { + return { + tempoHeightAbove: 0, + partHeightAbove: 0, + volumeHeightAbove: 0, + dynamicHeightAbove: 0, + endingHeightAbove: 0, + chordHeightAbove: 0, + lyricHeightAbove: 0, + lyricHeightBelow: 0, + chordHeightBelow: 0, + volumeHeightBelow: 0, + dynamicHeightBelow: 0 + }; +} + +function getLyricHeight(voice) { + var maxLyricHeight = 0; + for (var ii = 0; ii < voice.children.length; ii++) { + var curAbs = voice.children[ii]; + if (curAbs.specialY) { + if (curAbs.specialY.lyricHeightBelow > maxLyricHeight) { + maxLyricHeight = curAbs.specialY.lyricHeightBelow; + } + } + } + return maxLyricHeight; // add spacing +} + +function buildTabName(plugin, renderer, dest) { + var stringSemantics = plugin.semantics; + var textSize = renderer.controller.getTextSize; + var tabName = stringSemantics.tabInfos(plugin); + var suppress = stringSemantics.suppress(plugin); + var doDraw = true; + + if (suppress) { + doDraw = false + } + + + if (doDraw) { + var size = textSize.calc(tabName, 'tablabelfont', 'text instrumentname'); + dest.tabNameInfos = { + textSize: { height: size.height, width: size.width }, + name: tabName + }; + return size.height; + } + return 0 + +} + +function islastTabInStaff(index, staffGroup) { + if (staffGroup[index].isTabStaff) { + if (index === staffGroup.length - 1) return true; + if (staffGroup[index + 1].isTabStaff) { + return false; + } else { + return true; + } + } + return false; +} + +function getStaffNumbers(staffs) { + var nbStaffs = 0; + for (var ii = 0; ii < staffs.length; ii++) { + if (!staffs[ii].isTabStaff) { + nbStaffs++; + } + } + return nbStaffs; +} + +function getParentStaffIndex(staffs, index) { + for (var ii = index; ii >= 0; ii--) { + if (!staffs[ii].isTabStaff) { + return ii; + } + } + return -1; +} + + +function linkStaffAndTabs(staffs) { + for (var ii = 0; ii < staffs.length; ii++) { + if (staffs[ii].isTabStaff) { + // link to parent staff + var parentIndex = getParentStaffIndex(staffs, ii); + staffs[ii].hasStaff = staffs[parentIndex]; + if (!staffs[parentIndex].hasTab) staffs[parentIndex].hasTab = []; + staffs[parentIndex].hasTab.push(staffs[ii]); + } + } +} + +function isMultiVoiceSingleStaff(staffs, parent) { + if (getStaffNumbers(staffs) === 1) { + if (parent.voices.length > 1) return true; + } + return false; +} + + +function getNextTabPos(tabIndex, staffGroup) { + var startIndex = 0; + var handledVoices = 0; + var inProgress = true; + var nbVoices = 0; + while (inProgress) { + //for (var ii = 0; ii < staffGroup.length; ii++) { + if (!staffGroup[startIndex]) + return -1; + if (!staffGroup[startIndex].isTabStaff) { + nbVoices = staffGroup[startIndex].voices.length; // get number of staff voices + } + if (staffGroup[startIndex].isTabStaff) { + handledVoices++; + if (islastTabInStaff(startIndex, staffGroup)) { + if (handledVoices < nbVoices) return startIndex + 1; + } + } else { + handledVoices = 0; + if (startIndex >= tabIndex) { + if (startIndex + 1 == staffGroup.length) return startIndex + 1; + if (!staffGroup[startIndex + 1].isTabStaff) return startIndex + 1; + } + } + startIndex++; + // out of space case + if (startIndex > staffGroup.length) return -1; + } +} + +function getLastStaff(staffs, lastTab) { + for (var ii = lastTab; ii >= 0; ii--) { + if (!staffs[ii].isTabStaff) { + return staffs[ii]; + } + } + return null; +} + +function checkVoiceKeySig(voices, ii) { + var curVoice = voices[ii]; + // on multivoice multistaff only the first voice has key signature + // folling consecutive do not have one => we should provide the first voice key sig back then + var elem0 = curVoice.children[0].abcelem; + if (elem0.el_type === 'clef') return null; + if (ii == 0) { + // not found => clef=none case + return 'none'; + } + return voices[ii - 1].children[0]; +} + +function tabRenderer(plugin, renderer, line, staffIndex) { + //console.log("RENDER tabRenderer") + var absolutes = new TabAbsoluteElements(); + var tabStaff = { + clef: { + type: 'TAB' + } + }; + var tabSize = (plugin.linePitch * plugin.nbLines); + var staffs = line.staff; + if (staffs) { + // give up on staffline=0 in key + var firstStaff = staffs[0]; + if (firstStaff) { + if (firstStaff.clef) { + if (firstStaff.clef.stafflines == 0) { + plugin.setError("No tablatures when stafflines=0"); + return; + } + } + } + staffs.splice( + staffs.length, 0, + tabStaff + ); + } + var staffGroup = line.staffGroup; + + var voices = staffGroup.voices; + var firstVoice = voices[0]; + // take lyrics into account if any + var lyricsHeight = getLyricHeight(firstVoice); + var padd = 3; + var prevIndex = staffIndex; + var previousStaff = staffGroup.staffs[prevIndex]; + var tabTop = tabSize + padd - previousStaff.bottom - lyricsHeight; + if (previousStaff.isTabStaff) { + tabTop = previousStaff.top; + } + var staffGroupInfos = { + bottom: -1, + isTabStaff: true, + specialY: initSpecialY(), + lines: plugin.nbLines, + linePitch: plugin.linePitch, + dy: 0.15, + top: tabTop, + }; + var nextTabPos = getNextTabPos(staffIndex, staffGroup.staffs); + if (nextTabPos === -1) + return; + staffGroupInfos.parentIndex = nextTabPos - 1; + staffGroup.staffs.splice(nextTabPos, 0, staffGroupInfos); + // staffGroup.staffs.push(staffGroupInfos); + staffGroup.height += tabSize + padd; + var parentStaff = getLastStaff(staffGroup.staffs, nextTabPos); + var nbVoices = 1; + if (isMultiVoiceSingleStaff(staffGroup.staffs, parentStaff)) { + nbVoices = parentStaff.voices.length; + } + // build from staff + tabStaff.voices = []; + for (var ii = 0; ii < nbVoices; ii++) { + var tabVoice = new VoiceElement(0, 0); + if (ii > 0) tabVoice.duplicate = true; + var nameHeight = buildTabName(plugin, renderer, tabVoice) / spacing.STEP; + nameHeight = Math.max(nameHeight, 1) // If there is no label for the tab line, then there needs to be a little padding + // This was pushing down the top staff by the tab label height + //staffGroup.staffs[staffIndex].top += nameHeight; + staffGroup.staffs[staffIndex].top += 1; + staffGroup.height += nameHeight; + tabVoice.staff = staffGroupInfos; + var tabVoiceIndex = voices.length + voices.splice(voices.length, 0, tabVoice); + var keySig = checkVoiceKeySig(voices, ii + staffIndex); + tabStaff.voices[ii] = []; + absolutes.build(plugin, voices, tabStaff.voices[ii], ii, staffIndex, keySig, tabVoiceIndex); + } + linkStaffAndTabs(staffGroup.staffs); // crossreference tabs and staff +} + +module.exports = tabRenderer; diff --git a/src/test/abc_midi_lint.js b/src/test/abc_midi_lint.js new file mode 100644 index 0000000000000000000000000000000000000000..77fbac779a7b0ffc5572288094e62dd8f3425f45 --- /dev/null +++ b/src/test/abc_midi_lint.js @@ -0,0 +1,33 @@ +// abc_vertical_lint.js: Analyzes the vertical position of the output object. + +//This file takes as input the output structure of the writing routine and lists the vertical position of all the elements. + +var pitchToNoteName = require('../synth/pitch-to-note-name'); + +var midiLint = function(tune) { + "use strict"; + + var ret = "Tempo: " + tune.tempo + "\nInstrument: " + tune.instrument + "\n"; + for (var i = 0; i < tune.tracks.length; i++) { + ret += "Track " + (i+1) + "\n"; + for (var j = 0; j < tune.tracks[i].length; j++) { + var event = tune.tracks[i][j]; + switch (event.cmd) { + case 'program': + ret += "\tProgram: ch=" + event.channel + " inst=" + event.instrument + "\n"; + break; + case 'note': + ret += "\tNote: pitch=" + pitchToNoteName[event.pitch] + " (" + event.pitch + ") start=" + event.start + " dur=" + event.duration + " gap=" + event.gap + " vol=" + event.volume + " inst=" + event.instrument + "\n"; + break; + case 'text': + ret += "\tText: " + event.type + '=' + event.text + "\n"; + break; + default: + ret += "\tUnknown: " + event.cmd + "\n"; + } + } + } + return ret; +}; + +module.exports = midiLint; diff --git a/src/test/abc_midi_sequencer_lint.js b/src/test/abc_midi_sequencer_lint.js new file mode 100644 index 0000000000000000000000000000000000000000..d71dcd76fa852e37f776137354f266c5cd453189 --- /dev/null +++ b/src/test/abc_midi_sequencer_lint.js @@ -0,0 +1,152 @@ +// abc_vertical_lint.js: Analyzes the vertical position of the output object. + +//This file takes as input the output structure of the writing routine and lists the vertical position of all the elements. + +/*globals toString */ + +var midiSequencerLint = function(tune) { + "use strict"; + + var breakSynonyms = [ 'break', '(break)', 'no chord', 'n.c.', 'tacet']; + var ret = ""; + for (var i = 0; i < tune.length; i++) { + var voice = tune[i]; + ret += 'Voice ' + (i+1) + ':\n'; + for (var j = 0; j < voice.length; j++) { + var element = voice[j]; + ret += '\t' + element.el_type + '\n'; + switch (element.el_type) { + case "note": + ret += "\t\t"; + ret += 'duration: ' + element.duration + '\n'; + if (element.decoration && element.decoration.length > 0) + ret += '\t\tdecoration: ' + element.decoration.join(", ") + '\n'; + ret += "\t\t"; + if (element.chord) { + ret += 'chord: '; + for (var c = 0; c < element.chord.length; c++) { + if (element.chord[c].position === 'default') { + ret += element.chord[c].name + ' '; + } else if (breakSynonyms.indexOf(element.chord[c].name.toLowerCase()) >= 0) { + ret += element.chord[c].name + ' '; + } + } + ret += "\n\t\t"; + } + if (element.rest) { + ret += 'rest: ' + element.rest.type; + } else { + if (element.startTriplet) + ret += 'startTriplet\n\t\t'; + if (element.endTriplet) + ret += 'endTriplet\n\t\t'; + if (element.gracenotes) { + ret += 'grace: '; + for (var g = 0; g < element.gracenotes.length; g++) { + ret += '(' + element.gracenotes[g].pitch + ',' + element.gracenotes[g].duration + ') '; + } + ret += "\n\t\t"; + } + ret += 'pitch:'; + for (var n = 0; n < element.pitches.length; n++) { + if (element.pitches[n].midipitch) + ret += ' ' + element.pitches[n].midipitch + ' (midi)'; + else { + ret += ' ' + element.pitches[n].pitch; + if (element.pitches[n].accidental) + ret += ' ' + element.pitches[n].accidental; + } + if (element.pitches[n].startTie) + ret += ' startTie'; + if (element.pitches[n].endTie) + ret += ' endTie'; + } + if (element.style && element.style !== "normal") + ret += "\n\t\tstyle: " + element.style; + if (element.noChordVoice) + ret += "\n\t\tnoChordVoice"; + } + ret += '\n'; + break; + case "key": + ret += "\t\t"; + for (var k = 0; k < element.accidentals.length; k++) { + ret += '[' + element.accidentals[k].note + ' ' + element.accidentals[k].acc + '] '; + } + ret += '\n'; + break; + case "meter": + ret += "\t\t"; + ret += element.num + '/' + element.den; + ret += '\n'; + break; + case "tempo": + ret += "\t\t"; + ret += element.qpm; + ret += '\n'; + break; + case "transpose": + ret += "\t\t"; + ret += element.transpose; + ret += '\n'; + break; + case "bar": + break; + case "bagpipes": + break; + case "instrument": + ret += "\t\t"; + ret += element.program; + ret += '\n'; + break; + case "channel": + ret += "\t\t"; + ret += element.channel; + ret += '\n'; + break; + case "gchord": + ret += "\t\t"; + ret += element.tacet ? 'tacet' : 'on'; + ret += '\n'; + break; + case "beat": + ret += "\t\t"; + ret += element.beats.join(","); + ret += '\n'; + break; + case "vol": + ret += "\t\t"; + ret += element.volume; + ret += '\n'; + break; + case "volinc": + ret += "\t\t"; + ret += element.volume; + ret += '\n'; + break; + case "beataccents": + ret += "\t\t"; + ret += element.value; + ret += '\n'; + break; + case "drum": + var params = element.params; + ret += "\t\t"; + ret += "[" + params.pattern.join(" ") + "] bars=" + params.bars + " intro=" + params.intro + " on=" + params.on; + ret += '\n'; + break; + case "name": + ret += "\t\t"; + ret += element.trackName; + ret += '\n'; + break; + default: + ret += "Unknown el_type: " + element.el_type + "\n"; + break; + } + } + } + return ret; +}; + +module.exports = midiSequencerLint; diff --git a/src/test/abc_parser_lint.js b/src/test/abc_parser_lint.js new file mode 100644 index 0000000000000000000000000000000000000000..cb6396ffd2b59405f7182a4bc6e221a262ea8889 --- /dev/null +++ b/src/test/abc_parser_lint.js @@ -0,0 +1,760 @@ +// abc_parser_lint.js: Analyzes the output of abc_parse. + +//This file takes as input the output of AbcParser and analyzes it to make sure there are no +//unexpected elements in it. It also returns a person-readable version of it that is suitable +//for regression tests. + +// Changes for V1.0.1: +// +// Added: +// media: screen | print +// infoline: boolean +//"abc-copyright": string +//"abc-creator": string +//"abc-version": string +//"abc-charset": string +//"abc-edited-by": string +//format.measurebox: true +//format.decorationPlacement: above, below +//format.header, format.footer are fleshed out +//image: string +//multicol: string +//newpage: string +//staffbreak: number +//staff.spacingAbove: length +// chord: { root, type } +// added { tonic, acc, mode } to keyProperties +// transpose to clef +// stafflines and scaling +// style: note head shape +// decorations: tremolos: / // /// +// decorations: xstem - extend the stem to the staff above +// rel_position for chord +// slur: direction and style +// note.noStem +// voice.gap +// voice.overlay +// voice.stem can also be auto and none +// columns +// note.vocalFont +// more meter properties +// note.beambr: number of beams to break +// note.stemConnectsToAbove: connect the stem to the note on the higher staff. +// line.vskip +// +// Changed from optional to manditory: +// pagewidth and pageheight +// +// Expanded: +// MIDI is now { cmd, param } + +var JSONSchema = require('./jsonschema-b4'); + +var ParserLint = function() { + "use strict"; + var decorationList = { type: 'array', optional: true, items: { type: 'string', Enum: [ + "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent", + "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge", + "open", "thumb", "snap", "turn", "roll", "irishroll", "breath", "shortphrase", "mediumphrase", "longphrase", + "segno", "coda", "D.S.", "D.C.", "fine", "crescendo(", "crescendo)", "diminuendo(", "diminuendo)", "glissando(", "glissando)", + "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz", "repeatbar", "repeatbar2", "slide", + "upbow", "downbow", "staccato", "trem1", "trem2", "trem3", "trem4", + "/", "//", "///", "////", "turnx", "invertedturn", "invertedturnx", "arpeggio", "trill(", "trill)", "xstem", + "mark", "marcato", "umarcato", "D.C.alcoda", "D.C.alfine", "D.S.alcoda", "D.S.alfine", "editorial", "courtesy" + ] } }; + + var tempoProperties = { + duration: { type: "array", optional: true, output: "join", requires: [ 'bpm'], items: { type: "number"} }, + bpm: { type: "number", optional: true, requires: [ 'duration'] }, + endChar: { type: 'number'}, + preString: { type: 'string', optional: true}, + postString: { type: 'string', optional: true}, + startChar: { type: 'number'}, + suppress: { type: 'boolean', Enum: [ true ], optional: true}, + suppressBpm: { type: 'boolean', Enum: [ true ], optional: true} + }; + + var appendPositioning = function(properties) { + var ret = Object.assign({},properties); + ret.startChar = { type: 'number' }; //, output: 'hidden' }; + ret.endChar = { type: 'number' }; //, output: 'hidden' }; + return ret; + }; + + var prependPositioning = function(properties) { + var ret = {}; + ret.startChar = { type: 'number' }; //, output: 'hidden' }; + ret.endChar = { type: 'number' }; //, output: 'hidden' }; + return Object.assign(ret, properties); + }; + + var fontType = { + type: 'object', optional: true, properties: { + box: { type: 'boolean', Enum: [ true ], optional: true }, + face: { type: 'string', optional: true }, + weight: { type: 'string', Enum: [ 'bold', 'normal' ], optional: true }, + style: { type: 'string',Enum: [ 'italic', 'normal' ], optional: true }, + decoration: { type: 'string', Enum: [ 'underline', 'none' ], optional: true }, + size: { type: 'number', optional: true } + } + }; + + var percMapElement = { + type: 'object', optional: true, properties: { + sound: { type: 'number', minimum: 35, maximum: 81 }, + noteHead: { type: 'string', Enum: ['normal', 'harmonic', 'rhythm', 'x', 'triangle'], optional: true }, + } + }; + var percMapProps = { + "C": percMapElement, + "_C": percMapElement, + "^C": percMapElement, + "=C": percMapElement, + "D": percMapElement, + "_D": percMapElement, + "^D": percMapElement, + "=D": percMapElement, + "E": percMapElement, + "_E": percMapElement, + "^E": percMapElement, + "=E": percMapElement, + "F": percMapElement, + "_F": percMapElement, + "^F": percMapElement, + "=F": percMapElement, + "G": percMapElement, + "_G": percMapElement, + "^G": percMapElement, + "=G": percMapElement, + "A": percMapElement, + "_A": percMapElement, + "^A": percMapElement, + "=A": percMapElement, + "B": percMapElement, + "_B": percMapElement, + "^B": percMapElement, + "=B": percMapElement, + "c": percMapElement, + "_c": percMapElement, + "^c": percMapElement, + "=c": percMapElement, + "d": percMapElement, + "_d": percMapElement, + "^d": percMapElement, + "=d": percMapElement, + "e": percMapElement, + "_e": percMapElement, + "^e": percMapElement, + "=e": percMapElement, + "f": percMapElement, + "_f": percMapElement, + "^f": percMapElement, + "=f": percMapElement, + "g": percMapElement, + "_g": percMapElement, + "^g": percMapElement, + "=g": percMapElement, + "a": percMapElement, + "_a": percMapElement, + "^a": percMapElement, + "=a": percMapElement, + }; + + var clefProperties = { + stafflines: { type: 'number', minimum: 0, maximum: 10, optional: true }, + staffscale: { type: 'number', minimum: 0.1, maximum: 10, optional: true }, + transpose: { type: 'number', minimum: -24, maximum: 24, optional: true }, + type: { type: 'string', Enum: [ 'treble', 'tenor', 'bass', 'alto', 'treble+8', 'tenor+8', 'bass+8', 'alto+8', 'treble-8', 'tenor-8', 'bass-8', 'alto-8', 'none', 'perc' ] }, + verticalPos: { type: 'number', minimum: -20, maximum: 10 }, // the pitch that goes in the middle of the staff C=0 + clefPos: { type: 'number', minimum: 2, maximum: 10, optional: true } // this is needed if there is a clef, but should not be present if the clef is 'none' + }; + + var chordProperties = { + type: "object", properties: { + name: { type: 'string'}, + chord: { type: 'object', optional: true, properties: { + root: { type: 'string', Enum: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ]}, + type: { type: 'string', Enum: [ 'm', '7', 'm7', 'maj7', 'M7', '6', 'm6', 'aug', '+', 'aug7', 'dim', 'dim7', '9', 'm9', 'maj9', 'M9', '11', 'dim9', 'sus', 'sus9', '7sus4', '7sus9', '5' ]} + } + }, + position: { type: 'string', Enum: [ 'above', 'below', 'left', 'right', 'default' ], optional: true, prohibits: [ 'rel_position' ] }, + rel_position: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, optional: true, prohibits: [ 'position' ] } + } + }; + + var slurProperties = { type: 'array', optional: true, output: "noindex", items: { + type: 'object', optional: true, properties: { + label: { type: 'number', minimum: 0 }, + direction: { type: 'string', optional: true, Enum: [ 'up', 'down' ] }, + style: { type: 'string', optional: true, Enum: [ 'dotted' ] } + } + } + }; + + var tieProperties = { type: 'object', optional: true, properties: { + direction: { type: 'string', optional: true, Enum: [ 'up', 'down' ] }, + style: { type: 'string', optional: true, Enum: [ 'dotted' ] } + } }; + + var barProperties = { + barNumber: { type: 'number', optional: true }, + chord: { type: 'array', optional: true, output: "noindex", items: chordProperties }, + decoration: decorationList, + endEnding: { type: 'boolean', Enum: [ true ], optional: true }, + startEnding: { type: 'string', optional: true }, + type: { type: 'string', Enum: [ 'bar_dbl_repeat', 'bar_right_repeat', 'bar_left_repeat', 'bar_invisible', 'bar_thick_thin', 'bar_thin_thin', 'bar_thin', 'bar_thin_thick' ] } + }; + + var noteProperties = { + beambr: { type: 'number', minimum: 1, maximum: 5, optional: true }, + chord: { type: 'array', optional: true, output: "noindex", items: chordProperties }, + decoration: decorationList, + duration: { type: 'number' }, + endBeam: { type: 'boolean', Enum: [ true ], prohibits: [ 'startBeam', 'beambr' ], optional: true }, + endSlur: { type: 'array', optional: true, output: "join", items: { type: 'number', minimum: 0 } }, + endTriplet: { type: 'boolean', Enum: [ true ], optional: true }, + fonts: { type: 'object', optional: true, properties: { + annotationfont: fontType, + gchordfont: fontType, + measurefont: fontType, + repeatfont: fontType, + tripletfont: fontType, + vocalfont: fontType + }}, + gracenotes: { type: 'array', optional: true, output: "noindex", items: { + type: "object", properties: { + acciaccatura: { type: 'boolean', Enum: [ true ], optional: true}, + accidental: { type: 'string', Enum: [ 'sharp', 'flat', 'natural', 'dblsharp', 'dblflat', 'quarterflat', 'quartersharp' ], optional: true }, + duration: { type: 'number' }, + endBeam: { type: 'boolean', Enum: [ true ], prohibits: [ 'startBeam', 'beambr' ], optional: true }, + endSlur: { type: 'array', optional: true, output: "join", items: { type: 'number', minimum: 0 } }, + endTie: { type: 'boolean', Enum: [ true ], optional: true }, + midipitch: { type: 'number', optional: true }, + name: { type: 'string' }, + pitch: { type: 'number' }, + verticalPos: { type: 'number' }, + startBeam: { type: 'boolean', Enum: [ true ], prohibits: [ 'endBeam', 'beambr' ], optional: true }, + startSlur: slurProperties, + startTie: tieProperties, + } + }}, + lyric: { type: 'array', optional: true, output: "noindex", items: { + type: 'object', properties: { + syllable: { type :'string' }, + divider: { type: 'string', Enum: [ '-', ' ', '_' ]} + }}}, + noStem: { type: 'boolean', Enum: [ true ], optional: true }, + pitches: { type: 'array', optional: true, output: "noindex", prohibits: [ 'rest' ], items: { + type: 'object', properties: { + accidental: { type: 'string', Enum: [ 'sharp', 'flat', 'natural', 'dblsharp', 'dblflat', 'quarterflat', 'quartersharp' ], optional: true }, + endSlur: { type: 'array', optional: true, output: "join", items: { type: 'number', minimum: 0 } }, + endTie: { type: 'boolean', Enum: [ true ], optional: true }, + midipitch: { type: 'number', optional: true }, + name: { type: 'string' }, + pitch: { type: 'number' }, + verticalPos: { type: 'number' }, + startSlur: slurProperties, + startTie: tieProperties, + style: { type: 'string', Enum: ['normal', 'harmonic', 'rhythm', 'x', 'triangle'], optional: true }, + } + }}, + positioning: { type: 'object', optional: true, properties: { + chordPosition: { type: 'string', Enum: [ 'above', 'below', 'hidden' ], optional: true}, + dynamicPosition: { type: 'string', Enum: [ 'above', 'below', 'hidden' ], optional: true}, + ornamentPosition: { type: 'string', Enum: [ 'above', 'below', 'hidden' ], optional: true}, + vocalPosition: { type: 'string', Enum: [ 'above', 'below', 'hidden' ], optional: true}, + volumePosition: { type: 'string', Enum: [ 'above', 'below', 'hidden' ], optional: true} + }}, + rest: { type: 'object', optional: true, prohibits: [ 'pitches', 'lyric' ], properties: { + type: { type: 'string', Enum: [ 'invisible', 'spacer', 'rest', 'multimeasure', 'invisible-multimeasure', 'whole' ] }, // multimeasure requires duration to be the number of measures. + text: { type: 'number', minimum: 1, maximum: 100, optional: true }, + endTie: { type: 'boolean', Enum: [ true ], optional: true }, + startTie: tieProperties + }}, + startBeam: { type: 'boolean', Enum: [ true ], prohibits: [ 'endBeam', 'beambr' ], optional: true }, + startSlur: slurProperties, + startTriplet: { type: 'number', minimum: 2, maximum: 9, optional: true }, + tripletMultiplier: { type: 'number', minimum: .1, maximum: 9, optional: true }, + tripletR: { type: 'number', minimum: .1, maximum: 9, optional: true }, + stemConnectsToAbove: { type: 'boolean', Enum: [ true ], optional: true }, + style: { type: 'string', Enum: ['normal', 'harmonic', 'rhythm', 'x', 'triangle'], optional: true } +}; + + var keyProperties = { // change deepCopyKey (in parse_header) if there are changes around here + accidentals: { type: 'array', optional: true, output: "noindex", items: { + type: 'object', properties: { + acc: { type: 'string', Enum: [ 'flat', 'natural', 'sharp', 'dblsharp', 'dblflat', 'quarterflat', 'quartersharp' ] }, + note: { type: 'string', Enum: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'a', 'b', 'c', 'd', 'e', 'f', 'g' ] }, + verticalPos: { type: 'number', minimum: 0, maximum: 13 } + } + } }, + root: { type: 'string', Enum: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'HP', 'Hp', 'none']}, + acc: { type: 'string', Enum: ['', '#', 'b']}, + mode: { type: 'string', Enum: ['', 'm', 'Dor', 'Mix', 'Loc', 'Phr', 'Lyd']} + }; + + var meterProperties = { + type: { type: 'string', Enum: [ 'common_time', 'cut_time', 'specified', 'tempus_perfectum', 'tempus_imperfectum', 'tempus_perfectum_prolatio', 'tempus_imperfectum_prolatio' ] }, + // 'tempus perfectum'=o, 'tempus imperfectum'=c, 'tempus perfectum prolatio'=o., 'tempus imperfectum prolatio'=c. + value: { type: 'array', optional: true, output: 'noindex', // TODO-PER: Check for type=specified and require these in that case. + items: { + type: 'object', properties: { + num: { type: 'string' }, + den: { type: 'string', optional: true } + } + } + }, + beat_division: { type: 'array', optional: true, output: 'noindex', // This is displayed inside parens, but is just an elaboration of the "value" field, not more info. + items: { + type: 'object', properties: { + num: { type: 'string' }, + den: { type: 'string', optional: true } + } + } + } + }; + + var midiProperties = { + cmd: { type: 'string', Enum: [ + "nobarlines", "barlines", "beataccents", "nobeataccents", "droneon", "droneoff", "noportamento", "channel", "c", + "drumon", "drumoff", "fermatafixed", "fermataproportional", "gchordon", "gchordoff", "bassvol", "chordvol", "bassprog", "chordprog", + "controlcombo", "temperamentnormal", "gchord", "ptstress", "beatmod", "deltaloudness", "drumbars", "pitchbend", + "gracedivider", "makechordchannels", "randomchordattack", "chordattack", "stressmodel", "transpose", + "rtranspose", "volinc", "program", "ratio", "snt", "bendvelocity", "control", "temperamentlinear", "beat", "beatstring", + "drone", "bassprog", "chordprog", "drummap", "portamento", "expand", "grace", "trim", "drum", "chordname" + ]}, + params: { type: 'array', output: 'join', + items: { + type: 'stringorinteger' + } + } + }; + + var voiceItem = { type: "union", + field: "el_type", + types: [ + { value: "clef", properties: appendPositioning(clefProperties) }, + { value: "color", properties: { color: {type: "string", optional: true } } }, + { value: "bar", properties: prependPositioning(barProperties) }, + { value: "gap", properties: { type: "number", optional: true } }, // staffbreak + { value: "key", properties: appendPositioning(keyProperties) }, + { value: "meter", properties: appendPositioning(meterProperties) }, + { value: "midi", properties: appendPositioning(midiProperties) }, + { value: "overlay", properties: { type: 'array', items: { // This goes back to the last measure to start the notes in this note array. + type: prependPositioning(noteProperties)} } }, + { value: "part", properties: prependPositioning({ title: { type: 'string' } }) }, + { value: "scale", properties: { size: {type: "number", optional: true, minimum: 0.5, maximum: 2 } } }, + { value: 'stem', properties: { + direction: { type: 'string', Enum: [ 'up', 'down', 'auto', 'none' ] } + }}, + { value: 'style', properties: { + head: { type: 'string', Enum: [ 'normal', 'harmonic', 'rhythm', 'x', 'triangle' ] } + }}, + { value: 'tempo', properties: appendPositioning(tempoProperties) }, + { value: 'transpose', properties: { steps: { type: "number" } } }, + { value: "note", properties: prependPositioning(noteProperties) } + ] + }; + + var textFieldProperties = { type: "stringorarray", optional: true, output: 'noindex', + items: { + type: 'object', properties: { + endChar: { type: 'number', optional: true}, + font: fontType, + text: { type: 'string' }, + center: { type: 'boolean', Enum: [ true ], optional: true }, + startChar: { type: 'number', optional: true} + } + } + }; + + var drummapProps = { + "C": { type: "number", optional: true }, + "_C": { type: "number", optional: true }, + "^C": { type: "number", optional: true }, + "=C": { type: "number", optional: true }, + "D": {type: "number", optional: true}, + "_D": {type: "number", optional: true}, + "^D": {type: "number", optional: true}, + "=D": {type: "number", optional: true}, + "E": {type: "number", optional: true}, + "_E": {type: "number", optional: true}, + "^E": {type: "number", optional: true}, + "=E": {type: "number", optional: true}, + "F": {type: "number", optional: true}, + "_F": {type: "number", optional: true}, + "^F": {type: "number", optional: true}, + "=F": {type: "number", optional: true}, + "G": {type: "number", optional: true}, + "_G": {type: "number", optional: true}, + "^G": {type: "number", optional: true}, + "=G": {type: "number", optional: true}, + "A": {type: "number", optional: true}, + "_A": {type: "number", optional: true}, + "^A": {type: "number", optional: true}, + "=A": {type: "number", optional: true}, + "B": {type: "number", optional: true}, + "_B": {type: "number", optional: true}, + "^B": {type: "number", optional: true}, + "=B": {type: "number", optional: true}, + "c": {type: "number", optional: true}, + "_c": {type: "number", optional: true}, + "^c": {type: "number", optional: true}, + "=c": {type: "number", optional: true}, + "d": {type: "number", optional: true}, + "_d": {type: "number", optional: true}, + "^d": {type: "number", optional: true}, + "=d": {type: "number", optional: true}, + "e": {type: "number", optional: true}, + "_e": {type: "number", optional: true}, + "^e": {type: "number", optional: true}, + "=e": {type: "number", optional: true}, + "f": {type: "number", optional: true}, + "_f": {type: "number", optional: true}, + "^f": {type: "number", optional: true}, + "=f": {type: "number", optional: true}, + "g": {type: "number", optional: true}, + "_g": {type: "number", optional: true}, + "^g": {type: "number", optional: true}, + "=g": {type: "number", optional: true}, + "a": {type: "number", optional: true}, + "_a": {type: "number", optional: true}, + "^a": {type: "number", optional: true}, + "=a": {type: "number", optional: true}, + }; + + var formattingProperties = { + type:"object", + properties: { + accentAbove: { type: "boolean", optional: true }, + alignbars: { type: "number", optional: true }, + aligncomposer: { type: "string", Enum: [ 'left', 'center','right' ], optional: true }, + annotationfont: fontType, + auquality: { type: "string", optional: true }, + bagpipes: { type: "boolean", optional: true }, + barlabelfont: fontType, + barnumberfont: fontType, + botmargin: { type: "number", optional: true }, + botspace: { type: "number", optional: true }, + bstemdown: { type: "boolean", optional: true }, + composerfont: fontType, + composerspace: { type: "number", optional: true }, + continueall: { type: "boolean", optional: true }, + continuous: { type: "string", optional: true }, + dynalign: { type: "boolean", optional: true }, + exprabove: { type: "boolean", optional: true }, + exprbelow: { type: "boolean", optional: true }, + flatbeams: { type: "boolean", optional: true }, + footer: { type: "string", optional: true }, + footerfont: fontType, + freegchord: { type: "boolean", optional: true }, + gchordbox: { type: "boolean", optional: true }, + gchordfont: fontType, + graceSlurs: { type: "boolean", optional: true }, + gracespacebefore: { type: "number", optional: true }, + gracespaceinside: { type: "number", optional: true }, + gracespaceafter: { type: "number", optional: true }, + header: { type: "string", optional: true }, + headerfont: fontType, + historyfont: fontType, + indent: { type: "number", optional: true }, + infofont: fontType, + infoline: { type: "boolean", optional: true }, + infospace: { type: "number", optional: true }, +// landscape: { type: "boolean", optional: true }, + jazzchords: { type: "boolean", optional: true }, + germanAlphabet: { type: "boolean", optional: true }, + leftmargin: { type: "number", optional: true }, + linesep: { type: "number", optional: true }, + lineskipfac: { type: "number", optional: true }, + lineThickness: { type: "number", optional: true }, + map: { type: "string", optional: true }, + maxshrink: { type: "number", optional: true }, + maxstaffsep: { type: "number", optional: true }, + maxsysstaffsep: { type: "number", optional: true }, + measurebox: { type: "boolean", optional: true }, + measurefont: fontType, + midi: { type: "object", optional: true, properties: { + barlines: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + bassprog: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + bassvol: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + beat: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + beataccents: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + beatmod: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + beatstring: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + bendvelocity: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + c: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + channel: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + chordattack: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + chordname: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + chordprog: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + chordvol: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + control: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + controlcombo: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + deltaloudness: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + drone: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + droneoff: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + droneon: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + drum: { type: 'array', optional: true, output: 'join', items: { type: "stringorinteger" } }, + drumbars: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + drummap: { type: 'object', optional: true, properties: drummapProps }, + drumoff: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + drumon: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + expand: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + fermatafixed: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + fermataproportional: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + gchord: { type: 'array', optional: true, output: 'join', items: { type: "stringorinteger" } }, + gchordon: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + gchordoff: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + grace: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + gracedivider: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + makechordchannels: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + nobarlines: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + nobeataccents: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + noportamento: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + pitchbend: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + program: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + portamento: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + ptstress: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + randomchordattack: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + ratio: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + rtranspose: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + snt: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + stressmodel: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + temperamentlinear: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + temperamentnormal: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + transpose: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + trim: { type: 'array', optional: true, output: 'join', items: { type: "number" } }, + volinc: { type: 'array', optional: true, output: 'join', items: { type: "number" } } + }}, + musicspace: { type: "number", optional: true }, + nobarcheck: { type: "string", optional: true }, + notespacingfactor: { type: "number", optional: true }, + pageheight: { type: "number" }, + pagewidth: { type: "number" }, + parskipfac: { type: "number", optional: true }, + partsbox: { type: "boolean", optional: true }, + partsfont: fontType, + partsspace: { type: "number", optional: true }, + percmap: {type: 'object', optional: true, properties: percMapProps}, + playtempo: { type: "string", optional: true }, + repeatfont: fontType, + rightmargin: { type: "number", optional: true }, + scale: { type: "number", optional: true }, + score: { type: "string", optional: true }, + slurheight: { type: "number", optional: true }, + splittune: { type: "boolean", optional: true }, + squarebreve: { type: "boolean", optional: true }, + staffsep: { type: "number", optional: true }, + staffwidth: { type: "number", optional: true }, + stemheight: { type: "number", optional: true }, + straightflags: { type: "boolean", optional: true }, + stretchlast: {type: "number", optional: true, minimum: 0, maximum: 1 }, + stretchstaff: { type: "boolean", optional: true }, + subtitlefont: fontType, + subtitlespace: { type: "number", optional: true }, + sysstaffsep: { type: "number", optional: true }, + systemsep: { type: "number", optional: true }, + stafftopmargin: { type: "number", optional: true }, + tabgracefont: fontType, + tablabelfont: fontType, + tabnumberfont: fontType, + tempofont: fontType, + textfont: fontType, + textspace: { type: "number", optional: true }, + titlefont: fontType, + titleformat: { type: "string", optional: true }, + titleleft: { type: "boolean", optional: true }, + titlespace: { type: "number", optional: true }, + topmargin: { type: "number", optional: true }, + topspace: { type: "number", optional: true }, + tripletfont: fontType, + vocalabove: { type: "boolean", optional: true }, + vocalfont: fontType, + vocalspace: { type: "number", optional: true }, + voicefont: fontType, + wordsfont: fontType, + wordsspace: { type: "number", optional: true } + } + }; + + var addProhibits = function(obj, arr) { + var ret = Object.assign({},obj); + ret.prohibits = arr; + return ret; + }; + + var lineProperties = { + type:"array", + description: "This is an array of horizontal elements. It is usually a staff of music. For multi-stave music, each staff is an element, just like single-staff. The difference is the connector properties.", + items: { type: "object", + properties: { + columns: { type: 'array', optional: true, // The width of the columns is fixed and even for all columns. That is known by the number of columns in the array. + items: { type: 'object', + properties: { + formatting: formattingProperties, + lines: lineProperties + } + } + }, + image: { type: 'string', optional: true }, // Corresponds to %%EPS directive. + newpage: { type: 'number', optional: true }, // page number if positive, or -1 for auto + staffbreak: { type: 'number', optional: true }, + separator: { type: 'object', optional: true, prohibits: [ 'staff', 'text', 'subtitle' ], + properties: { + endChar: { type: 'number'}, + lineLength: { type: 'number', optional: true }, + spaceAbove: { type: 'number', optional: true }, + spaceBelow: { type: 'number', optional: true }, + startChar: { type: 'number'} + } + }, + subtitle: { type: "object", optional: true, prohibits: [ 'staff', 'text', 'separator' ], + properties: { + endChar: { type: 'number'}, + startChar: { type: 'number'}, + text: { type: 'string'} + } + }, + text: { type: "object", optional: true, prohibits: [ 'staff', 'subtitle', 'separator' ], + properties: { + endChar: { type: 'number'}, + startChar: { type: 'number'}, + text: textFieldProperties + } + }, + staff: { type: 'array', optional: true, prohibits: [ 'subtitle', 'text', 'separator' ], + items: { type: 'object', + properties: { + barNumber: { type: 'number', optional: true }, + brace: { type: 'string', optional: true, Enum: [ "start", "continue", "end" ] }, + bracket: { type: 'string', optional: true, Enum: [ "start", "continue", "end" ] }, + clef: { type: 'object', optional: true, properties: clefProperties }, + connectBarLines: { type: 'string', optional: true, Enum: [ "start", "continue", "end" ] }, + gchordfont: fontType, + tripletfont: fontType, + vocalfont: fontType, + key: { type: 'object', optional: true, properties: keyProperties }, + meter: { type: 'object', optional: true, properties: meterProperties }, + spacingAbove: { type: 'number', optional: true }, // the vskip directive + spacingBelow: { type: 'number', optional: true }, + stafflines: { type: 'number', optional: true, minimum: 0, maximum: 10 }, + staffscale: { type: 'number', minimum: 0.5, maximum: 3, optional: true }, + title: { type: 'array', optional: true, items: { type: 'string' } }, + voices: { type: 'array', output: 'hidden', + items: { + type: "array", optional: true, output: "noindex", + items: voiceItem + } + } + } + } + }, + vskip: { type: 'number', optional: true } // how much extra space to leave before this line (can be negative) + } + } + }; + + var musicSchema = { + description:"ABC Internal Music Representation", + type:"object", + properties: { + version: { type: "string", Enum: [ "1.1.0" ] }, + media: { type: "string", Enum: [ "screen", "print" ] }, + + formatting: formattingProperties, + lines: lineProperties, + + metaText: {type:"object", + description: "There can only be one of these per tune", + properties: { + "abc-copyright": { type: "string", optional: true }, + "abc-creator": { type: "string", optional: true }, + "abc-version": { type: "string", optional: true }, + "abc-charset": { type: "string", optional: true }, + "abc-edited-by": { type: "string", optional: true }, + author: { type: "string", optional: true }, + book: { type: "string", optional: true }, + composer: { type: "string", optional: true }, + decorationPlacement: { type: '', Enum: [ 'above', 'below' ], optional: true }, + discography: { type: "string", optional: true }, + footer: { type: 'object', optional: true, // The strings %P, %P0, and %P1 should be replaced with the page number + properties: { + left: { type: 'string' }, + center: { type: 'string' }, + right: { type: 'string' } + } + }, + group: { type: "string", optional: true }, + header: { type: 'object', optional: true, + properties: { + left: { type: 'string' }, + center: { type: 'string' }, + right: { type: 'string' } + } + }, + history: { type: "string", optional: true }, + instruction: { type: "string", optional: true }, + measurebox: { type: 'boolean', Enum: [ true ], optional: true }, + notes: { type: "string", optional: true }, + origin: { type: "string", optional: true }, + partOrder: { type: "string", optional: true }, + rhythm: { type: "string", optional: true }, + source: { type: "string", optional: true }, + tempo: { type: "object", optional: true, properties: tempoProperties }, + textBlock: { type: "string", optional: true }, + title: { type: "string", optional: true }, + transcription: { type: "string", optional: true }, + unalignedWords: { type: 'array', optional: true, items: textFieldProperties }, + url: { type: "string", optional: true } + } + }, + metaTextInfo: {type:"object", + description: "There can only be one of these per tune", + properties: { + "abc-copyright": { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + "abc-creator": { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + "abc-version": { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + "abc-charset": { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + "abc-edited-by": { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + author: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + book: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + composer: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + discography: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + footer: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + group: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + header: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + history: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + instruction: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + notes: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + origin: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + partOrder: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + rhythm: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + source: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + tempo: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + textBlock: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + title: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + transcription: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + unalignedWords: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + url: { type: "object", optional: true, properties: { startChar: { type: "number"}, endChar: { type: "number"}, } }, + } + }, + } + }; + + this.lint = function(tune, warnings) { + var ret = JSONSchema.validate(tune, musicSchema); + var err = ""; + ret.errors.forEach(function(e) { + err += e.property + ": " + e.message + "\n"; + }); + var out = ret.output.join("\n"); + + var warn = warnings === undefined ? "No errors" : warnings.join('\n'); + warn = warn.replace(//g, '$$$$$$$$'); + warn = warn.replace(/<\/span>/g, '$$$$$$$$'); + return "Error:------\n" + err + "\nObj:-------\n" + out + "\nWarn:------\n" + warn; + }; +}; + +module.exports = ParserLint; diff --git a/src/test/abc_vertical_lint.js b/src/test/abc_vertical_lint.js new file mode 100644 index 0000000000000000000000000000000000000000..e530b3f84c29cdb74a2ea58493a30ba64c11e00e --- /dev/null +++ b/src/test/abc_vertical_lint.js @@ -0,0 +1,457 @@ +// abc_vertical_lint.js: Analyzes the vertical position of the output object. + +//This file takes as input the output structure of the writing routine and lists the vertical position of all the elements. + +/*globals toString */ + +var verticalLint = function(tunes) { + "use strict"; + + function fixed(digits, zero, num) { + if (num === undefined) + return "undefined"; + if (typeof num !== "number") + return num; + var str = num.toFixed(digits); + if (str.indexOf(zero) > 0) + return str.split('.')[0]; + while (str.length && str[str.length-1] === '0') + str = str.substr(0,str.length-1); + return str; + } + function fixed1(num) { + return fixed(1, ".0", num); + } + function fixed2(num) { + return fixed(2, ".00", num); + } + function fixed4(num) { + return fixed(4, ".0000", num); + } + function formatY(obj) { + return "y= ( " + fixed1(obj.bottom) + ' , ' + fixed1(obj.top) + " )"; + } + function formatX(obj) { + return " x=" + fixed1(obj.x) + ' w=' + fixed1(obj.width); + } + function formatArrayStart(tabs, i) { + return tabs + i + ": "; + } + function collectBrace(brace) { + var ret; + if (brace) { + ret = []; + for (var b1 = 0; b1 < brace.length; b1++) { + var bracket = { + x: brace[b1].x, + top: fixed1(brace[b1].startY), + bottom: fixed1(brace[b1].endY) + }; + if (brace[b1].header) + bracket.header = brace[b1].header; + ret.push(bracket); + } + } + return ret; + } + function getType(obj) { + if (obj.$type.indexOf('staff-extra') >= 0) { + if (obj.elem.length === 1) { + switch(obj.elem[0]) { + case "symbol clefs.G": + return "Treble Clef"; + case "symbol clefs.F": + return "Bass Clef"; + case "symbol clefs.C": + return "C Clef"; + case "symbol timesig.common": + return "Time Common"; + case "symbol timesig.cut": + return "Time Cut"; + case "symbol timesig.imperfectum": + return "Time Imperfectum"; + case "symbol timesig.imperfectum2": + return "Time Imperfectum 2"; + case "symbol timesig.perfectum": + return "Time Perfectum"; + case "symbol timesig.perfectum2": + return "Time Perfectum 2"; + case "symbol clefs.perc": + return "Percussion Clef"; + } + } else if (obj.elem.length === 2) { + switch(obj.elem[0]) { + case "symbol clefs.G": + if (obj.elem[1] === 'symbol 8') + return "Treble Clef 8"; + else if (obj.elem[1].indexOf("barNumber") === 0) + return "Treble Clef [bar=" + obj.elem[1].substring(10) + "]"; + break; + case "symbol clefs.F": + if (obj.elem[1] === 'symbol 8') + return "Bass Clef 8"; + break; + case "symbol clefs.C": + if (obj.elem[1] === 'symbol 8') + return "C Clef 8"; + break; + } + var left = obj.elem[0].replace("symbol ", ""); + var right = obj.elem[1].replace("symbol ", ""); + left = parseInt(left,10); + right = parseInt(right,10); + if (!isNaN(left) && !isNaN(right)) + return "Time Sig " + left + "/" + right; + } + if (obj.elem[0] === "symbol accidentals.sharp" || obj.elem[0] === "symbol accidentals.flat" || obj.elem[0] === "symbol accidentals.nat" || obj.elem[0] === "symbol accidentals.halfsharp" || obj.elem[0] === "symbol accidentals.halfflat") + return "Key Sig " + obj.elem.join(" ").replace(/symbol/g,""); + if (obj.elem.length > 0) { + if (obj.elem[0] === 'symbol 1' || obj.elem[0] === 'symbol 2' || obj.elem[0] === 'symbol 3' || obj.elem[0] === 'symbol 4' || obj.elem[0] === 'symbol 5' || obj.elem[0] === 'symbol 6' || obj.elem[0] === 'symbol 7' || obj.elem[0] === 'symbol 8' || obj.elem[0] === 'symbol 9') + return "Time Sig (odd): " + obj.elem.join('').replace(/symbol /g,""); + } + } else if (obj.$type.indexOf("note") === 0 || obj.$type.indexOf("rest") === 0) { + return obj.$type + " " + fixed4(obj.duration) + ' ' + obj.elem.join(" ").replace(/symbol /g,"").replace(/\n/g, "\\n"); + } else if (obj.$type === 'bar') + return "Bar"; + else if (obj.$type === 'part') + return obj.elem[0]; + else if (obj.$type === 'tempo') + return "Tempo " + obj.elem[0]; + return "unknown"; + } + + function formatStaffs(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = "\n"; + for (i = 0; i < arr.length; i++) { + var obj = arr[i]; + str += formatArrayStart(tabs, i) + formatY(obj) + "\n"; + } + return tabs + str; + } + + function formatVoices(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = "\n"; + for (i = 0; i < arr.length; i++) { + var obj = arr[i]; + str += formatArrayStart(tabs, i) + formatY(obj); + str += formatElements(obj.voiceChildren, indent+1); + if (obj.otherChildren.length > 0) + str += formatOtherChildren(obj.otherChildren, indent+1); + if (obj.beams.length > 0) + str += formatBeams(obj.beams, indent+1); + } + return tabs + str; + } + + function formatClasses(obj) { + if (obj.classes) + return " " + obj.classes; + return ''; + } + + function formatElements(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = "\n"; + for (i = 0; i < arr.length; i++) { + var obj = arr[i]; + var type = getType(obj); + if (type === "unknown") + str += formatArrayStart(tabs, i) + "\n" + formatObject(obj, indent) + formatClasses(obj) + "\n"; + else + str += formatArrayStart(tabs, i) + type + ' ' + formatY(obj) + formatX(obj) + formatClasses(obj) + "\n"; + } + return tabs + str; + } + + function formatOtherChildren(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = tabs + "Other Children:\n"; + for (i = 0; i < arr.length; i++) { + str += formatArrayStart(tabs, i); + var keys = Object.keys(arr[i].params); + keys = keys.sort(); + var params = ""; + for (var j = 0; j < keys.length; j++) + params += keys[j] + ": " + arr[i].params[keys[j]] + " "; + str += arr[i].type + " (" + params + ")" + formatClasses(arr[i].params) + "\n"; + } + return str + "\n"; + } + + function formatBeams(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = tabs + "Beams:\n"; + for (i = 0; i < arr.length; i++) { + str += formatArrayStart(tabs, i); + var keys = Object.keys(arr[i].params); + keys = keys.sort(); + var params = ""; + for (var j = 0; j < keys.length; j++) + params += keys[j] + ": " + arr[i].params[keys[j]] + " "; + str += arr[i].type + " (" + params + ")\n"; + } + return str + "\n"; + } + + function formatArray(arr, indent) { + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + var str = " [\n"; + for (i = 0; i < arr.length; i++) { + var obj = arr[i]; + if (typeof obj === "string" || typeof obj === "number") + str += tabs + i + ": " + obj + "\n"; + else + str += tabs + i + ":\n" + formatObject(obj, indent+1) +"\n"; + } + return tabs + str + tabs + "]"; + } + + function formatObject(obj, indent) { + var str = []; + var tabs = ""; + for (var i = 0; i < indent; i++) tabs += "\t"; + + if (typeof obj === "string" || typeof obj === "number") + return tabs + obj; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = obj[key]; + var prefix = tabs + key + ": "; + if (key === 'staffs') + str.push(prefix + formatStaffs(value, indent+1)); + else if (key === 'voiceChildren') + str.push(prefix + formatElements(value, indent+1)); + else if (toString.call(value) === "[object Array]") + str.push(prefix + formatArray(value, indent+1)); + else if (typeof value === "object") + str.push(prefix + formatObject(value, indent+1)); + else if (typeof value === "number") + str.push(prefix + fixed1(value)); + else + str.push(prefix + value); + } + } + str = str.sort(); + return str.join("\n"); + } + + function addHeader(obj) { + if (obj.header) + return " " + obj.header; + return ""; + } + + function formatLine(line, lineNum) { + var str = ""; + str += "Line: " + lineNum + ": (" + fixed1(line.height) + ")\n"; + if (line.brace) { + for (var i = 0; i < line.brace.length; i++) + str += "brace: " + fixed1(line.brace[i].x) + " " + formatY(line.brace[i]) + addHeader(line.brace[i]) + "\n"; + } + if (line.bracket) { + for (var i2 = 0; i2 < line.bracket.length; i2++) + str += "bracket: " + fixed1(line.bracket[i2].x) + " " + formatY(line.bracket[i2]) + addHeader(line.bracket[i2]) + "\n"; + } + str += "staffs: " + formatStaffs(line.staffs, 1); + str += "voices: " + formatVoices(line.voices, 1); + return str; + } + + function setSpecialY(obj) { + var ret = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if (obj[ret]) + ret[key] = obj[ret]; + } + } + return ret; + } + + function extractPositioningInfo(staffGroup, lineNum) { + var ret = { height: staffGroup.height, minSpace: staffGroup.minspace, spacingUnits: staffGroup.spacingunits, width: staffGroup.w, startX: staffGroup.startX, staffs: [], voices: [] }; + ret.brace = collectBrace(staffGroup.brace); + ret.bracket = collectBrace(staffGroup.bracket); + for (var i = 0; i < staffGroup.staffs.length; i++) { + var staff = staffGroup.staffs[i]; + ret.staffs.push({bottom: fixed1(staff.bottom), top: fixed1(staff.top), specialY: setSpecialY(staff) }); + } + for (i = 0; i < staffGroup.voices.length; i++) { + var voice = staffGroup.voices[i]; + var obj = { bottom: fixed1(voice.bottom), top: fixed1(voice.top), specialY: setSpecialY(voice), width: fixed1(voice.w), startX: voice.startX, voiceChildren: [], otherChildren: [], beams: [] }; + for (var j = 0; j < voice.children.length; j++) { + var child = voice.children[j]; + var type = child.type; + var classes = []; + if (child.elemset) { + for (var jj = 0; jj < child.elemset.length; jj++) { + var cl = child.elemset[jj].classList; + if (cl && cl.length > 0) { + for (var jjj = 0; jjj < cl.length; jjj++) + classes.push(cl[jjj]); + } + } + } + if (type === 'note' || type === 'rest') { + if (child.abcelem.pitches) { + var pitches = []; + for (var ii = 0; ii < child.abcelem.pitches.length; ii++) pitches.push(child.abcelem.pitches[ii].verticalPos); + type += "(" + pitches.join(',') + ")"; + } else { + var r = "rest"; + switch (child.abcelem.rest.type) { + case "invisible": r += "/inv"; break; + case "spacer": r += "/sp"; break; + } + type = "note(" + r + ")"; + } + if (child.abcelem.lyric && child.abcelem.lyric.length > 0) type += " " + child.abcelem.lyric[0].syllable; + } + var obj2 = { $type: type, bottom: fixed1(child.bottom), top: fixed1(child.top), specialY: setSpecialY(child), minSpacing: child.minspacing, duration: child.duration, width: child.w, x: child.x }; + if (classes.length > 0) + obj2.classes = classes.join(" "); + obj2.elem = []; + if (type === 'tempo') { + var tempo = child.children[0].tempo; + var tempoNote = child.children[0].note; + var arr = []; + if (tempo.preString) + arr.push(tempo.preString); + else + arr.push('*'); + arr.push(tempo.duration); + if (tempo.postString) + arr.push(tempo.postString); + else + arr.push('*'); + if (tempoNote) { + arr.push(' note(' + fixed1(tempoNote.top) + ',' + fixed1(tempoNote.bottom) + ',' + fixed1(tempoNote.x) + ',' + fixed1(tempoNote.w) + ')'); + } + obj2.elem.push(arr.join(' ')); + } + else if (child.children.length) { + for (var k = 0; k < child.children.length; k++) { + var str = child.children[k].type; + if (child.children[k].c) + str += " " + child.children[k].c; + obj2.elem.push(str); + } + } + obj.voiceChildren.push(obj2); + } + for (j = 0; j < voice.otherchildren.length; j++) { + var otherChild = voice.otherchildren[j]; + var className = otherChild.constructor.name; + var ch = { type: className, params: {} }; + if (className === "String") + ch.params.value = otherChild; + else { + for (var key in otherChild) { + if (otherChild.hasOwnProperty(key)) { + var value = otherChild[key]; + if (value === null) + ch.params[key] = "null"; + else if (value === className && key === 'type') + ; + else if (key === "elemset") { + var cls = []; + for (var j2 = 0; j2 < otherChild.elemset.length; j2++) { + var c2 = otherChild.elemset[j2].classList; + if (c2 && c2.length > 0) { + for (var j3 = 0; j3 < c2.length; j3++) + cls.push(c2[j3]); + } + } + ch.params.classes = cls.join(" "); + } + else if (typeof value === "object") { + var obj3 = value.constructor.name + '['; + switch (value.constructor.name) { + case "AbsoluteElement": + obj3 += value.type; + break; + case "RelativeElement": + obj3 += value.pitch; + break; + case "Array": + obj3 += value.length; + break; + default: + console.log(value.constructor.name); + ch.params[key] = value.constructor.name; + break; + } + ch.params[key] = obj3 +']'; + } else if (typeof value === "number") + ch.params[key] = fixed1(value); + else + ch.params[key] = value; + } + } + } + obj.otherChildren.push(ch); + } + for (j = 0; j < voice.beams.length; j++) { + var beam = voice.beams[j]; + var className2 = beam.constructor.name; + var ch2 = { type: className2, params: {} }; + if (className2 === "String") + ch2.params.value = beam; + else { + for (var key2 in beam) { + if (beam.hasOwnProperty(key2)) { + var value2 = beam[key2]; + if (typeof value2 === "object") + ch2.params[key2] = "object"; + else + ch2.params[key2] = value2; + } + } + } + obj.beams.push(ch2); + } + // TODO: there is also extra[], heads[], elemset[], and right[] to parse. + ret.voices.push(obj); + } + return formatLine(ret, lineNum); + } + + function extractText(label, arr) { + var items = []; + items.push(label); + for (var i = 0; i < arr.length; i++) { + var item = arr[i]; + items.push("\t" + JSON.stringify(item)); + } + items.push(""); + return items.join("\n"); + } + + var positioning = []; + for (var i = 0; i < tunes.length; i++) { + var tune = tunes[i]; + if (tune.topText && tune.topText.rows.length > 0) + positioning.push(extractText("Top Text", tune.topText.rows)); + for (var j = 0; j < tune.lines.length; j++) { + var line = tune.lines[j]; + if (line.staffGroup) + positioning.push(extractPositioningInfo(tune.lines[j].staffGroup, j)); + else + positioning.push(JSON.stringify(line)); + } + } + if (tune.bottomText && tune.bottomText.rows.length > 0) + positioning.push(extractText("Bottom Text", tune.bottomText.rows)); + return positioning; +}; + +module.exports = verticalLint; diff --git a/src/test/jsonschema-b4.js b/src/test/jsonschema-b4.js new file mode 100644 index 0000000000000000000000000000000000000000..aae833e4e2aa810db331bcdd25c661b039402e5f --- /dev/null +++ b/src/test/jsonschema-b4.js @@ -0,0 +1,303 @@ +/** + * JSONSchema Validator - Validates JavaScript objects using JSON Schemas + * (http://www.json.com/json-schema-proposal/) + * + * Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com) + * Licensed under the MIT (MIT-LICENSE.txt) license. +To use the validator call JSONSchema.validate with an instance object and an optional schema object. +If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), +that schema will be used to validate and the schema parameter is not necessary (if both exist, +both validations will occur). +The validate method will return an array of validation errors. If there are no errors, then an +empty list will be returned. A validation error will have two properties: +"property" which indicates which property had the error +"message" which indicates what the error was + */ + +/* + * Paul Rosen changes: + * various bug fixes + * additionalProperties defaults to true instead of false + * array types cannot contain non-numeric properties + * change enum type to Enum to avoid reserved word. + * added union type so that enums can specify what other data is in an object. + * added printout of the object as output. + * added prohibits, which is the opposite of requires. + * added stringorarray, which allows either a string, or the array specified. + * added stringorinteger, which allows either a string or an integer. + */ + +var JSONSchema = { + validate : function(/*Any*/instance,/*Object*/schema) { + "use strict"; + // Summary: + // To use the validator call JSONSchema.validate with an instance object and an optional schema object. + // If a schema is provided, it will be used to validate. If the instance object refers to a schema (self-validating), + // that schema will be used to validate and the schema parameter is not necessary (if both exist, + // both validations will occur). + // The validate method will return an object with two properties: + // valid: A boolean indicating if the instance is valid by the schema + // errors: An array of validation errors. If there are no errors, then an + // empty list will be returned. A validation error will have two properties: + // property: which indicates which property had the error + // message: which indicates what the error was + // + return this._validate(instance,schema,false); + }, + _validate : function(/*Any*/instance,/*Object*/schema,/*Boolean*/ _changing) { + "use strict"; + var recursion = -1; + var errors = []; + var prettyPrint = []; + function addPrint(indent, message){ + var str = ""; + for (var i = 0; i < indent; i++) + str += "\t"; + prettyPrint.push(str + message); + } + function appendPrint(message){ + if (prettyPrint.length > 0) + prettyPrint[prettyPrint.length-1] += " " + message; + } + + // validate a value against a property definition + function checkProp(value, schema, path,i, recursion){ + var l; + path += path ? typeof i === 'number' ? '[' + i + ']' : typeof i === 'undefined' ? '' : '.' + i : i; + function addError(message){ + errors.push({property:path,message:message}); + } + + if((typeof schema !== 'object' || schema instanceof Array) && (path || typeof schema !== 'function')){ + if(typeof schema === 'function'){ + if(!(value instanceof schema)){ + addError("is not an instance of the class/constructor " + schema.name); + } + }else if(schema){ + addError("Invalid schema/property definition " + schema); + } + return null; + } + if(_changing && schema.readonly){ + addError("is a readonly field, it can not be changed"); + } + if(schema['extends']){ // if it extends another schema, it must pass that schema as well + checkProp(value,schema['extends'],path,i, recursion); + } + // validate a value against a type definition + function checkType(type,value){ + if(type){ + if(typeof type === 'string' && type !== 'any' && + (type === 'null' ? value !== null : typeof value !== type) && + !(value instanceof Array && type === 'array') && + !(type === 'integer' && value%1===0) && + !(type === 'string' && typeof value === 'string') && + !(type === 'stringorinteger' && (typeof value === 'string' || value%1===0)) && + !(type === 'stringorarray' && (typeof value === 'string' || value instanceof Array)) + ){ + return [{property:path,message:(typeof value) + " value found, but a " + type + " is required"}]; + } + if(type instanceof Array){ + var unionErrors=[]; + for(var j = 0; j < type.length; j++){ // a union type + if(!(unionErrors=checkType(type[j],value)).length){ + break; + } + } + if(unionErrors.length){ + return unionErrors; + } + }else if(typeof type === 'object'){ + var priorErrors = errors; + errors = []; + checkProp(value,type,path, recursion); + var theseErrors = errors; + errors = priorErrors; + return theseErrors; + } + } + return []; + } + if(value === undefined){ + if(!schema.optional){ + addError("is missing and it is not optional"); + } + }else{ + if (schema.type === 'union') { + var enumField = value[schema.field]; + if (enumField === undefined) + addError("Expected " + schema.field + " field in " + path); + else { + addPrint(recursion, enumField+':'); + var ttFound = false; + for (var tt = 0; tt < schema.types.length; tt++) { + if (enumField === schema.types[tt].value) { + if (schema.types[tt].properties[schema.field] === undefined) // Don't throw an error when we encounter the original enum field + schema.types[tt].properties[schema.field] = { type: "string", output: "hidden" }; + errors.concat(checkObj(value,schema.types[tt].properties,path+"."+schema.field+'='+enumField,schema.additionalProperties, recursion+1)); + ttFound = true; + break; + } + } + if (ttFound === false) { + addError("Unknown type " + enumField + " in " + path); + } + } + } else + errors = errors.concat(checkType(schema.type,value)); + if(schema.disallow && !checkType(schema.disallow,value).length){ + addError(" disallowed value was matched"); + } + if(value !== null){ + if(value instanceof Array){ + if(schema.items){ + if(schema.items instanceof Array){ + for(i=0,l=value.length; i schema.maxItems){ + addError("There must be a maximum of " + schema.maxItems + " in the array"); + } + if (schema.additionalProperties !== true) { + for(i in value){ + if(value.hasOwnProperty(i) && typeof value[i] !== 'function') { + var num = parseInt(i); + var index = "" + num; + var isNum = i === index; + if(!isNum ){ + addError((typeof value[i]) + " The property " + i + + " is not defined in the schema and the schema does not allow additional properties"); + } + } + } + } + }else if(schema.properties){ + errors.concat(checkObj(value,schema.properties,path,schema.additionalProperties, recursion+1)); + } + if(schema.pattern && typeof value === 'string' && !value.match(schema.pattern)){ + addError("does not match the regex pattern " + schema.pattern); + } + if(schema.maxLength && typeof value === 'string' && value.length > schema.maxLength){ + addError("may only be " + schema.maxLength + " characters long"); + } + if(schema.minLength && typeof value === 'string' && value.length < schema.minLength){ + addError("must be at least " + schema.minLength + " characters long"); + } + if(typeof schema.minimum !== undefined && typeof value === typeof schema.minimum && + schema.minimum > value){ + addError("must have a minimum value of " + schema.minimum); + } + if(typeof schema.maximum !== undefined && typeof value === typeof schema.maximum && + schema.maximum < value){ + addError("must have a maximum value of " + schema.maximum); + } + if(schema.Enum){ + var enumer = schema.Enum; + l = enumer.length; + var found; + for(var j = 0; j < l; j++){ + if(enumer[j]===value){ + found=1; + break; + } + } + if(!found){ + addError(value + " does not have a value in the enumeration " + enumer.join(", ")); + } + } + + if(typeof schema.maxDecimal === 'number' && + (value.toString().match(new RegExp("\\.[0-9]{" + (schema.maxDecimal + 1) + ",}")))){ + addError("may only have " + schema.maxDecimal + " digits of decimal places"); + } + } + } + return null; + } + + function checkAllProps(instance,objTypeDef,path,recursion) { + for(var i in objTypeDef){ + if(objTypeDef.hasOwnProperty(i)){ + var value = instance[i]; + var propDef = objTypeDef[i]; + if (propDef.output !== 'hidden' && value !== undefined) { + if (value === null) + addPrint(recursion, i+": null"); + else if (typeof value === 'object') + addPrint(recursion, i+":"); + else + addPrint(recursion, i+": "+value); + } + checkProp(value,propDef,path,i, recursion); + } + } + } + + // validate an object against a schema + function checkObj(instance,objTypeDef,path,additionalProp, recursion){ + + checkAllProps(instance,objTypeDef,path,recursion); + + for(var i in instance){ + if(instance.hasOwnProperty(i) && typeof instance[i] !== 'function') { + if (objTypeDef[i]) { + var requires = objTypeDef[i].requires; + if(requires && !(requires in instance)){ + errors.push({property:path,message:"the presence of the property " + i + " requires that " + requires + " also be present"}); + } + var prohibits = objTypeDef[i].prohibits; + if(prohibits && (prohibits in instance)){ + errors.push({property:path,message:"the presence of the property " + i + " prohibits " + requires + " from being present"}); + } + var value = instance[i]; + if(!(i in objTypeDef)){ + checkProp(value,additionalProp,path,i, recursion); + } + if(!_changing && value && value.$schema){ + errors = errors.concat(checkProp(value,value.$schema,path,i, recursion)); + } + } else if (additionalProp!==true){ + errors.push({property:path+' '+i,message:(typeof instance[i]) + " The property " + i + + " is not defined in the schema and the schema does not allow additional properties"}); + } + } + } + return errors; + } + if(schema){ + checkProp(instance,schema,'',_changing || '', recursion); + } + if(!_changing && instance && instance.$schema){ + checkProp(instance,instance.$schema,'','', recursion); + } + return {valid:!errors.length,errors:errors, output: prettyPrint}; + } + /* will add this later + newFromSchema : function() { + } +*/ +}; + +module.exports = JSONSchema; diff --git a/src/test/rendering-lint.js b/src/test/rendering-lint.js new file mode 100644 index 0000000000000000000000000000000000000000..51c576a15e13335e5f3a8e403e66839dbbe8657b --- /dev/null +++ b/src/test/rendering-lint.js @@ -0,0 +1,293 @@ +var renderingLint = function(history, abcString) { + var output = []; + for (var i = 0; i < history.length; i++) { + output.push(lintOne(history[i], abcString)); + } + return output.join("\n\n"); +}; + +function lintOne(history, abcString) { + var items = []; + var svgEl = history.svgEl; + var absEl = history.absEl; + var originalText = "``"; + if (history.absEl && history.absEl.abcelem && history.absEl.abcelem.startChar >= 0) + originalText = '`' + abcString.substring(history.absEl.abcelem.startChar, history.absEl.abcelem.endChar) + '`'; + originalText += " selectable"; + if (history.isDraggable) + originalText += " draggable"; + else + originalText += " not-draggable"; + items.push(originalText); + var size = svgEl.getBBox(); + items.push("size: " + Math.round(size.x) + "," + Math.round(size.y) + " - " + Math.round(size.width) + "," + Math.round(size.height)); + + items.push(listSvgElement(svgEl, 0)); + items.push("attributes:" + listAttributes(svgEl, 1)); + items.push("abc:" + listAbcElement(absEl.abcelem, 1)); + items.push(listAbsElement(absEl, 0)); + + return items.join("\n"); +} + +function createIndent(indent) { + var str = ""; + for (var i = 0; i < indent; i++) + str += "\t"; + return str; +} + +function listSvgElement(svgEl, indent) { + var tab = createIndent(indent); + var tab2 = createIndent(indent+1); + var output = []; + output.push("el: " + svgEl.tagName + listClasses(svgEl)); + switch (svgEl.tagName) { + case "text": + output.push("Text: " + svgEl.textContent); + break; + case "path": + break; + case "g": + output.push("children: " + listGroupChildren(svgEl)); + break; + default: + console.log(svgEl.outerHTML) + } + return tab + output.join("\n"+tab2); +} + +function listGroupChildren(g) { + var output = []; + for (var i = 0; i < g.children.length; i++) { + var el = g.children[i]; + output.push(el.tagName + listClasses(el)); + } + return output.join(" "); +} + +function listAttributes(el, indent) { + var tab = createIndent(indent); + if (el.hasAttributes()) { + var attrs = el.attributes; + var output = []; + for(var i = attrs.length - 1; i >= 0; i--) { + if (attrs[i].name !== "class" && attrs[i].name !== "d") + output.push(attrs[i].name + ": " + attrs[i].value); + } + output = output.sort(); + return "\n"+tab + output.join("\n"+tab); + } else { + return ""; + } +} + +function listClasses(el) { + var classes = el.classList; + var klasses = []; + for (var i = 0; i < classes.length; i++) + klasses.push(classes[i]); + klasses = klasses.sort(); + if (klasses.length === 0) + return "[none]"; + return '.' + klasses.join("."); +} + +function listAbsElement(absEl, indent) { + var tab = createIndent(indent); + var tab2 = createIndent(indent+1); + var output = ["abs:"]; + var keys = Object.keys(absEl).sort(); + for (var i = 0; i < keys.length; i++) { + var item = absEl[keys[i]]; + switch(keys[i]) { + case "abcelem": + case "highlight": + case "unhighlight": + break; + case "elemset": + // List this at the bottom + break; + case "specialY": + var yVals = []; + var yKeys = Object.keys(item).sort(); + for (var y = 0; y < yKeys.length; y++) { + if (item[yKeys[y]] !== 0) + yVals.push(yKeys[y] + "=" + item[yKeys[y]]); + } + if (yVals.length > 0) + output.push(keys[i] + ": " + yVals.join(', ')); + break; + case "beam": + if (item.length > 0) + console.log(keys[i], item); + break; + case "heads": + case "extra": + case "children": + case "right": + if (item.length > 0) { + output.push(keys[i] + ":"); + for (var k = 0; k < item.length; k++) { + output.push(listRelativeElement(item[k], indent + 1)); + } + } + break; + case "tuneNumber": + case "bottom": + case "duration": + case "durationClass": + case "extraw": + case "minspacing": + case "top": + case "type": + case "w": + case "x": + output.push(keys[i] + ": " + item); + break; + case "invisible": + case "isClef": + // Don't clutter with false items + if (item) + output.push(keys[i] + ": " + item); + break; + default: + if (item !== undefined) + output.push(keys[i] + ": " + item); + } + } + if (absEl.elemset) { + item = absEl.elemset; + output.push("elemset: (" + item.length + ")"); + for (var j = 0; j < item.length; j++) { + output.push(listSvgElement(item[j], indent+1)); + } + } + return tab + output.join("\n"+tab2); +} + +function listRelativeElement(relativeElement, indent) { + var tab2 = createIndent(indent+1); + var output = []; + output.push("relative element: " + relativeElement.type); + var keys = Object.keys(relativeElement).sort(); + for (var i = 0; i < keys.length; i++) { + var item = relativeElement[keys[i]]; + switch (keys[i]) { + case "parent": + case "part": + break; + case "dim": + output.push(keys[i] +": "+JSON.stringify(item.attr)); + break; + case "graphelem": + if (item) { + output.push(keys[i] +": "); + var svg = listSvgElement(item, indent+1); + svg = svg.substring(indent); // Remove the indent on the first line because that will be added as the last line of this method. + output.push(svg); + } + break; + case "note": + break; + case "tempo": + var tempoDetails = []; + if (item.preString) + tempoDetails.push("preString: " + item.preString); + if (item.duration && item.duration.length) + tempoDetails.push("duration: " + item.duration.join(",")); + if (item.bpm) + tempoDetails.push("bpm: " + item.bpm); + if (item.postString) + tempoDetails.push("postString: " + item.postString); + output.push(keys[i] + ": " + tempoDetails.join(", ")); + break; + default: + if (item !== undefined) + output.push(keys[i] + ": " + (''+item).replace(/\n/g, "\\n")); + break; + } + } + return output.join("\n"+tab2); +} + +function listAbcElement(abcelem, indent) { + var tab = createIndent(indent); + var output = []; + var ignored = []; + var keys = Object.keys(abcelem).sort(); + for (var i = 0; i < keys.length; i++) { + var item = abcelem[keys[i]]; + switch(keys[i]) { + case "el_type": + case "startChar": + case "endChar": + case "text": + case "type": + case "verticalPos": + case "clefPos": + case "root": + case "acc": + case "mode": + case "preString": + case "postString": + case "duration": + case "bpm": + case "title": + case "decoration": + case "averagepitch": + case "minpitch": + case "maxpitch": + case "startBeam": + case "endBeam": + case "barNumber": + case "endTriplet": + case "startTriplet": + case "tripletMultiplier": + case "startEnding": + case "endEnding": + output.push(keys[i] + ": " + item); + break; + case "gracenotes": + case "chord": + case "rest": + case "value": + case "lyric": + case "pitches": + case "accidentals": + output.push(listArrayOfObjects(keys[i], item, indent+1)); + break; + case "abselem": + break; + default: + ignored.push(keys[i]); + console.log(keys[i] + ' ' + item) + } + } + if (ignored.length > 0) + output.push("ignored: " + ignored.join(" ")); + return "\n"+tab + output.join("\n"+tab); +} + +function listArrayOfObjects(key, arr, indent) { + var tab = createIndent(indent); + var output = []; + output.push(key + ":"); + for (var i = 0; i < arr.length; i++) { + var item = arr[i]; + var keys = Object.keys(item).sort(); + for (var j = 0; j < keys.length; j++) { + switch(keys[j]) { + case "startSlur": + case "startTie": + output.push(listArrayOfObjects(keys[j], item[keys[j]], indent+1)); + break; + default: + output.push(keys[j] + ': ' + item[keys[j]]); + } + } + } + return output.join("\n"+tab); +} + +module.exports = renderingLint; diff --git a/src/write/README.md b/src/write/README.md new file mode 100644 index 0000000000000000000000000000000000000000..01b8f5d5885f8a9d4012186b7885e0d63021d0eb --- /dev/null +++ b/src/write/README.md @@ -0,0 +1,31 @@ +# Architecture of the engraver + +The engraver code is handled here. This accepts the output of the parser and does three things: + +* Creates an SVG +* Adds data to the parser input +* Makes the visual elements interactive + +## Steps + +There are separate passes and sub-passes to the processing. The separate passes match the subfolder organization here. + +They are: + +### Element Creation + +The structure passed in from the parser is traversed and all elements are created. An element consists of two parts: + +* The Absolute Element: a visual piece of music that is always moved together. For instance, in the key signature of D, the absolute element is an object that contains two sharp symbols. The absolute element has an X- and Y- position in the SVG, and an array of Relative Elements. + +* Relative Element: These are always attached to an absolute element. They have a relative position to the absolute element and consist of all the glyphs needed. For instance, a dotted eighth note of Bb that is trilled is an absolute element that contains an array of relative elements consisting of a trill, a flat sign, a filled note head, a stem, a flag, and a dot. + +Each of these elements is a separate object, created with `new` and placed in the structure passed in from the parser. + +### Layout + +After all the elements are created the size of all of them are known, so the layout process begins. There are separate layout algorithms for the horizontal and the vertical positioning. + +### Drawing + +After the layout is finished, it is a rote process to draw the music. All decisions have been made. The structure is then traversed and each element is created in the SVG. diff --git a/src/write/creation/abstract-engraver.js b/src/write/creation/abstract-engraver.js new file mode 100644 index 0000000000000000000000000000000000000000..be80e9d67c8646a204ba3ad2263c1005b79fa897 --- /dev/null +++ b/src/write/creation/abstract-engraver.js @@ -0,0 +1,1046 @@ +// abc_abstract_engraver.js: Creates a data structure suitable for printing a line of abc + +var AbsoluteElement = require('./elements/absolute-element'); +var BeamElem = require('./elements/beam-element'); +var BraceElem = require('./elements/brace-element'); +var createClef = require('./create-clef'); +var createKeySignature = require('./create-key-signature'); +var createNoteHead = require('./create-note-head'); +var createTimeSignature = require('./create-time-signature'); +var Decoration = require('./decoration'); +var EndingElem = require('./elements/ending-element'); +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); +var spacing = require('../helpers/spacing'); +var StaffGroupElement = require('./elements/staff-group-element'); +var TempoElement = require('./elements/tempo-element'); +var TieElem = require('./elements/tie-element'); +var TripletElem = require('./elements/triplet-element'); +var VoiceElement = require('./elements/voice-element'); +var addChord = require('./add-chord'); +var pitchesToPerc = require('../../synth/pitches-to-perc') + +var parseCommon = require('../../parse/abc_common'); + +var getDuration = function (elem) { + var d = 0; + if (elem.duration) { + d = elem.duration; + } + return d; +}; + +var hint = false; + +var chartable = { + rest: { 0: "rests.whole", 1: "rests.half", 2: "rests.quarter", 3: "rests.8th", 4: "rests.16th", 5: "rests.32nd", 6: "rests.64th", 7: "rests.128th", "multi": "rests.multimeasure" }, + note: { "-1": "noteheads.dbl", 0: "noteheads.whole", 1: "noteheads.half", 2: "noteheads.quarter", 3: "noteheads.quarter", 4: "noteheads.quarter", 5: "noteheads.quarter", 6: "noteheads.quarter", 7: "noteheads.quarter", 'nostem': "noteheads.quarter" }, + rhythm: { "-1": "noteheads.slash.whole", 0: "noteheads.slash.whole", 1: "noteheads.slash.whole", 2: "noteheads.slash.quarter", 3: "noteheads.slash.quarter", 4: "noteheads.slash.quarter", 5: "noteheads.slash.quarter", 6: "noteheads.slash.quarter", 7: "noteheads.slash.quarter", nostem: "noteheads.slash.nostem" }, + x: { "-1": "noteheads.indeterminate", 0: "noteheads.indeterminate", 1: "noteheads.indeterminate", 2: "noteheads.indeterminate", 3: "noteheads.indeterminate", 4: "noteheads.indeterminate", 5: "noteheads.indeterminate", 6: "noteheads.indeterminate", 7: "noteheads.indeterminate", nostem: "noteheads.indeterminate" }, + harmonic: { "-1": "noteheads.harmonic.quarter", 0: "noteheads.harmonic.quarter", 1: "noteheads.harmonic.quarter", 2: "noteheads.harmonic.quarter", 3: "noteheads.harmonic.quarter", 4: "noteheads.harmonic.quarter", 5: "noteheads.harmonic.quarter", 6: "noteheads.harmonic.quarter", 7: "noteheads.harmonic.quarter", nostem: "noteheads.harmonic.quarter" }, + triangle: { "-1": "noteheads.triangle.quarter", 0: "noteheads.triangle.quarter", 1: "noteheads.triangle.quarter", 2: "noteheads.triangle.quarter", 3: "noteheads.triangle.quarter", 4: "noteheads.triangle.quarter", 5: "noteheads.triangle.quarter", 6: "noteheads.triangle.quarter", 7: "noteheads.triangle.quarter", nostem: "noteheads.triangle.quarter" }, + uflags: { 3: "flags.u8th", 4: "flags.u16th", 5: "flags.u32nd", 6: "flags.u64th" }, + dflags: { 3: "flags.d8th", 4: "flags.d16th", 5: "flags.d32nd", 6: "flags.d64th" } +}; + +var AbstractEngraver = function (getTextSize, tuneNumber, options) { + this.decoration = new Decoration(); + this.getTextSize = getTextSize; + this.tuneNumber = tuneNumber; + this.isBagpipes = options.bagpipes; + this.flatBeams = options.flatbeams; + this.graceSlurs = options.graceSlurs; + this.percmap = options.percmap; + this.initialClef = options.initialClef + this.jazzchords = !!options.jazzchords + this.accentAbove = !!options.accentAbove + this.germanAlphabet = !!options.germanAlphabet + this.reset(); +}; + +AbstractEngraver.prototype.reset = function () { + this.slurs = {}; + this.ties = []; + this.voiceScale = 1; + this.voiceColor = undefined; + this.slursbyvoice = {}; + this.tiesbyvoice = {}; + this.endingsbyvoice = {}; + this.scaleByVoice = {}; + this.colorByVoice = {}; + this.tripletmultiplier = 1; + + this.abcline = undefined; + this.accidentalSlot = undefined; + this.accidentalshiftx = undefined; + this.dotshiftx = undefined; + this.hasVocals = false; + this.minY = undefined; + this.partstartelem = undefined; + this.startlimitelem = undefined; + this.stemdir = undefined; +}; + +AbstractEngraver.prototype.setStemHeight = function (heightInPixels) { + this.stemHeight = Math.round(heightInPixels * 10 / spacing.STEP) / 10; +}; + +AbstractEngraver.prototype.getCurrentVoiceId = function (s, v) { + return "s" + s + "v" + v; +}; + +AbstractEngraver.prototype.pushCrossLineElems = function (s, v) { + this.slursbyvoice[this.getCurrentVoiceId(s, v)] = this.slurs; + this.tiesbyvoice[this.getCurrentVoiceId(s, v)] = this.ties; + this.endingsbyvoice[this.getCurrentVoiceId(s, v)] = this.partstartelem; + this.scaleByVoice[this.getCurrentVoiceId(s, v)] = this.voiceScale; + if (this.voiceColor) + this.colorByVoice[this.getCurrentVoiceId(s, v)] = this.voiceColor; +}; + +AbstractEngraver.prototype.popCrossLineElems = function (s, v) { + this.slurs = this.slursbyvoice[this.getCurrentVoiceId(s, v)] || {}; + this.ties = this.tiesbyvoice[this.getCurrentVoiceId(s, v)] || []; + this.partstartelem = this.endingsbyvoice[this.getCurrentVoiceId(s, v)]; + this.voiceScale = this.scaleByVoice[this.getCurrentVoiceId(s, v)]; + if (this.voiceScale === undefined) this.voiceScale = 1; + this.voiceColor = this.colorByVoice[this.getCurrentVoiceId(s, v)]; +}; + +AbstractEngraver.prototype.containsLyrics = function (staves) { + for (var i = 0; i < staves.length; i++) { + for (var j = 0; j < staves[i].voices.length; j++) { + for (var k = 0; k < staves[i].voices[j].length; k++) { + var el = staves[i].voices[j][k]; + if (el.lyric) { + // We just want to see if there are vocals below the music to know where to put the dynamics. + if (!el.positioning || el.positioning.vocalPosition === 'below') + this.hasVocals = true; + return; + } + } + } + } +}; + +AbstractEngraver.prototype.createABCLine = function (staffs, tempo, l) { + this.minY = 2; // PER: This will be the lowest that any note reaches. It will be used to set the dynamics row. + // See if there are any lyrics on this line. + this.containsLyrics(staffs); + var staffgroup = new StaffGroupElement(this.getTextSize); + this.tempoSet = false; + for (var s = 0; s < staffs.length; s++) { + if (hint) + this.restoreState(); + hint = false; + this.createABCStaff(staffgroup, staffs[s], tempo, s, l); + } + return staffgroup; +}; + +AbstractEngraver.prototype.createABCStaff = function (staffgroup, abcstaff, tempo, s, l) { + // If the tempo is passed in, then the first element should get the tempo attached to it. + staffgroup.getTextSize.updateFonts(abcstaff); + for (var v = 0; v < abcstaff.voices.length; v++) { + var voice = new VoiceElement(v, abcstaff.voices.length); + if (v === 0) { + voice.barfrom = (abcstaff.connectBarLines === "start" || abcstaff.connectBarLines === "continue"); + voice.barto = (abcstaff.connectBarLines === "continue" || abcstaff.connectBarLines === "end"); + } else { + voice.duplicate = true; // bar lines and other duplicate info need not be created + } + if (abcstaff.title && abcstaff.title[v]) { + voice.header = abcstaff.title[v].replace(/\\n/g, "\n"); + voice.headerPosition = 6 + staffgroup.getTextSize.baselineToCenter(voice.header, "voicefont", 'staff-extra voice-name', v, abcstaff.voices.length) / spacing.STEP; + } + if (abcstaff.clef && abcstaff.clef.type === "perc") + voice.isPercussion = true; + var clef = (!this.initialClef || l === 0) && createClef(abcstaff.clef, this.tuneNumber); + if (clef) { + if (v === 0 && abcstaff.barNumber) { + this.addMeasureNumber(abcstaff.barNumber, clef); + } + voice.addChild(clef); + this.startlimitelem = clef; // limit ties here + } + var keySig = createKeySignature(abcstaff.key, this.tuneNumber); + if (keySig) { + voice.addChild(keySig); + this.startlimitelem = keySig; // limit ties here + } + if (abcstaff.meter) { + if (abcstaff.meter.type === 'specified') { + this.measureLength = abcstaff.meter.value[0].num / abcstaff.meter.value[0].den; + } else + this.measureLength = 1; + var ts = createTimeSignature(abcstaff.meter, this.tuneNumber); + voice.addChild(ts); + this.startlimitelem = ts; // limit ties here + } + if (voice.duplicate) + voice.children = []; // we shouldn't reprint the above if we're reusing the same staff. We just created them to get the right spacing. + var staffLines = abcstaff.clef.stafflines || abcstaff.clef.stafflines === 0 ? abcstaff.clef.stafflines : 5; + staffgroup.addVoice(voice, s, staffLines); + var isSingleLineStaff = staffLines === 1; + this.createABCVoice(abcstaff.voices[v], tempo, s, v, isSingleLineStaff, voice); + staffgroup.setStaffLimits(voice); + if (v === 0) { + // only do brace and bracket processing on the first voice, otherwise it would be done twice. + if (abcstaff.brace === "start" || (!staffgroup.brace && abcstaff.brace)) { + if (!staffgroup.brace) + staffgroup.brace = []; + staffgroup.brace.push(new BraceElem(voice, "brace")); + } else if (abcstaff.brace === "end" && staffgroup.brace) { + staffgroup.brace[staffgroup.brace.length - 1].setBottomStaff(voice); + } else if (abcstaff.brace === "continue" && staffgroup.brace) { + staffgroup.brace[staffgroup.brace.length - 1].continuing(voice); + } + if (abcstaff.bracket === "start" || (!staffgroup.bracket && abcstaff.bracket)) { + if (!staffgroup.bracket) + staffgroup.bracket = []; + staffgroup.bracket.push(new BraceElem(voice, "bracket")); + } else if (abcstaff.bracket === "end" && staffgroup.bracket) { + staffgroup.bracket[staffgroup.bracket.length - 1].setBottomStaff(voice); + } else if (abcstaff.bracket === "continue" && staffgroup.bracket) { + staffgroup.bracket[staffgroup.bracket.length - 1].continuing(voice); + } + } + } +}; + +function getBeamGroup(abcline, pos) { + // If there are notes beamed together, they are handled as a group, so find all of them here. + var elem = abcline[pos]; + if (elem.el_type !== 'note' || !elem.startBeam || elem.endBeam) + return { count: 1, elem: elem }; + + var group = []; + while (pos < abcline.length && abcline[pos].el_type === 'note') { + group.push(abcline[pos]); + if (abcline[pos].endBeam) + break; + pos++; + } + return { count: group.length, elem: group }; +} + +AbstractEngraver.prototype.createABCVoice = function (abcline, tempo, s, v, isSingleLineStaff, voice) { + this.popCrossLineElems(s, v); + this.stemdir = (this.isBagpipes) ? "down" : null; + this.abcline = abcline; + if (this.partstartelem) { + this.partstartelem = new EndingElem("", null, null); + voice.addOther(this.partstartelem); + } + var voiceNumber = voice.voicetotal < 2 ? -1 : voice.voicenumber; + for (var slur in this.slurs) { + if (this.slurs.hasOwnProperty(slur)) { + // this is already a slur element, but it was created for the last line, so recreate it. + this.slurs[slur] = new TieElem({ force: this.slurs[slur].force, voiceNumber: voiceNumber, stemDir: this.slurs[slur].stemDir, style: this.slurs[slur].dotted }); + if (hint) this.slurs[slur].setHint(); + voice.addOther(this.slurs[slur]); + } + } + for (var i = 0; i < this.ties.length; i++) { + // this is already a tie element, but it was created for the last line, so recreate it. + this.ties[i] = new TieElem({ force: this.ties[i].force, stemDir: this.ties[i].stemDir, voiceNumber: voiceNumber, style: this.ties[i].dotted }); + if (hint) this.ties[i].setHint(); + voice.addOther(this.ties[i]); + } + + for (var j = 0; j < this.abcline.length; j++) { + setAveragePitch(this.abcline[j]); + this.minY = Math.min(this.abcline[j].minpitch, this.minY); + } + + var isFirstStaff = (s === 0); + var pos = 0; + while (pos < this.abcline.length) { + var ret = getBeamGroup(this.abcline, pos); + var abselems = this.createABCElement(isFirstStaff, isSingleLineStaff, voice, ret.elem); + if (abselems) { + for (i = 0; i < abselems.length; i++) { + if (!this.tempoSet && tempo && !tempo.suppress) { + this.tempoSet = true; + var tempoElement = new AbsoluteElement(tempo, 0, 0, "tempo", this.tuneNumber, {}); + tempoElement.addFixedX(new TempoElement(tempo, this.tuneNumber, createNoteHead)); + voice.addChild(tempoElement); + } + voice.addChild(abselems[i]); + } + } + pos += ret.count; + } + this.pushCrossLineElems(s, v); +}; + +AbstractEngraver.prototype.saveState = function () { + this.tiesSave = parseCommon.cloneArray(this.ties); + this.slursSave = parseCommon.cloneHashOfHash(this.slurs); + this.slursbyvoiceSave = parseCommon.cloneHashOfHash(this.slursbyvoice); + this.tiesbyvoiceSave = parseCommon.cloneHashOfArrayOfHash(this.tiesbyvoice); +}; + +AbstractEngraver.prototype.restoreState = function () { + this.ties = parseCommon.cloneArray(this.tiesSave); + this.slurs = parseCommon.cloneHashOfHash(this.slursSave); + this.slursbyvoice = parseCommon.cloneHashOfHash(this.slursbyvoiceSave); + this.tiesbyvoice = parseCommon.cloneHashOfArrayOfHash(this.tiesbyvoiceSave); +}; + +// function writeMeasureWidth(voice) { +// var width = 0; +// for (var i = voice.children.length-1; i >= 0; i--) { +// var elem = voice.children[i]; +// if (elem.abcelem.el_type === 'bar') +// break; +// width += elem.w; +// } +// return new RelativeElement(width.toFixed(2), -70, 0, undefined, {type:"debug"}); +// } + +// return an array of AbsoluteElement +AbstractEngraver.prototype.createABCElement = function (isFirstStaff, isSingleLineStaff, voice, elem) { + var elemset = []; + switch (elem.el_type) { + case undefined: + // it is undefined if we were passed an array in - an array means a set of notes that should be beamed together. + elemset = this.createBeam(isSingleLineStaff, voice, elem); + break; + case "note": + elemset[0] = this.createNote(elem, false, isSingleLineStaff, voice); + if (this.triplet && this.triplet.isClosed()) { + voice.addOther(this.triplet); + this.triplet = null; + this.tripletmultiplier = 1; + } + break; + case "bar": + elemset[0] = this.createBarLine(voice, elem, isFirstStaff); + if (voice.duplicate && elemset.length > 0) elemset[0].invisible = true; + // elemset[0].addChild(writeMeasureWidth(voice)); + break; + case "meter": + elemset[0] = createTimeSignature(elem, this.tuneNumber); + this.startlimitelem = elemset[0]; // limit ties here + if (voice.duplicate && elemset.length > 0) elemset[0].invisible = true; + break; + case "clef": + elemset[0] = createClef(elem, this.tuneNumber); + if (!elemset[0]) return null; + if (voice.duplicate && elemset.length > 0) elemset[0].invisible = true; + break; + case "key": + var absKey = createKeySignature(elem, this.tuneNumber); + if (absKey) { + elemset[0] = absKey; + this.startlimitelem = elemset[0]; // limit ties here + } + if (voice.duplicate && elemset.length > 0) elemset[0].invisible = true; + break; + case "stem": + this.stemdir = elem.direction === "auto" ? undefined : elem.direction; + break; + case "part": + var abselem = new AbsoluteElement(elem, 0, 0, 'part', this.tuneNumber); + var dim = this.getTextSize.calc(elem.title, 'partsfont', "part"); + abselem.addFixedX(new RelativeElement(elem.title, 0, 0, undefined, { type: "part", height: dim.height / spacing.STEP })); + elemset[0] = abselem; + break; + case "tempo": + var abselem3 = new AbsoluteElement(elem, 0, 0, 'tempo', this.tuneNumber); + abselem3.addFixedX(new TempoElement(elem, this.tuneNumber, createNoteHead)); + elemset[0] = abselem3; + break; + case "style": + if (elem.head === "normal") + delete this.style; + else + this.style = elem.head; + break; + case "hint": + hint = true; + this.saveState(); + break; + case "midi": + // This has no effect on the visible music, so just skip it. + break; + case "scale": + this.voiceScale = elem.size; + break; + case "color": + this.voiceColor = elem.color; + voice.color = this.voiceColor; + break; + + default: + var abselem2 = new AbsoluteElement(elem, 0, 0, 'unsupported', this.tuneNumber); + abselem2.addFixed(new RelativeElement("element type " + elem.el_type, 0, 0, undefined, { type: "debug" })); + elemset[0] = abselem2; + } + + return elemset; +}; + +function setAveragePitch(elem) { + if (elem.pitches) { + sortPitch(elem); + var sum = 0; + for (var p = 0; p < elem.pitches.length; p++) { + sum += elem.pitches[p].verticalPos; + } + elem.averagepitch = sum / elem.pitches.length; + elem.minpitch = elem.pitches[0].verticalPos; + elem.maxpitch = elem.pitches[elem.pitches.length - 1].verticalPos; + } +} + +AbstractEngraver.prototype.createBeam = function (isSingleLineStaff, voice, elems) { + var abselemset = []; + + var beamelem = new BeamElem(this.stemHeight * this.voiceScale, this.stemdir, this.flatBeams, elems[0]); + if (hint) beamelem.setHint(); + for (var i = 0; i < elems.length; i++) { + // Do a first pass to figure out the stem direction before creating the notes, so that staccatos and other decorations can be placed correctly. + beamelem.runningDirection(elems[i]) + } + beamelem.setStemDirection() + var tempStemDir = this.stemdir + this.stemdir = beamelem.stemsUp ? 'up' : 'down' + for (i = 0; i < elems.length; i++) { + var elem = elems[i]; + var abselem = this.createNote(elem, true, isSingleLineStaff, voice); + abselemset.push(abselem); + beamelem.add(abselem); + if (this.triplet && this.triplet.isClosed()) { + voice.addOther(this.triplet); + this.triplet = null; + this.tripletmultiplier = 1; + } + } + beamelem.calcDir(); + voice.addBeam(beamelem); + this.stemdir = tempStemDir + return abselemset; +}; + +var sortPitch = function (elem) { + var sorted; + do { + sorted = true; + for (var p = 0; p < elem.pitches.length - 1; p++) { + if (elem.pitches[p].pitch > elem.pitches[p + 1].pitch) { + sorted = false; + var tmp = elem.pitches[p]; + elem.pitches[p] = elem.pitches[p + 1]; + elem.pitches[p + 1] = tmp; + } + } + } while (!sorted); +}; + +var ledgerLines = function (abselem, minPitch, maxPitch, isRest, symbolWidth, additionalLedgers, dir, dx, scale) { + for (var i = maxPitch; i > 11; i--) { + if (i % 2 === 0 && !isRest) { + abselem.addFixed(new RelativeElement(null, dx, (symbolWidth + 4) * scale, i, { type: "ledger" })); + } + } + + for (i = minPitch; i < 1; i++) { + if (i % 2 === 0 && !isRest) { + abselem.addFixed(new RelativeElement(null, dx, (symbolWidth + 4) * scale, i, { type: "ledger" })); + } + } + + for (i = 0; i < additionalLedgers.length; i++) { // PER: draw additional ledgers + var ofs = symbolWidth; + if (dir === 'down') ofs = -ofs; + abselem.addFixed(new RelativeElement(null, ofs + dx, (symbolWidth + 4) * scale, additionalLedgers[i], { type: "ledger" })); + } +}; + +AbstractEngraver.prototype.addGraceNotes = function (elem, voice, abselem, notehead, stemHeight, isBagpipes, roomtaken) { + var gracescale = 3 / 5; + var graceScaleStem = 3.5 / 5; // TODO-PER: empirically found constant. + stemHeight = Math.round(stemHeight * graceScaleStem); + var gracebeam = null; + var flag; + + if (elem.gracenotes.length > 1) { + gracebeam = new BeamElem(stemHeight, "grace", isBagpipes); + if (hint) gracebeam.setHint(); + gracebeam.mainNote = abselem; // this gives us a reference back to the note this is attached to so that the stems can be attached somewhere. + } + + var i; + var graceoffsets = []; + for (i = elem.gracenotes.length - 1; i >= 0; i--) { // figure out where to place each gracenote + roomtaken += 10; + graceoffsets[i] = roomtaken; + if (elem.gracenotes[i].accidental) { + roomtaken += 7; + } + } + + for (i = 0; i < elem.gracenotes.length; i++) { + var gracepitch = elem.gracenotes[i].verticalPos; + + flag = (gracebeam) ? null : chartable.uflags[(isBagpipes) ? 5 : 3]; + var accidentalSlot = []; + var ret = createNoteHead(abselem, "noteheads.quarter", elem.gracenotes[i], + { dir: "up", headx: -graceoffsets[i], extrax: -graceoffsets[i], flag: flag, scale: gracescale * this.voiceScale, accidentalSlot: accidentalSlot }); + ret.notehead.highestVert = ret.notehead.pitch + stemHeight; + var grace = ret.notehead; + this.addSlursAndTies(abselem, elem.gracenotes[i], grace, voice, "up", true); + + abselem.addExtra(grace); + // PER: added acciaccatura slash + if (elem.gracenotes[i].acciaccatura) { + var pos = elem.gracenotes[i].verticalPos + 7 * gracescale; // the same formula that determines the flag position. + var dAcciaccatura = gracebeam ? 5 : 6; // just an offset to make it line up correctly. + abselem.addRight(new RelativeElement("flags.ugrace", -graceoffsets[i] + dAcciaccatura, 0, pos, { scalex: gracescale, scaley: gracescale })); + } + if (gracebeam) { // give the beam the necessary info + var graceDuration = elem.gracenotes[i].duration / 2; + if (isBagpipes) graceDuration /= 2; + var pseudoabselem = { + heads: [grace], + abcelem: { averagepitch: gracepitch, minpitch: gracepitch, maxpitch: gracepitch, duration: graceDuration } + }; + gracebeam.add(pseudoabselem); + } else { // draw the stem + var p1 = gracepitch + 1 / 3 * gracescale; + var p2 = gracepitch + 7 * gracescale; + var dx = grace.dx + grace.w; + var width = -0.6; + abselem.addExtra(new RelativeElement(null, dx, 0, p1, { "type": "stem", "pitch2": p2, linewidth: width })); + } + ledgerLines(abselem, gracepitch, gracepitch, false, glyphs.getSymbolWidth("noteheads.quarter"), [], true, grace.dx - 1, 0.6); + + // if this is the first grace note, we might want to start a slur. + // there is a slur if graceSlurs is specifically set. + // there is no slur if it is bagpipes. + // there is not a slur if the element is a spacer or invisible rest. + var isInvisibleRest = elem.rest && (elem.rest.type === "spacer" || elem.rest.type === "invisible"); + if (i === 0 && !isBagpipes && this.graceSlurs && !isInvisibleRest) { + // This is the overall slur that is under the grace notes. + voice.addOther(new TieElem({ anchor1: grace, anchor2: notehead, isGrace: true })); + } + } + + if (gracebeam) { + gracebeam.calcDir(); + voice.addBeam(gracebeam); + } + return roomtaken; +}; + +function addRestToAbsElement(abselem, elem, duration, dot, isMultiVoice, stemdir, isSingleLineStaff, durlog, voiceScale) { + var c; + var restpitch = 7; + var noteHead; + var roomTaken; + var roomTakenRight; + + if (isMultiVoice) { + if (stemdir === "down") restpitch = 3; + if (stemdir === "up") restpitch = 11; + } + // There is special placement for the percussion staff. If there is one staff line, then move the rest position. + if (isSingleLineStaff) { + // The half and whole rests are attached to different lines normally, so we need to tweak their position to get them to both be attached to the same one. + if (duration < 0.5) + restpitch = 7; + else if (duration < 1) + restpitch = 7; // half rest + else + restpitch = 5; // whole rest + } + switch (elem.rest.type) { + case "whole": + c = chartable.rest[0]; + elem.averagepitch = restpitch; + elem.minpitch = restpitch; + elem.maxpitch = restpitch; + dot = 0; + break; + case "rest": + if (elem.style === "rhythm") // special case for rhythm: rests are a handy way to express the rhythm. + c = chartable.rhythm[-durlog]; + else + c = chartable.rest[-durlog]; + elem.averagepitch = restpitch; + elem.minpitch = restpitch; + elem.maxpitch = restpitch; + break; + case "invisible": + case "invisible-multimeasure": + case "spacer": + c = ""; + elem.averagepitch = restpitch; + elem.minpitch = restpitch; + elem.maxpitch = restpitch; + break; + case "multimeasure": + c = chartable.rest['multi']; + elem.averagepitch = restpitch; + elem.minpitch = restpitch; + elem.maxpitch = restpitch; + dot = 0; + var mmWidth = glyphs.getSymbolWidth(c); + abselem.addHead(new RelativeElement(c, mmWidth, mmWidth * 2, 7)); + var numMeasures = new RelativeElement("" + elem.rest.text, mmWidth, mmWidth, 16, { type: "multimeasure-text" }); + abselem.addExtra(numMeasures); + } + if (elem.rest.type.indexOf("multimeasure") < 0 && elem.rest.type !== "invisible") { + var ret = createNoteHead(abselem, c, { verticalPos: restpitch }, + { dot: dot, scale: voiceScale }); + noteHead = ret.notehead; + if (noteHead) { + abselem.addHead(noteHead); + roomTaken = ret.accidentalshiftx; + roomTakenRight = ret.dotshiftx; + } + } + return { noteHead: noteHead, roomTaken: roomTaken, roomTakenRight: roomTakenRight }; +} + +function addIfNotExist(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (JSON.stringify(arr[i]) === JSON.stringify(item)) + return; + } + arr.push(item); +} + +AbstractEngraver.prototype.addNoteToAbcElement = function (abselem, elem, dot, stemdir, style, zeroDuration, durlog, nostem, voice) { + var dotshiftx = 0; // room taken by chords with displaced noteheads which cause dots to shift + var noteHead; + var roomTaken = 0; + var roomTakenRight = 0; + var min; + var i; + var additionalLedgers = []; + // The accidentalSlot will hold a list of all the accidentals on this chord. Each element is a vertical place, + // and contains a pitch, which is the last pitch that contains an accidental in that slot. The slots are numbered + // from closest to the note to farther left. We only need to know the last accidental we placed because + // we know that the pitches are sorted by now. + var accidentalSlot = []; + var symbolWidth = 0; + + var dir = (elem.averagepitch >= 6) ? "down" : "up"; + if (stemdir) dir = stemdir; + + style = elem.style ? elem.style : style; // get the style of note head. + if (!style || style === "normal") style = "note"; + var noteSymbol; + if (zeroDuration) + noteSymbol = chartable[style].nostem; + else + noteSymbol = chartable[style][-durlog]; + if (!noteSymbol) + console.log("noteSymbol:", style, durlog, zeroDuration); + + // determine elements of chords which should be shifted + var p; + for (p = (dir === "down") ? elem.pitches.length - 2 : 1; (dir === "down") ? p >= 0 : p < elem.pitches.length; p = (dir === "down") ? p - 1 : p + 1) { + var prev = elem.pitches[(dir === "down") ? p + 1 : p - 1]; + var curr = elem.pitches[p]; + var delta = (dir === "down") ? prev.pitch - curr.pitch : curr.pitch - prev.pitch; + if (delta <= 1 && !prev.printer_shift) { + curr.printer_shift = (delta) ? "different" : "same"; + if (curr.verticalPos > 11 || curr.verticalPos < 1) { // PER: add extra ledger line + additionalLedgers.push(curr.verticalPos - (curr.verticalPos % 2)); + } + if (dir === "down") { + roomTaken = glyphs.getSymbolWidth(noteSymbol) + 2; + } else { + dotshiftx = glyphs.getSymbolWidth(noteSymbol) + 2; + } + } + } + + var pp = elem.pitches.length; + for (p = 0; p < elem.pitches.length; p++) { + + if (!nostem) { + var flag; + if ((dir === "down" && p !== 0) || (dir === "up" && p !== pp - 1)) { // not the stemmed elem of the chord + flag = null; + } else { + flag = chartable[(dir === "down") ? "dflags" : "uflags"][-durlog]; + } + } + var c; + if (elem.pitches[p].style) { // There is a style for the whole group of pitches, but there could also be an override for a particular pitch. + c = chartable[elem.pitches[p].style][-durlog]; + } else if (voice.isPercussion && this.percmap) { + c = noteSymbol; + var percHead = this.percmap[pitchesToPerc(elem.pitches[p])]; + if (percHead && percHead.noteHead) { + if (chartable[percHead.noteHead]) + c = chartable[percHead.noteHead][-durlog]; + } + } else + c = noteSymbol; + // The highest position for the sake of placing slurs is itself if the slur is internal. It is the highest position possible if the slur is for the whole chord. + // If the note is the only one in the chord, then any slur it has counts as if it were on the whole chord. + elem.pitches[p].highestVert = elem.pitches[p].verticalPos; + var isTopWhenStemIsDown = (stemdir === "up" || dir === "up") && p === 0; + var isBottomWhenStemIsUp = (stemdir === "down" || dir === "down") && p === pp - 1; + if (isTopWhenStemIsDown || isBottomWhenStemIsUp) { // place to put slurs if not already on pitches + + if (elem.startSlur || pp === 1) { + elem.pitches[p].highestVert = elem.pitches[pp - 1].verticalPos; + if (getDuration(elem) < 1 && (stemdir === "up" || dir === "up")) + elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem + } + if (elem.startSlur) { + if (!elem.pitches[p].startSlur) elem.pitches[p].startSlur = []; //TODO possibly redundant, provided array is not optional + for (i = 0; i < elem.startSlur.length; i++) { + addIfNotExist(elem.pitches[p].startSlur, elem.startSlur[i]); + } + } + + if (elem.endSlur) { + elem.pitches[p].highestVert = elem.pitches[pp - 1].verticalPos; + if (getDuration(elem) < 1 && (stemdir === "up" || dir === "up")) + elem.pitches[p].highestVert += 6; // If the stem is up, then compensate for the length of the stem + if (!elem.pitches[p].endSlur) elem.pitches[p].endSlur = []; //TODO possibly redundant, provided array is not optional + for (i = 0; i < elem.endSlur.length; i++) { + addIfNotExist(elem.pitches[p].endSlur, elem.endSlur[i]); + } + } + } + + var hasStem = !nostem && durlog <= -1; + var ret = createNoteHead(abselem, c, elem.pitches[p], + { dir: dir, extrax: -roomTaken, flag: flag, dot: dot, dotshiftx: dotshiftx, scale: this.voiceScale, accidentalSlot: accidentalSlot, shouldExtendStem: !stemdir, printAccidentals: !voice.isPercussion }); + symbolWidth = Math.max(glyphs.getSymbolWidth(c), symbolWidth); + abselem.extraw -= ret.extraLeft; + noteHead = ret.notehead; + if (noteHead) { + this.addSlursAndTies(abselem, elem.pitches[p], noteHead, voice, hasStem ? dir : null, false); + + if (elem.gracenotes && elem.gracenotes.length > 0) + noteHead.bottom = noteHead.bottom - 1; // If there is a tie to the grace notes, leave a little more room for the note to avoid collisions. + abselem.addHead(noteHead); + } + roomTaken += ret.accidentalshiftx; + roomTakenRight = Math.max(roomTakenRight, ret.dotshiftx); + } + + // draw stem from the furthest note to a pitch above/below the stemmed note + if (hasStem) { + var stemHeight = Math.round(70 * this.voiceScale) / 10; + var p1 = (dir === "down") ? elem.minpitch - stemHeight : elem.minpitch + 1 / 3; + // PER added stemdir test to make the line meet the note. + if (p1 > 6 && !stemdir) p1 = 6; + var p2 = (dir === "down") ? elem.maxpitch - 1 / 3 : elem.maxpitch + stemHeight; + // PER added stemdir test to make the line meet the note. + if (p2 < 6 && !stemdir) p2 = 6; + var dx = (dir === "down" || abselem.heads.length === 0) ? 0 : abselem.heads[0].w; + var width = (dir === "down") ? 1 : -1; + // TODO-PER-HACK: One type of note head has a different placement of the stem. This should be more generically calculated: + if (noteHead && noteHead.c === 'noteheads.slash.quarter') { + if (dir === 'down') + p2 -= 1; + else + p1 += 1; + } + if (noteHead && noteHead.c === 'noteheads.triangle.quarter') { + if (dir === 'down') + p2 -= 0.7; + else + p1 -= 1.2; + } + abselem.addRight(new RelativeElement(null, dx, 0, p1, { "type": "stem", "pitch2": p2, linewidth: width, bottom: p1 - 1 })); + //var RelativeElement = function RelativeElement(c, dx, w, pitch, opt) { + min = Math.min(p1, p2); + } + return { noteHead: noteHead, roomTaken: roomTaken, roomTakenRight: roomTakenRight, min: min, additionalLedgers: additionalLedgers, dir: dir, symbolWidth: symbolWidth }; +}; + +AbstractEngraver.prototype.addLyric = function (abselem, elem) { + var lyricStr = ""; + elem.lyric.forEach(function (ly) { + var div = ly.divider === ' ' ? "" : ly.divider; + lyricStr += ly.syllable + div + "\n"; + }); + var lyricDim = this.getTextSize.calc(lyricStr, 'vocalfont', "lyric"); + var position = elem.positioning ? elem.positioning.vocalPosition : 'below'; + abselem.addCentered(new RelativeElement(lyricStr, 0, lyricDim.width, undefined, { type: "lyric", position: position, height: lyricDim.height / spacing.STEP, dim: this.getTextSize.attr('vocalfont', "lyric") })); +}; + +AbstractEngraver.prototype.createNote = function (elem, nostem, isSingleLineStaff, voice) { //stem presence: true for drawing stemless notehead + var notehead = null; + var roomtaken = 0; // room needed to the left of the note + var roomtakenright = 0; // room needed to the right of the note + var symbolWidth = 0; + var additionalLedgers = []; // PER: handle the case of [bc'], where the b doesn't have a ledger line + + var dir; + + var duration = getDuration(elem); + var zeroDuration = false; + if (duration === 0) { zeroDuration = true; duration = 0.25; nostem = true; } //PER: zero duration will draw a quarter note head. + var durlog = Math.floor(Math.log(duration) / Math.log(2)); //TODO use getDurlog + var dot = 0; + + for (var tot = Math.pow(2, durlog), inc = tot / 2; tot < duration; dot++, tot += inc, inc /= 2); + + + if (elem.startTriplet) { + this.tripletmultiplier = elem.tripletMultiplier; + } + + var durationForSpacing = duration * this.tripletmultiplier; + if (elem.rest && elem.rest.type === 'multimeasure') + durationForSpacing = 1; + if (elem.rest && elem.rest.type === 'invisible-multimeasure') + durationForSpacing = this.measureLength * elem.rest.text; + var absType = elem.rest ? "rest" : "note"; + var abselem = new AbsoluteElement(elem, durationForSpacing, 1, absType, this.tuneNumber, { durationClassOveride: elem.duration * this.tripletmultiplier }); + if (hint) abselem.setHint(); + + if (elem.rest) { + if (this.measureLength === duration && elem.rest.type !== 'invisible' && elem.rest.type !== 'spacer' && elem.rest.type.indexOf('multimeasure') < 0) + elem.rest.type = 'whole'; // If the rest is exactly a measure, always use a whole rest + var ret1 = addRestToAbsElement(abselem, elem, duration, dot, voice.voicetotal > 1, this.stemdir, isSingleLineStaff, durlog, this.voiceScale); + notehead = ret1.noteHead; + roomtaken = ret1.roomTaken; + roomtakenright = ret1.roomTakenRight; + } else { + var ret2 = this.addNoteToAbcElement(abselem, elem, dot, this.stemdir, this.style, zeroDuration, durlog, nostem, voice); + if (ret2.min !== undefined) + this.minY = Math.min(ret2.min, this.minY); + notehead = ret2.noteHead; + roomtaken = ret2.roomTaken; + roomtakenright = ret2.roomTakenRight; + additionalLedgers = ret2.additionalLedgers; + dir = ret2.dir; + symbolWidth = ret2.symbolWidth; + } + + if (elem.lyric !== undefined) { + this.addLyric(abselem, elem); + } + + if (elem.gracenotes !== undefined) { + roomtaken += this.addGraceNotes(elem, voice, abselem, notehead, this.stemHeight * this.voiceScale, this.isBagpipes, roomtaken); + } + + if (elem.decoration) { + // TODO-PER: nostem is true if this is beamed. In that case we don't know where to place the decoration yet so just make a guess. This should be refactored to not place decorations until after the beams are determined. + // This should probably be combined with moveDecorations() + var bottom = nostem && dir !== 'up' ? Math.min(-3, abselem.bottom - 6) : abselem.bottom + this.decoration.createDecoration(voice, elem.decoration, abselem.top, (notehead) ? notehead.w : 0, abselem, roomtaken, dir, bottom, elem.positioning, this.hasVocals, this.accentAbove); + } + + if (elem.barNumber) { + abselem.addFixed(new RelativeElement(elem.barNumber, -10, 0, 0, { type: "barNumber" })); + } + + // ledger lines + ledgerLines(abselem, elem.minpitch, elem.maxpitch, elem.rest, symbolWidth, additionalLedgers, dir, -2, 1); + + if (elem.chord !== undefined) { + var ret3 = addChord(this.getTextSize, abselem, elem, roomtaken, roomtakenright, symbolWidth, this.jazzchords, this.germanAlphabet); + roomtaken = ret3.roomTaken; + roomtakenright = ret3.roomTakenRight; + } + + if (elem.startTriplet) { + this.triplet = new TripletElem(elem.startTriplet, notehead, { flatBeams: this.flatBeams }); // above is opposite from case of slurs + } + + if (elem.endTriplet && this.triplet) { + this.triplet.setCloseAnchor(notehead); + } + + if (this.triplet && !elem.startTriplet && !elem.endTriplet && !(elem.rest && elem.rest.type === "spacer")) { + this.triplet.middleNote(notehead); + } + + return abselem; +}; + +AbstractEngraver.prototype.addSlursAndTies = function (abselem, pitchelem, notehead, voice, dir, isGrace) { + if (pitchelem.endTie) { + if (this.ties.length > 0) { + // If there are multiple open ties, find the one that applies by matching the pitch, if possible. + var found = false; + for (var j = 0; j < this.ties.length; j++) { + if (this.ties[j].anchor1 && this.ties[j].anchor1.pitch === notehead.pitch) { + this.ties[j].setEndAnchor(notehead); + voice.setRange(this.ties[j]) + this.ties.splice(j, 1); + found = true; + break; + } + } + if (!found) { + this.ties[0].setEndAnchor(notehead); + voice.setRange(this.ties[0]) + this.ties.splice(0, 1); + } + } + } + + var voiceNumber = voice.voicetotal < 2 ? -1 : voice.voicenumber; + if (pitchelem.startTie) { + var tie = new TieElem({ anchor1: notehead, force: (this.stemdir === "down" || this.stemdir === "up"), stemDir: this.stemdir, isGrace: isGrace, voiceNumber: voiceNumber, style: pitchelem.startTie.style }); + if (hint) tie.setHint(); + + this.ties[this.ties.length] = tie; + voice.addOther(tie); + // HACK-PER: For the animation, we need to know if a note is tied to the next one, so here's a flag. + // Unfortunately, only some of the notes in the current event might be tied, but this will consider it + // tied if any one of them is. That will work for most cases. + abselem.startTie = true; + } + + var slur; + var slurid; + if (pitchelem.endSlur) { + for (var i = 0; i < pitchelem.endSlur.length; i++) { + slurid = pitchelem.endSlur[i]; + if (this.slurs[slurid]) { + slur = this.slurs[slurid]; + slur.setEndAnchor(notehead); + voice.setRange(slur) + delete this.slurs[slurid]; + } else { + slur = new TieElem({ anchor2: notehead, stemDir: this.stemdir, voiceNumber: voiceNumber }); + if (hint) slur.setHint(); + voice.addOther(slur); + } + if (this.startlimitelem) { + slur.setStartX(this.startlimitelem); + } + } + } else if (!isGrace) { + for (var s in this.slurs) { + if (this.slurs.hasOwnProperty(s)) { + this.slurs[s].addInternalNote(notehead); + } + } + } + + if (pitchelem.startSlur) { + for (i = 0; i < pitchelem.startSlur.length; i++) { + slurid = pitchelem.startSlur[i].label; + slur = new TieElem({ anchor1: notehead, stemDir: this.stemdir, voiceNumber: voiceNumber, style: pitchelem.startSlur[i].style }); + if (hint) slur.setHint(); + this.slurs[slurid] = slur; + voice.addOther(slur); + } + } +}; + +AbstractEngraver.prototype.addMeasureNumber = function (number, abselem) { + var measureNumDim = this.getTextSize.calc(number, "measurefont", 'bar-number'); + var dx = 0; + if (abselem.isClef) // If this is a clef rather than bar line, then the number shouldn't be centered because it could overlap the left side. This is an easy way to let it be centered but move it over, too. + dx += measureNumDim.width / 2 + var vert = measureNumDim.width > 10 && abselem.abcelem.type === "treble" ? 13 : 11 + abselem.addFixed(new RelativeElement(number, dx, measureNumDim.width, vert + measureNumDim.height / spacing.STEP, { type: "barNumber", dim: this.getTextSize.attr("measurefont", 'bar-number') })); +}; + +AbstractEngraver.prototype.createBarLine = function (voice, elem, isFirstStaff) { + // bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat + + var abselem = new AbsoluteElement(elem, 0, 10, 'bar', this.tuneNumber); + var anchor = null; // place to attach part lines + var dx = 0; + + if (elem.barNumber) { + this.addMeasureNumber(elem.barNumber, abselem); + } + + + var firstdots = (elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat"); + var firstthin = (elem.type !== "bar_left_repeat" && elem.type !== "bar_thick_thin" && elem.type !== "bar_invisible"); + var thick = (elem.type === "bar_right_repeat" || elem.type === "bar_dbl_repeat" || elem.type === "bar_left_repeat" || + elem.type === "bar_thin_thick" || elem.type === "bar_thick_thin"); + var secondthin = (elem.type === "bar_left_repeat" || elem.type === "bar_thick_thin" || elem.type === "bar_thin_thin" || elem.type === "bar_dbl_repeat"); + var seconddots = (elem.type === "bar_left_repeat" || elem.type === "bar_dbl_repeat"); + + // limit positioning of slurs + if (firstdots || seconddots) { + for (var slur in this.slurs) { + if (this.slurs.hasOwnProperty(slur)) { + this.slurs[slur].setEndX(abselem); + } + } + this.startlimitelem = abselem; + } + + if (firstdots) { + abselem.addRight(new RelativeElement("dots.dot", dx, 1, 7)); + abselem.addRight(new RelativeElement("dots.dot", dx, 1, 5)); + dx += 6; //2 hardcoded, twice; + } + + if (firstthin) { + anchor = new RelativeElement(null, dx, 1, 2, { "type": "bar", "pitch2": 10, linewidth: 0.6 }); + abselem.addRight(anchor); + } + + if (elem.type === "bar_invisible") { + anchor = new RelativeElement(null, dx, 1, 2, { "type": "none", "pitch2": 10, linewidth: 0.6 }); + abselem.addRight(anchor); + } + + if (elem.decoration) { + this.decoration.createDecoration(voice, elem.decoration, 12, (thick) ? 3 : 1, abselem, 0, "down", 2, elem.positioning, this.hasVocals, this.accentAbove); + } + + if (thick) { + dx += 4; //3 hardcoded; + anchor = new RelativeElement(null, dx, 4, 2, { "type": "bar", "pitch2": 10, linewidth: 4 }); + abselem.addRight(anchor); + dx += 5; + } + + // if (this.partstartelem && (thick || (firstthin && secondthin))) { // means end of nth part + // this.partstartelem.anchor2=anchor; + // this.partstartelem = null; + // } + + if (this.partstartelem && elem.endEnding) { + this.partstartelem.anchor2 = anchor; + this.partstartelem = null; + } + + if (secondthin) { + dx += 3; //3 hardcoded; + anchor = new RelativeElement(null, dx, 1, 2, { "type": "bar", "pitch2": 10, linewidth: 0.6 }); + abselem.addRight(anchor); // 3 is hardcoded + } + + if (seconddots) { + dx += 3; //3 hardcoded; + abselem.addRight(new RelativeElement("dots.dot", dx, 1, 7)); + abselem.addRight(new RelativeElement("dots.dot", dx, 1, 5)); + } // 2 is hardcoded + + if (elem.startEnding && isFirstStaff) { // only put the first & second ending marks on the first staff + var textWidth = this.getTextSize.calc(elem.startEnding, "repeatfont", '').width; + abselem.minspacing += textWidth + 10; // Give plenty of room for the ending number. + this.partstartelem = new EndingElem(elem.startEnding, anchor, null); + voice.addOther(this.partstartelem); + } + + // Add a little space to the left of the bar line so that nothing can crowd it. + abselem.extraw -= 5; + + if (elem.chord !== undefined) { + var ret3 = addChord(this.getTextSize, abselem, elem, 0, 0, 0, false, this.germanAlphabet); + } + + return abselem; + +}; + +module.exports = AbstractEngraver; diff --git a/src/write/creation/add-chord.js b/src/write/creation/add-chord.js new file mode 100644 index 0000000000000000000000000000000000000000..a038419c7f2b1598a766d6107e8ca6f3b78ef83f --- /dev/null +++ b/src/write/creation/add-chord.js @@ -0,0 +1,122 @@ +var RelativeElement = require('./elements/relative-element'); +var spacing = require('../helpers/spacing'); +const translateChord = require("./translate-chord"); + +var addChord = function (getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet) { + for (var i = 0; i < elem.chord.length; i++) { + var pos = elem.chord[i].position; + var rel_position = elem.chord[i].rel_position; + var isAnnotation = pos === "left" || pos === "right" || pos === "below" || pos === "above" || !!rel_position + var font; + var klass; + if (isAnnotation) { + font = 'annotationfont'; + klass = "abcjs-annotation"; + } else { + font = 'gchordfont'; + klass = "abcjs-chord"; + } + var attr = getTextSize.attr(font, klass); + + var name = elem.chord[i].name + var ret; + //console.log("chord",name) + if (typeof name === "string") { + ret = chordString(name, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet) + roomTaken = ret.roomTaken + roomTakenRight = ret.roomTakenRight + } else { + for (var j = 0; j < name.length; j++) { + ret = chordString(name[j].text, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet) + roomTaken = ret.roomTaken + roomTakenRight = ret.roomTakenRight + } + } + } + return { roomTaken: roomTaken, roomTakenRight: roomTakenRight }; +}; + +function chordString(chordString, pos, rel_position, isAnnotation, font, klass, attr, getTextSize, abselem, elem, roomTaken, roomTakenRight, noteheadWidth, jazzchords, germanAlphabet) { + var chords = chordString.split("\n"); + for (var j = chords.length - 1; j >= 0; j--) { // parse these in opposite order because we place them from bottom to top. + var chord = chords[j]; + var x = 0; + var y; + if (!isAnnotation) + chord = translateChord(chord, jazzchords, germanAlphabet); + var dim = getTextSize.calc(chord, font, klass); + var chordWidth = dim.width; + var chordHeight = dim.height / spacing.STEP; + switch (pos) { + case "left": + roomTaken += chordWidth + 7; + x = -roomTaken; // TODO-PER: This is just a guess from trial and error + y = elem.averagepitch; + abselem.addExtra(new RelativeElement(chord, x, chordWidth + 4, y, { + type: "text", + height: chordHeight, + dim: attr, + position: "left" + })); + break; + case "right": + roomTakenRight += 4; + x = roomTakenRight;// TODO-PER: This is just a guess from trial and error + y = elem.averagepitch; + abselem.addRight(new RelativeElement(chord, x, chordWidth + 4, y, { + type: "text", + height: chordHeight, + dim: attr, + position: "right" + })); + break; + case "below": + // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is. + abselem.addRight(new RelativeElement(chord, 0, 0, undefined, { + type: "text", + position: "below", + height: chordHeight, + dim: attr, + realWidth: chordWidth + })); + break; + case "above": + // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is. + abselem.addRight(new RelativeElement(chord, 0, 0, undefined, { + type: "text", + position: "above", + height: chordHeight, + dim: attr, + realWidth: chordWidth + })); + break; + default: + if (rel_position) { + var relPositionY = rel_position.y + 3 * spacing.STEP; // TODO-PER: this is a fudge factor to make it line up with abcm2ps + abselem.addRight(new RelativeElement(chord, x + rel_position.x, 0, elem.minpitch + relPositionY / spacing.STEP, { + position: "relative", + type: "text", + height: chordHeight, + dim: attr + })); + } else { + // setting the y-coordinate to undefined for now: it will be overwritten later on, after we figure out what the highest element on the line is. + var pos2 = 'above'; + if (elem.positioning && elem.positioning.chordPosition) + pos2 = elem.positioning.chordPosition; + + if (pos2 !== 'hidden') { + abselem.addCentered(new RelativeElement(chord, noteheadWidth / 2, chordWidth, undefined, { + type: "chord", + position: pos2, + height: chordHeight, + dim: attr, + realWidth: chordWidth + })); + } + } + } + } + return { roomTaken: roomTaken, roomTakenRight: roomTakenRight }; +} +module.exports = addChord; diff --git a/src/write/creation/add-text-if.js b/src/write/creation/add-text-if.js new file mode 100644 index 0000000000000000000000000000000000000000..164b036b65dcfa59d8a801cae1bda31eafa350f2 --- /dev/null +++ b/src/write/creation/add-text-if.js @@ -0,0 +1,33 @@ +function addTextIf(rows, params, getTextSize) { + if (!params.text) + return; + if (!params.marginLeft) params.marginLeft = 0; + if (!params.klass) params.klass = ''; + if (!params.anchor) params.anchor = 'start'; + if (!params.info) params.info = { startChar: -2, endChar: -2 } + + if (params.marginTop) + rows.push({ move: params.marginTop }); + var attr = { left: params.marginLeft, text: params.text, font: params.font, anchor: params.anchor, startChar: params.info.startChar, endChar: params.info.endChar, 'dominant-baseline': params['dominant-baseline'] }; + if (params.absElemType) + attr.absElemType = params.absElemType; + if (!params.inGroup && params.klass) + attr.klass = params.klass; + if (params.name) + attr.name = params.name; + + rows.push(attr); + // If there are blank lines they won't be counted by getTextSize, so just get the height of one line and multiply + var size = getTextSize.calc("A", params.font, params.klass); + var numLines = params.text.split("\n").length; + if (params.text[params.text.length - 1] === '\n') + numLines--; // If there is a new line at the end of the string, then an extra line will be counted. + if (!params.noMove) { + var h = (size.height * 1.1) * numLines; + rows.push({ move: Math.round(h) }); + if (params.marginBottom) + rows.push({ move: params.marginBottom }); + } +} + +module.exports = addTextIf; diff --git a/src/write/creation/calc-height.js b/src/write/creation/calc-height.js new file mode 100644 index 0000000000000000000000000000000000000000..f1dfea6680abf5048edaa9f49fcb7814f5a363f8 --- /dev/null +++ b/src/write/creation/calc-height.js @@ -0,0 +1,17 @@ +var calcHeight = function (staffGroup) { + // the height is calculated here in a parallel way to the drawing below in hopes that both of these functions will be modified together. + // TODO-PER: also add the space between staves. (That's systemStaffSeparation, which is the minimum distance between the staff LINES.) + var height = 0; + for (var i = 0; i < staffGroup.voices.length; i++) { + var staff = staffGroup.voices[i].staff; + if (!staffGroup.voices[i].duplicate) { + height += staff.top; + //if (staff.bottom < 0) + height += -staff.bottom; + } + } + return height; +}; + +module.exports = calcHeight; + diff --git a/src/write/creation/create-clef.js b/src/write/creation/create-clef.js new file mode 100644 index 0000000000000000000000000000000000000000..f035ccc706690bc5f9fbefa0db4056426d1b22c7 --- /dev/null +++ b/src/write/creation/create-clef.js @@ -0,0 +1,72 @@ +// abc_create_clef.js + +var AbsoluteElement = require('./elements/absolute-element'); +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); + +var createClef = function (elem, tuneNumber) { + var clef; + var octave = 0; + elem.el_type = "clef"; + var abselem = new AbsoluteElement(elem, 0, 10, 'staff-extra clef', tuneNumber); + abselem.isClef = true; + switch (elem.type) { + case "treble": clef = "clefs.G"; break; + case "tenor": clef = "clefs.C"; break; + case "alto": clef = "clefs.C"; break; + case "bass": clef = "clefs.F"; break; + case 'treble+8': clef = "clefs.G"; octave = 1; break; + case 'tenor+8': clef = "clefs.C"; octave = 1; break; + case 'bass+8': clef = "clefs.F"; octave = 1; break; + case 'alto+8': clef = "clefs.C"; octave = 1; break; + case 'treble-8': clef = "clefs.G"; octave = -1; break; + case 'tenor-8': clef = "clefs.C"; octave = -1; break; + case 'bass-8': clef = "clefs.F"; octave = -1; break; + case 'alto-8': clef = "clefs.C"; octave = -1; break; + case 'none': return null; + case 'perc': clef = "clefs.perc"; break; + default: abselem.addFixed(new RelativeElement("clef=" + elem.type, 0, 0, undefined, { type: "debug" })); + } + // if (elem.verticalPos) { + // pitch = elem.verticalPos; + // } + var dx = 5; + if (clef) { + var height = glyphs.symbolHeightInPitches(clef); + var ofs = clefOffsets(clef); + abselem.addRight(new RelativeElement(clef, dx, glyphs.getSymbolWidth(clef), elem.clefPos, { top: height + elem.clefPos + ofs, bottom: elem.clefPos + ofs })); + + if (octave !== 0) { + var scale = 2 / 3; + var adjustspacing = (glyphs.getSymbolWidth(clef) - glyphs.getSymbolWidth("8") * scale) / 2; + var pitch = (octave > 0) ? abselem.top + 3 : abselem.bottom - 1; + var top = (octave > 0) ? abselem.top + 3 : abselem.bottom - 3; + var bottom = top - 2; + if (elem.type === "bass-8") { + // The placement for bass octave is a little different. It should hug the clef. + pitch = 3; + adjustspacing = 0; + } + abselem.addRight(new RelativeElement("8", dx + adjustspacing, glyphs.getSymbolWidth("8") * scale, pitch, { + scalex: scale, + scaley: scale, + top: top, + bottom: bottom + })); + //abselem.top += 2; + } + } + return abselem; +}; + +function clefOffsets(clef) { + switch (clef) { + case "clefs.G": return -5; + case "clefs.C": return -4; + case "clefs.F": return -4; + case "clefs.perc": return -2; + default: return 0; + } +} + +module.exports = createClef; diff --git a/src/write/creation/create-key-signature.js b/src/write/creation/create-key-signature.js new file mode 100644 index 0000000000000000000000000000000000000000..05cbae5e5b15b443ec89b6f252d8af8e4c596adb --- /dev/null +++ b/src/write/creation/create-key-signature.js @@ -0,0 +1,31 @@ +// abc_create_key_signature.js + +var AbsoluteElement = require('./elements/absolute-element'); +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); + +var createKeySignature = function (elem, tuneNumber) { + elem.el_type = "keySignature"; + if (!elem.accidentals || elem.accidentals.length === 0) + return null; + var abselem = new AbsoluteElement(elem, 0, 10, 'staff-extra key-signature', tuneNumber); + abselem.isKeySig = true; + var dx = 0; + elem.accidentals.forEach(function (acc) { + var symbol; + var fudge = 0; + switch (acc.acc) { + case "sharp": symbol = "accidentals.sharp"; fudge = -3; break; + case "natural": symbol = "accidentals.nat"; break; + case "flat": symbol = "accidentals.flat"; fudge = -1.2; break; + case "quartersharp": symbol = "accidentals.halfsharp"; fudge = -2.5; break; + case "quarterflat": symbol = "accidentals.halfflat"; fudge = -1.2; break; + default: symbol = "accidentals.flat"; + } + abselem.addRight(new RelativeElement(symbol, dx, glyphs.getSymbolWidth(symbol), acc.verticalPos, { thickness: glyphs.symbolHeightInPitches(symbol), top: acc.verticalPos + glyphs.symbolHeightInPitches(symbol) + fudge, bottom: acc.verticalPos + fudge })); + dx += glyphs.getSymbolWidth(symbol) + 2; + }, this); + return abselem; +}; + +module.exports = createKeySignature; diff --git a/src/write/creation/create-note-head.js b/src/write/creation/create-note-head.js new file mode 100644 index 0000000000000000000000000000000000000000..606d7a96ad4fa573b9e1dc83068c3dba6376edc3 --- /dev/null +++ b/src/write/creation/create-note-head.js @@ -0,0 +1,107 @@ +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); + +var createNoteHead = function (abselem, c, pitchelem, options) { + if (!options) options = {}; + var dir = (options.dir !== undefined) ? options.dir : null; + var headx = (options.headx !== undefined) ? options.headx : 0; + var extrax = (options.extrax !== undefined) ? options.extrax : 0; + var flag = (options.flag !== undefined) ? options.flag : null; + var dot = (options.dot !== undefined) ? options.dot : 0; + var dotshiftx = (options.dotshiftx !== undefined) ? options.dotshiftx : 0; + var scale = (options.scale !== undefined) ? options.scale : 1; + var accidentalSlot = (options.accidentalSlot !== undefined) ? options.accidentalSlot : []; + var shouldExtendStem = (options.shouldExtendStem !== undefined) ? options.shouldExtendStem : false; + var printAccidentals = (options.printAccidentals !== undefined) ? options.printAccidentals : true; + + // TODO scale the dot as well + var pitch = pitchelem.verticalPos; + var notehead; + var accidentalshiftx = 0; + var newDotShiftX = 0; + var extraLeft = 0; + if (c === undefined) + abselem.addFixed(new RelativeElement("pitch is undefined", 0, 0, 0, { type: "debug" })); + else if (c === "") { + notehead = new RelativeElement(null, 0, 0, pitch); + } else { + var shiftheadx = headx; + if (pitchelem.printer_shift) { + var adjust = (pitchelem.printer_shift === "same") ? 1 : 0; + shiftheadx = (dir === "down") ? -glyphs.getSymbolWidth(c) * scale + adjust : glyphs.getSymbolWidth(c) * scale - adjust; + } + var opts = { scalex: scale, scaley: scale, thickness: glyphs.symbolHeightInPitches(c) * scale, name: pitchelem.name }; + notehead = new RelativeElement(c, shiftheadx, glyphs.getSymbolWidth(c) * scale, pitch, opts); + notehead.stemDir = dir; + if (flag) { + var pos = pitch + ((dir === "down") ? -7 : 7) * scale; + // if this is a regular note, (not grace or tempo indicator) then the stem will have been stretched to the middle line if it is far from the center. + if (shouldExtendStem) { + if (dir === "down" && pos > 6) + pos = 6; + if (dir === "up" && pos < 6) + pos = 6; + } + //if (scale===1 && (dir==="down")?(pos>6):(pos<6)) pos=6; + var xdelta = (dir === "down") ? headx : headx + notehead.w - 0.6; + abselem.addRight(new RelativeElement(flag, xdelta, glyphs.getSymbolWidth(flag) * scale, pos, { scalex: scale, scaley: scale })); + } + newDotShiftX = notehead.w + dotshiftx - 2 + 5 * dot; + for (; dot > 0; dot--) { + var dotadjusty = (1 - Math.abs(pitch) % 2); //PER: take abs value of the pitch. And the shift still happens on ledger lines. + abselem.addRight(new RelativeElement("dots.dot", notehead.w + dotshiftx - 2 + 5 * dot, glyphs.getSymbolWidth("dots.dot"), pitch + dotadjusty)); + } + } + if (notehead) + notehead.highestVert = pitchelem.highestVert; + + if (printAccidentals && pitchelem.accidental) { + var symb; + switch (pitchelem.accidental) { + case "quartersharp": + symb = "accidentals.halfsharp"; + break; + case "dblsharp": + symb = "accidentals.dblsharp"; + break; + case "sharp": + symb = "accidentals.sharp"; + break; + case "quarterflat": + symb = "accidentals.halfflat"; + break; + case "flat": + symb = "accidentals.flat"; + break; + case "dblflat": + symb = "accidentals.dblflat"; + break; + case "natural": + symb = "accidentals.nat"; + } + // if a note is at least a sixth away, it can share a slot with another accidental + var accSlotFound = false; + var accPlace = extrax; + for (var j = 0; j < accidentalSlot.length; j++) { + if (pitch - accidentalSlot[j][0] >= 6) { + accidentalSlot[j][0] = pitch; + accPlace = accidentalSlot[j][1]; + accSlotFound = true; + break; + } + } + if (accSlotFound === false) { + accPlace -= (glyphs.getSymbolWidth(symb) * scale + 2); + accidentalSlot.push([pitch, accPlace]); + accidentalshiftx = (glyphs.getSymbolWidth(symb) * scale + 2); + } + var h = glyphs.symbolHeightInPitches(symb); + abselem.addExtra(new RelativeElement(symb, accPlace, glyphs.getSymbolWidth(symb), pitch, { scalex: scale, scaley: scale, top: pitch + h / 2, bottom: pitch - h / 2 })); + extraLeft = glyphs.getSymbolWidth(symb) / 2; // TODO-PER: We need a little extra width if there is an accidental, but I'm not sure why it isn't the full width of the accidental. + } + + return { notehead: notehead, accidentalshiftx: accidentalshiftx, dotshiftx: newDotShiftX, extraLeft: extraLeft }; + +}; + +module.exports = createNoteHead; diff --git a/src/write/creation/create-time-signature.js b/src/write/creation/create-time-signature.js new file mode 100644 index 0000000000000000000000000000000000000000..5931017a31b8d3ad0756f6d6fcb31a9951b97516 --- /dev/null +++ b/src/write/creation/create-time-signature.js @@ -0,0 +1,55 @@ +// abc_create_time_signature.js + +var AbsoluteElement = require('./elements/absolute-element'); +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); + +var createTimeSignature = function (elem, tuneNumber) { + elem.el_type = "timeSignature"; + var abselem = new AbsoluteElement(elem, 0, 10, 'staff-extra time-signature', tuneNumber); + if (elem.type === "specified") { + var x = 0; + for (var i = 0; i < elem.value.length; i++) { + if (i !== 0) { + abselem.addRight(new RelativeElement('+', x + 1, glyphs.getSymbolWidth("+"), 6, { thickness: glyphs.symbolHeightInPitches("+") })); + x += glyphs.getSymbolWidth("+") + 2; + } + if (elem.value[i].den) { + var numWidth = 0; + for (var i2 = 0; i2 < elem.value[i].num.length; i2++) + numWidth += glyphs.getSymbolWidth(elem.value[i].num[i2]); + var denWidth = 0; + for (i2 = 0; i2 < elem.value[i].num.length; i2++) + denWidth += glyphs.getSymbolWidth(elem.value[i].den[i2]); + var maxWidth = Math.max(numWidth, denWidth); + abselem.addRight(new RelativeElement(elem.value[i].num, x + (maxWidth - numWidth) / 2, numWidth, 8, { thickness: glyphs.symbolHeightInPitches(elem.value[i].num[0]) })); + abselem.addRight(new RelativeElement(elem.value[i].den, x + (maxWidth - denWidth) / 2, denWidth, 4, { thickness: glyphs.symbolHeightInPitches(elem.value[i].den[0]) })); + x += maxWidth + } else { + var thisWidth = 0; + for (var i3 = 0; i3 < elem.value[i].num.length; i3++) + thisWidth += glyphs.getSymbolWidth(elem.value[i].num[i3]); + abselem.addRight(new RelativeElement(elem.value[i].num, x, thisWidth, 6, { thickness: glyphs.symbolHeightInPitches(elem.value[i].num[0]) })); + x += thisWidth; + } + } + } else if (elem.type === "common_time") { + abselem.addRight(new RelativeElement("timesig.common", 0, glyphs.getSymbolWidth("timesig.common"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.common") })); + + } else if (elem.type === "cut_time") { + abselem.addRight(new RelativeElement("timesig.cut", 0, glyphs.getSymbolWidth("timesig.cut"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.cut") })); + } else if (elem.type === "tempus_imperfectum") { + abselem.addRight(new RelativeElement("timesig.imperfectum", 0, glyphs.getSymbolWidth("timesig.imperfectum"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.imperfectum") })); + } else if (elem.type === "tempus_imperfectum_prolatio") { + abselem.addRight(new RelativeElement("timesig.imperfectum2", 0, glyphs.getSymbolWidth("timesig.imperfectum2"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.imperfectum2") })); + } else if (elem.type === "tempus_perfectum") { + abselem.addRight(new RelativeElement("timesig.perfectum", 0, glyphs.getSymbolWidth("timesig.perfectum"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.perfectum") })); + } else if (elem.type === "tempus_perfectum_prolatio") { + abselem.addRight(new RelativeElement("timesig.perfectum2", 0, glyphs.getSymbolWidth("timesig.perfectum2"), 6, { thickness: glyphs.symbolHeightInPitches("timesig.perfectum2") })); + } else { + console.log("time signature:", elem); + } + return abselem; +}; + +module.exports = createTimeSignature; diff --git a/src/write/creation/decoration.js b/src/write/creation/decoration.js new file mode 100644 index 0000000000000000000000000000000000000000..822b4218d96d5d97bef222bf33889573f2b4b24b --- /dev/null +++ b/src/write/creation/decoration.js @@ -0,0 +1,366 @@ +// abc_decoration.js: Creates a data structure suitable for printing a line of abc + +var DynamicDecoration = require('./elements/dynamic-decoration'); +var CrescendoElem = require('./elements/crescendo-element'); +var GlissandoElem = require('./elements/glissando-element'); +var glyphs = require('./glyphs'); +var RelativeElement = require('./elements/relative-element'); +var TieElem = require('./elements/tie-element'); + +var Decoration = function Decoration() { + this.startDiminuendoX = undefined; + this.startCrescendoX = undefined; + this.minTop = 12; // TODO-PER: this is assuming a 5-line staff. Pass that info in. + this.minBottom = 0; +}; + +var closeDecoration = function (voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, accentAbove) { + var yPos; + for (var i = 0; i < decoration.length; i++) { + if (decoration[i] === "staccato" || decoration[i] === "tenuto" || (decoration[i] === "accent" && !accentAbove)) { + var symbol = "scripts." + decoration[i]; + if (decoration[i] === "accent") symbol = "scripts.sforzato"; + if (yPos === undefined) + yPos = (dir === "down") ? pitch + 2 : minPitch - 2; + else + yPos = (dir === "down") ? yPos + 2 : yPos - 2; + if (decoration[i] === "accent") { + // Always place the accent three pitches away, no matter whether that is a line or space. + if (dir === "up") yPos--; + else yPos++; + } else { + // don't place on a stave line. The stave lines are 2,4,6,8,10 + switch (yPos) { + case 2: + case 4: + case 6: + case 8: + case 10: + if (dir === "up") yPos--; + else yPos++; + break; + } + } + if (pitch > 9) yPos++; // take up some room of those that are above + var deltaX = width / 2; + if (glyphs.getSymbolAlign(symbol) !== "center") { + deltaX -= (glyphs.getSymbolWidth(symbol) / 2); + } + abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), yPos)); + } + if (decoration[i] === "slide" && abselem.heads[0]) { + var yPos2 = abselem.heads[0].pitch; + yPos2 -= 2; // TODO-PER: not sure what this fudge factor is. + var blank1 = new RelativeElement("", -roomtaken - 15, 0, yPos2 - 1); + var blank2 = new RelativeElement("", -roomtaken - 5, 0, yPos2 + 1); + abselem.addFixedX(blank1); + abselem.addFixedX(blank2); + voice.addOther(new TieElem({ anchor1: blank1, anchor2: blank2, fixedY: true })); + } + } + if (yPos === undefined) + yPos = pitch; + + return { above: yPos, below: abselem.bottom }; +}; + +var volumeDecoration = function (voice, decoration, abselem, positioning) { + for (var i = 0; i < decoration.length; i++) { + switch (decoration[i]) { + case "p": + case "mp": + case "pp": + case "ppp": + case "pppp": + case "f": + case "ff": + case "fff": + case "ffff": + case "sfz": + case "mf": + var elem = new DynamicDecoration(abselem, decoration[i], positioning); + voice.addOther(elem); + } + } +}; + +var compoundDecoration = function (decoration, pitch, width, abselem, dir) { + function highestPitch() { + if (abselem.heads.length === 0) + return 10; // TODO-PER: I don't know if this can happen, but we'll return the top of the staff if so. + var pitch = abselem.heads[0].pitch; + for (var i = 1; i < abselem.heads.length; i++) + pitch = Math.max(pitch, abselem.heads[i].pitch); + return pitch; + } + function lowestPitch() { + if (abselem.heads.length === 0) + return 2; // TODO-PER: I don't know if this can happen, but we'll return the bottom of the staff if so. + var pitch = abselem.heads[0].pitch; + for (var i = 1; i < abselem.heads.length; i++) + pitch = Math.min(pitch, abselem.heads[i].pitch); + return pitch; + } + function compoundDecoration(symbol, count) { + var placement = (dir === 'down') ? lowestPitch() + 1 : highestPitch() + 9; + if (dir !== 'down' && count === 1) + placement--; + var deltaX = width / 2; + deltaX += (dir === 'down') ? -5 : 3; + for (var i = 0; i < count; i++) { + placement -= 1; + abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), placement)); + } + } + + for (var i = 0; i < decoration.length; i++) { + switch (decoration[i]) { + case "/": compoundDecoration("flags.ugrace", 1); break; + case "//": compoundDecoration("flags.ugrace", 2); break; + case "///": compoundDecoration("flags.ugrace", 3); break; + case "////": compoundDecoration("flags.ugrace", 4); break; + } + } +}; + +var stackedDecoration = function (decoration, width, abselem, yPos, positioning, minTop, minBottom, accentAbove) { + function incrementPlacement(placement, height) { + if (placement === 'above') + yPos.above += height; + else + yPos.below -= height; + } + function getPlacement(placement) { + var y; + if (placement === 'above') { + y = yPos.above; + if (y < minTop) + y = minTop; + } else { + y = yPos.below; + if (y > minBottom) + y = minBottom; + } + return y; + } + function textDecoration(text, placement, anchor) { + var y = getPlacement(placement); + var textFudge = 2; + var textHeight = 5; + // TODO-PER: Get the height of the current font and use that for the thickness. + abselem.addFixedX(new RelativeElement(text, width / 2, 0, y + textFudge, { type: "decoration", klass: 'ornament', thickness: 3, anchor: anchor })); + + incrementPlacement(placement, textHeight); + } + function symbolDecoration(symbol, placement) { + var deltaX = width / 2; + if (glyphs.getSymbolAlign(symbol) !== "center") { + deltaX -= (glyphs.getSymbolWidth(symbol) / 2); + } + var height = glyphs.symbolHeightInPitches(symbol) + 1; // adding a little padding so nothing touches. + var y = getPlacement(placement); + y = (placement === 'above') ? y + height / 2 : y - height / 2;// Center the element vertically. + abselem.addFixedX(new RelativeElement(symbol, deltaX, glyphs.getSymbolWidth(symbol), y, { klass: 'ornament', thickness: glyphs.symbolHeightInPitches(symbol), position: placement })); + + incrementPlacement(placement, height); + } + + var symbolList = { + "+": "scripts.stopped", + "open": "scripts.open", + "snap": "scripts.snap", + "wedge": "scripts.wedge", + "thumb": "scripts.thumb", + "shortphrase": "scripts.shortphrase", + "mediumphrase": "scripts.mediumphrase", + "longphrase": "scripts.longphrase", + "trill": "scripts.trill", + "roll": "scripts.roll", + "irishroll": "scripts.roll", + "marcato": "scripts.umarcato", + "dmarcato": "scripts.dmarcato", + "umarcato": "scripts.umarcato", + "turn": "scripts.turn", + "uppermordent": "scripts.prall", + "pralltriller": "scripts.prall", + "mordent": "scripts.mordent", + "lowermordent": "scripts.mordent", + "downbow": "scripts.downbow", + "upbow": "scripts.upbow", + "fermata": "scripts.ufermata", + "invertedfermata": "scripts.dfermata", + "breath": ",", + "coda": "scripts.coda", + "segno": "scripts.segno" + }; + + var hasOne = false; + for (var i = 0; i < decoration.length; i++) { + switch (decoration[i]) { + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "D.C.": + case "D.S.": + textDecoration(decoration[i], positioning, 'middle'); + hasOne = true; + break; + case "D.C.alcoda": + textDecoration("D.C. al coda", positioning, 'end'); + hasOne = true; + break; + case "D.C.alfine": + textDecoration("D.C. al fine", positioning, 'end'); + hasOne = true; + break; + case "D.S.alcoda": + textDecoration("D.S. al coda", positioning, 'end'); + hasOne = true; + break; + case "D.S.alfine": + textDecoration("D.S. al fine", positioning, 'end'); + hasOne = true; + break; + case "fine": + textDecoration("FINE", positioning, 'middle'); + hasOne = true; + break; + case "+": + case "open": + case "snap": + case "wedge": + case "thumb": + case "shortphrase": + case "mediumphrase": + case "longphrase": + case "trill": + case "roll": + case "irishroll": + case "marcato": + case "dmarcato": + case "turn": + case "uppermordent": + case "pralltriller": + case "mordent": + case "lowermordent": + case "downbow": + case "upbow": + case "fermata": + case "breath": + case "umarcato": + case "coda": + case "segno": + symbolDecoration(symbolList[decoration[i]], positioning); + hasOne = true; + break; + case "invertedfermata": + symbolDecoration(symbolList[decoration[i]], 'below'); + hasOne = true; + break; + case "mark": + abselem.klass = "mark"; + break; + case "accent": + if (accentAbove) { + symbolDecoration("scripts.sforzato", positioning); + hasOne = true; + } + break; + } + } + return hasOne; +}; + +function leftDecoration(decoration, abselem, roomtaken) { + for (var i = 0; i < decoration.length; i++) { + switch (decoration[i]) { + case "arpeggio": + // The arpeggio symbol is the height of a note (that is, two Y units). This stacks as many as we need to go from the + // top note to the bottom note. The arpeggio should also be a little taller than the stacked notes, so there is an extra + // one drawn and it is offset by half of a note height (that is, one Y unit). + for (var j = abselem.abcelem.minpitch - 1; j <= abselem.abcelem.maxpitch; j += 2) { + abselem.addExtra( + new RelativeElement( + "scripts.arpeggio", + -glyphs.getSymbolWidth("scripts.arpeggio") * 2 - roomtaken, + 0, + j + 2, + { klass: 'ornament', thickness: glyphs.symbolHeightInPitches("scripts.arpeggio") } + ) + ); + } + break; + } + } +} + +Decoration.prototype.dynamicDecoration = function (voice, decoration, abselem, positioning) { + var diminuendo; + var crescendo; + var glissando; + for (var i = 0; i < decoration.length; i++) { + switch (decoration[i]) { + case "diminuendo(": + this.startDiminuendoX = abselem; + diminuendo = undefined; + break; + case "diminuendo)": + diminuendo = { start: this.startDiminuendoX, stop: abselem }; + this.startDiminuendoX = undefined; + break; + case "crescendo(": + this.startCrescendoX = abselem; + crescendo = undefined; + break; + case "crescendo)": + crescendo = { start: this.startCrescendoX, stop: abselem }; + this.startCrescendoX = undefined; + break; + case '~(': + case "glissando(": + this.startGlissandoX = abselem; + glissando = undefined; + break; + case '~)': + case "glissando)": + glissando = { start: this.startGlissandoX, stop: abselem }; + this.startGlissandoX = undefined; + break; + } + } + if (diminuendo) { + voice.addOther(new CrescendoElem(diminuendo.start, diminuendo.stop, ">", positioning)); + } + if (crescendo) { + voice.addOther(new CrescendoElem(crescendo.start, crescendo.stop, "<", positioning)); + } + if (glissando) { + voice.addOther(new GlissandoElem(glissando.start, glissando.stop)); + } +}; + +Decoration.prototype.createDecoration = function (voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, positioning, hasVocals, accentAbove) { + if (!positioning) + positioning = { ornamentPosition: 'above', volumePosition: hasVocals ? 'above' : 'below', dynamicPosition: hasVocals ? 'above' : 'below' }; + // These decorations don't affect the placement of other decorations + volumeDecoration(voice, decoration, abselem, positioning.volumePosition); + this.dynamicDecoration(voice, decoration, abselem, positioning.dynamicPosition); + compoundDecoration(decoration, pitch, width, abselem, dir); + + // treat staccato, accent, and tenuto first (may need to shift other markers) + var yPos = closeDecoration(voice, decoration, pitch, width, abselem, roomtaken, dir, minPitch, accentAbove); + // yPos is an object containing 'above' and 'below'. That is the placement of the next symbol on either side. + + yPos.above = Math.max(yPos.above, this.minTop); + yPos.below = Math.min(yPos.below, minPitch); + var hasOne = stackedDecoration(decoration, width, abselem, yPos, positioning.ornamentPosition, this.minTop, minPitch, accentAbove); + //if (hasOne) { + // abselem.top = Math.max(yPos.above + 3, abselem.top); // TODO-PER: Not sure why we need this fudge factor. + //} + leftDecoration(decoration, abselem, roomtaken); +}; + +module.exports = Decoration; diff --git a/src/write/creation/elements/absolute-element.js b/src/write/creation/elements/absolute-element.js new file mode 100644 index 0000000000000000000000000000000000000000..cffec3b1087394935bc65859df9d455dca407213 --- /dev/null +++ b/src/write/creation/elements/absolute-element.js @@ -0,0 +1,242 @@ +// abc_absolute_element.js: Definition of the AbsoluteElement class. + +var highlight = require("../../interactive/highlight"); +var unhighlight = require("../../interactive/unhighlight"); + +// Everything that is placed in the SVG is first created as an absolute element. This is one unit of graphic information. +// That is, it embodies a concept: a clef, a time signature, a bar line,etc. or most complexly: +// a note with its accidental, grace note, chord symbol, trill, stem, eighth flags, etc. +// In the largest sense, these are placed on the page at a particular place that is determined during the layout phase. +// This object doesn't contain any of the drawing information, though. That information is contained in an array of +// RelativeElements as the "children" of this class. +// During the layout phase, the width of all the children is calculated and the X coordinate of the absolute element is set. +// +// So, after the AbsoluteElement is placed, then its children can be placed relative to that. There are different types of +// relative elements that are placed with different rules: +// 1) Fixed - these elements don't move relative to the absolute element's coordinates. These are things like the notehead, +// any ledger lines, accidentals, etc. +// 2) Slotted - these elements can move vertically and don't get Y coordinates until after the absolute element is placed. +// These are things like the chord symbol, many decorations, the lyrics, etc. +// +// Relative elements are also classified by how they are related. This could be: +// 1) Increases the absolute element's width to the left. This doesn't change the center point of +// the absolute element, so adding a sharp to the note won't move it to the right. However, if the elements +// are close together then this enforces a minimum distance. +// 2) Has no effect on the width. Annotations and the tempo act like this. No matter how long they are the width doesn't change. +// 3) Increases the absolute element's width to the right. This doesn't change the center point, +// but it will increase the minimum distance. +// 4) Sets the width on both sides. This is the note heads. They are centered on both sides of the absolute element's X coordinate. + +// duration - actual musical duration - different from notehead duration in triplets. refer to abcelem to get the notehead duration +// minspacing - spacing which must be taken on top of the width defined by the duration +// type is a meta-type for the element. It is not necessary for drawing, but it is useful to make semantic sense of the element. For instance, it can be used in the element's class name. +var AbsoluteElement = function AbsoluteElement(abcelem, duration, minspacing, type, tuneNumber, options) { + // console.log("Absolute:",abcelem, duration, minspacing, type, tuneNumber, options); + if (!options) + options = {}; + this.tuneNumber = tuneNumber; + this.abcelem = abcelem; + this.duration = duration; + this.durationClass = options.durationClassOveride ? options.durationClassOveride : this.duration; + this.minspacing = minspacing || 0; + this.x = 0; + this.children = []; + this.heads = []; + this.extra = []; + this.extraw = 0; + this.w = 0; + this.right = []; + this.invisible = false; + this.bottom = undefined; + this.top = undefined; + this.type = type; + + // The following are the dimensions of the fixed part of the element. + // That is, the chord text will be a different height depending on lot of factors, but the 8th flag will always be in the same place. + this.fixed = { w: 0, t: undefined, b: undefined }; // there is no x-coord here, because that is set later. + + // these are the heights of all of the vertical elements that can't be placed until the end of the line. + // the vertical order of elements that are above is: tempo, part, volume/dynamic, ending/chord, lyric + // the vertical order of elements that are below is: lyric, chord, volume/dynamic + this.specialY = { + tempoHeightAbove: 0, + partHeightAbove: 0, + volumeHeightAbove: 0, + dynamicHeightAbove: 0, + endingHeightAbove: 0, + chordHeightAbove: 0, + lyricHeightAbove: 0, + + lyricHeightBelow: 0, + chordHeightBelow: 0, + volumeHeightBelow: 0, + dynamicHeightBelow: 0 + }; +}; + +AbsoluteElement.prototype.getFixedCoords = function () { + return { x: this.x, w: this.fixed.w, t: this.fixed.t, b: this.fixed.b }; +}; + +AbsoluteElement.prototype.addExtra = function (extra) { + // used for accidentals, multi-measure rest text, + // left-side decorations, gracenote heads, + // left annotations, gracenote stems. + // if (!(extra.c && extra.c.indexOf("accidentals") >= 0) && + // !(extra.c && extra.c.indexOf("arpeggio") >= 0) && + // extra.type !== "multimeasure-text" && + // !(extra.c === "noteheads.quarter" && (extra.scalex === 0.6 || extra.scalex === 0.36)) && + // !(extra.type === "stem" && extra.linewidth === -0.6) && + // extra.position !== "left" + // ) + // console.log("extra", extra); + + this.fixed.w = Math.max(this.fixed.w, extra.dx + extra.w); + if (this.fixed.t === undefined) this.fixed.t = extra.top; else this.fixed.t = Math.max(this.fixed.t, extra.top); + if (this.fixed.b === undefined) this.fixed.b = extra.bottom; else this.fixed.b = Math.min(this.fixed.b, extra.bottom); + if (extra.dx < this.extraw) this.extraw = extra.dx; + this.extra[this.extra.length] = extra; + this._addChild(extra); +}; + +AbsoluteElement.prototype.addHead = function (head) { + if (head.dx < this.extraw) this.extraw = head.dx; + this.heads[this.heads.length] = head; + this.addRight(head); +}; + +AbsoluteElement.prototype.addRight = function (right) { + // // used for clefs, note heads, bar lines, stems, key-signature accidentals, non-beamed flags, dots + // if (!(right.c && right.c.indexOf("clefs") >= 0) && + // !(right.c && right.c.indexOf("noteheads") >= 0) && + // !(right.c && right.c.indexOf("flags") >= 0) && + // !(right.c && right.c.indexOf("rests") >= 0) && + // !(right.c && right.c.indexOf("dots.dot") >= 0) && + // right.type !== "stem" && + // right.type !== "bar" && + // right.type !== "none" && // used when an invisible anchor is needed. + // !(this.type.indexOf("clef") >= -1 && right.c === "8") && + // this.type.indexOf("key-signature") === -1 && + // this.type.indexOf("time-signature") === -1 && + // !(this.abcelem && this.abcelem.rest && this.abcelem.rest.type === "spacer") && + // !(this.abcelem && this.abcelem.rest && this.abcelem.rest.type === "invisible") && + // !(right.type === "text" && right.position === "relative") && + // !(right.type === "text" && right.position === "right") && + // !(right.type === "text" && right.position === "above") && + // !(right.type === "text" && right.position === "below") + // ) + // console.log("right", right); + // These are the elements that are the fixed part. + this.fixed.w = Math.max(this.fixed.w, right.dx + right.w); + if (right.top !== undefined) { + if (this.fixed.t === undefined) this.fixed.t = right.top; else this.fixed.t = Math.max(this.fixed.t, right.top); + } + if (right.bottom !== undefined) { + if (this.fixed.b === undefined) this.fixed.b = right.bottom; else this.fixed.b = Math.min(this.fixed.b, right.bottom); + } + // if (isNaN(this.fixed.t) || isNaN(this.fixed.b)) + // debugger; + if (right.dx + right.w > this.w) this.w = right.dx + right.w; + this.right[this.right.length] = right; + this._addChild(right); +}; + +AbsoluteElement.prototype.addFixed = function (elem) { + // used for elements that can't move relative to other elements after they have been placed. + // used for ledger lines, bar numbers, debug msgs, clef, key sigs, time sigs + this._addChild(elem); +}; + +AbsoluteElement.prototype.addFixedX = function (elem) { + // used for elements that can't move horizontally relative to other elements after they have been placed. + // used for parts, tempo, decorations + this._addChild(elem); +}; + +AbsoluteElement.prototype.addCentered = function (elem) { + // // used for chord labels, lyrics + // if (!(elem.type === "chord" && elem.position === "above") && + // !(elem.type === "chord" && elem.position === "below") && + // elem.type !== 'lyric' + // ) + // console.log("centered", elem); + var half = elem.w / 2; + if (-half < this.extraw) this.extraw = -half; + this.extra[this.extra.length] = elem; + if (elem.dx + half > this.w) this.w = elem.dx + half; + this.right[this.right.length] = elem; + this._addChild(elem); +}; + +AbsoluteElement.prototype.setLimit = function (member, child) { + if (!child[member]) return; + if (!this.specialY[member]) + this.specialY[member] = child[member]; + else + this.specialY[member] = Math.max(this.specialY[member], child[member]); +}; + +AbsoluteElement.prototype._addChild = function (child) { + // console.log("Relative:",child); + child.parent = this; + this.children[this.children.length] = child; + this.pushTop(child.top); + this.pushBottom(child.bottom); + this.setLimit('tempoHeightAbove', child); + this.setLimit('partHeightAbove', child); + this.setLimit('volumeHeightAbove', child); + this.setLimit('dynamicHeightAbove', child); + this.setLimit('endingHeightAbove', child); + this.setLimit('chordHeightAbove', child); + this.setLimit('lyricHeightAbove', child); + this.setLimit('lyricHeightBelow', child); + this.setLimit('chordHeightBelow', child); + this.setLimit('volumeHeightBelow', child); + this.setLimit('dynamicHeightBelow', child); +}; + +AbsoluteElement.prototype.pushTop = function (top) { + if (top !== undefined) { + if (this.top === undefined) + this.top = top; + else + this.top = Math.max(top, this.top); + } +}; + +AbsoluteElement.prototype.pushBottom = function (bottom) { + if (bottom !== undefined) { + if (this.bottom === undefined) + this.bottom = bottom; + else + this.bottom = Math.min(bottom, this.bottom); + } +}; + +AbsoluteElement.prototype.setX = function (x) { + this.x = x; + for (var i = 0; i < this.children.length; i++) + this.children[i].setX(x); +}; + +AbsoluteElement.prototype.center = function (before, after) { + // Used to center whole rests + var midpoint = (after.x - before.x) / 2 + before.x; + this.x = midpoint - this.w / 2; + for (var k = 0; k < this.children.length; k++) + this.children[k].setX(this.x); +}; + +AbsoluteElement.prototype.setHint = function () { + this.hint = true; +}; + +AbsoluteElement.prototype.highlight = function (klass, color) { + highlight.bind(this)(klass, color); +}; + +AbsoluteElement.prototype.unhighlight = function (klass, color) { + unhighlight.bind(this)(klass, color); +}; + +module.exports = AbsoluteElement; diff --git a/src/write/creation/elements/beam-element.js b/src/write/creation/elements/beam-element.js new file mode 100644 index 0000000000000000000000000000000000000000..49021152a35eff41314e4b62a327b4759b8c665f --- /dev/null +++ b/src/write/creation/elements/beam-element.js @@ -0,0 +1,113 @@ +// abc_beam_element.js: Definition of the BeamElem class. + +// Most elements on the page are related to a particular absolute element -- notes, rests, bars, etc. Beams, however, span multiple elements. +// This means that beams can't be laid out until the absolute elements are placed. There is the further complication that the stems for beamed +// notes can't be laid out until the beams are because we don't know how long they will be until we know the slope of the beam and the horizontal +// spacing of the absolute elements. +// +// So, when a beam is detected, a BeamElem is created, then all notes belonging to that beam are added to it. These notes are not given stems at that time. +// Then, after the horizontal layout is complete, all of the BeamElem are iterated to set the beam position, then all of the notes that are beamed are given +// stems. After that, we are ready for the drawing step. + +// There are three phases: the setup phase, when new elements are being discovered, the layout phase, when everything is calculated, and the drawing phase, +// when the object is not changed, but is used to put the elements on the page. + +// +// Setup phase +// +var BeamElem = function BeamElem(stemHeight, type, flat, firstElement) { + // type is "grace", "up", "down", or undefined. flat is used to force flat beams, as it commonly found in the grace notes of bagpipe music. + this.type = "BeamElem"; + this.isflat = !!flat; + this.isgrace = !!(type && type === "grace"); + this.forceup = !!(this.isgrace || (type && type === "up")); + this.forcedown = !!(type && type === "down"); + this.elems = []; // all the AbsoluteElements that this beam touches. It may include embedded rests. + this.total = 0; + this.average = 6; // use middle line as start for average. + this.allrests = true; + this.stemHeight = stemHeight; + this.beams = []; // During the layout phase, this will become a list of the beams that need to be drawn. + if (firstElement && firstElement.duration) { + this.duration = firstElement.duration; + if (firstElement.startTriplet) { + this.duration *= firstElement.tripletMultiplier; + } + this.duration = Math.round(this.duration * 1000) / 1000; + } else + this.duration = 0; +}; + +BeamElem.prototype.setHint = function () { + this.hint = true; +}; + +BeamElem.prototype.runningDirection = function (abcelem) { + var pitch = abcelem.averagepitch; + if (pitch === undefined) return; // don't include elements like spacers in beams + this.total = Math.round(this.total + pitch); + if (!this.count) + this.count = 0; + this.count++ +}; + +BeamElem.prototype.add = function (abselem) { + var pitch = abselem.abcelem.averagepitch; + if (pitch === undefined) return; // don't include elements like spacers in beams + if (!abselem.abcelem.rest) + this.allrests = false; + abselem.beam = this; + this.elems.push(abselem); + this.total = Math.round(this.total + pitch); + if (this.min === undefined || abselem.abcelem.minpitch < this.min) { + this.min = abselem.abcelem.minpitch; + } + if (this.max === undefined || abselem.abcelem.maxpitch > this.max) { + this.max = abselem.abcelem.maxpitch; + } +}; + +BeamElem.prototype.addBeam = function (beam) { + this.beams.push(beam); +}; + +BeamElem.prototype.setStemDirection = function () { + // Have to figure this out before the notes are placed because placing the notes also places the decorations. + this.average = calcAverage(this.total, this.count); + if (this.forceup) { + this.stemsUp = true; + } else if (this.forcedown) { + this.stemsUp = false; + } else { + var middleLine = 6; // hardcoded 6 is B + this.stemsUp = this.average < middleLine; // true is up, false is down; + } + delete this.count; + this.total = 0; +}; + +BeamElem.prototype.calcDir = function () { + this.average = calcAverage(this.total, this.elems.length); + if (this.forceup) { + this.stemsUp = true; + } else if (this.forcedown) { + this.stemsUp = false; + } else { + var middleLine = 6; // hardcoded 6 is B + this.stemsUp = this.average < middleLine; // true is up, false is down; + } + var dir = this.stemsUp ? 'up' : 'down'; + for (var i = 0; i < this.elems.length; i++) { + for (var j = 0; j < this.elems[i].heads.length; j++) { + this.elems[i].heads[j].stemDir = dir; + } + } +}; + +function calcAverage(total, numElements) { + if (!numElements) + return 0; + return total / numElements; +} + +module.exports = BeamElem; diff --git a/src/write/creation/elements/bottom-text.js b/src/write/creation/elements/bottom-text.js new file mode 100644 index 0000000000000000000000000000000000000000..5d5553818905bb6dd82b59ebd3a8e98f36894872 --- /dev/null +++ b/src/write/creation/elements/bottom-text.js @@ -0,0 +1,93 @@ +const addTextIf = require("../add-text-if"); +const richText = require("./rich-text"); + +function BottomText(metaText, width, isPrint, paddingLeft, spacing, shouldAddClasses, getTextSize) { + this.rows = []; + if (metaText.unalignedWords && metaText.unalignedWords.length > 0) + this.unalignedWords(metaText.unalignedWords, paddingLeft, spacing, shouldAddClasses, getTextSize); + this.extraText(metaText, paddingLeft, spacing, shouldAddClasses, getTextSize); + if (metaText.footer && isPrint) + this.footer(metaText.footer, width, paddingLeft, getTextSize); +} + +BottomText.prototype.unalignedWords = function (unalignedWords, marginLeft, spacing, shouldAddClasses, getTextSize) { + var klass = shouldAddClasses ? 'abcjs-unaligned-words' : '' + var defFont = 'wordsfont'; + var space = getTextSize.calc("i", defFont, klass); + + this.rows.push({ move: spacing.words }); + + addMultiLine(this.rows, '', unalignedWords, marginLeft, defFont, "unalignedWords", "unalignedWords", klass, "unalignedWords", spacing, shouldAddClasses, getTextSize) + this.rows.push({ move: space.height }); +} + +function addSingleLine(rows, preface, text, marginLeft, klass, shouldAddClasses, getTextSize) { + if (text) { + if (preface) { + if (typeof text === 'string') + text = preface + text + else + text = [{text: preface}].concat(text) + } + klass = shouldAddClasses ? 'abcjs-extra-text '+klass : '' + richText(rows, text, 'historyfont', klass, "description", marginLeft, {absElemType: "extraText", anchor: 'start'}, getTextSize) + } + +} + +function addMultiLine(rows, preface, content, marginLeft, defFont, absElemType, groupName, klass, name, spacing, shouldAddClasses, getTextSize) { + if (content) { + klass = shouldAddClasses ? 'abcjs-extra-text '+klass : '' + var size = getTextSize.calc("A", defFont, klass); + if (typeof content === 'string') { + if (preface) + content = preface + "\n" + content + addTextIf(rows, { marginLeft: marginLeft, text: content, font: defFont, absElemType: "extraText", name: name, 'dominant-baseline': 'middle', klass: klass }, getTextSize); + //rows.push({move: size.height*3/4}) + } else { + rows.push({ startGroup: groupName, klass: klass, name: name }); + rows.push({move: spacing.info}) + if (preface) { + addTextIf(rows, { marginLeft: marginLeft, text: preface, font: defFont, absElemType: "extraText", name: name, 'dominant-baseline': 'middle' }, getTextSize); + rows.push({move: size.height*3/4}) + } + + for (var j = 0; j < content.length; j++) { + richText(rows, content[j], defFont, '', name, marginLeft, {anchor: 'start'}, getTextSize) + // TODO-PER: Hack! the string and rich lines should have used up the same amount of space without this. + if (j < content.length-1 && typeof content[j] === 'string' && typeof content[j+1] !== 'string') + rows.push({move: size.height*3/4}) + } + rows.push({ endGroup: groupName, absElemType: absElemType, startChar: -1, endChar: -1, name: name }); + rows.push({move: size.height}) + } + } +} +BottomText.prototype.extraText = function (metaText, marginLeft, spacing, shouldAddClasses, getTextSize) { + addSingleLine(this.rows, "Book: ", metaText.book, marginLeft, 'abcjs-book', shouldAddClasses, getTextSize) + addSingleLine(this.rows, "Source: ", metaText.source, marginLeft, 'abcjs-source', shouldAddClasses, getTextSize) + addSingleLine(this.rows, "Discography: ", metaText.discography, marginLeft, 'abcjs-discography', shouldAddClasses, getTextSize) + + addMultiLine(this.rows, 'Notes:', metaText.notes, marginLeft, 'historyfont', "extraText", "notes", 'abcjs-notes', "description", spacing, shouldAddClasses, getTextSize) + + addSingleLine(this.rows, "Transcription: ", metaText.transcription, marginLeft, 'abcjs-transcription', shouldAddClasses, getTextSize) + + addMultiLine(this.rows, "History:", metaText.history, marginLeft, 'historyfont', "extraText", "history", 'abcjs-history', "description", spacing, shouldAddClasses, getTextSize) + + addSingleLine(this.rows, "Copyright: ", metaText['abc-copyright'], marginLeft, 'abcjs-copyright', shouldAddClasses, getTextSize) + addSingleLine(this.rows, "Creator: ", metaText['abc-creator'], marginLeft, 'abcjs-creator', shouldAddClasses, getTextSize) + addSingleLine(this.rows, "Edited By: ", metaText['abc-edited-by'], marginLeft, 'abcjs-edited-by', shouldAddClasses, getTextSize) + +} + +BottomText.prototype.footer = function (footer, width, paddingLeft, getTextSize) { + var klass = 'header meta-bottom'; + var font = "footerfont"; + this.rows.push({ startGroup: "footer", klass: klass }); + // Note: whether there is a footer or not doesn't change any other positioning, so this doesn't change the Y-coordinate. + addTextIf(this.rows, { marginLeft: paddingLeft, text: footer.left, font: font, klass: klass, name: "footer" }, getTextSize); + addTextIf(this.rows, { marginLeft: paddingLeft + width / 2, text: footer.center, font: font, klass: klass, anchor: 'middle', name: "footer" }, getTextSize); + addTextIf(this.rows, { marginLeft: paddingLeft + width, text: footer.right, font: font, klass: klass, anchor: 'end', name: "footer" }, getTextSize); +} + +module.exports = BottomText; diff --git a/src/write/creation/elements/brace-element.js b/src/write/creation/elements/brace-element.js new file mode 100644 index 0000000000000000000000000000000000000000..3589890a839dca52e77dac54eaf7bc778d239206 --- /dev/null +++ b/src/write/creation/elements/brace-element.js @@ -0,0 +1,32 @@ +// abc_brace_element.js: Definition of the BraceElement class. + +var BraceElem = function BraceElem(voice, type) { + this.startVoice = voice; + this.type = type; +}; + +BraceElem.prototype.setBottomStaff = function (voice) { + this.endVoice = voice; + // If only the start brace has a name then the name belongs to the brace instead of the staff. + if (this.startVoice.header && !this.endVoice.header) { + this.header = this.startVoice.header; + delete this.startVoice.header; + } +}; + +BraceElem.prototype.continuing = function (voice) { + // If the final staff isn't present, then use the last one we saw. + this.lastContinuedVoice = voice; +}; + +BraceElem.prototype.getWidth = function () { + return 10; // TODO-PER: right now the drawing function doesn't vary the width at all. If it does in the future then this will change. +}; + +BraceElem.prototype.isStartVoice = function (voice) { + if (this.startVoice && this.startVoice.staff && this.startVoice.staff.voices.length > 0 && this.startVoice.staff.voices[0] === voice) + return true; + return false; +}; + +module.exports = BraceElem; diff --git a/src/write/creation/elements/crescendo-element.js b/src/write/creation/elements/crescendo-element.js new file mode 100644 index 0000000000000000000000000000000000000000..888e263460d33b5b389f972c82fb008d6489dc2e --- /dev/null +++ b/src/write/creation/elements/crescendo-element.js @@ -0,0 +1,15 @@ +// abc_crescendo_element.js: Definition of the CrescendoElem class. + +var CrescendoElem = function CrescendoElem(anchor1, anchor2, dir, positioning) { + this.type = "CrescendoElem"; + this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after keysig) + this.anchor2 = anchor2; // must have a .x property or be null (means ends at the end of the line) + this.dir = dir; // either "<" or ">" + if (positioning === 'above') + this.dynamicHeightAbove = 6; + else + this.dynamicHeightBelow = 6; + this.pitch = undefined; // This will be set later +}; + +module.exports = CrescendoElem; diff --git a/src/write/creation/elements/dynamic-decoration.js b/src/write/creation/elements/dynamic-decoration.js new file mode 100644 index 0000000000000000000000000000000000000000..c2292d8c9948a223545ddecae7d49417e950caa4 --- /dev/null +++ b/src/write/creation/elements/dynamic-decoration.js @@ -0,0 +1,14 @@ +// abc_dynamic_decoration.js: Definition of the DynamicDecoration class. + +var DynamicDecoration = function DynamicDecoration(anchor, dec, position) { + this.type = "DynamicDecoration"; + this.anchor = anchor; + this.dec = dec; + if (position === 'below') + this.volumeHeightBelow = 6; + else + this.volumeHeightAbove = 6; + this.pitch = undefined; // This will be set later +}; + +module.exports = DynamicDecoration; diff --git a/src/write/creation/elements/ending-element.js b/src/write/creation/elements/ending-element.js new file mode 100644 index 0000000000000000000000000000000000000000..224bcd18dd466295ab974a382b55928c97ee04e8 --- /dev/null +++ b/src/write/creation/elements/ending-element.js @@ -0,0 +1,12 @@ +// abc_ending_element.js: Definition of the EndingElement class. + +var EndingElem = function EndingElem(text, anchor1, anchor2) { + this.type = "EndingElem"; + this.text = text; // text to be displayed top left + this.anchor1 = anchor1; // must have a .x property or be null (means starts at the "beginning" of the line - after keysig) + this.anchor2 = anchor2; // must have a .x property or be null (means ends at the end of the line) + this.endingHeightAbove = 5; + this.pitch = undefined; // This will be set later +}; + +module.exports = EndingElem; diff --git a/src/write/creation/elements/free-text.js b/src/write/creation/elements/free-text.js new file mode 100644 index 0000000000000000000000000000000000000000..ccfe115fd4d3a91cb4db8ef5b75d5ebfbe41a89d --- /dev/null +++ b/src/write/creation/elements/free-text.js @@ -0,0 +1,41 @@ +function FreeText(info, vskip, getFontAndAttr, paddingLeft, width, getTextSize) { + var text = info.text; + this.rows = []; + var size; + if (vskip) + this.rows.push({ move: vskip }); + var hash = getFontAndAttr.calc('textfont', 'defined-text'); + if (text === "") { // we do want to print out blank lines if they have been specified. + this.rows.push({ move: hash.attr['font-size'] * 2 }); // move the distance of the line, plus the distance of the margin, which is also one line. + } else if (typeof text === 'string') { + this.rows.push({ move: hash.attr['font-size'] / 2 }); // TODO-PER: move down some - the y location should be the top of the text, but we output text specifying the center line. + this.rows.push({ left: paddingLeft, text: text, font: 'textfont', klass: 'defined-text', anchor: "start", startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text" }); + size = getTextSize.calc(text, 'textfont', 'defined-text'); + this.rows.push({ move: size.height }); + } else if (text) { + var maxHeight = 0; + var leftSide = paddingLeft; + var currentFont = 'textfont'; + for (var i = 0; i < text.length; i++) { + if (text[i].font) { + currentFont = text[i].font; + } else + currentFont = 'textfont'; + this.rows.push({ left: leftSide, text: text[i].text, font: currentFont, klass: 'defined-text', anchor: 'start', startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text" }); + size = getTextSize.calc(text[i].text, getFontAndAttr.calc(currentFont, 'defined-text').font, 'defined-text'); + leftSide += size.width + size.height / 2; // add a little padding to the right side. The height of the font is probably a close enough approximation. + maxHeight = Math.max(maxHeight, size.height) + } + this.rows.push({ move: maxHeight }); + } else { + // The structure is wrong here: it requires an array to do centering, but it shouldn't have. + if (info.length === 1) { + var x = width / 2; + this.rows.push({ left: x, text: info[0].text, font: 'textfont', klass: 'defined-text', anchor: 'middle', startChar: info.startChar, endChar: info.endChar, absElemType: "freeText", name: "free-text" }); + size = getTextSize.calc(info[0].text, 'textfont', 'defined-text'); + this.rows.push({ move: size.height }); + } + } +} + +module.exports = FreeText; diff --git a/src/write/creation/elements/glissando-element.js b/src/write/creation/elements/glissando-element.js new file mode 100644 index 0000000000000000000000000000000000000000..e0b948d9f6aea99a003641528beac94193e2f739 --- /dev/null +++ b/src/write/creation/elements/glissando-element.js @@ -0,0 +1,7 @@ +var GlissandoElem = function GlissandoElem(anchor1, anchor2) { + this.type = "GlissandoElem"; + this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after keysig) + this.anchor2 = anchor2; // must have a .x property or be null (means ends at the end of the line) +}; + +module.exports = GlissandoElem; diff --git a/src/write/creation/elements/relative-element.js b/src/write/creation/elements/relative-element.js new file mode 100644 index 0000000000000000000000000000000000000000..30d475918c6b1b8c0d842875b1fe666ed69585a3 --- /dev/null +++ b/src/write/creation/elements/relative-element.js @@ -0,0 +1,125 @@ +// abc_relative_element.js: Definition of the RelativeElement class. + +var RelativeElement = function RelativeElement(c, dx, w, pitch, opt) { + opt = opt || {}; + this.x = 0; + this.c = c; // character or path or string + this.dx = dx; // relative x position + this.w = w; // minimum width taken up by this element (can include gratuitous space) + this.pitch = pitch; // relative y position by pitch + this.scalex = opt.scalex || 1; // should the character/path be scaled? + this.scaley = opt.scaley || 1; // should the character/path be scaled? + this.type = opt.type || "symbol"; // cheap types. + this.pitch2 = opt.pitch2; + this.linewidth = opt.linewidth; + this.klass = opt.klass; + this.anchor = opt.anchor ? opt.anchor : 'middle' + this.top = pitch; + if (this.pitch2 !== undefined && this.pitch2 > this.top) this.top = this.pitch2; + this.bottom = pitch; + if (this.pitch2 !== undefined && this.pitch2 < this.bottom) this.bottom = this.pitch2; + if (opt.thickness) { + this.top += opt.thickness / 2; + this.bottom -= opt.thickness / 2; + } + if (opt.stemHeight) { + if (opt.stemHeight > 0) + this.top += opt.stemHeight; + else + this.bottom += opt.stemHeight; + } + if (opt.dim) + this.dim = opt.dim; + if (opt.position) + this.position = opt.position; + this.height = opt.height ? opt.height : 4; // The +1 is to give a little bit of padding. + if (opt.top) + this.top = opt.top; + if (opt.bottom) + this.bottom = opt.bottom; + if (opt.name) + this.name = opt.name; + else if (this.c) + this.name = this.c; + else + this.name = this.type; + if (opt.realWidth) + this.realWidth = opt.realWidth; + else + this.realWidth = this.w; + this.centerVertically = false; + switch (this.type) { + case "debug": + this.chordHeightAbove = this.height; + break; + case "lyric": + if (opt.position && opt.position === 'below') + this.lyricHeightBelow = this.height; + else + this.lyricHeightAbove = this.height; + break; + case "chord": + if (opt.position && opt.position === 'below') + this.chordHeightBelow = this.height; + else + this.chordHeightAbove = this.height; + break; + case "text": + if (this.pitch === undefined) { + if (opt.position && opt.position === 'below') + this.chordHeightBelow = this.height; + else + this.chordHeightAbove = this.height; + } else + this.centerVertically = true; + break; + case "part": this.partHeightAbove = this.height; break; + } +}; + +RelativeElement.prototype.getChordDim = function () { + if (this.type === "debug") + return null; + if (!this.chordHeightAbove && !this.chordHeightBelow) + return null; + // Chords are centered, annotations are left justified. + // NOTE: the font reports extra space to the left and right anyway, so there is a built in margin. + // We add a little margin so that items can't touch - we use half the font size as the margin, so that is 1/4 on each side. + // if there is only one character that we're printing, use half of that margin. + // var margin = this.dim.font.size/4; + // if (this.c.length === 1) + // margin = margin / 2; + var margin = 0; + + var offset = this.type === "chord" ? this.realWidth / 2 : 0; + var left = this.x - offset - margin; + var right = left + this.realWidth + margin; + return { left: left, right: right }; +}; + +RelativeElement.prototype.invertLane = function (total) { + if (this.lane === undefined) + this.lane = 0; + this.lane = total - this.lane - 1; +}; + +RelativeElement.prototype.putChordInLane = function (i) { + this.lane = i; + // Add some extra space to account for the character's descenders. + if (this.chordHeightAbove) + this.chordHeightAbove = (this.height * 1.25) * this.lane; + else + this.chordHeightBelow = (this.height * 1.25) * this.lane; +}; + +RelativeElement.prototype.getLane = function () { + if (this.lane === undefined) + return 0; + return this.lane; +}; + +RelativeElement.prototype.setX = function (x) { + this.x = x + this.dx; +}; + +module.exports = RelativeElement; diff --git a/src/write/creation/elements/rich-text.js b/src/write/creation/elements/rich-text.js new file mode 100644 index 0000000000000000000000000000000000000000..18f198a1c61d02e6fb73cfb5a8e740b4cf4d986b --- /dev/null +++ b/src/write/creation/elements/rich-text.js @@ -0,0 +1,51 @@ +const addTextIf = require("../add-text-if"); + +function richText(rows, str, defFont, klass, name, paddingLeft, attr, getTextSize) { + var space = getTextSize.calc("i", defFont, klass); + if (str === '') { + rows.push({ move: space.height }); + } else { + if (typeof str === 'string') { + addTextIf(rows, { marginLeft: paddingLeft, text: str, font: defFont, klass: klass, marginTop: attr.marginTop, anchor: attr.anchor, absElemType: attr.absElemType, info: attr.info, name: name }, getTextSize); + return + } + if (attr.marginTop) + rows.push({move: attr.marginTop}) + + var largestY = 0; + var gap = 0; + var row = { + left: paddingLeft, + anchor: attr.anchor, + phrases: [] + } + if (klass) + row.klass = klass + rows.push(row) + for (var k = 0; k < str.length; k++) { + var thisWord = str[k]; + var font = (thisWord.font) ? thisWord.font : getTextSize.attr(defFont, klass).font; + var phrase = { + content: thisWord.text, + } + if (font) + phrase.attrs = { + "font-family": getTextSize.getFamily(font.face), + "font-size": font.size, + "font-weight": font.weight, + "font-style": font.style, + "font-decoration": font.decoration, + } + //if (thisWord.text) { + row.phrases.push(phrase); + var size = getTextSize.calc(thisWord.text, font, klass); + largestY = Math.max(largestY, size.height); + if (thisWord.text[thisWord.text.length - 1] === ' ') { + gap = space.width + } + } + rows.push({ move: largestY }); + } +} + +module.exports = richText; diff --git a/src/write/creation/elements/separator.js b/src/write/creation/elements/separator.js new file mode 100644 index 0000000000000000000000000000000000000000..b183c9300a374837a6bc082a34904072f6fd3cb9 --- /dev/null +++ b/src/write/creation/elements/separator.js @@ -0,0 +1,10 @@ +function Separator(spaceAbove, lineLength, spaceBelow) { + this.rows = []; + if (spaceAbove) + this.rows.push({ move: spaceAbove }); + this.rows.push({ separator: lineLength, absElemType: "separator" }); + if (spaceBelow) + this.rows.push({ move: spaceBelow }); +} + +module.exports = Separator; diff --git a/src/write/creation/elements/staff-group-element.js b/src/write/creation/elements/staff-group-element.js new file mode 100644 index 0000000000000000000000000000000000000000..3b5aa0e41b6f222b7af6c3331a3d083ee356e656 --- /dev/null +++ b/src/write/creation/elements/staff-group-element.js @@ -0,0 +1,105 @@ +// abc_staff_group_element.js: Definition of the StaffGroupElement class. + +// StaffGroupElement contains all the elements that go together to make one line of music. +// That might be multiple staves that are tied together, and it might be multiple voices on one staff. +// +// Methods: +// constructor: some basic initialization +// addVoice(): Called once for each voice. May add a new staff if needed. +// finished(): Called only internally by layout() +// layout(): This does all the layout. It sets the following: spacingunits, startx, minspace, w, and the x-coordinate of each element in each voice. +// draw(): Calls the underlying methods on the voice objects to do the drawing. Sets y and height. +// +// Members: +// staffs: an array of all the staves in this group. Each staff contains the following elements: +// { top, bottom, highest, lowest, y } +// voices: array of VoiceElement objects. This is mostly passed in, but the VoiceElement objects are modified here. +// +// spacingunits: number of relative x-units in the line. Used by the calling function to pass back in as the "spacing" input parameter. +// TODO-PER: This should actually be passed back as a return value. +// minspace: smallest space between two notes. Used by the calling function to pass back in as the "spacing" input parameter. +// TODO-PER: This should actually be passed back as a return value. +// startx: The left edge, taking the margin and the optional voice name. Used by the draw() method. +// w: The width of the line. Used by calling function to pass back in as the "spacing" input parameter, and the draw() method. +// TODO-PER: This should actually be passed back as a return value. (TODO-PER: in pixels or spacing units?) +// y: The top of the staff group, in pixels. This is set in the draw method. +// TODO-PER: Where is that used? It looks like it might not be needed. +// height: Set in the draw() method to the height actually used. Used by the calling function to know where to start the next staff group. +// TODO-PER: This should actually be set in the layout method and passed back as a return value. +var calcHeight = require('../calc-height'); + +var StaffGroupElement = function (getTextSize) { + this.getTextSize = getTextSize; + this.voices = []; + this.staffs = []; + this.brace = undefined; //tony + this.bracket = undefined; +}; + +StaffGroupElement.prototype.setLimit = function (member, voice) { + if (!voice.specialY[member]) return; + if (!voice.staff.specialY[member]) + voice.staff.specialY[member] = voice.specialY[member]; + else + voice.staff.specialY[member] = Math.max(voice.staff.specialY[member], voice.specialY[member]); +}; + +StaffGroupElement.prototype.addVoice = function (voice, staffnumber, stafflines) { + var voiceNum = this.voices.length; + this.voices[voiceNum] = voice; + if (this.staffs[staffnumber]) + this.staffs[staffnumber].voices.push(voiceNum); + else { + // TODO-PER: how does the min/max change when stafflines is not 5? + this.staffs[this.staffs.length] = { + top: 10, + bottom: 2, + lines: stafflines, + voices: [voiceNum], + specialY: { + tempoHeightAbove: 0, + partHeightAbove: 0, + volumeHeightAbove: 0, + dynamicHeightAbove: 0, + endingHeightAbove: 0, + chordHeightAbove: 0, + lyricHeightAbove: 0, + + lyricHeightBelow: 0, + chordHeightBelow: 0, + volumeHeightBelow: 0, + dynamicHeightBelow: 0 + } + }; + } + voice.staff = this.staffs[staffnumber]; +}; + +StaffGroupElement.prototype.setHeight = function () { + this.height = calcHeight(this); +}; + +StaffGroupElement.prototype.setWidth = function (width) { + this.w = width; + for (var i = 0; i < this.voices.length; i++) { + this.voices[i].setWidth(width); + } +}; + +StaffGroupElement.prototype.setStaffLimits = function (voice) { + voice.staff.top = Math.max(voice.staff.top, voice.top); + voice.staff.bottom = Math.min(voice.staff.bottom, voice.bottom); + this.setLimit('tempoHeightAbove', voice); + this.setLimit('partHeightAbove', voice); + this.setLimit('volumeHeightAbove', voice); + this.setLimit('dynamicHeightAbove', voice); + this.setLimit('endingHeightAbove', voice); + this.setLimit('chordHeightAbove', voice); + this.setLimit('lyricHeightAbove', voice); + this.setLimit('lyricHeightBelow', voice); + this.setLimit('chordHeightBelow', voice); + this.setLimit('volumeHeightBelow', voice); + this.setLimit('dynamicHeightBelow', voice); +}; + +module.exports = StaffGroupElement; diff --git a/src/write/creation/elements/subtitle.js b/src/write/creation/elements/subtitle.js new file mode 100644 index 0000000000000000000000000000000000000000..66eae057e289184a9757c7fd97ec22334ff97050 --- /dev/null +++ b/src/write/creation/elements/subtitle.js @@ -0,0 +1,12 @@ +function Subtitle(spaceAbove, formatting, info, center, paddingLeft, getTextSize) { + this.rows = []; + if (spaceAbove) + this.rows.push({ move: spaceAbove }); + var tAnchor = formatting.titleleft ? 'start' : 'middle'; + var tLeft = formatting.titleleft ? paddingLeft : center; + this.rows.push({ left: tLeft, text: info.text, font: 'subtitlefont', klass: 'text subtitle', anchor: tAnchor, startChar: info.startChar, endChar: info.endChar, absElemType: "subtitle", name: "subtitle" }); + var size = getTextSize.calc(info.text, 'subtitlefont', 'text subtitle'); + this.rows.push({ move: size.height }); +} + +module.exports = Subtitle; diff --git a/src/write/creation/elements/tempo-element.js b/src/write/creation/elements/tempo-element.js new file mode 100644 index 0000000000000000000000000000000000000000..19c173fbdeb4b6a00ca232b4fdce838fc8dd2e54 --- /dev/null +++ b/src/write/creation/elements/tempo-element.js @@ -0,0 +1,63 @@ +// abc_tempo_element.js: Definition of the TempoElement class. + +var AbsoluteElement = require('./absolute-element'); +var RelativeElement = require('./relative-element'); + +var TempoElement = function TempoElement(tempo, tuneNumber, createNoteHead) { + this.type = "TempoElement"; + this.tempo = tempo; + this.tempo.type = "tempo"; /// TODO-PER: this should be set earlier, in the parser, probably. + this.tuneNumber = tuneNumber; + // TODO: can these two properties be merged? + this.totalHeightInPitches = 6; + this.tempoHeightAbove = this.totalHeightInPitches; + this.pitch = undefined; // This will be set later + if (this.tempo.duration && !this.tempo.suppressBpm) { + this.note = this.createNote(createNoteHead, tempo, tuneNumber); + } +}; + +TempoElement.prototype.setX = function (x) { + this.x = x; +}; + +TempoElement.prototype.createNote = function (createNoteHead, tempo, tuneNumber) { + var temposcale = 0.75; + var duration = tempo.duration[0]; // TODO when multiple durations + var absElem = new AbsoluteElement(tempo, duration, 1, 'tempo', tuneNumber); + // There aren't an infinite number of note values, but we are passed a float, so just in case something is off upstream, + // merge all of the in between points. + var dot; + var flag; + var note; + if (duration <= 1 / 32) { note = "noteheads.quarter"; flag = "flags.u32nd"; dot = 0; } + else if (duration <= 1 / 16) { note = "noteheads.quarter"; flag = "flags.u16th"; dot = 0; } + else if (duration <= 3 / 32) { note = "noteheads.quarter"; flag = "flags.u16nd"; dot = 1; } + else if (duration <= 1 / 8) { note = "noteheads.quarter"; flag = "flags.u8th"; dot = 0; } + else if (duration <= 3 / 16) { note = "noteheads.quarter"; flag = "flags.u8th"; dot = 1; } + else if (duration <= 1 / 4) { note = "noteheads.quarter"; dot = 0; } + else if (duration <= 3 / 8) { note = "noteheads.quarter"; dot = 1; } + else if (duration <= 1 / 2) { note = "noteheads.half"; dot = 0; } + else if (duration <= 3 / 4) { note = "noteheads.half"; dot = 1; } + else if (duration <= 1) { note = "noteheads.whole"; dot = 0; } + else if (duration <= 1.5) { note = "noteheads.whole"; dot = 1; } + else if (duration <= 2) { note = "noteheads.dbl"; dot = 0; } + else { note = "noteheads.dbl"; dot = 1; } + + var ret = createNoteHead(absElem, note, { verticalPos: 0 }, // This is just temporary: we'll offset the vertical positioning when we get the actual vertical spot. + { dir: "up", flag: flag, dot: dot, scale: temposcale }); + var tempoNote = ret.notehead; + absElem.addHead(tempoNote); + var stem; + if (note !== "noteheads.whole" && note !== "noteheads.dbl") { + var p1 = 1 / 3 * temposcale; + var p2 = 5 * temposcale; + var dx = tempoNote.dx + tempoNote.w; + var width = -0.6; + stem = new RelativeElement(null, dx, 0, p1, { "type": "stem", "pitch2": p2, linewidth: width }); + absElem.addRight(stem); + } + return absElem; +}; + +module.exports = TempoElement; diff --git a/src/write/creation/elements/tie-element.js b/src/write/creation/elements/tie-element.js new file mode 100644 index 0000000000000000000000000000000000000000..7ef8615e5464a59bb432ded21b788f9b7c99cd57 --- /dev/null +++ b/src/write/creation/elements/tie-element.js @@ -0,0 +1,254 @@ +// abc_tie_element.js: Definition of the TieElement class. + +var TieElem = function TieElem(options) { + this.type = "TieElem"; + // console.log("constructor", options.anchor1 ? options.anchor1.pitch : "N/A", options.anchor2 ? options.anchor2.pitch : "N/A", options.isTie, options.isGrace); + this.anchor1 = options.anchor1; // must have a .x and a .pitch, and a .parent property or be null (means starts at the "beginning" of the line - after keysig) + this.anchor2 = options.anchor2; // must have a .x and a .pitch property or be null (means ends at the end of the line) + if (options.isGrace) + this.isGrace = true; + if (options.fixedY) + this.fixedY = true; + if (options.stemDir) + this.stemDir = options.stemDir; + if (options.voiceNumber !== undefined) + this.voiceNumber = options.voiceNumber; + if (options.style !== undefined) + this.dotted = true; + this.internalNotes = []; +}; + +TieElem.prototype.addInternalNote = function (note) { + this.internalNotes.push(note); +}; + +TieElem.prototype.setEndAnchor = function (anchor2) { + // console.log("end", this.anchor1 ? this.anchor1.pitch : "N/A", anchor2 ? anchor2.pitch : "N/A", this.isTie, this.isGrace); + this.anchor2 = anchor2; // must have a .x and a .pitch property or be null (means ends at the end of the line) + + // we don't really have enough info to know what the vertical extent is yet and we won't until drawing. This will just give it enough + // room on either side (we don't even know if the slur will be above yet). We need to set this so that we can make sure the voice has + // at least enough room that the line doesn't get cut off if the tie or slur is the lowest thing. + if (this.anchor1) { + this.top = Math.max(this.anchor1.pitch, this.anchor2.pitch) + 4 + this.bottom = Math.min(this.anchor1.pitch, this.anchor2.pitch) - 4 + } else { + this.top = this.anchor2.pitch + 4 + this.bottom = this.anchor2.pitch - 4 + } +}; + +// If we encounter a repeat sign, then we don't want to extend either a tie or a slur past it, so these are called to be a limit. +TieElem.prototype.setStartX = function (startLimitElem) { + this.startLimitX = startLimitElem; +}; + +TieElem.prototype.setEndX = function (endLimitElem) { + this.endLimitX = endLimitElem; +}; + +TieElem.prototype.setHint = function () { + this.hint = true; +}; + +TieElem.prototype.calcTieDirection = function () { + // The rules: + // 1) If it is in a grace note group, then the direction is always BELOW. + // 2) If it is in a single voice, then the direction is always OPPOSITE of the stem (or where the stem would have been in the case of whole notes.) + // 3) If the stem direction is forced (probably because there are two voices on the same line), then the direction is the SAME as the stem direction. + + if (this.isGrace) + this.above = false; + else if (this.voiceNumber === 0) + this.above = true; + else if (this.voiceNumber > 0) + this.above = false; + else { + var referencePitch; + if (this.anchor1) + referencePitch = this.anchor1.pitch; + else if (this.anchor2) + referencePitch = this.anchor2.pitch; + else + referencePitch = 14; // TODO-PER: this can't really happen normally. This would imply that a tie crossed over three lines, something like "C-\nz\nC" + // Put the arc in the opposite direction of the stem. That isn't always the pitch if one or both of the notes are beamed with something that affects its stem. + if ((this.anchor1 && this.anchor1.stemDir === 'down') && (this.anchor2 && this.anchor2.stemDir === "down")) + this.above = true; + else if ((this.anchor1 && this.anchor1.stemDir === 'up') && (this.anchor2 && this.anchor2.stemDir === "up")) + this.above = false; + else if (this.anchor1 && this.anchor2) + this.above = referencePitch >= 6; + else if (this.anchor1) + this.above = this.anchor1.stemDir === "down"; + else if (this.anchor2) + this.above = this.anchor2.stemDir === "down"; + else + this.above = referencePitch >= 6; + } +}; + +// From "standard music notation practice" by Music Publishers’ Association: +// 1) Slurs are placed under the note heads if all stems go up. +// 2) Slurs are placed over the note heads if all stems go down. +// 3) If there are both up stems and down stems, prefer placing the slur over. +// 4) When the staff has opposite stemmed voices, all slurs should be on the stemmed side. + +TieElem.prototype.calcSlurDirection = function () { + if (this.isGrace) + this.above = false; + else if (this.voiceNumber === 0) + this.above = true; + else if (this.voiceNumber > 0) + this.above = false; + else { + var hasDownStem = false; + if (this.anchor1 && this.anchor1.stemDir === "down") + hasDownStem = true; + if (this.anchor2 && this.anchor2.stemDir === "down") + hasDownStem = true; + for (var i = 0; i < this.internalNotes.length; i++) { + var n = this.internalNotes[i]; + if (n.stemDir === "down") + hasDownStem = true; + } + this.above = hasDownStem; + } +}; + +TieElem.prototype.calcX = function (lineStartX, lineEndX) { + if (this.anchor1) { + this.startX = this.anchor1.x; // The normal case where there is a starting element to attach to. + if (this.anchor1.scalex < 1) // this is a grace note - don't offset the tie as much. + this.startX -= 3; + } else if (this.startLimitX) + this.startX = this.startLimitX.x + this.startLimitX.w; // if there is no start element, but there is a repeat mark before the start of the line. + else { + if (this.anchor2) + this.startX = this.anchor2.x - 20; // There is no element and no repeat mark: make a small arc + else + this.startX = lineStartX; // Don't have any guidance, so extend to beginning of line + } + if (!this.anchor1 && this.dotted) + this.startX -= 3; // The arc needs to be long enough to tell that it is dotted. + + if (this.anchor2) + this.endX = this.anchor2.x; // The normal case where there is a starting element to attach to. + else if (this.endLimitX) + this.endX = this.endLimitX.x; // if there is no start element, but there is a repeat mark before the start of the line. + else + this.endX = lineEndX; // There is no element and no repeat mark: extend to the beginning of the line. +}; + +TieElem.prototype.calcTieY = function () { + // If the tie comes from another line, then one or both anchors will be missing. + if (this.anchor1) + this.startY = this.anchor1.pitch; + else if (this.anchor2) + this.startY = this.anchor2.pitch; + else + this.startY = this.above ? 14 : 0; + + if (this.anchor2) + this.endY = this.anchor2.pitch; + else if (this.anchor1) + this.endY = this.anchor1.pitch; + else + this.endY = this.above ? 14 : 0; +}; + +// From "standard music notation practice" by Music Publishers’ Association: +// 1) If the anchor note is down stem, the slur points to the note head. +// 2) If the anchor note is up stem, and the slur is over, then point to middle of stem. + +TieElem.prototype.calcSlurY = function () { + if (this.anchor1 && this.anchor2) { + if (this.above && this.anchor1.stemDir === "up" && !this.fixedY) { + this.startY = (this.anchor1.highestVert + this.anchor1.pitch) / 2; + this.startX += this.anchor1.w / 2; // When going to the middle of the stem, bump the line to the right a little bit to make it look right. + } else + this.startY = this.anchor1.pitch; + + // If the closing note has an up stem, and it is beamed, and it isn't the first note in the beam, then the beam will get in the way. + var beamInterferes = this.anchor2.parent.beam && this.anchor2.parent.beam.stemsUp && this.anchor2.parent.beam.elems[0] !== this.anchor2.parent; + var midPoint = (this.anchor2.highestVert + this.anchor2.pitch) / 2; + if (this.above && this.anchor2.stemDir === "up" && !this.fixedY && !beamInterferes && (midPoint < this.startY)) { + this.endY = midPoint; + this.endX += Math.round(this.anchor2.w / 2); // When going to the middle of the stem, bump the line to the right a little bit to make it look right. + } else + this.endY = this.above && beamInterferes ? this.anchor2.highestVert : this.anchor2.pitch; + + if (this.anchor1.scalex === 1) { // Need a way to tell if this is a grace note - if so then keep the slur as close as possible. TODO-PER-HACK: this should be more declaratively determined. + var hasBeam1 = !!this.anchor1.parent.beam + var hasBeam2 = !!this.anchor2.parent.beam + if (hasBeam1) { + var isLastInBeam = this.anchor1.parent === this.anchor1.parent.beam.elems[this.anchor1.parent.beam.elems.length-1] + if (!isLastInBeam) { + if (this.above) + this.startY = this.anchor1.parent.fixed.t + else + this.startY = this.anchor1.parent.fixed.b + } + } + + if (hasBeam2) { + var isFirstInBeam = this.anchor2.parent === this.anchor2.parent.beam.elems[0] + if (!isFirstInBeam) { + if (this.above) + this.endY = this.anchor2.parent.fixed.t + else + this.endY = this.anchor2.parent.fixed.b + } + } + } + } else if (this.anchor1) { + this.startY = this.endY = this.anchor1.pitch; + } else if (this.anchor2) { + this.startY = this.endY = this.anchor2.pitch; + } else { + // This is the case where the slur covers the entire line. + // TODO-PER: figure out where the real top and bottom of the line are. + this.startY = this.above ? 14 : 0; + this.endY = this.above ? 14 : 0; + } +}; + +TieElem.prototype.avoidCollisionAbove = function () { + // Double check that an interior note in the slur isn't so high that it interferes. + if (this.above) { + var maxInnerHeight = -50; + for (var i = 0; i < this.internalNotes.length; i++) { + if (this.internalNotes[i].highestVert > maxInnerHeight) + maxInnerHeight = this.internalNotes[i].highestVert; + } + if (maxInnerHeight > this.startY && maxInnerHeight > this.endY) + this.startY = this.endY = maxInnerHeight - 1; + } +}; + +TieElem.prototype.getYBounds = function () { + var lineStartX = 10 // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much + var lineEndX = 1000 // TODO-PER: I'm not sure where to get this number from but it probably doesn't matter much + if (this.isTie) { + this.calcTieDirection(); + this.calcX(lineStartX, lineEndX); + this.calcTieY(); + + } else { + this.calcSlurDirection(); + this.calcX(lineStartX, lineEndX); + this.calcSlurY(); + } + var top; + var bottom; + // TODO-PER: It's hard to tell how far the arc is, so I'm just using 3 as the max + if (this.above) { + bottom = Math.min(this.startY, this.endY) + top = bottom + 3 + } else { + top = Math.min(this.startY, this.endY) + bottom = top - 3 + } + return [ top, bottom ] +}; + +module.exports = TieElem; diff --git a/src/write/creation/elements/top-text.js b/src/write/creation/elements/top-text.js new file mode 100644 index 0000000000000000000000000000000000000000..17df564b94e3f06c0db53f8cd2a00eb849914670 --- /dev/null +++ b/src/write/creation/elements/top-text.js @@ -0,0 +1,80 @@ +const addTextIf = require("../add-text-if"); +const richText = require("./rich-text"); + +function TopText(metaText, metaTextInfo, formatting, lines, width, isPrint, paddingLeft, spacing, shouldAddClasses, getTextSize) { + this.rows = []; + + if (metaText.header && isPrint) { + // Note: whether there is a header or not doesn't change any other positioning, so this doesn't change the Y-coordinate. + // This text goes above the margin, so we'll temporarily move up. + var headerTextHeight = getTextSize.calc("X", "headerfont", 'abcjs-header abcjs-meta-top').height; + addTextIf(this.rows, { marginLeft: paddingLeft, text: metaText.header.left, font: 'headerfont', klass: 'header meta-top', marginTop: -headerTextHeight, info: metaTextInfo.header, name: "header" }, getTextSize); + addTextIf(this.rows, { marginLeft: paddingLeft + width / 2, text: metaText.header.center, font: 'headerfont', klass: 'header meta-top', marginTop: -headerTextHeight, anchor: 'middle', info: metaTextInfo.header, name: "header" }, getTextSize); + addTextIf(this.rows, { marginLeft: paddingLeft + width, text: metaText.header.right, font: 'headerfont', klass: 'header meta-top', marginTop: -headerTextHeight, anchor: 'end', info: metaTextInfo.header, name: "header" }, getTextSize); + + // TopText.prototype.addTextIf = function (marginLeft, text, font, klass, marginTop, marginBottom, anchor, getTextSize, absElemType, noMove) { + } + if (isPrint) + this.rows.push({ move: spacing.top }); + var tAnchor = formatting.titleleft ? 'start' : 'middle'; + var tLeft = formatting.titleleft ? paddingLeft : paddingLeft + width / 2; + if (metaText.title) { + var klass = shouldAddClasses ? 'abcjs-title' : '' + richText(this.rows, metaText.title, "titlefont", klass, 'title', tLeft, {marginTop: spacing.title, anchor: tAnchor, absElemType: "title", info: metaTextInfo.title}, getTextSize) + } + if (lines.length) { + var index = 0; + while (index < lines.length && lines[index].subtitle) { + var klass = shouldAddClasses ? 'abcjs-text abcjs-subtitle' : '' + richText(this.rows, lines[index].subtitle.text, "subtitlefont", klass, 'subtitle', tLeft, {marginTop: spacing.subtitle, anchor: tAnchor, absElemType: "subtitle", info: lines[index].subtitle}, getTextSize) + index++; + } + } + + if (metaText.rhythm || metaText.origin || metaText.composer) { + this.rows.push({ move: spacing.composer }); + if (metaText.rhythm && metaText.rhythm.length > 0) { + var noMove = !!(metaText.composer || metaText.origin); + var klass = shouldAddClasses ? 'abcjs-rhythm' : '' + addTextIf(this.rows, { marginLeft: paddingLeft, text: metaText.rhythm, font: 'infofont', klass: klass, absElemType: "rhythm", noMove: noMove, info: metaTextInfo.rhythm, name: "rhythm" }, getTextSize); + } + var hasSimpleComposerLine = true + if (metaText.composer && typeof metaText.composer !== 'string') + hasSimpleComposerLine = false + if (metaText.origin && typeof metaText.origin !== 'string') + hasSimpleComposerLine = false + + var composerLine = metaText.composer ? metaText.composer : ''; + if (metaText.origin) { + if (typeof composerLine === 'string' && typeof metaText.origin === 'string') + composerLine += ' (' + metaText.origin + ')'; + else if (typeof composerLine === 'string' && typeof metaText.origin !== 'string') { + composerLine = [{text:composerLine}] + composerLine.push({text:" ("}) + composerLine = composerLine.concat(metaText.origin) + composerLine.push({text:")"}) + } else { + composerLine.push({text:" ("}) + composerLine = composerLine.concat(metaText.origin) + composerLine.push({text:")"}) + } + } + if (composerLine) { + var klass = shouldAddClasses ? 'abcjs-composer' : '' + richText(this.rows, composerLine, 'composerfont', klass, "composer", paddingLeft+width, {anchor: "end", absElemType: "composer", info: metaTextInfo.composer, ingroup: true}, getTextSize) + } + } + + if (metaText.author && metaText.author.length > 0) { + var klass = shouldAddClasses ? 'abcjs-author' : '' + richText(this.rows, metaText.author, 'composerfont', klass, "author", paddingLeft+width, {anchor: "end", absElemType: "author", info: metaTextInfo.author}, getTextSize) + } + + if (metaText.partOrder && metaText.partOrder.length > 0) { + var klass = shouldAddClasses ? 'abcjs-part-order' : '' + richText(this.rows, metaText.partOrder, 'partsfont', klass, "part-order", paddingLeft, {absElemType: "partOrder", info: metaTextInfo.partOrder, anchor: 'start'}, getTextSize) + + } +} + +module.exports = TopText; diff --git a/src/write/creation/elements/triplet-element.js b/src/write/creation/elements/triplet-element.js new file mode 100644 index 0000000000000000000000000000000000000000..4ed5e6572d0b0b1c56b6e8431af0d7c81dc47dcd --- /dev/null +++ b/src/write/creation/elements/triplet-element.js @@ -0,0 +1,28 @@ +// abc_triplet_element.js: Definition of the TripletElem class. + +var TripletElem = function TripletElem(number, anchor1, options) { + this.type = "TripletElem"; + this.anchor1 = anchor1; // must have a .x and a .parent property or be null (means starts at the "beginning" of the line - after key signature) + this.number = number; + this.durationClass = ('d' + (Math.round(anchor1.parent.durationClass * 1000) / 1000)).replace(/\./, '-'); + this.middleElems = []; // This is to calculate the highest interior pitch. It is used to make sure that the drawn bracket never crosses a really high middle note. + this.flatBeams = options.flatBeams; +}; + +TripletElem.prototype.isClosed = function () { + return !!this.anchor2; +}; + +TripletElem.prototype.middleNote = function (elem) { + this.middleElems.push(elem); +}; + +TripletElem.prototype.setCloseAnchor = function (anchor2) { + this.anchor2 = anchor2; + // TODO-PER: This used to be just for beamed triplets but it looks like bracketed triplets need extra room, too. The only one that doesn't is stem down and beamed + //if (this.anchor1.parent.beam) + if (!this.anchor1.parent.beam || this.anchor1.stemDir === 'up') + this.endingHeightAbove = 4; +}; + +module.exports = TripletElem; diff --git a/src/write/creation/elements/voice-element.js b/src/write/creation/elements/voice-element.js new file mode 100644 index 0000000000000000000000000000000000000000..f6a45424620ff6b8b78389e6c2aa529cf9d41c60 --- /dev/null +++ b/src/write/creation/elements/voice-element.js @@ -0,0 +1,94 @@ +// abc_voice_element.js: Definition of the VoiceElement class. + +var VoiceElement = function VoiceElement(voicenumber, voicetotal) { + this.children = []; + this.beams = []; + this.otherchildren = []; // ties, slurs, triplets + this.w = 0; + this.duplicate = false; + this.voicenumber = voicenumber; //number of the voice on a given stave (not staffgroup) + this.voicetotal = voicetotal; + this.bottom = 7; + this.top = 7; + this.specialY = { + tempoHeightAbove: 0, + partHeightAbove: 0, + volumeHeightAbove: 0, + dynamicHeightAbove: 0, + endingHeightAbove: 0, + chordHeightAbove: 0, + lyricHeightAbove: 0, + + lyricHeightBelow: 0, + chordHeightBelow: 0, + volumeHeightBelow: 0, + dynamicHeightBelow: 0 + }; +}; + +VoiceElement.prototype.addChild = function (absElem) { + // This is always passed an AbsoluteElement + if (absElem.type === 'bar') { + var firstItem = true; + for (var i = 0; firstItem && i < this.children.length; i++) { + if (this.children[i].type.indexOf("staff-extra") < 0 && this.children[i].type !== "tempo") + firstItem = false; + } + if (!firstItem) { + this.beams.push("bar"); + this.otherchildren.push("bar"); + } + } + this.children[this.children.length] = absElem; + this.setRange(absElem); +}; + +VoiceElement.prototype.setLimit = function (member, child) { + // Sometimes we get an absolute element in here and sometimes we get some type of relative element. + // If there is a "specialY" element, then assume it is an absolute element. If that doesn't exist, look for the + // same members at the top level, because that's where they are in relative elements. + var specialY = child.specialY; + if (!specialY) specialY = child; + if (!specialY[member]) return; + if (!this.specialY[member]) + this.specialY[member] = specialY[member]; + else + this.specialY[member] = Math.max(this.specialY[member], specialY[member]); +}; + +VoiceElement.prototype.adjustRange = function (child) { + if (child.bottom !== undefined) + this.bottom = Math.min(this.bottom, child.bottom); + if (child.top !== undefined) + this.top = Math.max(this.top, child.top); +}; + +VoiceElement.prototype.setRange = function (child) { + this.adjustRange(child); + this.setLimit('tempoHeightAbove', child); + this.setLimit('partHeightAbove', child); + this.setLimit('volumeHeightAbove', child); + this.setLimit('dynamicHeightAbove', child); + this.setLimit('endingHeightAbove', child); + this.setLimit('chordHeightAbove', child); + this.setLimit('lyricHeightAbove', child); + this.setLimit('lyricHeightBelow', child); + this.setLimit('chordHeightBelow', child); + this.setLimit('volumeHeightBelow', child); + this.setLimit('dynamicHeightBelow', child); +}; + +VoiceElement.prototype.addOther = function (child) { + this.otherchildren.push(child); + this.setRange(child); +}; + +VoiceElement.prototype.addBeam = function (child) { + this.beams.push(child); +}; + +VoiceElement.prototype.setWidth = function (width) { + this.w = width; +}; + +module.exports = VoiceElement; diff --git a/src/write/creation/glyphs.js b/src/write/creation/glyphs.js new file mode 100644 index 0000000000000000000000000000000000000000..0d3fe7b9300344d1dc26fa929cbf8f25ea216814 --- /dev/null +++ b/src/write/creation/glyphs.js @@ -0,0 +1,226 @@ +var spacing = require('../helpers/spacing'); + +/** + * Glyphs and some methods to adjust for their x and y baseline + */ +var glyphs = +{ + '0': { d: [['M', 4.83, -14.97], ['c', 0.33, -0.03, 1.11, 0.00, 1.47, 0.06], ['c', 1.68, 0.36, 2.97, 1.59, 3.78, 3.60], ['c', 1.20, 2.97, 0.81, 6.96, -0.90, 9.27], ['c', -0.78, 1.08, -1.71, 1.71, -2.91, 1.95], ['c', -0.45, 0.09, -1.32, 0.09, -1.77, 0.00], ['c', -0.81, -0.18, -1.47, -0.51, -2.07, -1.02], ['c', -2.34, -2.07, -3.15, -6.72, -1.74, -10.20], ['c', 0.87, -2.16, 2.28, -3.42, 4.14, -3.66], ['z'], ['m', 1.11, 0.87], ['c', -0.21, -0.06, -0.69, -0.09, -0.87, -0.06], ['c', -0.54, 0.12, -0.87, 0.42, -1.17, 0.99], ['c', -0.36, 0.66, -0.51, 1.56, -0.60, 3.00], ['c', -0.03, 0.75, -0.03, 4.59, 0.00, 5.31], ['c', 0.09, 1.50, 0.27, 2.40, 0.60, 3.06], ['c', 0.24, 0.48, 0.57, 0.78, 0.96, 0.90], ['c', 0.27, 0.09, 0.78, 0.09, 1.05, 0.00], ['c', 0.39, -0.12, 0.72, -0.42, 0.96, -0.90], ['c', 0.33, -0.66, 0.51, -1.56, 0.60, -3.06], ['c', 0.03, -0.72, 0.03, -4.56, 0.00, -5.31], ['c', -0.09, -1.47, -0.27, -2.37, -0.60, -3.03], ['c', -0.24, -0.48, -0.54, -0.78, -0.93, -0.90], ['z']], w: 10.78, h: 14.959 }, + '1': { d: [['M', 3.30, -15.06], ['c', 0.06, -0.06, 0.21, -0.03, 0.66, 0.15], ['c', 0.81, 0.39, 1.08, 0.39, 1.83, 0.03], ['c', 0.21, -0.09, 0.39, -0.15, 0.42, -0.15], ['c', 0.12, 0.00, 0.21, 0.09, 0.27, 0.21], ['c', 0.06, 0.12, 0.06, 0.33, 0.06, 5.94], ['c', 0.00, 3.93, 0.00, 5.85, 0.03, 6.03], ['c', 0.06, 0.36, 0.15, 0.69, 0.27, 0.96], ['c', 0.36, 0.75, 0.93, 1.17, 1.68, 1.26], ['c', 0.30, 0.03, 0.39, 0.09, 0.39, 0.30], ['c', 0.00, 0.15, -0.03, 0.18, -0.09, 0.24], ['c', -0.06, 0.06, -0.09, 0.06, -0.48, 0.06], ['c', -0.42, 0.00, -0.69, -0.03, -2.10, -0.24], ['c', -0.90, -0.15, -1.77, -0.15, -2.67, 0.00], ['c', -1.41, 0.21, -1.68, 0.24, -2.10, 0.24], ['c', -0.39, 0.00, -0.42, 0.00, -0.48, -0.06], ['c', -0.06, -0.06, -0.06, -0.09, -0.06, -0.24], ['c', 0.00, -0.21, 0.06, -0.27, 0.36, -0.30], ['c', 0.75, -0.09, 1.32, -0.51, 1.68, -1.26], ['c', 0.12, -0.27, 0.21, -0.60, 0.27, -0.96], ['c', 0.03, -0.18, 0.03, -1.59, 0.03, -4.29], ['c', 0.00, -3.87, 0.00, -4.05, -0.06, -4.14], ['c', -0.09, -0.15, -0.18, -0.24, -0.39, -0.24], ['c', -0.12, 0.00, -0.15, 0.03, -0.21, 0.06], ['c', -0.03, 0.06, -0.45, 0.99, -0.96, 2.13], ['c', -0.48, 1.14, -0.90, 2.10, -0.93, 2.16], ['c', -0.06, 0.15, -0.21, 0.24, -0.33, 0.24], ['c', -0.24, 0.00, -0.42, -0.18, -0.42, -0.39], ['c', 0.00, -0.06, 3.27, -7.62, 3.33, -7.74], ['z']], w: 8.94, h: 15.058 }, + '2': { d: [['M', 4.23, -14.97], ['c', 0.57, -0.06, 1.68, 0.00, 2.34, 0.18], ['c', 0.69, 0.18, 1.50, 0.54, 2.01, 0.90], ['c', 1.35, 0.96, 1.95, 2.25, 1.77, 3.81], ['c', -0.15, 1.35, -0.66, 2.34, -1.68, 3.15], ['c', -0.60, 0.48, -1.44, 0.93, -3.12, 1.65], ['c', -1.32, 0.57, -1.80, 0.81, -2.37, 1.14], ['c', -0.57, 0.33, -0.57, 0.33, -0.24, 0.27], ['c', 0.39, -0.09, 1.26, -0.09, 1.68, 0.00], ['c', 0.72, 0.15, 1.41, 0.45, 2.10, 0.90], ['c', 0.99, 0.63, 1.86, 0.87, 2.55, 0.75], ['c', 0.24, -0.06, 0.42, -0.15, 0.57, -0.30], ['c', 0.12, -0.09, 0.30, -0.42, 0.30, -0.51], ['c', 0.00, -0.09, 0.12, -0.21, 0.24, -0.24], ['c', 0.18, -0.03, 0.39, 0.12, 0.39, 0.30], ['c', 0.00, 0.12, -0.15, 0.57, -0.30, 0.87], ['c', -0.54, 1.02, -1.56, 1.74, -2.79, 2.01], ['c', -0.42, 0.09, -1.23, 0.09, -1.62, 0.03], ['c', -0.81, -0.18, -1.32, -0.45, -2.01, -1.11], ['c', -0.45, -0.45, -0.63, -0.57, -0.96, -0.69], ['c', -0.84, -0.27, -1.89, 0.12, -2.25, 0.90], ['c', -0.12, 0.21, -0.21, 0.54, -0.21, 0.72], ['c', 0.00, 0.12, -0.12, 0.21, -0.27, 0.24], ['c', -0.15, 0.00, -0.27, -0.03, -0.33, -0.15], ['c', -0.09, -0.21, 0.09, -1.08, 0.33, -1.71], ['c', 0.24, -0.66, 0.66, -1.26, 1.29, -1.89], ['c', 0.45, -0.45, 0.90, -0.81, 1.92, -1.56], ['c', 1.29, -0.93, 1.89, -1.44, 2.34, -1.98], ['c', 0.87, -1.05, 1.26, -2.19, 1.20, -3.63], ['c', -0.06, -1.29, -0.39, -2.31, -0.96, -2.91], ['c', -0.36, -0.33, -0.72, -0.51, -1.17, -0.54], ['c', -0.84, -0.03, -1.53, 0.42, -1.59, 1.05], ['c', -0.03, 0.33, 0.12, 0.60, 0.57, 1.14], ['c', 0.45, 0.54, 0.54, 0.87, 0.42, 1.41], ['c', -0.15, 0.63, -0.54, 1.11, -1.08, 1.38], ['c', -0.63, 0.33, -1.20, 0.33, -1.83, 0.00], ['c', -0.24, -0.12, -0.33, -0.18, -0.54, -0.39], ['c', -0.18, -0.18, -0.27, -0.30, -0.36, -0.51], ['c', -0.24, -0.45, -0.27, -0.84, -0.21, -1.38], ['c', 0.12, -0.75, 0.45, -1.41, 1.02, -1.98], ['c', 0.72, -0.72, 1.74, -1.17, 2.85, -1.32], ['z']], w: 10.764, h: 14.97 }, + '3': { d: [['M', 3.78, -14.97], ['c', 0.30, -0.03, 1.41, 0.00, 1.83, 0.06], ['c', 2.22, 0.30, 3.51, 1.32, 3.72, 2.91], ['c', 0.03, 0.33, 0.03, 1.26, -0.03, 1.65], ['c', -0.12, 0.84, -0.48, 1.47, -1.05, 1.77], ['c', -0.27, 0.15, -0.36, 0.24, -0.45, 0.39], ['c', -0.09, 0.21, -0.09, 0.36, 0.00, 0.57], ['c', 0.09, 0.15, 0.18, 0.24, 0.51, 0.39], ['c', 0.75, 0.42, 1.23, 1.14, 1.41, 2.13], ['c', 0.06, 0.42, 0.06, 1.35, 0.00, 1.71], ['c', -0.18, 0.81, -0.48, 1.38, -1.02, 1.95], ['c', -0.75, 0.72, -1.80, 1.20, -3.18, 1.38], ['c', -0.42, 0.06, -1.56, 0.06, -1.95, 0.00], ['c', -1.89, -0.33, -3.18, -1.29, -3.51, -2.64], ['c', -0.03, -0.12, -0.03, -0.33, -0.03, -0.60], ['c', 0.00, -0.36, 0.00, -0.42, 0.06, -0.63], ['c', 0.12, -0.30, 0.27, -0.51, 0.51, -0.75], ['c', 0.24, -0.24, 0.45, -0.39, 0.75, -0.51], ['c', 0.21, -0.06, 0.27, -0.06, 0.60, -0.06], ['c', 0.33, 0.00, 0.39, 0.00, 0.60, 0.06], ['c', 0.30, 0.12, 0.51, 0.27, 0.75, 0.51], ['c', 0.36, 0.33, 0.57, 0.75, 0.60, 1.20], ['c', 0.00, 0.21, 0.00, 0.27, -0.06, 0.42], ['c', -0.09, 0.18, -0.12, 0.24, -0.54, 0.54], ['c', -0.51, 0.36, -0.63, 0.54, -0.60, 0.87], ['c', 0.06, 0.54, 0.54, 0.90, 1.38, 0.99], ['c', 0.36, 0.06, 0.72, 0.03, 0.96, -0.06], ['c', 0.81, -0.27, 1.29, -1.23, 1.44, -2.79], ['c', 0.03, -0.45, 0.03, -1.95, -0.03, -2.37], ['c', -0.09, -0.75, -0.33, -1.23, -0.75, -1.44], ['c', -0.33, -0.18, -0.45, -0.18, -1.98, -0.18], ['c', -1.35, 0.00, -1.41, 0.00, -1.50, -0.06], ['c', -0.18, -0.12, -0.24, -0.39, -0.12, -0.60], ['c', 0.12, -0.15, 0.15, -0.15, 1.68, -0.15], ['c', 1.50, 0.00, 1.62, 0.00, 1.89, -0.15], ['c', 0.18, -0.09, 0.42, -0.36, 0.54, -0.57], ['c', 0.18, -0.42, 0.27, -0.90, 0.30, -1.95], ['c', 0.03, -1.20, -0.06, -1.80, -0.36, -2.37], ['c', -0.24, -0.48, -0.63, -0.81, -1.14, -0.96], ['c', -0.30, -0.06, -1.08, -0.06, -1.38, 0.03], ['c', -0.60, 0.15, -0.90, 0.42, -0.96, 0.84], ['c', -0.03, 0.30, 0.06, 0.45, 0.63, 0.84], ['c', 0.33, 0.24, 0.42, 0.39, 0.45, 0.63], ['c', 0.03, 0.72, -0.57, 1.50, -1.32, 1.65], ['c', -1.05, 0.27, -2.10, -0.57, -2.10, -1.65], ['c', 0.00, -0.45, 0.15, -0.96, 0.39, -1.38], ['c', 0.12, -0.21, 0.54, -0.63, 0.81, -0.81], ['c', 0.57, -0.42, 1.38, -0.69, 2.25, -0.81], ['z']], w: 9.735, h: 14.967 }, + '4': { d: [['M', 8.64, -14.94], ['c', 0.27, -0.09, 0.42, -0.12, 0.54, -0.03], ['c', 0.09, 0.06, 0.15, 0.21, 0.15, 0.30], ['c', -0.03, 0.06, -1.92, 2.31, -4.23, 5.04], ['c', -2.31, 2.73, -4.23, 4.98, -4.26, 5.01], ['c', -0.03, 0.06, 0.12, 0.06, 2.55, 0.06], ['l', 2.61, 0.00], ['l', 0.00, -2.37], ['c', 0.00, -2.19, 0.03, -2.37, 0.06, -2.46], ['c', 0.03, -0.06, 0.21, -0.18, 0.57, -0.42], ['c', 1.08, -0.72, 1.38, -1.08, 1.86, -2.16], ['c', 0.12, -0.30, 0.24, -0.54, 0.27, -0.57], ['c', 0.12, -0.12, 0.39, -0.06, 0.45, 0.12], ['c', 0.06, 0.09, 0.06, 0.57, 0.06, 3.96], ['l', 0.00, 3.90], ['l', 1.08, 0.00], ['c', 1.05, 0.00, 1.11, 0.00, 1.20, 0.06], ['c', 0.24, 0.15, 0.24, 0.54, 0.00, 0.69], ['c', -0.09, 0.06, -0.15, 0.06, -1.20, 0.06], ['l', -1.08, 0.00], ['l', 0.00, 0.33], ['c', 0.00, 0.57, 0.09, 1.11, 0.30, 1.53], ['c', 0.36, 0.75, 0.93, 1.17, 1.68, 1.26], ['c', 0.30, 0.03, 0.39, 0.09, 0.39, 0.30], ['c', 0.00, 0.15, -0.03, 0.18, -0.09, 0.24], ['c', -0.06, 0.06, -0.09, 0.06, -0.48, 0.06], ['c', -0.42, 0.00, -0.69, -0.03, -2.10, -0.24], ['c', -0.90, -0.15, -1.77, -0.15, -2.67, 0.00], ['c', -1.41, 0.21, -1.68, 0.24, -2.10, 0.24], ['c', -0.39, 0.00, -0.42, 0.00, -0.48, -0.06], ['c', -0.06, -0.06, -0.06, -0.09, -0.06, -0.24], ['c', 0.00, -0.21, 0.06, -0.27, 0.36, -0.30], ['c', 0.75, -0.09, 1.32, -0.51, 1.68, -1.26], ['c', 0.21, -0.42, 0.30, -0.96, 0.30, -1.53], ['l', 0.00, -0.33], ['l', -2.70, 0.00], ['c', -2.91, 0.00, -2.85, 0.00, -3.09, -0.15], ['c', -0.18, -0.12, -0.30, -0.39, -0.27, -0.54], ['c', 0.03, -0.06, 0.18, -0.24, 0.33, -0.45], ['c', 0.75, -0.90, 1.59, -2.07, 2.13, -3.03], ['c', 0.33, -0.54, 0.84, -1.62, 1.05, -2.16], ['c', 0.57, -1.41, 0.84, -2.64, 0.90, -4.05], ['c', 0.03, -0.63, 0.06, -0.72, 0.24, -0.81], ['l', 0.12, -0.06], ['l', 0.45, 0.12], ['c', 0.66, 0.18, 1.02, 0.24, 1.47, 0.27], ['c', 0.60, 0.03, 1.23, -0.09, 2.01, -0.33], ['z']], w: 11.795, h: 14.994 }, + '5': { d: [['M', 1.02, -14.94], ['c', 0.12, -0.09, 0.03, -0.09, 1.08, 0.06], ['c', 2.49, 0.36, 4.35, 0.36, 6.96, -0.06], ['c', 0.57, -0.09, 0.66, -0.06, 0.81, 0.06], ['c', 0.15, 0.18, 0.12, 0.24, -0.15, 0.51], ['c', -1.29, 1.26, -3.24, 2.04, -5.58, 2.31], ['c', -0.60, 0.09, -1.20, 0.12, -1.71, 0.12], ['c', -0.39, 0.00, -0.45, 0.00, -0.57, 0.06], ['c', -0.09, 0.06, -0.15, 0.12, -0.21, 0.21], ['l', -0.06, 0.12], ['l', 0.00, 1.65], ['l', 0.00, 1.65], ['l', 0.21, -0.21], ['c', 0.66, -0.57, 1.41, -0.96, 2.19, -1.14], ['c', 0.33, -0.06, 1.41, -0.06, 1.95, 0.00], ['c', 2.61, 0.36, 4.02, 1.74, 4.26, 4.14], ['c', 0.03, 0.45, 0.03, 1.08, -0.03, 1.44], ['c', -0.18, 1.02, -0.78, 2.01, -1.59, 2.70], ['c', -0.72, 0.57, -1.62, 1.02, -2.49, 1.20], ['c', -1.38, 0.27, -3.03, 0.06, -4.20, -0.54], ['c', -1.08, -0.54, -1.71, -1.32, -1.86, -2.28], ['c', -0.09, -0.69, 0.09, -1.29, 0.57, -1.74], ['c', 0.24, -0.24, 0.45, -0.39, 0.75, -0.51], ['c', 0.21, -0.06, 0.27, -0.06, 0.60, -0.06], ['c', 0.33, 0.00, 0.39, 0.00, 0.60, 0.06], ['c', 0.30, 0.12, 0.51, 0.27, 0.75, 0.51], ['c', 0.36, 0.33, 0.57, 0.75, 0.60, 1.20], ['c', 0.00, 0.21, 0.00, 0.27, -0.06, 0.42], ['c', -0.09, 0.18, -0.12, 0.24, -0.54, 0.54], ['c', -0.18, 0.12, -0.36, 0.30, -0.42, 0.33], ['c', -0.36, 0.42, -0.18, 0.99, 0.36, 1.26], ['c', 0.51, 0.27, 1.47, 0.36, 2.01, 0.27], ['c', 0.93, -0.21, 1.47, -1.17, 1.65, -2.91], ['c', 0.06, -0.45, 0.06, -1.89, 0.00, -2.31], ['c', -0.15, -1.20, -0.51, -2.10, -1.05, -2.55], ['c', -0.21, -0.18, -0.54, -0.36, -0.81, -0.39], ['c', -0.30, -0.06, -0.84, -0.03, -1.26, 0.06], ['c', -0.93, 0.18, -1.65, 0.60, -2.16, 1.20], ['c', -0.15, 0.21, -0.27, 0.30, -0.39, 0.30], ['c', -0.15, 0.00, -0.30, -0.09, -0.36, -0.18], ['c', -0.06, -0.09, -0.06, -0.15, -0.06, -3.66], ['c', 0.00, -3.39, 0.00, -3.57, 0.06, -3.66], ['c', 0.03, -0.06, 0.09, -0.15, 0.15, -0.18], ['z']], w: 10.212, h: 14.997 }, + '6': { d: [['M', 4.98, -14.97], ['c', 0.36, -0.03, 1.20, 0.00, 1.59, 0.06], ['c', 0.90, 0.15, 1.68, 0.51, 2.25, 1.05], ['c', 0.57, 0.51, 0.87, 1.23, 0.84, 1.98], ['c', -0.03, 0.51, -0.21, 0.90, -0.60, 1.26], ['c', -0.24, 0.24, -0.45, 0.39, -0.75, 0.51], ['c', -0.21, 0.06, -0.27, 0.06, -0.60, 0.06], ['c', -0.33, 0.00, -0.39, 0.00, -0.60, -0.06], ['c', -0.30, -0.12, -0.51, -0.27, -0.75, -0.51], ['c', -0.39, -0.36, -0.57, -0.78, -0.57, -1.26], ['c', 0.00, -0.27, 0.00, -0.30, 0.09, -0.42], ['c', 0.03, -0.09, 0.18, -0.21, 0.30, -0.30], ['c', 0.12, -0.09, 0.30, -0.21, 0.39, -0.27], ['c', 0.09, -0.06, 0.21, -0.18, 0.27, -0.24], ['c', 0.06, -0.12, 0.09, -0.15, 0.09, -0.33], ['c', 0.00, -0.18, -0.03, -0.24, -0.09, -0.36], ['c', -0.24, -0.39, -0.75, -0.60, -1.38, -0.57], ['c', -0.54, 0.03, -0.90, 0.18, -1.23, 0.48], ['c', -0.81, 0.72, -1.08, 2.16, -0.96, 5.37], ['l', 0.00, 0.63], ['l', 0.30, -0.12], ['c', 0.78, -0.27, 1.29, -0.33, 2.10, -0.27], ['c', 1.47, 0.12, 2.49, 0.54, 3.27, 1.29], ['c', 0.48, 0.51, 0.81, 1.11, 0.96, 1.89], ['c', 0.06, 0.27, 0.06, 0.42, 0.06, 0.93], ['c', 0.00, 0.54, 0.00, 0.69, -0.06, 0.96], ['c', -0.15, 0.78, -0.48, 1.38, -0.96, 1.89], ['c', -0.54, 0.51, -1.17, 0.87, -1.98, 1.08], ['c', -1.14, 0.30, -2.40, 0.33, -3.24, 0.03], ['c', -1.50, -0.48, -2.64, -1.89, -3.27, -4.02], ['c', -0.36, -1.23, -0.51, -2.82, -0.42, -4.08], ['c', 0.30, -3.66, 2.28, -6.30, 4.95, -6.66], ['z'], ['m', 0.66, 7.41], ['c', -0.27, -0.09, -0.81, -0.12, -1.08, -0.06], ['c', -0.72, 0.18, -1.08, 0.69, -1.23, 1.71], ['c', -0.06, 0.54, -0.06, 3.00, 0.00, 3.54], ['c', 0.18, 1.26, 0.72, 1.77, 1.80, 1.74], ['c', 0.39, -0.03, 0.63, -0.09, 0.90, -0.27], ['c', 0.66, -0.42, 0.90, -1.32, 0.90, -3.24], ['c', 0.00, -2.22, -0.36, -3.12, -1.29, -3.42], ['z']], w: 9.956, h: 14.982 }, + '7': { d: [['M', 0.21, -14.97], ['c', 0.21, -0.06, 0.45, 0.00, 0.54, 0.15], ['c', 0.06, 0.09, 0.06, 0.15, 0.06, 0.39], ['c', 0.00, 0.24, 0.00, 0.33, 0.06, 0.42], ['c', 0.06, 0.12, 0.21, 0.24, 0.27, 0.24], ['c', 0.03, 0.00, 0.12, -0.12, 0.24, -0.21], ['c', 0.96, -1.20, 2.58, -1.35, 3.99, -0.42], ['c', 0.15, 0.12, 0.42, 0.30, 0.54, 0.45], ['c', 0.48, 0.39, 0.81, 0.57, 1.29, 0.60], ['c', 0.69, 0.03, 1.50, -0.30, 2.13, -0.87], ['c', 0.09, -0.09, 0.27, -0.30, 0.39, -0.45], ['c', 0.12, -0.15, 0.24, -0.27, 0.30, -0.30], ['c', 0.18, -0.06, 0.39, 0.03, 0.51, 0.21], ['c', 0.06, 0.18, 0.06, 0.24, -0.27, 0.72], ['c', -0.18, 0.24, -0.54, 0.78, -0.78, 1.17], ['c', -2.37, 3.54, -3.54, 6.27, -3.87, 9.00], ['c', -0.03, 0.33, -0.03, 0.66, -0.03, 1.26], ['c', 0.00, 0.90, 0.00, 1.08, 0.15, 1.89], ['c', 0.06, 0.45, 0.06, 0.48, 0.03, 0.60], ['c', -0.06, 0.09, -0.21, 0.21, -0.30, 0.21], ['c', -0.03, 0.00, -0.27, -0.06, -0.54, -0.15], ['c', -0.84, -0.27, -1.11, -0.30, -1.65, -0.30], ['c', -0.57, 0.00, -0.84, 0.03, -1.56, 0.27], ['c', -0.60, 0.18, -0.69, 0.21, -0.81, 0.15], ['c', -0.12, -0.06, -0.21, -0.18, -0.21, -0.30], ['c', 0.00, -0.15, 0.60, -1.44, 1.20, -2.61], ['c', 1.14, -2.22, 2.73, -4.68, 5.10, -8.01], ['c', 0.21, -0.27, 0.36, -0.48, 0.33, -0.48], ['c', 0.00, 0.00, -0.12, 0.06, -0.27, 0.12], ['c', -0.54, 0.30, -0.99, 0.39, -1.56, 0.39], ['c', -0.75, 0.03, -1.20, -0.18, -1.83, -0.75], ['c', -0.99, -0.90, -1.83, -1.17, -2.31, -0.72], ['c', -0.18, 0.15, -0.36, 0.51, -0.45, 0.84], ['c', -0.06, 0.24, -0.06, 0.33, -0.09, 1.98], ['c', 0.00, 1.62, -0.03, 1.74, -0.06, 1.80], ['c', -0.15, 0.24, -0.54, 0.24, -0.69, 0.00], ['c', -0.06, -0.09, -0.06, -0.15, -0.06, -3.57], ['c', 0.00, -3.42, 0.00, -3.48, 0.06, -3.57], ['c', 0.03, -0.06, 0.09, -0.12, 0.15, -0.15], ['z']], w: 10.561, h: 15.093 }, + '8': { d: [['M', 4.98, -14.97], ['c', 0.33, -0.03, 1.02, -0.03, 1.32, 0.00], ['c', 1.32, 0.12, 2.49, 0.60, 3.21, 1.32], ['c', 0.39, 0.39, 0.66, 0.81, 0.78, 1.29], ['c', 0.09, 0.36, 0.09, 1.08, 0.00, 1.44], ['c', -0.21, 0.84, -0.66, 1.59, -1.59, 2.55], ['l', -0.30, 0.30], ['l', 0.27, 0.18], ['c', 1.47, 0.93, 2.31, 2.31, 2.25, 3.75], ['c', -0.03, 0.75, -0.24, 1.35, -0.63, 1.95], ['c', -0.45, 0.66, -1.02, 1.14, -1.83, 1.53], ['c', -1.80, 0.87, -4.20, 0.87, -6.00, 0.03], ['c', -1.62, -0.78, -2.52, -2.16, -2.46, -3.66], ['c', 0.06, -0.99, 0.54, -1.77, 1.80, -2.97], ['c', 0.54, -0.51, 0.54, -0.54, 0.48, -0.57], ['c', -0.39, -0.27, -0.96, -0.78, -1.20, -1.14], ['c', -0.75, -1.11, -0.87, -2.40, -0.30, -3.60], ['c', 0.69, -1.35, 2.25, -2.25, 4.20, -2.40], ['z'], ['m', 1.53, 0.69], ['c', -0.42, -0.09, -1.11, -0.12, -1.38, -0.06], ['c', -0.30, 0.06, -0.60, 0.18, -0.81, 0.30], ['c', -0.21, 0.12, -0.60, 0.51, -0.72, 0.72], ['c', -0.51, 0.87, -0.42, 1.89, 0.21, 2.52], ['c', 0.21, 0.21, 0.36, 0.30, 1.95, 1.23], ['c', 0.96, 0.54, 1.74, 0.99, 1.77, 1.02], ['c', 0.09, 0.00, 0.63, -0.60, 0.99, -1.11], ['c', 0.21, -0.36, 0.48, -0.87, 0.57, -1.23], ['c', 0.06, -0.24, 0.06, -0.36, 0.06, -0.72], ['c', 0.00, -0.45, -0.03, -0.66, -0.15, -0.99], ['c', -0.39, -0.81, -1.29, -1.44, -2.49, -1.68], ['z'], ['m', -1.44, 8.07], ['l', -1.89, -1.08], ['c', -0.03, 0.00, -0.18, 0.15, -0.39, 0.33], ['c', -1.20, 1.08, -1.65, 1.95, -1.59, 3.00], ['c', 0.09, 1.59, 1.35, 2.85, 3.21, 3.24], ['c', 0.33, 0.06, 0.45, 0.06, 0.93, 0.06], ['c', 0.63, 0.00, 0.81, -0.03, 1.29, -0.27], ['c', 0.90, -0.42, 1.47, -1.41, 1.41, -2.40], ['c', -0.06, -0.66, -0.39, -1.29, -0.90, -1.65], ['c', -0.12, -0.09, -1.05, -0.63, -2.07, -1.23], ['z']], w: 10.926, h: 14.989 }, + '9': { d: [['M', 4.23, -14.97], ['c', 0.42, -0.03, 1.29, 0.00, 1.62, 0.06], ['c', 0.51, 0.12, 0.93, 0.30, 1.38, 0.57], ['c', 1.53, 1.02, 2.52, 3.24, 2.73, 5.94], ['c', 0.18, 2.55, -0.48, 4.98, -1.83, 6.57], ['c', -1.05, 1.26, -2.40, 1.89, -3.93, 1.83], ['c', -1.23, -0.06, -2.31, -0.45, -3.03, -1.14], ['c', -0.57, -0.51, -0.87, -1.23, -0.84, -1.98], ['c', 0.03, -0.51, 0.21, -0.90, 0.60, -1.26], ['c', 0.24, -0.24, 0.45, -0.39, 0.75, -0.51], ['c', 0.21, -0.06, 0.27, -0.06, 0.60, -0.06], ['c', 0.33, 0.00, 0.39, 0.00, 0.60, 0.06], ['c', 0.30, 0.12, 0.51, 0.27, 0.75, 0.51], ['c', 0.39, 0.36, 0.57, 0.78, 0.57, 1.26], ['c', 0.00, 0.27, 0.00, 0.30, -0.09, 0.42], ['c', -0.03, 0.09, -0.18, 0.21, -0.30, 0.30], ['c', -0.12, 0.09, -0.30, 0.21, -0.39, 0.27], ['c', -0.09, 0.06, -0.21, 0.18, -0.27, 0.24], ['c', -0.06, 0.12, -0.06, 0.15, -0.06, 0.33], ['c', 0.00, 0.18, 0.00, 0.24, 0.06, 0.36], ['c', 0.24, 0.39, 0.75, 0.60, 1.38, 0.57], ['c', 0.54, -0.03, 0.90, -0.18, 1.23, -0.48], ['c', 0.81, -0.72, 1.08, -2.16, 0.96, -5.37], ['l', 0.00, -0.63], ['l', -0.30, 0.12], ['c', -0.78, 0.27, -1.29, 0.33, -2.10, 0.27], ['c', -1.47, -0.12, -2.49, -0.54, -3.27, -1.29], ['c', -0.48, -0.51, -0.81, -1.11, -0.96, -1.89], ['c', -0.06, -0.27, -0.06, -0.42, -0.06, -0.96], ['c', 0.00, -0.51, 0.00, -0.66, 0.06, -0.93], ['c', 0.15, -0.78, 0.48, -1.38, 0.96, -1.89], ['c', 0.15, -0.12, 0.33, -0.27, 0.42, -0.36], ['c', 0.69, -0.51, 1.62, -0.81, 2.76, -0.93], ['z'], ['m', 1.17, 0.66], ['c', -0.21, -0.06, -0.57, -0.06, -0.81, -0.03], ['c', -0.78, 0.12, -1.26, 0.69, -1.41, 1.74], ['c', -0.12, 0.63, -0.15, 1.95, -0.09, 2.79], ['c', 0.12, 1.71, 0.63, 2.40, 1.77, 2.46], ['c', 1.08, 0.03, 1.62, -0.48, 1.80, -1.74], ['c', 0.06, -0.54, 0.06, -3.00, 0.00, -3.54], ['c', -0.15, -1.05, -0.51, -1.53, -1.26, -1.68], ['z']], w: 9.959, h: 14.986 }, + 'rests.multimeasure': { d: [['M', 0, -4], ['l', 0, 16], ['l', 1, 0], ['l', 0, -5], ['l', 40, 0], ['l', 0, 5], ['l', 1, 0], ['l', 0, -16], ['l', -1, 0], ['l', 0, 5], ['l', -40, 0], ['l', 0, -5], ['z']], w: 42, h: 18 }, + 'rests.whole': { d: [['M', 0.06, 0.03], ['l', 0.09, -0.06], ['l', 5.46, 0.00], ['l', 5.49, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 2.19], ['l', 0.00, 2.19], ['l', -0.06, 0.09], ['l', -0.09, 0.06], ['l', -5.49, 0.00], ['l', -5.46, 0.00], ['l', -0.09, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -2.19], ['l', 0.00, -2.19], ['z']], w: 11.25, h: 4.68 }, + 'rests.half': { d: [['M', 0.06, -4.62], ['l', 0.09, -0.06], ['l', 5.46, 0.00], ['l', 5.49, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 2.19], ['l', 0.00, 2.19], ['l', -0.06, 0.09], ['l', -0.09, 0.06], ['l', -5.49, 0.00], ['l', -5.46, 0.00], ['l', -0.09, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -2.19], ['l', 0.00, -2.19], ['z']], w: 11.25, h: 4.68 }, + 'rests.quarter': { d: [['M', 1.89, -11.82], ['c', 0.12, -0.06, 0.24, -0.06, 0.36, -0.03], ['c', 0.09, 0.06, 4.74, 5.58, 4.86, 5.82], ['c', 0.21, 0.39, 0.15, 0.78, -0.15, 1.26], ['c', -0.24, 0.33, -0.72, 0.81, -1.62, 1.56], ['c', -0.45, 0.36, -0.87, 0.75, -0.96, 0.84], ['c', -0.93, 0.99, -1.14, 2.49, -0.60, 3.63], ['c', 0.18, 0.39, 0.27, 0.48, 1.32, 1.68], ['c', 1.92, 2.25, 1.83, 2.16, 1.83, 2.34], ['c', 0.00, 0.18, -0.18, 0.36, -0.36, 0.39], ['c', -0.15, 0.00, -0.27, -0.06, -0.48, -0.27], ['c', -0.75, -0.75, -2.46, -1.29, -3.39, -1.08], ['c', -0.45, 0.09, -0.69, 0.27, -0.90, 0.69], ['c', -0.12, 0.30, -0.21, 0.66, -0.24, 1.14], ['c', -0.03, 0.66, 0.09, 1.35, 0.30, 2.01], ['c', 0.15, 0.42, 0.24, 0.66, 0.45, 0.96], ['c', 0.18, 0.24, 0.18, 0.33, 0.03, 0.42], ['c', -0.12, 0.06, -0.18, 0.03, -0.45, -0.30], ['c', -1.08, -1.38, -2.07, -3.36, -2.40, -4.83], ['c', -0.27, -1.05, -0.15, -1.77, 0.27, -2.07], ['c', 0.21, -0.12, 0.42, -0.15, 0.87, -0.15], ['c', 0.87, 0.06, 2.10, 0.39, 3.30, 0.90], ['l', 0.39, 0.18], ['l', -1.65, -1.95], ['c', -2.52, -2.97, -2.61, -3.09, -2.70, -3.27], ['c', -0.09, -0.24, -0.12, -0.48, -0.03, -0.75], ['c', 0.15, -0.48, 0.57, -0.96, 1.83, -2.01], ['c', 0.45, -0.36, 0.84, -0.72, 0.93, -0.78], ['c', 0.69, -0.75, 1.02, -1.80, 0.90, -2.79], ['c', -0.06, -0.33, -0.21, -0.84, -0.39, -1.11], ['c', -0.09, -0.15, -0.45, -0.60, -0.81, -1.05], ['c', -0.36, -0.42, -0.69, -0.81, -0.72, -0.87], ['c', -0.09, -0.18, 0.00, -0.42, 0.21, -0.51], ['z']], w: 7.888, h: 21.435 }, + 'rests.8th': { d: [['M', 1.68, -6.12], ['c', 0.66, -0.09, 1.23, 0.09, 1.68, 0.51], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.12, 0.27, 0.33, 0.45, 0.60, 0.48], ['c', 0.12, 0.00, 0.18, 0.00, 0.33, -0.09], ['c', 0.39, -0.18, 1.32, -1.29, 1.68, -1.98], ['c', 0.09, -0.21, 0.24, -0.30, 0.39, -0.30], ['c', 0.12, 0.00, 0.27, 0.09, 0.33, 0.18], ['c', 0.03, 0.06, -0.27, 1.11, -1.86, 6.42], ['c', -1.02, 3.48, -1.89, 6.39, -1.92, 6.42], ['c', 0.00, 0.03, -0.12, 0.12, -0.24, 0.15], ['c', -0.18, 0.09, -0.21, 0.09, -0.45, 0.09], ['c', -0.24, 0.00, -0.30, 0.00, -0.48, -0.06], ['c', -0.09, -0.06, -0.21, -0.12, -0.21, -0.15], ['c', -0.06, -0.03, 0.15, -0.57, 1.68, -4.92], ['c', 0.96, -2.67, 1.74, -4.89, 1.71, -4.89], ['l', -0.51, 0.15], ['c', -1.08, 0.36, -1.74, 0.48, -2.55, 0.48], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.33, -0.45, 0.84, -0.81, 1.38, -0.90], ['z']], w: 7.534, h: 13.883 }, + 'rests.16th': { d: [['M', 3.33, -6.12], ['c', 0.66, -0.09, 1.23, 0.09, 1.68, 0.51], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.15, 0.39, 0.57, 0.57, 0.87, 0.42], ['c', 0.39, -0.18, 1.20, -1.23, 1.62, -2.07], ['c', 0.06, -0.15, 0.24, -0.24, 0.36, -0.24], ['c', 0.12, 0.00, 0.27, 0.09, 0.33, 0.18], ['c', 0.03, 0.06, -0.45, 1.86, -2.67, 10.17], ['c', -1.50, 5.55, -2.73, 10.14, -2.76, 10.17], ['c', -0.03, 0.03, -0.12, 0.12, -0.24, 0.15], ['c', -0.18, 0.09, -0.21, 0.09, -0.45, 0.09], ['c', -0.24, 0.00, -0.30, 0.00, -0.48, -0.06], ['c', -0.09, -0.06, -0.21, -0.12, -0.21, -0.15], ['c', -0.06, -0.03, 0.12, -0.57, 1.44, -4.92], ['c', 0.81, -2.67, 1.47, -4.86, 1.47, -4.89], ['c', -0.03, 0.00, -0.27, 0.06, -0.54, 0.15], ['c', -1.08, 0.36, -1.77, 0.48, -2.58, 0.48], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.72, -1.05, 2.22, -1.23, 3.06, -0.42], ['c', 0.30, 0.33, 0.42, 0.60, 0.60, 1.38], ['c', 0.09, 0.45, 0.21, 0.78, 0.33, 0.90], ['c', 0.09, 0.09, 0.27, 0.18, 0.45, 0.21], ['c', 0.12, 0.00, 0.18, 0.00, 0.33, -0.09], ['c', 0.33, -0.15, 1.02, -0.93, 1.41, -1.59], ['c', 0.12, -0.21, 0.18, -0.39, 0.39, -1.08], ['c', 0.66, -2.10, 1.17, -3.84, 1.17, -3.87], ['c', 0.00, 0.00, -0.21, 0.06, -0.42, 0.15], ['c', -0.51, 0.15, -1.20, 0.33, -1.68, 0.42], ['c', -0.33, 0.06, -0.51, 0.06, -0.96, 0.06], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.33, -0.45, 0.84, -0.81, 1.38, -0.90], ['z']], w: 9.724, h: 21.383 }, + 'rests.32nd': { d: [['M', 4.23, -13.62], ['c', 0.66, -0.09, 1.23, 0.09, 1.68, 0.51], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.12, 0.27, 0.33, 0.45, 0.60, 0.48], ['c', 0.12, 0.00, 0.18, 0.00, 0.27, -0.06], ['c', 0.33, -0.21, 0.99, -1.11, 1.44, -1.98], ['c', 0.09, -0.24, 0.21, -0.33, 0.39, -0.33], ['c', 0.12, 0.00, 0.27, 0.09, 0.33, 0.18], ['c', 0.03, 0.06, -0.57, 2.67, -3.21, 13.89], ['c', -1.80, 7.62, -3.30, 13.89, -3.30, 13.92], ['c', -0.03, 0.06, -0.12, 0.12, -0.24, 0.18], ['c', -0.21, 0.09, -0.24, 0.09, -0.48, 0.09], ['c', -0.24, 0.00, -0.30, 0.00, -0.48, -0.06], ['c', -0.09, -0.06, -0.21, -0.12, -0.21, -0.15], ['c', -0.06, -0.03, 0.09, -0.57, 1.23, -4.92], ['c', 0.69, -2.67, 1.26, -4.86, 1.29, -4.89], ['c', 0.00, -0.03, -0.12, -0.03, -0.48, 0.12], ['c', -1.17, 0.39, -2.22, 0.57, -3.00, 0.54], ['c', -0.42, -0.03, -0.75, -0.12, -1.11, -0.30], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.72, -1.05, 2.22, -1.23, 3.06, -0.42], ['c', 0.30, 0.33, 0.42, 0.60, 0.60, 1.38], ['c', 0.09, 0.45, 0.21, 0.78, 0.33, 0.90], ['c', 0.12, 0.09, 0.30, 0.18, 0.48, 0.21], ['c', 0.12, 0.00, 0.18, 0.00, 0.30, -0.09], ['c', 0.42, -0.21, 1.29, -1.29, 1.56, -1.89], ['c', 0.03, -0.12, 1.23, -4.59, 1.23, -4.65], ['c', 0.00, -0.03, -0.18, 0.03, -0.39, 0.12], ['c', -0.63, 0.18, -1.20, 0.36, -1.74, 0.45], ['c', -0.39, 0.06, -0.54, 0.06, -1.02, 0.06], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.72, -1.05, 2.22, -1.23, 3.06, -0.42], ['c', 0.30, 0.33, 0.42, 0.60, 0.60, 1.38], ['c', 0.09, 0.45, 0.21, 0.78, 0.33, 0.90], ['c', 0.18, 0.18, 0.51, 0.27, 0.72, 0.15], ['c', 0.30, -0.12, 0.69, -0.57, 1.08, -1.17], ['c', 0.42, -0.60, 0.39, -0.51, 1.05, -3.03], ['c', 0.33, -1.26, 0.60, -2.31, 0.60, -2.34], ['c', 0.00, 0.00, -0.21, 0.03, -0.45, 0.12], ['c', -0.57, 0.18, -1.14, 0.33, -1.62, 0.42], ['c', -0.33, 0.06, -0.51, 0.06, -0.96, 0.06], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.33, -0.45, 0.84, -0.81, 1.38, -0.90], ['z']], w: 11.373, h: 28.883 }, + 'rests.64th': { d: [['M', 5.13, -13.62], ['c', 0.66, -0.09, 1.23, 0.09, 1.68, 0.51], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.15, 0.63, 0.21, 0.81, 0.33, 0.96], ['c', 0.18, 0.21, 0.54, 0.30, 0.75, 0.18], ['c', 0.24, -0.12, 0.63, -0.66, 1.08, -1.56], ['c', 0.33, -0.66, 0.39, -0.72, 0.60, -0.72], ['c', 0.12, 0.00, 0.27, 0.09, 0.33, 0.18], ['c', 0.03, 0.06, -0.69, 3.66, -3.54, 17.64], ['c', -1.95, 9.66, -3.57, 17.61, -3.57, 17.64], ['c', -0.03, 0.06, -0.12, 0.12, -0.24, 0.18], ['c', -0.21, 0.09, -0.24, 0.09, -0.48, 0.09], ['c', -0.24, 0.00, -0.30, 0.00, -0.48, -0.06], ['c', -0.09, -0.06, -0.21, -0.12, -0.21, -0.15], ['c', -0.06, -0.03, 0.06, -0.57, 1.05, -4.95], ['c', 0.60, -2.70, 1.08, -4.89, 1.08, -4.92], ['c', 0.00, 0.00, -0.24, 0.06, -0.51, 0.15], ['c', -0.66, 0.24, -1.20, 0.36, -1.77, 0.48], ['c', -0.42, 0.06, -0.57, 0.06, -1.05, 0.06], ['c', -0.69, 0.00, -0.87, -0.03, -1.35, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.72, -1.05, 2.22, -1.23, 3.06, -0.42], ['c', 0.30, 0.33, 0.42, 0.60, 0.60, 1.38], ['c', 0.09, 0.45, 0.21, 0.78, 0.33, 0.90], ['c', 0.09, 0.09, 0.27, 0.18, 0.45, 0.21], ['c', 0.21, 0.03, 0.39, -0.09, 0.72, -0.42], ['c', 0.45, -0.45, 1.02, -1.26, 1.17, -1.65], ['c', 0.03, -0.09, 0.27, -1.14, 0.54, -2.34], ['c', 0.27, -1.20, 0.48, -2.19, 0.51, -2.22], ['c', 0.00, -0.03, -0.09, -0.03, -0.48, 0.12], ['c', -1.17, 0.39, -2.22, 0.57, -3.00, 0.54], ['c', -0.42, -0.03, -0.75, -0.12, -1.11, -0.30], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.15, 0.39, 0.57, 0.57, 0.90, 0.42], ['c', 0.36, -0.18, 1.20, -1.26, 1.47, -1.89], ['c', 0.03, -0.09, 0.30, -1.20, 0.57, -2.43], ['l', 0.51, -2.28], ['l', -0.54, 0.18], ['c', -1.11, 0.36, -1.80, 0.48, -2.61, 0.48], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.15, 0.63, 0.21, 0.81, 0.33, 0.96], ['c', 0.21, 0.21, 0.54, 0.30, 0.75, 0.18], ['c', 0.36, -0.18, 0.93, -0.93, 1.29, -1.68], ['c', 0.12, -0.24, 0.18, -0.48, 0.63, -2.55], ['l', 0.51, -2.31], ['c', 0.00, -0.03, -0.18, 0.03, -0.39, 0.12], ['c', -1.14, 0.36, -2.10, 0.54, -2.82, 0.51], ['c', -0.42, -0.03, -0.75, -0.12, -1.11, -0.30], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.33, -0.45, 0.84, -0.81, 1.38, -0.90], ['z']], w: 12.453, h: 36.383 }, + 'rests.128th': { d: [['M', 6.03, -21.12], ['c', 0.66, -0.09, 1.23, 0.09, 1.68, 0.51], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.12, 0.27, 0.33, 0.45, 0.60, 0.48], ['c', 0.21, 0.00, 0.33, -0.06, 0.54, -0.36], ['c', 0.15, -0.21, 0.54, -0.93, 0.78, -1.47], ['c', 0.15, -0.33, 0.18, -0.39, 0.30, -0.48], ['c', 0.18, -0.09, 0.45, 0.00, 0.51, 0.15], ['c', 0.03, 0.09, -7.11, 42.75, -7.17, 42.84], ['c', -0.03, 0.03, -0.15, 0.09, -0.24, 0.15], ['c', -0.18, 0.06, -0.24, 0.06, -0.45, 0.06], ['c', -0.24, 0.00, -0.30, 0.00, -0.48, -0.06], ['c', -0.09, -0.06, -0.21, -0.12, -0.21, -0.15], ['c', -0.06, -0.03, 0.03, -0.57, 0.84, -4.98], ['c', 0.51, -2.70, 0.93, -4.92, 0.90, -4.92], ['c', 0.00, 0.00, -0.15, 0.06, -0.36, 0.12], ['c', -0.78, 0.27, -1.62, 0.48, -2.31, 0.57], ['c', -0.15, 0.03, -0.54, 0.03, -0.81, 0.03], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.12, 0.27, 0.33, 0.45, 0.63, 0.48], ['c', 0.12, 0.00, 0.18, 0.00, 0.30, -0.09], ['c', 0.42, -0.21, 1.14, -1.11, 1.50, -1.83], ['c', 0.12, -0.27, 0.12, -0.27, 0.54, -2.52], ['c', 0.24, -1.23, 0.42, -2.25, 0.39, -2.25], ['c', 0.00, 0.00, -0.24, 0.06, -0.51, 0.18], ['c', -1.26, 0.39, -2.25, 0.57, -3.06, 0.54], ['c', -0.42, -0.03, -0.75, -0.12, -1.11, -0.30], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.15, 0.63, 0.21, 0.81, 0.33, 0.96], ['c', 0.18, 0.21, 0.51, 0.30, 0.75, 0.18], ['c', 0.36, -0.15, 1.05, -0.99, 1.41, -1.77], ['l', 0.15, -0.30], ['l', 0.42, -2.25], ['c', 0.21, -1.26, 0.42, -2.28, 0.39, -2.28], ['l', -0.51, 0.15], ['c', -1.11, 0.39, -1.89, 0.51, -2.70, 0.51], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.15, 0.63, 0.21, 0.81, 0.33, 0.96], ['c', 0.18, 0.18, 0.48, 0.27, 0.72, 0.21], ['c', 0.33, -0.12, 1.14, -1.26, 1.41, -1.95], ['c', 0.00, -0.09, 0.21, -1.11, 0.45, -2.34], ['c', 0.21, -1.20, 0.39, -2.22, 0.39, -2.28], ['c', 0.03, -0.03, 0.00, -0.03, -0.45, 0.12], ['c', -0.57, 0.18, -1.20, 0.33, -1.71, 0.42], ['c', -0.30, 0.06, -0.51, 0.06, -0.93, 0.06], ['c', -0.66, 0.00, -0.84, -0.03, -1.32, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.36, -0.54, 0.96, -0.87, 1.65, -0.93], ['c', 0.54, -0.03, 1.02, 0.15, 1.41, 0.54], ['c', 0.27, 0.30, 0.39, 0.54, 0.57, 1.26], ['c', 0.09, 0.33, 0.18, 0.66, 0.21, 0.72], ['c', 0.12, 0.27, 0.33, 0.45, 0.60, 0.48], ['c', 0.18, 0.00, 0.36, -0.09, 0.57, -0.33], ['c', 0.33, -0.36, 0.78, -1.14, 0.93, -1.56], ['c', 0.03, -0.12, 0.24, -1.20, 0.45, -2.40], ['c', 0.24, -1.20, 0.42, -2.22, 0.42, -2.28], ['c', 0.03, -0.03, 0.00, -0.03, -0.39, 0.09], ['c', -1.05, 0.36, -1.80, 0.48, -2.58, 0.48], ['c', -0.63, 0.00, -0.84, -0.03, -1.29, -0.27], ['c', -1.32, -0.63, -1.77, -2.16, -1.02, -3.30], ['c', 0.33, -0.45, 0.84, -0.81, 1.38, -0.90], ['z']], w: 12.992, h: 43.883 }, + 'accidentals.sharp': { d: [['M', 5.73, -11.19], ['c', 0.21, -0.12, 0.54, -0.03, 0.66, 0.24], ['c', 0.06, 0.12, 0.06, 0.21, 0.06, 2.31], ['c', 0.00, 1.23, 0.00, 2.22, 0.03, 2.22], ['c', 0.00, 0.00, 0.27, -0.12, 0.60, -0.24], ['c', 0.69, -0.27, 0.78, -0.30, 0.96, -0.15], ['c', 0.21, 0.15, 0.21, 0.18, 0.21, 1.38], ['c', 0.00, 1.02, 0.00, 1.11, -0.06, 1.20], ['c', -0.03, 0.06, -0.09, 0.12, -0.12, 0.15], ['c', -0.06, 0.03, -0.42, 0.21, -0.84, 0.36], ['l', -0.75, 0.33], ['l', -0.03, 2.43], ['c', 0.00, 1.32, 0.00, 2.43, 0.03, 2.43], ['c', 0.00, 0.00, 0.27, -0.12, 0.60, -0.24], ['c', 0.69, -0.27, 0.78, -0.30, 0.96, -0.15], ['c', 0.21, 0.15, 0.21, 0.18, 0.21, 1.38], ['c', 0.00, 1.02, 0.00, 1.11, -0.06, 1.20], ['c', -0.03, 0.06, -0.09, 0.12, -0.12, 0.15], ['c', -0.06, 0.03, -0.42, 0.21, -0.84, 0.36], ['l', -0.75, 0.33], ['l', -0.03, 2.52], ['c', 0.00, 2.28, -0.03, 2.55, -0.06, 2.64], ['c', -0.21, 0.36, -0.72, 0.36, -0.93, 0.00], ['c', -0.03, -0.09, -0.06, -0.33, -0.06, -2.43], ['l', 0.00, -2.31], ['l', -1.29, 0.51], ['l', -1.26, 0.51], ['l', 0.00, 2.43], ['c', 0.00, 2.58, 0.00, 2.52, -0.15, 2.67], ['c', -0.06, 0.09, -0.27, 0.18, -0.36, 0.18], ['c', -0.12, 0.00, -0.33, -0.09, -0.39, -0.18], ['c', -0.15, -0.15, -0.15, -0.09, -0.15, -2.43], ['c', 0.00, -1.23, 0.00, -2.22, -0.03, -2.22], ['c', 0.00, 0.00, -0.27, 0.12, -0.60, 0.24], ['c', -0.69, 0.27, -0.78, 0.30, -0.96, 0.15], ['c', -0.21, -0.15, -0.21, -0.18, -0.21, -1.38], ['c', 0.00, -1.02, 0.00, -1.11, 0.06, -1.20], ['c', 0.03, -0.06, 0.09, -0.12, 0.12, -0.15], ['c', 0.06, -0.03, 0.42, -0.21, 0.84, -0.36], ['l', 0.78, -0.33], ['l', 0.00, -2.43], ['c', 0.00, -1.32, 0.00, -2.43, -0.03, -2.43], ['c', 0.00, 0.00, -0.27, 0.12, -0.60, 0.24], ['c', -0.69, 0.27, -0.78, 0.30, -0.96, 0.15], ['c', -0.21, -0.15, -0.21, -0.18, -0.21, -1.38], ['c', 0.00, -1.02, 0.00, -1.11, 0.06, -1.20], ['c', 0.03, -0.06, 0.09, -0.12, 0.12, -0.15], ['c', 0.06, -0.03, 0.42, -0.21, 0.84, -0.36], ['l', 0.78, -0.33], ['l', 0.00, -2.52], ['c', 0.00, -2.28, 0.03, -2.55, 0.06, -2.64], ['c', 0.21, -0.36, 0.72, -0.36, 0.93, 0.00], ['c', 0.03, 0.09, 0.06, 0.33, 0.06, 2.43], ['l', 0.03, 2.31], ['l', 1.26, -0.51], ['l', 1.26, -0.51], ['l', 0.00, -2.43], ['c', 0.00, -2.28, 0.00, -2.43, 0.06, -2.55], ['c', 0.06, -0.12, 0.12, -0.18, 0.27, -0.24], ['z'], ['m', -0.33, 10.65], ['l', 0.00, -2.43], ['l', -1.29, 0.51], ['l', -1.26, 0.51], ['l', 0.00, 2.46], ['l', 0.00, 2.43], ['l', 0.09, -0.03], ['c', 0.06, -0.03, 0.63, -0.27, 1.29, -0.51], ['l', 1.17, -0.48], ['l', 0.00, -2.46], ['z']], w: 8.25, h: 22.462 }, + 'accidentals.halfsharp': { d: [['M', 2.43, -10.05], ['c', 0.21, -0.12, 0.54, -0.03, 0.66, 0.24], ['c', 0.06, 0.12, 0.06, 0.21, 0.06, 2.01], ['c', 0.00, 1.05, 0.00, 1.89, 0.03, 1.89], ['l', 0.72, -0.48], ['c', 0.69, -0.48, 0.69, -0.51, 0.87, -0.51], ['c', 0.15, 0.00, 0.18, 0.03, 0.27, 0.09], ['c', 0.21, 0.15, 0.21, 0.18, 0.21, 1.41], ['c', 0.00, 1.11, -0.03, 1.14, -0.09, 1.23], ['c', -0.03, 0.03, -0.48, 0.39, -1.02, 0.75], ['l', -0.99, 0.66], ['l', 0.00, 2.37], ['c', 0.00, 1.32, 0.00, 2.37, 0.03, 2.37], ['l', 0.72, -0.48], ['c', 0.69, -0.48, 0.69, -0.51, 0.87, -0.51], ['c', 0.15, 0.00, 0.18, 0.03, 0.27, 0.09], ['c', 0.21, 0.15, 0.21, 0.18, 0.21, 1.41], ['c', 0.00, 1.11, -0.03, 1.14, -0.09, 1.23], ['c', -0.03, 0.03, -0.48, 0.39, -1.02, 0.75], ['l', -0.99, 0.66], ['l', 0.00, 2.25], ['c', 0.00, 1.95, 0.00, 2.28, -0.06, 2.37], ['c', -0.06, 0.12, -0.12, 0.21, -0.24, 0.27], ['c', -0.27, 0.12, -0.54, 0.03, -0.69, -0.24], ['c', -0.06, -0.12, -0.06, -0.21, -0.06, -2.01], ['c', 0.00, -1.05, 0.00, -1.89, -0.03, -1.89], ['l', -0.72, 0.48], ['c', -0.69, 0.48, -0.69, 0.48, -0.87, 0.48], ['c', -0.15, 0.00, -0.18, 0.00, -0.27, -0.06], ['c', -0.21, -0.15, -0.21, -0.18, -0.21, -1.41], ['c', 0.00, -1.11, 0.03, -1.14, 0.09, -1.23], ['c', 0.03, -0.03, 0.48, -0.39, 1.02, -0.75], ['l', 0.99, -0.66], ['l', 0.00, -2.37], ['c', 0.00, -1.32, 0.00, -2.37, -0.03, -2.37], ['l', -0.72, 0.48], ['c', -0.69, 0.48, -0.69, 0.48, -0.87, 0.48], ['c', -0.15, 0.00, -0.18, 0.00, -0.27, -0.06], ['c', -0.21, -0.15, -0.21, -0.18, -0.21, -1.41], ['c', 0.00, -1.11, 0.03, -1.14, 0.09, -1.23], ['c', 0.03, -0.03, 0.48, -0.39, 1.02, -0.75], ['l', 0.99, -0.66], ['l', 0.00, -2.25], ['c', 0.00, -2.13, 0.00, -2.28, 0.06, -2.40], ['c', 0.06, -0.12, 0.12, -0.18, 0.27, -0.24], ['z']], w: 5.25, h: 20.174 }, + 'accidentals.nat': { d: [['M', 0.21, -11.40], ['c', 0.24, -0.06, 0.78, 0.00, 0.99, 0.15], ['c', 0.03, 0.03, 0.03, 0.48, 0.00, 2.61], ['c', -0.03, 1.44, -0.03, 2.61, -0.03, 2.61], ['c', 0.00, 0.03, 0.75, -0.09, 1.68, -0.24], ['c', 0.96, -0.18, 1.71, -0.27, 1.74, -0.27], ['c', 0.15, 0.03, 0.27, 0.15, 0.36, 0.30], ['l', 0.06, 0.12], ['l', 0.09, 8.67], ['c', 0.09, 6.96, 0.12, 8.67, 0.09, 8.67], ['c', -0.03, 0.03, -0.12, 0.06, -0.21, 0.09], ['c', -0.24, 0.09, -0.72, 0.09, -0.96, 0.00], ['c', -0.09, -0.03, -0.18, -0.06, -0.21, -0.09], ['c', -0.03, -0.03, -0.03, -0.48, 0.00, -2.61], ['c', 0.03, -1.44, 0.03, -2.61, 0.03, -2.61], ['c', 0.00, -0.03, -0.75, 0.09, -1.68, 0.24], ['c', -0.96, 0.18, -1.71, 0.27, -1.74, 0.27], ['c', -0.15, -0.03, -0.27, -0.15, -0.36, -0.30], ['l', -0.06, -0.15], ['l', -0.09, -7.53], ['c', -0.06, -4.14, -0.09, -8.04, -0.12, -8.67], ['l', 0.00, -1.11], ['l', 0.15, -0.06], ['c', 0.09, -0.03, 0.21, -0.06, 0.27, -0.09], ['z'], ['m', 3.75, 8.40], ['c', 0.00, -0.33, 0.00, -0.42, -0.03, -0.42], ['c', -0.12, 0.00, -2.79, 0.45, -2.79, 0.48], ['c', -0.03, 0.00, -0.09, 6.30, -0.09, 6.33], ['c', 0.03, 0.00, 2.79, -0.45, 2.82, -0.48], ['c', 0.00, 0.00, 0.09, -4.53, 0.09, -5.91], ['z']], w: 5.4, h: 22.8 }, + 'accidentals.flat': { d: [['M', -0.36, -14.07], ['c', 0.33, -0.06, 0.87, 0.00, 1.08, 0.15], ['c', 0.06, 0.03, 0.06, 0.36, -0.03, 5.25], ['c', -0.06, 2.85, -0.09, 5.19, -0.09, 5.19], ['c', 0.00, 0.03, 0.12, -0.03, 0.24, -0.12], ['c', 0.63, -0.42, 1.41, -0.66, 2.19, -0.72], ['c', 0.81, -0.03, 1.47, 0.21, 2.04, 0.78], ['c', 0.57, 0.54, 0.87, 1.26, 0.93, 2.04], ['c', 0.03, 0.57, -0.09, 1.08, -0.36, 1.62], ['c', -0.42, 0.81, -1.02, 1.38, -2.82, 2.61], ['c', -1.14, 0.78, -1.44, 1.02, -1.80, 1.44], ['c', -0.18, 0.18, -0.39, 0.39, -0.45, 0.42], ['c', -0.27, 0.18, -0.57, 0.15, -0.81, -0.06], ['c', -0.06, -0.09, -0.12, -0.18, -0.15, -0.27], ['c', -0.03, -0.06, -0.09, -3.27, -0.18, -8.34], ['c', -0.09, -4.53, -0.15, -8.58, -0.18, -9.03], ['l', 0.00, -0.78], ['l', 0.12, -0.06], ['c', 0.06, -0.03, 0.18, -0.09, 0.27, -0.12], ['z'], ['m', 3.18, 11.01], ['c', -0.21, -0.12, -0.54, -0.15, -0.81, -0.06], ['c', -0.54, 0.15, -0.99, 0.63, -1.17, 1.26], ['c', -0.06, 0.30, -0.12, 2.88, -0.06, 3.87], ['c', 0.03, 0.42, 0.03, 0.81, 0.06, 0.90], ['l', 0.03, 0.12], ['l', 0.45, -0.39], ['c', 0.63, -0.54, 1.26, -1.17, 1.56, -1.59], ['c', 0.30, -0.42, 0.60, -0.99, 0.72, -1.41], ['c', 0.18, -0.69, 0.09, -1.47, -0.18, -2.07], ['c', -0.15, -0.30, -0.33, -0.51, -0.60, -0.63], ['z']], w: 6.75, h: 18.801 }, + 'accidentals.halfflat': { d: [['M', 4.83, -14.07], ['c', 0.33, -0.06, 0.87, 0.00, 1.08, 0.15], ['c', 0.06, 0.03, 0.06, 0.60, -0.12, 9.06], ['c', -0.09, 5.55, -0.15, 9.06, -0.18, 9.12], ['c', -0.03, 0.09, -0.09, 0.18, -0.15, 0.27], ['c', -0.24, 0.21, -0.54, 0.24, -0.81, 0.06], ['c', -0.06, -0.03, -0.27, -0.24, -0.45, -0.42], ['c', -0.36, -0.42, -0.66, -0.66, -1.80, -1.44], ['c', -1.23, -0.84, -1.83, -1.32, -2.25, -1.77], ['c', -0.66, -0.78, -0.96, -1.56, -0.93, -2.46], ['c', 0.09, -1.41, 1.11, -2.58, 2.40, -2.79], ['c', 0.30, -0.06, 0.84, -0.03, 1.23, 0.06], ['c', 0.54, 0.12, 1.08, 0.33, 1.53, 0.63], ['c', 0.12, 0.09, 0.24, 0.15, 0.24, 0.12], ['c', 0.00, 0.00, -0.12, -8.37, -0.18, -9.75], ['l', 0.00, -0.66], ['l', 0.12, -0.06], ['c', 0.06, -0.03, 0.18, -0.09, 0.27, -0.12], ['z'], ['m', -1.65, 10.95], ['c', -0.60, -0.18, -1.08, 0.09, -1.38, 0.69], ['c', -0.27, 0.60, -0.36, 1.38, -0.18, 2.07], ['c', 0.12, 0.42, 0.42, 0.99, 0.72, 1.41], ['c', 0.30, 0.42, 0.93, 1.05, 1.56, 1.59], ['l', 0.48, 0.39], ['l', 0.00, -0.12], ['c', 0.03, -0.09, 0.03, -0.48, 0.06, -0.90], ['c', 0.03, -0.57, 0.03, -1.08, 0.00, -2.22], ['c', -0.03, -1.62, -0.03, -1.62, -0.24, -2.07], ['c', -0.21, -0.42, -0.60, -0.75, -1.02, -0.84], ['z']], w: 6.728, h: 18.801 }, + 'accidentals.dblflat': { d: [['M', -0.36, -14.07], ['c', 0.33, -0.06, 0.87, 0.00, 1.08, 0.15], ['c', 0.06, 0.03, 0.06, 0.36, -0.03, 5.25], ['c', -0.06, 2.85, -0.09, 5.19, -0.09, 5.19], ['c', 0.00, 0.03, 0.12, -0.03, 0.24, -0.12], ['c', 0.63, -0.42, 1.41, -0.66, 2.19, -0.72], ['c', 0.81, -0.03, 1.47, 0.21, 2.04, 0.78], ['c', 0.57, 0.54, 0.87, 1.26, 0.93, 2.04], ['c', 0.03, 0.57, -0.09, 1.08, -0.36, 1.62], ['c', -0.42, 0.81, -1.02, 1.38, -2.82, 2.61], ['c', -1.14, 0.78, -1.44, 1.02, -1.80, 1.44], ['c', -0.18, 0.18, -0.39, 0.39, -0.45, 0.42], ['c', -0.27, 0.18, -0.57, 0.15, -0.81, -0.06], ['c', -0.06, -0.09, -0.12, -0.18, -0.15, -0.27], ['c', -0.03, -0.06, -0.09, -3.27, -0.18, -8.34], ['c', -0.09, -4.53, -0.15, -8.58, -0.18, -9.03], ['l', 0.00, -0.78], ['l', 0.12, -0.06], ['c', 0.06, -0.03, 0.18, -0.09, 0.27, -0.12], ['z'], ['m', 3.18, 11.01], ['c', -0.21, -0.12, -0.54, -0.15, -0.81, -0.06], ['c', -0.54, 0.15, -0.99, 0.63, -1.17, 1.26], ['c', -0.06, 0.30, -0.12, 2.88, -0.06, 3.87], ['c', 0.03, 0.42, 0.03, 0.81, 0.06, 0.90], ['l', 0.03, 0.12], ['l', 0.45, -0.39], ['c', 0.63, -0.54, 1.26, -1.17, 1.56, -1.59], ['c', 0.30, -0.42, 0.60, -0.99, 0.72, -1.41], ['c', 0.18, -0.69, 0.09, -1.47, -0.18, -2.07], ['c', -0.15, -0.30, -0.33, -0.51, -0.60, -0.63], ['z'], ['m', 3, -11], ['c', 0.33, -0.06, 0.87, 0.00, 1.08, 0.15], ['c', 0.06, 0.03, 0.06, 0.36, -0.03, 5.25], ['c', -0.06, 2.85, -0.09, 5.19, -0.09, 5.19], ['c', 0.00, 0.03, 0.12, -0.03, 0.24, -0.12], ['c', 0.63, -0.42, 1.41, -0.66, 2.19, -0.72], ['c', 0.81, -0.03, 1.47, 0.21, 2.04, 0.78], ['c', 0.57, 0.54, 0.87, 1.26, 0.93, 2.04], ['c', 0.03, 0.57, -0.09, 1.08, -0.36, 1.62], ['c', -0.42, 0.81, -1.02, 1.38, -2.82, 2.61], ['c', -1.14, 0.78, -1.44, 1.02, -1.80, 1.44], ['c', -0.18, 0.18, -0.39, 0.39, -0.45, 0.42], ['c', -0.27, 0.18, -0.57, 0.15, -0.81, -0.06], ['c', -0.06, -0.09, -0.12, -0.18, -0.15, -0.27], ['c', -0.03, -0.06, -0.09, -3.27, -0.18, -8.34], ['c', -0.09, -4.53, -0.15, -8.58, -0.18, -9.03], ['l', 0.00, -0.78], ['l', 0.12, -0.06], ['c', 0.06, -0.03, 0.18, -0.09, 0.27, -0.12], ['z'], ['m', 3.18, 11.01], ['c', -0.21, -0.12, -0.54, -0.15, -0.81, -0.06], ['c', -0.54, 0.15, -0.99, 0.63, -1.17, 1.26], ['c', -0.06, 0.30, -0.12, 2.88, -0.06, 3.87], ['c', 0.03, 0.42, 0.03, 0.81, 0.06, 0.90], ['l', 0.03, 0.12], ['l', 0.45, -0.39], ['c', 0.63, -0.54, 1.26, -1.17, 1.56, -1.59], ['c', 0.30, -0.42, 0.60, -0.99, 0.72, -1.41], ['c', 0.18, -0.69, 0.09, -1.47, -0.18, -2.07], ['c', -0.15, -0.30, -0.33, -0.51, -0.60, -0.63], ['z']], w: 12.1, h: 18.804 }, + 'accidentals.dblsharp': { d: [['M', -0.18, -3.96], ['c', 0.06, -0.03, 0.12, -0.06, 0.15, -0.06], ['c', 0.09, 0.00, 2.76, 0.27, 2.79, 0.30], ['c', 0.12, 0.03, 0.15, 0.12, 0.15, 0.51], ['c', 0.06, 0.96, 0.24, 1.59, 0.57, 2.10], ['c', 0.06, 0.09, 0.15, 0.21, 0.18, 0.24], ['l', 0.09, 0.06], ['l', 0.09, -0.06], ['c', 0.03, -0.03, 0.12, -0.15, 0.18, -0.24], ['c', 0.33, -0.51, 0.51, -1.14, 0.57, -2.10], ['c', 0.00, -0.39, 0.03, -0.45, 0.12, -0.51], ['c', 0.03, 0.00, 0.66, -0.09, 1.44, -0.15], ['c', 1.47, -0.15, 1.50, -0.15, 1.56, -0.03], ['c', 0.03, 0.06, 0.00, 0.42, -0.09, 1.44], ['c', -0.09, 0.72, -0.15, 1.35, -0.15, 1.38], ['c', 0.00, 0.03, -0.03, 0.09, -0.06, 0.12], ['c', -0.06, 0.06, -0.12, 0.09, -0.51, 0.09], ['c', -1.08, 0.06, -1.80, 0.30, -2.28, 0.75], ['l', -0.12, 0.09], ['l', 0.09, 0.09], ['c', 0.12, 0.15, 0.39, 0.33, 0.63, 0.45], ['c', 0.42, 0.18, 0.96, 0.27, 1.68, 0.33], ['c', 0.39, 0.00, 0.45, 0.03, 0.51, 0.09], ['c', 0.03, 0.03, 0.06, 0.09, 0.06, 0.12], ['c', 0.00, 0.03, 0.06, 0.66, 0.15, 1.38], ['c', 0.09, 1.02, 0.12, 1.38, 0.09, 1.44], ['c', -0.06, 0.12, -0.09, 0.12, -1.56, -0.03], ['c', -0.78, -0.06, -1.41, -0.15, -1.44, -0.15], ['c', -0.09, -0.06, -0.12, -0.12, -0.12, -0.54], ['c', -0.06, -0.93, -0.24, -1.56, -0.57, -2.07], ['c', -0.06, -0.09, -0.15, -0.21, -0.18, -0.24], ['l', -0.09, -0.06], ['l', -0.09, 0.06], ['c', -0.03, 0.03, -0.12, 0.15, -0.18, 0.24], ['c', -0.33, 0.51, -0.51, 1.14, -0.57, 2.07], ['c', 0.00, 0.42, -0.03, 0.48, -0.12, 0.54], ['c', -0.03, 0.00, -0.66, 0.09, -1.44, 0.15], ['c', -1.47, 0.15, -1.50, 0.15, -1.56, 0.03], ['c', -0.03, -0.06, 0.00, -0.42, 0.09, -1.44], ['c', 0.09, -0.72, 0.15, -1.35, 0.15, -1.38], ['c', 0.00, -0.03, 0.03, -0.09, 0.06, -0.12], ['c', 0.06, -0.06, 0.12, -0.09, 0.51, -0.09], ['c', 0.72, -0.06, 1.26, -0.15, 1.68, -0.33], ['c', 0.24, -0.12, 0.51, -0.30, 0.63, -0.45], ['l', 0.09, -0.09], ['l', -0.12, -0.09], ['c', -0.48, -0.45, -1.20, -0.69, -2.28, -0.75], ['c', -0.39, 0.00, -0.45, -0.03, -0.51, -0.09], ['c', -0.03, -0.03, -0.06, -0.09, -0.06, -0.12], ['c', 0.00, -0.03, -0.06, -0.63, -0.12, -1.38], ['c', -0.09, -0.72, -0.15, -1.35, -0.15, -1.38], ['z']], w: 7.95, h: 7.977 }, + 'dots.dot': { d: [['M', 1.32, -1.68], ['c', 0.09, -0.03, 0.27, -0.06, 0.39, -0.06], ['c', 0.96, 0.00, 1.74, 0.78, 1.74, 1.71], ['c', 0.00, 0.96, -0.78, 1.74, -1.71, 1.74], ['c', -0.96, 0.00, -1.74, -0.78, -1.74, -1.71], ['c', 0.00, -0.78, 0.54, -1.50, 1.32, -1.68], ['z']], w: 3.45, h: 3.45 }, + 'noteheads.dbl': { d: [['M', -0.69, -4.02], ['c', 0.18, -0.09, 0.36, -0.09, 0.54, 0.00], ['c', 0.18, 0.09, 0.24, 0.15, 0.33, 0.30], ['c', 0.06, 0.15, 0.06, 0.18, 0.06, 1.41], ['l', 0.00, 1.23], ['l', 0.12, -0.18], ['c', 0.72, -1.26, 2.64, -2.31, 4.86, -2.64], ['c', 0.81, -0.15, 1.11, -0.15, 2.13, -0.15], ['c', 0.99, 0.00, 1.29, 0.00, 2.10, 0.15], ['c', 0.75, 0.12, 1.38, 0.27, 2.04, 0.54], ['c', 1.35, 0.51, 2.34, 1.26, 2.82, 2.10], ['l', 0.12, 0.18], ['l', 0.00, -1.23], ['c', 0.00, -1.20, 0.00, -1.26, 0.06, -1.38], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['c', 0.18, -0.09, 0.36, -0.09, 0.54, 0.00], ['c', 0.18, 0.09, 0.24, 0.15, 0.33, 0.30], ['l', 0.06, 0.15], ['l', 0.00, 3.54], ['l', 0.00, 3.54], ['l', -0.06, 0.15], ['c', -0.09, 0.18, -0.15, 0.24, -0.33, 0.33], ['c', -0.18, 0.09, -0.36, 0.09, -0.54, 0.00], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['c', -0.06, -0.12, -0.06, -0.18, -0.06, -1.38], ['l', 0.00, -1.23], ['l', -0.12, 0.18], ['c', -0.48, 0.84, -1.47, 1.59, -2.82, 2.10], ['c', -0.84, 0.33, -1.71, 0.54, -2.85, 0.66], ['c', -0.45, 0.06, -2.16, 0.06, -2.61, 0.00], ['c', -1.14, -0.12, -2.01, -0.33, -2.85, -0.66], ['c', -1.35, -0.51, -2.34, -1.26, -2.82, -2.10], ['l', -0.12, -0.18], ['l', 0.00, 1.23], ['c', 0.00, 1.23, 0.00, 1.26, -0.06, 1.38], ['c', -0.09, 0.18, -0.15, 0.24, -0.33, 0.33], ['c', -0.18, 0.09, -0.36, 0.09, -0.54, 0.00], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['l', -0.06, -0.15], ['l', 0.00, -3.54], ['c', 0.00, -3.48, 0.00, -3.54, 0.06, -3.66], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['z'], ['m', 7.71, 0.63], ['c', -0.36, -0.06, -0.90, -0.06, -1.14, 0.00], ['c', -0.30, 0.03, -0.66, 0.24, -0.87, 0.42], ['c', -0.60, 0.54, -0.90, 1.62, -0.75, 2.82], ['c', 0.12, 0.93, 0.51, 1.68, 1.11, 2.31], ['c', 0.75, 0.72, 1.83, 1.20, 2.85, 1.26], ['c', 1.05, 0.06, 1.83, -0.54, 2.10, -1.65], ['c', 0.21, -0.90, 0.12, -1.95, -0.24, -2.82], ['c', -0.36, -0.81, -1.08, -1.53, -1.95, -1.95], ['c', -0.30, -0.15, -0.78, -0.30, -1.11, -0.39], ['z']], w: 16.83, h: 8.145 }, + 'noteheads.whole': { d: [['M', 6.51, -4.05], ['c', 0.51, -0.03, 2.01, 0.00, 2.52, 0.03], ['c', 1.41, 0.18, 2.64, 0.51, 3.72, 1.08], ['c', 1.20, 0.63, 1.95, 1.41, 2.19, 2.31], ['c', 0.09, 0.33, 0.09, 0.90, 0.00, 1.23], ['c', -0.24, 0.90, -0.99, 1.68, -2.19, 2.31], ['c', -1.08, 0.57, -2.28, 0.90, -3.75, 1.08], ['c', -0.66, 0.06, -2.31, 0.06, -2.97, 0.00], ['c', -1.47, -0.18, -2.67, -0.51, -3.75, -1.08], ['c', -1.20, -0.63, -1.95, -1.41, -2.19, -2.31], ['c', -0.09, -0.33, -0.09, -0.90, 0.00, -1.23], ['c', 0.24, -0.90, 0.99, -1.68, 2.19, -2.31], ['c', 1.20, -0.63, 2.61, -0.99, 4.23, -1.11], ['z'], ['m', 0.57, 0.66], ['c', -0.87, -0.15, -1.53, 0.00, -2.04, 0.51], ['c', -0.15, 0.15, -0.24, 0.27, -0.33, 0.48], ['c', -0.24, 0.51, -0.36, 1.08, -0.33, 1.77], ['c', 0.03, 0.69, 0.18, 1.26, 0.42, 1.77], ['c', 0.60, 1.17, 1.74, 1.98, 3.18, 2.22], ['c', 1.11, 0.21, 1.95, -0.15, 2.34, -0.99], ['c', 0.24, -0.51, 0.36, -1.08, 0.33, -1.80], ['c', -0.06, -1.11, -0.45, -2.04, -1.17, -2.76], ['c', -0.63, -0.63, -1.47, -1.05, -2.40, -1.20], ['z']], w: 14.985, h: 8.097 }, + 'noteheads.half': { d: [['M', 7.44, -4.05], ['c', 0.06, -0.03, 0.27, -0.03, 0.48, -0.03], ['c', 1.05, 0.00, 1.71, 0.24, 2.10, 0.81], ['c', 0.42, 0.60, 0.45, 1.35, 0.18, 2.40], ['c', -0.42, 1.59, -1.14, 2.73, -2.16, 3.39], ['c', -1.41, 0.93, -3.18, 1.44, -5.40, 1.53], ['c', -1.17, 0.03, -1.89, -0.21, -2.28, -0.81], ['c', -0.42, -0.60, -0.45, -1.35, -0.18, -2.40], ['c', 0.42, -1.59, 1.14, -2.73, 2.16, -3.39], ['c', 0.63, -0.42, 1.23, -0.72, 1.98, -0.96], ['c', 0.90, -0.30, 1.65, -0.42, 3.12, -0.54], ['z'], ['m', 1.29, 0.87], ['c', -0.27, -0.09, -0.63, -0.12, -0.90, -0.03], ['c', -0.72, 0.24, -1.53, 0.69, -3.27, 1.80], ['c', -2.34, 1.50, -3.30, 2.25, -3.57, 2.79], ['c', -0.36, 0.72, -0.06, 1.50, 0.66, 1.77], ['c', 0.24, 0.12, 0.69, 0.09, 0.99, 0.00], ['c', 0.84, -0.30, 1.92, -0.93, 4.14, -2.37], ['c', 1.62, -1.08, 2.37, -1.71, 2.61, -2.19], ['c', 0.36, -0.72, 0.06, -1.50, -0.66, -1.77], ['z']], w: 10.37, h: 8.132 }, + 'noteheads.quarter': { d: [['M', 6.09, -4.05], ['c', 0.36, -0.03, 1.20, 0.00, 1.53, 0.06], ['c', 1.17, 0.24, 1.89, 0.84, 2.16, 1.83], ['c', 0.06, 0.18, 0.06, 0.30, 0.06, 0.66], ['c', 0.00, 0.45, 0.00, 0.63, -0.15, 1.08], ['c', -0.66, 2.04, -3.06, 3.93, -5.52, 4.38], ['c', -0.54, 0.09, -1.44, 0.09, -1.83, 0.03], ['c', -1.23, -0.27, -1.98, -0.87, -2.25, -1.86], ['c', -0.06, -0.18, -0.06, -0.30, -0.06, -0.66], ['c', 0.00, -0.45, 0.00, -0.63, 0.15, -1.08], ['c', 0.24, -0.78, 0.75, -1.53, 1.44, -2.22], ['c', 1.20, -1.20, 2.85, -2.01, 4.47, -2.22], ['z']], w: 9.81, h: 8.094 }, + 'noteheads.slash.nostem': { d: [['M', 9.30, -7.77], ['c', 0.06, -0.06, 0.18, -0.06, 1.71, -0.06], ['l', 1.65, 0.00], ['l', 0.09, 0.09], ['c', 0.06, 0.06, 0.06, 0.09, 0.06, 0.15], ['c', -0.03, 0.12, -9.21, 15.24, -9.30, 15.33], ['c', -0.06, 0.06, -0.18, 0.06, -1.71, 0.06], ['l', -1.65, 0.00], ['l', -0.09, -0.09], ['c', -0.06, -0.06, -0.06, -0.09, -0.06, -0.15], ['c', 0.03, -0.12, 9.21, -15.24, 9.30, -15.33], ['z']], w: 12.81, h: 15.63 }, + 'noteheads.indeterminate': { d: [['M', 0.78, -4.05], ['c', 0.12, -0.03, 0.24, -0.03, 0.36, 0.03], ['c', 0.03, 0.03, 0.93, 0.72, 1.95, 1.56], ['l', 1.86, 1.50], ['l', 1.86, -1.50], ['c', 1.02, -0.84, 1.92, -1.53, 1.95, -1.56], ['c', 0.21, -0.12, 0.33, -0.09, 0.75, 0.24], ['c', 0.30, 0.27, 0.36, 0.36, 0.36, 0.54], ['c', 0.00, 0.03, -0.03, 0.12, -0.06, 0.18], ['c', -0.03, 0.06, -0.90, 0.75, -1.89, 1.56], ['l', -1.80, 1.47], ['c', 0.00, 0.03, 0.81, 0.69, 1.80, 1.50], ['c', 0.99, 0.81, 1.86, 1.50, 1.89, 1.56], ['c', 0.03, 0.06, 0.06, 0.15, 0.06, 0.18], ['c', 0.00, 0.18, -0.06, 0.27, -0.36, 0.54], ['c', -0.42, 0.33, -0.54, 0.36, -0.75, 0.24], ['c', -0.03, -0.03, -0.93, -0.72, -1.95, -1.56], ['l', -1.86, -1.50], ['l', -1.86, 1.50], ['c', -1.02, 0.84, -1.92, 1.53, -1.95, 1.56], ['c', -0.21, 0.12, -0.33, 0.09, -0.75, -0.24], ['c', -0.30, -0.27, -0.36, -0.36, -0.36, -0.54], ['c', 0.00, -0.03, 0.03, -0.12, 0.06, -0.18], ['c', 0.03, -0.06, 0.90, -0.75, 1.89, -1.56], ['l', 1.80, -1.47], ['c', 0.00, -0.03, -0.81, -0.69, -1.80, -1.50], ['c', -0.99, -0.81, -1.86, -1.50, -1.89, -1.56], ['c', -0.06, -0.12, -0.09, -0.21, -0.03, -0.36], ['c', 0.03, -0.09, 0.57, -0.57, 0.72, -0.63], ['z']], w: 9.843, h: 8.139 }, + 'scripts.ufermata': { d: [['M', -0.75, -10.77], ['c', 0.12, 0.00, 0.45, -0.03, 0.69, -0.03], ['c', 2.91, -0.03, 5.55, 1.53, 7.41, 4.35], ['c', 1.17, 1.71, 1.95, 3.72, 2.43, 6.03], ['c', 0.12, 0.51, 0.12, 0.57, 0.03, 0.69], ['c', -0.12, 0.21, -0.48, 0.27, -0.69, 0.12], ['c', -0.12, -0.09, -0.18, -0.24, -0.27, -0.69], ['c', -0.78, -3.63, -3.42, -6.54, -6.78, -7.38], ['c', -0.78, -0.21, -1.20, -0.24, -2.07, -0.24], ['c', -0.63, 0.00, -0.84, 0.00, -1.20, 0.06], ['c', -1.83, 0.27, -3.42, 1.08, -4.80, 2.37], ['c', -1.41, 1.35, -2.40, 3.21, -2.85, 5.19], ['c', -0.09, 0.45, -0.15, 0.60, -0.27, 0.69], ['c', -0.21, 0.15, -0.57, 0.09, -0.69, -0.12], ['c', -0.09, -0.12, -0.09, -0.18, 0.03, -0.69], ['c', 0.33, -1.62, 0.78, -3.00, 1.47, -4.38], ['c', 1.77, -3.54, 4.44, -5.67, 7.56, -5.97], ['z'], ['m', 0.33, 7.47], ['c', 1.38, -0.30, 2.58, 0.90, 2.31, 2.25], ['c', -0.15, 0.72, -0.78, 1.35, -1.47, 1.50], ['c', -1.38, 0.27, -2.58, -0.93, -2.31, -2.31], ['c', 0.15, -0.69, 0.78, -1.29, 1.47, -1.44], ['z']], w: 19.748, h: 11.289 }, + 'scripts.dfermata': { d: [['M', -9.63, -0.42], ['c', 0.15, -0.09, 0.36, -0.06, 0.51, 0.03], ['c', 0.12, 0.09, 0.18, 0.24, 0.27, 0.66], ['c', 0.78, 3.66, 3.42, 6.57, 6.78, 7.41], ['c', 0.78, 0.21, 1.20, 0.24, 2.07, 0.24], ['c', 0.63, 0.00, 0.84, 0.00, 1.20, -0.06], ['c', 1.83, -0.27, 3.42, -1.08, 4.80, -2.37], ['c', 1.41, -1.35, 2.40, -3.21, 2.85, -5.22], ['c', 0.09, -0.42, 0.15, -0.57, 0.27, -0.66], ['c', 0.21, -0.15, 0.57, -0.09, 0.69, 0.12], ['c', 0.09, 0.12, 0.09, 0.18, -0.03, 0.69], ['c', -0.33, 1.62, -0.78, 3.00, -1.47, 4.38], ['c', -1.92, 3.84, -4.89, 6.00, -8.31, 6.00], ['c', -3.42, 0.00, -6.39, -2.16, -8.31, -6.00], ['c', -0.48, -0.96, -0.84, -1.92, -1.14, -2.97], ['c', -0.18, -0.69, -0.42, -1.74, -0.42, -1.92], ['c', 0.00, -0.12, 0.09, -0.27, 0.24, -0.33], ['z'], ['m', 9.21, 0.00], ['c', 1.20, -0.27, 2.34, 0.63, 2.34, 1.86], ['c', 0.00, 0.90, -0.66, 1.68, -1.50, 1.89], ['c', -1.38, 0.27, -2.58, -0.93, -2.31, -2.31], ['c', 0.15, -0.69, 0.78, -1.29, 1.47, -1.44], ['z']], w: 19.744, h: 11.274 }, + 'scripts.sforzato': { d: [['M', -6.45, -3.69], ['c', 0.06, -0.03, 0.15, -0.06, 0.18, -0.06], ['c', 0.06, 0.00, 2.85, 0.72, 6.24, 1.59], ['l', 6.33, 1.65], ['c', 0.33, 0.06, 0.45, 0.21, 0.45, 0.51], ['c', 0.00, 0.30, -0.12, 0.45, -0.45, 0.51], ['l', -6.33, 1.65], ['c', -3.39, 0.87, -6.18, 1.59, -6.21, 1.59], ['c', -0.21, 0.00, -0.48, -0.24, -0.51, -0.45], ['c', 0.00, -0.15, 0.06, -0.36, 0.18, -0.45], ['c', 0.09, -0.06, 0.87, -0.27, 3.84, -1.05], ['c', 2.04, -0.54, 3.84, -0.99, 4.02, -1.02], ['c', 0.15, -0.06, 1.14, -0.24, 2.22, -0.42], ['c', 1.05, -0.18, 1.92, -0.36, 1.92, -0.36], ['c', 0.00, 0.00, -0.87, -0.18, -1.92, -0.36], ['c', -1.08, -0.18, -2.07, -0.36, -2.22, -0.42], ['c', -0.18, -0.03, -1.98, -0.48, -4.02, -1.02], ['c', -2.97, -0.78, -3.75, -0.99, -3.84, -1.05], ['c', -0.12, -0.09, -0.18, -0.30, -0.18, -0.45], ['c', 0.03, -0.15, 0.15, -0.30, 0.30, -0.39], ['z']], w: 13.5, h: 7.5 }, + 'scripts.staccato': { d: [['M', -0.36, -1.47], ['c', 0.93, -0.21, 1.86, 0.51, 1.86, 1.47], ['c', 0.00, 0.93, -0.87, 1.65, -1.80, 1.47], ['c', -0.54, -0.12, -1.02, -0.57, -1.14, -1.08], ['c', -0.21, -0.81, 0.27, -1.65, 1.08, -1.86], ['z']], w: 2.989, h: 3.004 }, + 'scripts.tenuto': { d: [['M', -4.20, -0.48], ['l', 0.12, -0.06], ['l', 4.08, 0.00], ['l', 4.08, 0.00], ['l', 0.12, 0.06], ['c', 0.39, 0.21, 0.39, 0.75, 0.00, 0.96], ['l', -0.12, 0.06], ['l', -4.08, 0.00], ['l', -4.08, 0.00], ['l', -0.12, -0.06], ['c', -0.39, -0.21, -0.39, -0.75, 0.00, -0.96], ['z']], w: 8.985, h: 1.08 }, + 'scripts.umarcato': { d: [['M', -0.15, -8.19], ['c', 0.15, -0.12, 0.36, -0.03, 0.45, 0.15], ['c', 0.21, 0.42, 3.45, 7.65, 3.45, 7.71], ['c', 0.00, 0.12, -0.12, 0.27, -0.21, 0.30], ['c', -0.03, 0.03, -0.51, 0.03, -1.14, 0.03], ['c', -1.05, 0.00, -1.08, 0.00, -1.17, -0.06], ['c', -0.09, -0.06, -0.24, -0.36, -1.17, -2.40], ['c', -0.57, -1.29, -1.05, -2.34, -1.08, -2.34], ['c', 0.00, -0.03, -0.51, 1.02, -1.08, 2.34], ['c', -0.93, 2.07, -1.08, 2.34, -1.14, 2.40], ['c', -0.06, 0.03, -0.15, 0.06, -0.18, 0.06], ['c', -0.15, 0.00, -0.33, -0.18, -0.33, -0.33], ['c', 0.00, -0.06, 3.24, -7.32, 3.45, -7.71], ['c', 0.03, -0.06, 0.09, -0.15, 0.15, -0.15], ['z']], w: 7.5, h: 8.245 }, + 'scripts.dmarcato': { d: [['M', -3.57, 0.03], ['c', 0.03, 0.00, 0.57, -0.03, 1.17, -0.03], ['c', 1.05, 0.00, 1.08, 0.00, 1.17, 0.06], ['c', 0.09, 0.06, 0.24, 0.36, 1.17, 2.40], ['c', 0.57, 1.29, 1.05, 2.34, 1.08, 2.34], ['c', 0.00, 0.03, 0.51, -1.02, 1.08, -2.34], ['c', 0.93, -2.07, 1.08, -2.34, 1.14, -2.40], ['c', 0.06, -0.03, 0.15, -0.06, 0.18, -0.06], ['c', 0.15, 0.00, 0.33, 0.18, 0.33, 0.33], ['c', 0.00, 0.09, -3.45, 7.74, -3.54, 7.83], ['c', -0.12, 0.12, -0.30, 0.12, -0.42, 0.00], ['c', -0.09, -0.09, -3.54, -7.74, -3.54, -7.83], ['c', 0.00, -0.09, 0.12, -0.27, 0.18, -0.30], ['z']], w: 7.5, h: 8.25 }, + 'scripts.stopped': { d: [['M', -0.27, -4.08], ['c', 0.18, -0.09, 0.36, -0.09, 0.54, 0.00], ['c', 0.18, 0.09, 0.24, 0.15, 0.33, 0.30], ['l', 0.06, 0.15], ['l', 0.00, 1.50], ['l', 0.00, 1.47], ['l', 1.47, 0.00], ['l', 1.50, 0.00], ['l', 0.15, 0.06], ['c', 0.15, 0.09, 0.21, 0.15, 0.30, 0.33], ['c', 0.09, 0.18, 0.09, 0.36, 0.00, 0.54], ['c', -0.09, 0.18, -0.15, 0.24, -0.33, 0.33], ['c', -0.12, 0.06, -0.18, 0.06, -1.62, 0.06], ['l', -1.47, 0.00], ['l', 0.00, 1.47], ['l', 0.00, 1.47], ['l', -0.06, 0.15], ['c', -0.09, 0.18, -0.15, 0.24, -0.33, 0.33], ['c', -0.18, 0.09, -0.36, 0.09, -0.54, 0.00], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['l', -0.06, -0.15], ['l', 0.00, -1.47], ['l', 0.00, -1.47], ['l', -1.47, 0.00], ['c', -1.44, 0.00, -1.50, 0.00, -1.62, -0.06], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['c', -0.09, -0.18, -0.09, -0.36, 0.00, -0.54], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['l', 0.15, -0.06], ['l', 1.47, 0.00], ['l', 1.47, 0.00], ['l', 0.00, -1.47], ['c', 0.00, -1.44, 0.00, -1.50, 0.06, -1.62], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['z']], w: 8.295, h: 8.295 }, + 'scripts.upbow': { d: [['M', -4.65, -15.54], ['c', 0.12, -0.09, 0.36, -0.06, 0.48, 0.03], ['c', 0.03, 0.03, 0.09, 0.09, 0.12, 0.15], ['c', 0.03, 0.06, 0.66, 2.13, 1.41, 4.62], ['c', 1.35, 4.41, 1.38, 4.56, 2.01, 6.96], ['l', 0.63, 2.46], ['l', 0.63, -2.46], ['c', 0.63, -2.40, 0.66, -2.55, 2.01, -6.96], ['c', 0.75, -2.49, 1.38, -4.56, 1.41, -4.62], ['c', 0.06, -0.15, 0.18, -0.21, 0.36, -0.24], ['c', 0.15, 0.00, 0.30, 0.06, 0.39, 0.18], ['c', 0.15, 0.21, 0.24, -0.18, -2.10, 7.56], ['c', -1.20, 3.96, -2.22, 7.32, -2.25, 7.41], ['c', 0.00, 0.12, -0.06, 0.27, -0.09, 0.30], ['c', -0.12, 0.21, -0.60, 0.21, -0.72, 0.00], ['c', -0.03, -0.03, -0.09, -0.18, -0.09, -0.30], ['c', -0.03, -0.09, -1.05, -3.45, -2.25, -7.41], ['c', -2.34, -7.74, -2.25, -7.35, -2.10, -7.56], ['c', 0.03, -0.03, 0.09, -0.09, 0.15, -0.12], ['z']], w: 9.73, h: 15.608 }, + 'scripts.downbow': { d: [['M', -5.55, -9.93], ['l', 0.09, -0.06], ['l', 5.46, 0.00], ['l', 5.46, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 4.77], ['c', 0.00, 5.28, 0.00, 4.89, -0.18, 5.01], ['c', -0.18, 0.12, -0.42, 0.06, -0.54, -0.12], ['c', -0.06, -0.09, -0.06, -0.18, -0.06, -2.97], ['l', 0.00, -2.85], ['l', -4.83, 0.00], ['l', -4.83, 0.00], ['l', 0.00, 2.85], ['c', 0.00, 2.79, 0.00, 2.88, -0.06, 2.97], ['c', -0.15, 0.24, -0.51, 0.24, -0.66, 0.00], ['c', -0.06, -0.09, -0.06, -0.21, -0.06, -4.89], ['l', 0.00, -4.77], ['z']], w: 11.22, h: 9.992 }, + 'scripts.turn': { d: [['M', -4.77, -3.90], ['c', 0.36, -0.06, 1.05, -0.06, 1.44, 0.03], ['c', 0.78, 0.15, 1.50, 0.51, 2.34, 1.14], ['c', 0.60, 0.45, 1.05, 0.87, 2.22, 2.01], ['c', 1.11, 1.08, 1.62, 1.50, 2.22, 1.86], ['c', 0.60, 0.36, 1.32, 0.57, 1.92, 0.57], ['c', 0.90, 0.00, 1.71, -0.57, 1.89, -1.35], ['c', 0.24, -0.93, -0.39, -1.89, -1.35, -2.10], ['l', -0.15, -0.06], ['l', -0.09, 0.15], ['c', -0.03, 0.09, -0.15, 0.24, -0.24, 0.33], ['c', -0.72, 0.72, -2.04, 0.54, -2.49, -0.36], ['c', -0.48, -0.93, 0.03, -1.86, 1.17, -2.19], ['c', 0.30, -0.09, 1.02, -0.09, 1.35, 0.00], ['c', 0.99, 0.27, 1.74, 0.87, 2.25, 1.83], ['c', 0.69, 1.41, 0.63, 3.00, -0.21, 4.26], ['c', -0.21, 0.30, -0.69, 0.81, -0.99, 1.02], ['c', -0.30, 0.21, -0.84, 0.45, -1.17, 0.54], ['c', -1.23, 0.36, -2.49, 0.15, -3.72, -0.60], ['c', -0.75, -0.48, -1.41, -1.02, -2.85, -2.46], ['c', -1.11, -1.08, -1.62, -1.50, -2.22, -1.86], ['c', -0.60, -0.36, -1.32, -0.57, -1.92, -0.57], ['c', -0.90, 0.00, -1.71, 0.57, -1.89, 1.35], ['c', -0.24, 0.93, 0.39, 1.89, 1.35, 2.10], ['l', 0.15, 0.06], ['l', 0.09, -0.15], ['c', 0.03, -0.09, 0.15, -0.24, 0.24, -0.33], ['c', 0.72, -0.72, 2.04, -0.54, 2.49, 0.36], ['c', 0.48, 0.93, -0.03, 1.86, -1.17, 2.19], ['c', -0.30, 0.09, -1.02, 0.09, -1.35, 0.00], ['c', -0.99, -0.27, -1.74, -0.87, -2.25, -1.83], ['c', -0.69, -1.41, -0.63, -3.00, 0.21, -4.26], ['c', 0.21, -0.30, 0.69, -0.81, 0.99, -1.02], ['c', 0.48, -0.33, 1.11, -0.57, 1.74, -0.66], ['z']], w: 16.366, h: 7.893 }, + 'scripts.trill': { d: [['M', -0.51, -16.02], ['c', 0.12, -0.09, 0.21, -0.18, 0.21, -0.18], ['l', -0.81, 4.02], ['l', -0.81, 4.02], ['c', 0.03, 0.00, 0.51, -0.27, 1.08, -0.60], ['c', 0.60, -0.30, 1.14, -0.63, 1.26, -0.66], ['c', 1.14, -0.54, 2.31, -0.60, 3.09, -0.18], ['c', 0.27, 0.15, 0.54, 0.36, 0.60, 0.51], ['l', 0.06, 0.12], ['l', 0.21, -0.21], ['c', 0.90, -0.81, 2.22, -0.99, 3.12, -0.42], ['c', 0.60, 0.42, 0.90, 1.14, 0.78, 2.07], ['c', -0.15, 1.29, -1.05, 2.31, -1.95, 2.25], ['c', -0.48, -0.03, -0.78, -0.30, -0.96, -0.81], ['c', -0.09, -0.27, -0.09, -0.90, -0.03, -1.20], ['c', 0.21, -0.75, 0.81, -1.23, 1.59, -1.32], ['l', 0.24, -0.03], ['l', -0.09, -0.12], ['c', -0.51, -0.66, -1.62, -0.63, -2.31, 0.03], ['c', -0.39, 0.42, -0.30, 0.09, -1.23, 4.77], ['l', -0.81, 4.14], ['c', -0.03, 0.00, -0.12, -0.03, -0.21, -0.09], ['c', -0.33, -0.15, -0.54, -0.18, -0.99, -0.18], ['c', -0.42, 0.00, -0.66, 0.03, -1.05, 0.18], ['c', -0.12, 0.06, -0.21, 0.09, -0.21, 0.09], ['c', 0.00, -0.03, 0.36, -1.86, 0.81, -4.11], ['c', 0.90, -4.47, 0.87, -4.26, 0.69, -4.53], ['c', -0.21, -0.36, -0.66, -0.51, -1.17, -0.36], ['c', -0.15, 0.06, -2.22, 1.14, -2.58, 1.38], ['c', -0.12, 0.09, -0.12, 0.09, -0.21, 0.60], ['l', -0.09, 0.51], ['l', 0.21, 0.24], ['c', 0.63, 0.75, 1.02, 1.47, 1.20, 2.19], ['c', 0.06, 0.27, 0.06, 0.36, 0.06, 0.81], ['c', 0.00, 0.42, 0.00, 0.54, -0.06, 0.78], ['c', -0.15, 0.54, -0.33, 0.93, -0.63, 1.35], ['c', -0.18, 0.24, -0.57, 0.63, -0.81, 0.78], ['c', -0.24, 0.15, -0.63, 0.36, -0.84, 0.42], ['c', -0.27, 0.06, -0.66, 0.06, -0.87, 0.03], ['c', -0.81, -0.18, -1.32, -1.05, -1.38, -2.46], ['c', -0.03, -0.60, 0.03, -0.99, 0.33, -2.46], ['c', 0.21, -1.08, 0.24, -1.32, 0.21, -1.29], ['c', -1.20, 0.48, -2.40, 0.75, -3.21, 0.72], ['c', -0.69, -0.06, -1.17, -0.30, -1.41, -0.72], ['c', -0.39, -0.75, -0.12, -1.80, 0.66, -2.46], ['c', 0.24, -0.18, 0.69, -0.42, 1.02, -0.51], ['c', 0.69, -0.18, 1.53, -0.15, 2.31, 0.09], ['c', 0.30, 0.09, 0.75, 0.30, 0.99, 0.45], ['c', 0.12, 0.09, 0.15, 0.09, 0.15, 0.03], ['c', 0.03, -0.03, 0.33, -1.59, 0.72, -3.45], ['c', 0.36, -1.86, 0.66, -3.42, 0.69, -3.45], ['c', 0.00, -0.03, 0.03, -0.03, 0.21, 0.03], ['c', 0.21, 0.06, 0.27, 0.06, 0.48, 0.06], ['c', 0.42, -0.03, 0.78, -0.18, 1.26, -0.48], ['c', 0.15, -0.12, 0.36, -0.27, 0.48, -0.39], ['z'], ['m', -5.73, 7.68], ['c', -0.27, -0.03, -0.96, -0.06, -1.20, -0.03], ['c', -0.81, 0.12, -1.35, 0.57, -1.50, 1.20], ['c', -0.18, 0.66, 0.12, 1.14, 0.75, 1.29], ['c', 0.66, 0.12, 1.92, -0.12, 3.18, -0.66], ['l', 0.33, -0.15], ['l', 0.09, -0.39], ['c', 0.06, -0.21, 0.09, -0.42, 0.09, -0.45], ['c', 0.00, -0.03, -0.45, -0.30, -0.75, -0.45], ['c', -0.27, -0.15, -0.66, -0.27, -0.99, -0.36], ['z'], ['m', 4.29, 3.63], ['c', -0.24, -0.39, -0.51, -0.75, -0.51, -0.69], ['c', -0.06, 0.12, -0.39, 1.92, -0.45, 2.28], ['c', -0.09, 0.54, -0.12, 1.14, -0.06, 1.38], ['c', 0.06, 0.42, 0.21, 0.60, 0.51, 0.57], ['c', 0.39, -0.06, 0.75, -0.48, 0.93, -1.14], ['c', 0.09, -0.33, 0.09, -1.05, 0.00, -1.38], ['c', -0.09, -0.39, -0.24, -0.69, -0.42, -1.02], ['z']], w: 17.963, h: 16.49 }, + 'scripts.segno': { d: [['M', -3.72, -11.22], ['c', 0.78, -0.09, 1.59, 0.03, 2.31, 0.42], ['c', 1.20, 0.60, 2.01, 1.71, 2.31, 3.09], ['c', 0.09, 0.42, 0.09, 1.20, 0.03, 1.50], ['c', -0.15, 0.45, -0.39, 0.81, -0.66, 0.93], ['c', -0.33, 0.18, -0.84, 0.21, -1.23, 0.15], ['c', -0.81, -0.18, -1.32, -0.93, -1.26, -1.89], ['c', 0.03, -0.36, 0.09, -0.57, 0.24, -0.90], ['c', 0.15, -0.33, 0.45, -0.60, 0.72, -0.75], ['c', 0.12, -0.06, 0.18, -0.09, 0.18, -0.12], ['c', 0.00, -0.03, -0.03, -0.15, -0.09, -0.24], ['c', -0.18, -0.45, -0.54, -0.87, -0.96, -1.08], ['c', -1.11, -0.57, -2.34, -0.18, -2.88, 0.90], ['c', -0.24, 0.51, -0.33, 1.11, -0.24, 1.83], ['c', 0.27, 1.92, 1.50, 3.54, 3.93, 5.13], ['c', 0.48, 0.33, 1.26, 0.78, 1.29, 0.78], ['c', 0.03, 0.00, 1.35, -2.19, 2.94, -4.89], ['l', 2.88, -4.89], ['l', 0.84, 0.00], ['l', 0.87, 0.00], ['l', -0.03, 0.06], ['c', -0.15, 0.21, -6.15, 10.41, -6.15, 10.44], ['c', 0.00, 0.00, 0.21, 0.15, 0.48, 0.27], ['c', 2.61, 1.47, 4.35, 3.03, 5.13, 4.65], ['c', 1.14, 2.34, 0.51, 5.07, -1.44, 6.39], ['c', -0.66, 0.42, -1.32, 0.63, -2.13, 0.69], ['c', -2.01, 0.09, -3.81, -1.41, -4.26, -3.54], ['c', -0.09, -0.42, -0.09, -1.20, -0.03, -1.50], ['c', 0.15, -0.45, 0.39, -0.81, 0.66, -0.93], ['c', 0.33, -0.18, 0.84, -0.21, 1.23, -0.15], ['c', 0.81, 0.18, 1.32, 0.93, 1.26, 1.89], ['c', -0.03, 0.36, -0.09, 0.57, -0.24, 0.90], ['c', -0.15, 0.33, -0.45, 0.60, -0.72, 0.75], ['c', -0.12, 0.06, -0.18, 0.09, -0.18, 0.12], ['c', 0.00, 0.03, 0.03, 0.15, 0.09, 0.24], ['c', 0.18, 0.45, 0.54, 0.87, 0.96, 1.08], ['c', 1.11, 0.57, 2.34, 0.18, 2.88, -0.90], ['c', 0.24, -0.51, 0.33, -1.11, 0.24, -1.83], ['c', -0.27, -1.92, -1.50, -3.54, -3.93, -5.13], ['c', -0.48, -0.33, -1.26, -0.78, -1.29, -0.78], ['c', -0.03, 0.00, -1.35, 2.19, -2.91, 4.89], ['l', -2.88, 4.89], ['l', -0.87, 0.00], ['l', -0.87, 0.00], ['l', 0.03, -0.06], ['c', 0.15, -0.21, 6.15, -10.41, 6.15, -10.44], ['c', 0.00, 0.00, -0.21, -0.15, -0.48, -0.30], ['c', -2.61, -1.44, -4.35, -3.00, -5.13, -4.62], ['c', -0.90, -1.89, -0.72, -4.02, 0.48, -5.52], ['c', 0.69, -0.84, 1.68, -1.41, 2.73, -1.53], ['z'], ['m', 8.76, 9.09], ['c', 0.03, -0.03, 0.15, -0.03, 0.27, -0.03], ['c', 0.33, 0.03, 0.57, 0.18, 0.72, 0.48], ['c', 0.09, 0.18, 0.09, 0.57, 0.00, 0.75], ['c', -0.09, 0.18, -0.21, 0.30, -0.36, 0.39], ['c', -0.15, 0.06, -0.21, 0.06, -0.39, 0.06], ['c', -0.21, 0.00, -0.27, 0.00, -0.39, -0.06], ['c', -0.30, -0.15, -0.48, -0.45, -0.48, -0.75], ['c', 0.00, -0.39, 0.24, -0.72, 0.63, -0.84], ['z'], ['m', -10.53, 2.61], ['c', 0.03, -0.03, 0.15, -0.03, 0.27, -0.03], ['c', 0.33, 0.03, 0.57, 0.18, 0.72, 0.48], ['c', 0.09, 0.18, 0.09, 0.57, 0.00, 0.75], ['c', -0.09, 0.18, -0.21, 0.30, -0.36, 0.39], ['c', -0.15, 0.06, -0.21, 0.06, -0.39, 0.06], ['c', -0.21, 0.00, -0.27, 0.00, -0.39, -0.06], ['c', -0.30, -0.15, -0.48, -0.45, -0.48, -0.75], ['c', 0.00, -0.39, 0.24, -0.72, 0.63, -0.84], ['z']], w: 15, h: 22.504 }, + 'scripts.coda': { d: [['M', -0.21, -10.47], ['c', 0.18, -0.12, 0.42, -0.06, 0.54, 0.12], ['c', 0.06, 0.09, 0.06, 0.18, 0.06, 1.50], ['l', 0.00, 1.38], ['l', 0.18, 0.00], ['c', 0.39, 0.06, 0.96, 0.24, 1.38, 0.48], ['c', 1.68, 0.93, 2.82, 3.24, 3.03, 6.12], ['c', 0.03, 0.24, 0.03, 0.45, 0.03, 0.45], ['c', 0.00, 0.03, 0.60, 0.03, 1.35, 0.03], ['c', 1.50, 0.00, 1.47, 0.00, 1.59, 0.18], ['c', 0.09, 0.12, 0.09, 0.30, 0.00, 0.42], ['c', -0.12, 0.18, -0.09, 0.18, -1.59, 0.18], ['c', -0.75, 0.00, -1.35, 0.00, -1.35, 0.03], ['c', 0.00, 0.00, 0.00, 0.21, -0.03, 0.42], ['c', -0.24, 3.15, -1.53, 5.58, -3.45, 6.36], ['c', -0.27, 0.12, -0.72, 0.24, -0.96, 0.27], ['l', -0.18, 0.00], ['l', 0.00, 1.38], ['c', 0.00, 1.32, 0.00, 1.41, -0.06, 1.50], ['c', -0.15, 0.24, -0.51, 0.24, -0.66, 0.00], ['c', -0.06, -0.09, -0.06, -0.18, -0.06, -1.50], ['l', 0.00, -1.38], ['l', -0.18, 0.00], ['c', -0.39, -0.06, -0.96, -0.24, -1.38, -0.48], ['c', -1.68, -0.93, -2.82, -3.24, -3.03, -6.15], ['c', -0.03, -0.21, -0.03, -0.42, -0.03, -0.42], ['c', 0.00, -0.03, -0.60, -0.03, -1.35, -0.03], ['c', -1.50, 0.00, -1.47, 0.00, -1.59, -0.18], ['c', -0.09, -0.12, -0.09, -0.30, 0.00, -0.42], ['c', 0.12, -0.18, 0.09, -0.18, 1.59, -0.18], ['c', 0.75, 0.00, 1.35, 0.00, 1.35, -0.03], ['c', 0.00, 0.00, 0.00, -0.21, 0.03, -0.45], ['c', 0.24, -3.12, 1.53, -5.55, 3.45, -6.33], ['c', 0.27, -0.12, 0.72, -0.24, 0.96, -0.27], ['l', 0.18, 0.00], ['l', 0.00, -1.38], ['c', 0.00, -1.53, 0.00, -1.50, 0.18, -1.62], ['z'], ['m', -0.18, 6.93], ['c', 0.00, -2.97, 0.00, -3.15, -0.06, -3.15], ['c', -0.09, 0.00, -0.51, 0.15, -0.66, 0.21], ['c', -0.87, 0.51, -1.38, 1.62, -1.56, 3.51], ['c', -0.06, 0.54, -0.12, 1.59, -0.12, 2.16], ['l', 0.00, 0.42], ['l', 1.20, 0.00], ['l', 1.20, 0.00], ['l', 0.00, -3.15], ['z'], ['m', 1.17, -3.06], ['c', -0.09, -0.03, -0.21, -0.06, -0.27, -0.09], ['l', -0.12, 0.00], ['l', 0.00, 3.15], ['l', 0.00, 3.15], ['l', 1.20, 0.00], ['l', 1.20, 0.00], ['l', 0.00, -0.81], ['c', -0.06, -2.40, -0.33, -3.69, -0.93, -4.59], ['c', -0.27, -0.39, -0.66, -0.69, -1.08, -0.81], ['z'], ['m', -1.17, 10.14], ['l', 0.00, -3.15], ['l', -1.20, 0.00], ['l', -1.20, 0.00], ['l', 0.00, 0.81], ['c', 0.03, 0.96, 0.06, 1.47, 0.15, 2.13], ['c', 0.24, 2.04, 0.96, 3.12, 2.13, 3.36], ['l', 0.12, 0.00], ['l', 0.00, -3.15], ['z'], ['m', 3.18, -2.34], ['l', 0.00, -0.81], ['l', -1.20, 0.00], ['l', -1.20, 0.00], ['l', 0.00, 3.15], ['l', 0.00, 3.15], ['l', 0.12, 0.00], ['c', 1.17, -0.24, 1.89, -1.32, 2.13, -3.36], ['c', 0.09, -0.66, 0.12, -1.17, 0.15, -2.13], ['z']], w: 16.035, h: 21.062 }, + 'scripts.comma': { d: [['M', 1.14, -4.62], ['c', 0.30, -0.12, 0.69, -0.03, 0.93, 0.15], ['c', 0.12, 0.12, 0.36, 0.45, 0.51, 0.78], ['c', 0.90, 1.77, 0.54, 4.05, -1.08, 6.75], ['c', -0.36, 0.63, -0.87, 1.38, -0.96, 1.44], ['c', -0.18, 0.12, -0.42, 0.06, -0.54, -0.12], ['c', -0.09, -0.18, -0.09, -0.30, 0.12, -0.60], ['c', 0.96, -1.44, 1.44, -2.97, 1.38, -4.35], ['c', -0.06, -0.93, -0.30, -1.68, -0.78, -2.46], ['c', -0.27, -0.39, -0.33, -0.63, -0.24, -0.96], ['c', 0.09, -0.27, 0.36, -0.54, 0.66, -0.63], ['z']], w: 3.042, h: 9.237 }, + 'scripts.roll': { d: [['M', 1.95, -6.00], ['c', 0.21, -0.09, 0.36, -0.09, 0.57, 0.00], ['c', 0.39, 0.15, 0.63, 0.39, 1.47, 1.35], ['c', 0.66, 0.75, 0.78, 0.87, 1.08, 1.05], ['c', 0.75, 0.45, 1.65, 0.42, 2.40, -0.06], ['c', 0.12, -0.09, 0.27, -0.27, 0.54, -0.60], ['c', 0.42, -0.54, 0.51, -0.63, 0.69, -0.63], ['c', 0.09, 0.00, 0.30, 0.12, 0.36, 0.21], ['c', 0.09, 0.12, 0.12, 0.30, 0.03, 0.42], ['c', -0.06, 0.12, -3.15, 3.90, -3.30, 4.08], ['c', -0.06, 0.06, -0.18, 0.12, -0.27, 0.18], ['c', -0.27, 0.12, -0.60, 0.06, -0.99, -0.27], ['c', -0.27, -0.21, -0.42, -0.39, -1.08, -1.14], ['c', -0.63, -0.72, -0.81, -0.90, -1.17, -1.08], ['c', -0.36, -0.18, -0.57, -0.21, -0.99, -0.21], ['c', -0.39, 0.00, -0.63, 0.03, -0.93, 0.18], ['c', -0.36, 0.15, -0.51, 0.27, -0.90, 0.81], ['c', -0.24, 0.27, -0.45, 0.51, -0.48, 0.54], ['c', -0.12, 0.09, -0.27, 0.06, -0.39, 0.00], ['c', -0.24, -0.15, -0.33, -0.39, -0.21, -0.60], ['c', 0.09, -0.12, 3.18, -3.87, 3.33, -4.02], ['c', 0.06, -0.06, 0.18, -0.15, 0.24, -0.21], ['z']], w: 10.817, h: 6.125 }, + 'scripts.prall': { d: [['M', -4.38, -3.69], ['c', 0.06, -0.03, 0.18, -0.06, 0.24, -0.06], ['c', 0.30, 0.00, 0.27, -0.03, 1.89, 1.95], ['l', 1.53, 1.83], ['c', 0.03, 0.00, 0.57, -0.84, 1.23, -1.83], ['c', 1.14, -1.68, 1.23, -1.83, 1.35, -1.89], ['c', 0.06, -0.03, 0.18, -0.06, 0.24, -0.06], ['c', 0.30, 0.00, 0.27, -0.03, 1.89, 1.95], ['l', 1.53, 1.83], ['l', 0.48, -0.69], ['c', 0.51, -0.78, 0.54, -0.84, 0.69, -0.90], ['c', 0.42, -0.18, 0.87, 0.15, 0.81, 0.60], ['c', -0.03, 0.12, -0.30, 0.51, -1.50, 2.37], ['c', -1.38, 2.07, -1.50, 2.22, -1.62, 2.28], ['c', -0.06, 0.03, -0.18, 0.06, -0.24, 0.06], ['c', -0.30, 0.00, -0.27, 0.03, -1.89, -1.95], ['l', -1.53, -1.83], ['c', -0.03, 0.00, -0.57, 0.84, -1.23, 1.83], ['c', -1.14, 1.68, -1.23, 1.83, -1.35, 1.89], ['c', -0.06, 0.03, -0.18, 0.06, -0.24, 0.06], ['c', -0.30, 0.00, -0.27, 0.03, -1.89, -1.95], ['l', -1.53, -1.83], ['l', -0.48, 0.69], ['c', -0.51, 0.78, -0.54, 0.84, -0.69, 0.90], ['c', -0.42, 0.18, -0.87, -0.15, -0.81, -0.60], ['c', 0.03, -0.12, 0.30, -0.51, 1.50, -2.37], ['c', 1.38, -2.07, 1.50, -2.22, 1.62, -2.28], ['z']], w: 15.011, h: 7.5 }, + 'scripts.arpeggio': { d: [['M', 1.5, 0], ['c', 1.5, 2, 1.5, 3, 1.5, 3], ['s', 0, 1, -2, 1.5], ['s', -0.5, 3, 1, 5.5], ['l', 1.5, 0], ['s', -1.75, -2, -1.9, -3.25], ['s', 2.15, -0.6, 2.95, -1.6], ['s', 0.45, -1, 0.5, -1.25], ['s', 0, -1, -2, -3.9], ['l', -1.5, 0], ['z']], w: 5, h: 10 }, + 'scripts.mordent': { d: [['M', -0.21, -4.95], ['c', 0.27, -0.15, 0.63, 0.00, 0.75, 0.27], ['c', 0.06, 0.12, 0.06, 0.24, 0.06, 1.44], ['l', 0.00, 1.29], ['l', 0.57, -0.84], ['c', 0.51, -0.75, 0.57, -0.84, 0.69, -0.90], ['c', 0.06, -0.03, 0.18, -0.06, 0.24, -0.06], ['c', 0.30, 0.00, 0.27, -0.03, 1.89, 1.95], ['l', 1.53, 1.83], ['l', 0.48, -0.69], ['c', 0.51, -0.78, 0.54, -0.84, 0.69, -0.90], ['c', 0.42, -0.18, 0.87, 0.15, 0.81, 0.60], ['c', -0.03, 0.12, -0.30, 0.51, -1.50, 2.37], ['c', -1.38, 2.07, -1.50, 2.22, -1.62, 2.28], ['c', -0.06, 0.03, -0.18, 0.06, -0.24, 0.06], ['c', -0.30, 0.00, -0.27, 0.03, -1.83, -1.89], ['c', -0.81, -0.99, -1.50, -1.80, -1.53, -1.86], ['c', -0.06, -0.03, -0.06, -0.03, -0.12, 0.03], ['c', -0.06, 0.06, -0.06, 0.15, -0.06, 2.28], ['c', 0.00, 1.95, 0.00, 2.25, -0.06, 2.34], ['c', -0.18, 0.45, -0.81, 0.48, -1.05, 0.03], ['c', -0.03, -0.06, -0.06, -0.24, -0.06, -1.41], ['l', 0.00, -1.35], ['l', -0.57, 0.84], ['c', -0.54, 0.78, -0.60, 0.87, -0.72, 0.93], ['c', -0.06, 0.03, -0.18, 0.06, -0.24, 0.06], ['c', -0.30, 0.00, -0.27, 0.03, -1.89, -1.95], ['l', -1.53, -1.83], ['l', -0.48, 0.69], ['c', -0.51, 0.78, -0.54, 0.84, -0.69, 0.90], ['c', -0.42, 0.18, -0.87, -0.15, -0.81, -0.60], ['c', 0.03, -0.12, 0.30, -0.51, 1.50, -2.37], ['c', 1.38, -2.07, 1.50, -2.22, 1.62, -2.28], ['c', 0.06, -0.03, 0.18, -0.06, 0.24, -0.06], ['c', 0.30, 0.00, 0.27, -0.03, 1.89, 1.95], ['l', 1.53, 1.83], ['c', 0.03, 0.00, 0.06, -0.06, 0.09, -0.09], ['c', 0.06, -0.12, 0.06, -0.15, 0.06, -2.28], ['c', 0.00, -1.92, 0.00, -2.22, 0.06, -2.31], ['c', 0.06, -0.15, 0.15, -0.24, 0.30, -0.30], ['z']], w: 15.011, h: 10.012 }, + 'flags.u8th': { d: [['M', -0.42, 3.75], ['l', 0.00, -3.75], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 0.18], ['c', 0.00, 0.30, 0.06, 0.84, 0.12, 1.23], ['c', 0.24, 1.53, 0.90, 3.12, 2.13, 5.16], ['l', 0.99, 1.59], ['c', 0.87, 1.44, 1.38, 2.34, 1.77, 3.09], ['c', 0.81, 1.68, 1.20, 3.06, 1.26, 4.53], ['c', 0.03, 1.53, -0.21, 3.27, -0.75, 5.01], ['c', -0.21, 0.69, -0.51, 1.50, -0.60, 1.59], ['c', -0.09, 0.12, -0.27, 0.21, -0.42, 0.21], ['c', -0.15, 0.00, -0.42, -0.12, -0.51, -0.21], ['c', -0.15, -0.18, -0.18, -0.42, -0.09, -0.66], ['c', 0.15, -0.33, 0.45, -1.20, 0.57, -1.62], ['c', 0.42, -1.38, 0.60, -2.58, 0.60, -3.90], ['c', 0.00, -0.66, 0.00, -0.81, -0.06, -1.11], ['c', -0.39, -2.07, -1.80, -4.26, -4.59, -7.14], ['l', -0.42, -0.45], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -3.75], ['z']], w: 6.692, h: 22.59 }, + 'flags.u16th': { d: [['M', -0.42, 7.50], ['l', 0.00, -7.50], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 0.39], ['c', 0.06, 1.08, 0.39, 2.19, 0.99, 3.39], ['c', 0.45, 0.90, 0.87, 1.59, 1.95, 3.12], ['c', 1.29, 1.86, 1.77, 2.64, 2.22, 3.57], ['c', 0.45, 0.93, 0.72, 1.80, 0.87, 2.64], ['c', 0.06, 0.51, 0.06, 1.50, 0.00, 1.92], ['c', -0.12, 0.60, -0.30, 1.20, -0.54, 1.71], ['l', -0.09, 0.24], ['l', 0.18, 0.45], ['c', 0.51, 1.20, 0.72, 2.22, 0.69, 3.42], ['c', -0.06, 1.53, -0.39, 3.03, -0.99, 4.53], ['c', -0.30, 0.75, -0.36, 0.81, -0.57, 0.90], ['c', -0.15, 0.09, -0.33, 0.06, -0.48, 0.00], ['c', -0.18, -0.09, -0.27, -0.18, -0.33, -0.33], ['c', -0.09, -0.18, -0.06, -0.30, 0.12, -0.75], ['c', 0.66, -1.41, 1.02, -2.88, 1.08, -4.32], ['c', 0.00, -0.60, -0.03, -1.05, -0.18, -1.59], ['c', -0.30, -1.20, -0.99, -2.40, -2.25, -3.87], ['c', -0.42, -0.48, -1.53, -1.62, -2.19, -2.22], ['l', -0.45, -0.42], ['l', -0.03, 1.11], ['l', 0.00, 1.11], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -7.50], ['z'], ['m', 1.65, 0.09], ['c', -0.30, -0.30, -0.69, -0.72, -0.90, -0.87], ['l', -0.33, -0.33], ['l', 0.00, 0.15], ['c', 0.00, 0.30, 0.06, 0.81, 0.15, 1.26], ['c', 0.27, 1.29, 0.87, 2.61, 2.04, 4.29], ['c', 0.15, 0.24, 0.60, 0.87, 0.96, 1.38], ['l', 1.08, 1.53], ['l', 0.42, 0.63], ['c', 0.03, 0.00, 0.12, -0.36, 0.21, -0.72], ['c', 0.06, -0.33, 0.06, -1.20, 0.00, -1.62], ['c', -0.33, -1.71, -1.44, -3.48, -3.63, -5.70], ['z']], w: 6.693, h: 26.337 }, + 'flags.u32nd': { d: [['M', -0.42, 11.25], ['l', 0.00, -11.25], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 0.36], ['c', 0.09, 1.68, 0.69, 3.27, 2.07, 5.46], ['l', 0.87, 1.35], ['c', 1.02, 1.62, 1.47, 2.37, 1.86, 3.18], ['c', 0.48, 1.02, 0.78, 1.92, 0.93, 2.88], ['c', 0.06, 0.48, 0.06, 1.50, 0.00, 1.89], ['c', -0.09, 0.42, -0.21, 0.87, -0.36, 1.26], ['l', -0.12, 0.30], ['l', 0.15, 0.39], ['c', 0.69, 1.56, 0.84, 2.88, 0.54, 4.38], ['c', -0.09, 0.45, -0.27, 1.08, -0.45, 1.47], ['l', -0.12, 0.24], ['l', 0.18, 0.36], ['c', 0.33, 0.72, 0.57, 1.56, 0.69, 2.34], ['c', 0.12, 1.02, -0.06, 2.52, -0.42, 3.84], ['c', -0.27, 0.93, -0.75, 2.13, -0.93, 2.31], ['c', -0.18, 0.15, -0.45, 0.18, -0.66, 0.09], ['c', -0.18, -0.09, -0.27, -0.18, -0.33, -0.33], ['c', -0.09, -0.18, -0.06, -0.30, 0.06, -0.60], ['c', 0.21, -0.36, 0.42, -0.90, 0.57, -1.38], ['c', 0.51, -1.41, 0.69, -3.06, 0.48, -4.08], ['c', -0.15, -0.81, -0.57, -1.68, -1.20, -2.55], ['c', -0.72, -0.99, -1.83, -2.13, -3.30, -3.33], ['l', -0.48, -0.42], ['l', -0.03, 1.53], ['l', 0.00, 1.56], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -11.25], ['z'], ['m', 1.26, -3.96], ['c', -0.27, -0.30, -0.54, -0.60, -0.66, -0.72], ['l', -0.18, -0.21], ['l', 0.00, 0.42], ['c', 0.06, 0.87, 0.24, 1.74, 0.66, 2.67], ['c', 0.36, 0.87, 0.96, 1.86, 1.92, 3.18], ['c', 0.21, 0.33, 0.63, 0.87, 0.87, 1.23], ['c', 0.27, 0.39, 0.60, 0.84, 0.75, 1.08], ['l', 0.27, 0.39], ['l', 0.03, -0.12], ['c', 0.12, -0.45, 0.15, -1.05, 0.09, -1.59], ['c', -0.27, -1.86, -1.38, -3.78, -3.75, -6.33], ['z'], ['m', -0.27, 6.09], ['c', -0.27, -0.21, -0.48, -0.42, -0.51, -0.45], ['c', -0.06, -0.03, -0.06, -0.03, -0.06, 0.21], ['c', 0.00, 0.90, 0.30, 2.04, 0.81, 3.09], ['c', 0.48, 1.02, 0.96, 1.77, 2.37, 3.63], ['c', 0.60, 0.78, 1.05, 1.44, 1.29, 1.77], ['c', 0.06, 0.12, 0.15, 0.21, 0.15, 0.18], ['c', 0.03, -0.03, 0.18, -0.57, 0.24, -0.87], ['c', 0.06, -0.45, 0.06, -1.32, -0.03, -1.74], ['c', -0.09, -0.48, -0.24, -0.90, -0.51, -1.44], ['c', -0.66, -1.35, -1.83, -2.70, -3.75, -4.38], ['z']], w: 6.697, h: 32.145 }, + 'flags.u64th': { d: [['M', -0.42, 15.00], ['l', 0.00, -15.00], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 0.36], ['c', 0.06, 1.20, 0.39, 2.37, 1.02, 3.66], ['c', 0.39, 0.81, 0.84, 1.56, 1.80, 3.09], ['c', 0.81, 1.26, 1.05, 1.68, 1.35, 2.22], ['c', 0.87, 1.50, 1.35, 2.79, 1.56, 4.08], ['c', 0.06, 0.54, 0.06, 1.56, -0.03, 2.04], ['c', -0.09, 0.48, -0.21, 0.99, -0.36, 1.35], ['l', -0.12, 0.27], ['l', 0.12, 0.27], ['c', 0.09, 0.15, 0.21, 0.45, 0.27, 0.66], ['c', 0.69, 1.89, 0.63, 3.66, -0.18, 5.46], ['l', -0.18, 0.39], ['l', 0.15, 0.33], ['c', 0.30, 0.66, 0.51, 1.44, 0.63, 2.10], ['c', 0.06, 0.48, 0.06, 1.35, 0.00, 1.71], ['c', -0.15, 0.57, -0.42, 1.20, -0.78, 1.68], ['l', -0.21, 0.27], ['l', 0.18, 0.33], ['c', 0.57, 1.05, 0.93, 2.13, 1.02, 3.18], ['c', 0.06, 0.72, 0.00, 1.83, -0.21, 2.79], ['c', -0.18, 1.02, -0.63, 2.34, -1.02, 3.09], ['c', -0.15, 0.33, -0.48, 0.45, -0.78, 0.30], ['c', -0.18, -0.09, -0.27, -0.18, -0.33, -0.33], ['c', -0.09, -0.18, -0.06, -0.30, 0.03, -0.54], ['c', 0.75, -1.50, 1.23, -3.45, 1.17, -4.89], ['c', -0.06, -1.02, -0.42, -2.01, -1.17, -3.15], ['c', -0.48, -0.72, -1.02, -1.35, -1.89, -2.22], ['c', -0.57, -0.57, -1.56, -1.50, -1.92, -1.77], ['l', -0.12, -0.09], ['l', 0.00, 1.68], ['l', 0.00, 1.68], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -15.00], ['z'], ['m', 0.93, -8.07], ['c', -0.27, -0.30, -0.48, -0.54, -0.51, -0.54], ['c', 0.00, 0.00, 0.00, 0.69, 0.03, 1.02], ['c', 0.15, 1.47, 0.75, 2.94, 2.04, 4.83], ['l', 1.08, 1.53], ['c', 0.39, 0.57, 0.84, 1.20, 0.99, 1.44], ['c', 0.15, 0.24, 0.30, 0.45, 0.30, 0.45], ['c', 0.00, 0.00, 0.03, -0.09, 0.06, -0.21], ['c', 0.36, -1.59, -0.15, -3.33, -1.47, -5.40], ['c', -0.63, -0.93, -1.35, -1.83, -2.52, -3.12], ['z'], ['m', 0.06, 6.72], ['c', -0.24, -0.21, -0.48, -0.42, -0.51, -0.45], ['l', -0.06, -0.06], ['l', 0.00, 0.33], ['c', 0.00, 1.20, 0.30, 2.34, 0.93, 3.60], ['c', 0.45, 0.90, 0.96, 1.68, 2.25, 3.51], ['c', 0.39, 0.54, 0.84, 1.17, 1.02, 1.44], ['c', 0.21, 0.33, 0.33, 0.51, 0.33, 0.48], ['c', 0.06, -0.09, 0.21, -0.63, 0.30, -0.99], ['c', 0.06, -0.33, 0.06, -0.45, 0.06, -0.96], ['c', 0.00, -0.60, -0.03, -0.84, -0.18, -1.35], ['c', -0.30, -1.08, -1.02, -2.28, -2.13, -3.57], ['c', -0.39, -0.45, -1.44, -1.47, -2.01, -1.98], ['z'], ['m', 0.00, 6.72], ['c', -0.24, -0.21, -0.48, -0.39, -0.51, -0.42], ['l', -0.06, -0.06], ['l', 0.00, 0.33], ['c', 0.00, 1.41, 0.45, 2.82, 1.38, 4.35], ['c', 0.42, 0.72, 0.72, 1.14, 1.86, 2.73], ['c', 0.36, 0.45, 0.75, 0.99, 0.87, 1.20], ['c', 0.15, 0.21, 0.30, 0.36, 0.30, 0.36], ['c', 0.06, 0.00, 0.30, -0.48, 0.39, -0.75], ['c', 0.09, -0.36, 0.12, -0.63, 0.12, -1.05], ['c', -0.06, -1.05, -0.45, -2.04, -1.20, -3.18], ['c', -0.57, -0.87, -1.11, -1.53, -2.07, -2.49], ['c', -0.36, -0.33, -0.84, -0.78, -1.08, -1.02], ['z']], w: 6.682, h: 39.694 }, + 'flags.d8th': { d: [['M', 5.67, -21.63], ['c', 0.24, -0.12, 0.54, -0.06, 0.69, 0.15], ['c', 0.06, 0.06, 0.21, 0.36, 0.39, 0.66], ['c', 0.84, 1.77, 1.26, 3.36, 1.32, 5.10], ['c', 0.03, 1.29, -0.21, 2.37, -0.81, 3.63], ['c', -0.60, 1.23, -1.26, 2.13, -3.21, 4.38], ['c', -1.35, 1.53, -1.86, 2.19, -2.40, 2.97], ['c', -0.63, 0.93, -1.11, 1.92, -1.38, 2.79], ['c', -0.15, 0.54, -0.27, 1.35, -0.27, 1.80], ['l', 0.00, 0.15], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -3.75], ['l', 0.00, -3.75], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.48, -0.30], ['c', 1.83, -1.11, 3.12, -2.10, 4.17, -3.12], ['c', 0.78, -0.81, 1.32, -1.53, 1.71, -2.31], ['c', 0.45, -0.93, 0.60, -1.74, 0.51, -2.88], ['c', -0.12, -1.56, -0.63, -3.18, -1.47, -4.68], ['c', -0.12, -0.21, -0.15, -0.33, -0.06, -0.51], ['c', 0.06, -0.15, 0.15, -0.24, 0.33, -0.33], ['z']], w: 8.492, h: 21.691 }, + 'flags.ugrace': { d: [['M', 6.03, 6.93], ['c', 0.15, -0.09, 0.33, -0.06, 0.51, 0.00], ['c', 0.15, 0.09, 0.21, 0.15, 0.30, 0.33], ['c', 0.09, 0.18, 0.06, 0.39, -0.03, 0.54], ['c', -0.06, 0.15, -10.89, 8.88, -11.07, 8.97], ['c', -0.15, 0.09, -0.33, 0.06, -0.48, 0.00], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['c', -0.09, -0.18, -0.06, -0.39, 0.03, -0.54], ['c', 0.06, -0.15, 10.89, -8.88, 11.07, -8.97], ['z']], w: 12.019, h: 9.954 }, + 'flags.dgrace': { d: [['M', -6.06, -15.93], ['c', 0.18, -0.09, 0.33, -0.12, 0.48, -0.06], ['c', 0.18, 0.09, 14.01, 8.04, 14.10, 8.10], ['c', 0.12, 0.12, 0.18, 0.33, 0.18, 0.51], ['c', -0.03, 0.21, -0.15, 0.39, -0.36, 0.48], ['c', -0.18, 0.09, -0.33, 0.12, -0.48, 0.06], ['c', -0.18, -0.09, -14.01, -8.04, -14.10, -8.10], ['c', -0.12, -0.12, -0.18, -0.33, -0.18, -0.51], ['c', 0.03, -0.21, 0.15, -0.39, 0.36, -0.48], ['z']], w: 15.12, h: 9.212 }, + 'flags.d16th': { d: [['M', 6.84, -22.53], ['c', 0.27, -0.12, 0.57, -0.06, 0.72, 0.15], ['c', 0.15, 0.15, 0.33, 0.87, 0.45, 1.56], ['c', 0.06, 0.33, 0.06, 1.35, 0.00, 1.65], ['c', -0.06, 0.33, -0.15, 0.78, -0.27, 1.11], ['c', -0.12, 0.33, -0.45, 0.96, -0.66, 1.32], ['l', -0.18, 0.27], ['l', 0.09, 0.18], ['c', 0.48, 1.02, 0.72, 2.25, 0.69, 3.30], ['c', -0.06, 1.23, -0.42, 2.28, -1.26, 3.45], ['c', -0.57, 0.87, -0.99, 1.32, -3.00, 3.39], ['c', -1.56, 1.56, -2.22, 2.40, -2.76, 3.45], ['c', -0.42, 0.84, -0.66, 1.80, -0.66, 2.55], ['l', 0.00, 0.15], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -7.50], ['l', 0.00, -7.50], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 1.14], ['l', 0.00, 1.11], ['l', 0.27, -0.15], ['c', 1.11, -0.57, 1.77, -0.99, 2.52, -1.47], ['c', 2.37, -1.56, 3.69, -3.15, 4.05, -4.83], ['c', 0.03, -0.18, 0.03, -0.39, 0.03, -0.78], ['c', 0.00, -0.60, -0.03, -0.93, -0.24, -1.50], ['c', -0.06, -0.18, -0.12, -0.39, -0.15, -0.45], ['c', -0.03, -0.24, 0.12, -0.48, 0.36, -0.60], ['z'], ['m', -0.63, 7.50], ['c', -0.06, -0.18, -0.15, -0.36, -0.15, -0.36], ['c', -0.03, 0.00, -0.03, 0.03, -0.06, 0.06], ['c', -0.06, 0.12, -0.96, 1.02, -1.95, 1.98], ['c', -0.63, 0.57, -1.26, 1.17, -1.44, 1.35], ['c', -1.53, 1.62, -2.28, 2.85, -2.55, 4.32], ['c', -0.03, 0.18, -0.03, 0.54, -0.06, 0.99], ['l', 0.00, 0.69], ['l', 0.18, -0.09], ['c', 0.93, -0.54, 2.10, -1.29, 2.82, -1.83], ['c', 0.69, -0.51, 1.02, -0.81, 1.53, -1.29], ['c', 1.86, -1.89, 2.37, -3.66, 1.68, -5.82], ['z']], w: 8.475, h: 22.591 }, + 'flags.d32nd': { d: [['M', 6.84, -29.13], ['c', 0.27, -0.12, 0.57, -0.06, 0.72, 0.15], ['c', 0.12, 0.12, 0.27, 0.63, 0.36, 1.11], ['c', 0.33, 1.59, 0.06, 3.06, -0.81, 4.47], ['l', -0.18, 0.27], ['l', 0.09, 0.15], ['c', 0.12, 0.24, 0.33, 0.69, 0.45, 1.05], ['c', 0.63, 1.83, 0.45, 3.57, -0.57, 5.22], ['l', -0.18, 0.30], ['l', 0.15, 0.27], ['c', 0.42, 0.87, 0.60, 1.71, 0.57, 2.61], ['c', -0.06, 1.29, -0.48, 2.46, -1.35, 3.78], ['c', -0.54, 0.81, -0.93, 1.29, -2.46, 3.00], ['c', -0.51, 0.54, -1.05, 1.17, -1.26, 1.41], ['c', -1.56, 1.86, -2.25, 3.36, -2.37, 5.01], ['l', 0.00, 0.33], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -11.25], ['l', 0.00, -11.25], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 1.35], ['l', 0.03, 1.35], ['l', 0.78, -0.39], ['c', 1.38, -0.69, 2.34, -1.26, 3.24, -1.92], ['c', 1.38, -1.02, 2.28, -2.13, 2.64, -3.21], ['c', 0.15, -0.48, 0.18, -0.72, 0.18, -1.29], ['c', 0.00, -0.57, -0.06, -0.90, -0.24, -1.47], ['c', -0.06, -0.18, -0.12, -0.39, -0.15, -0.45], ['c', -0.03, -0.24, 0.12, -0.48, 0.36, -0.60], ['z'], ['m', -0.63, 7.20], ['c', -0.09, -0.18, -0.12, -0.21, -0.12, -0.15], ['c', -0.03, 0.09, -1.02, 1.08, -2.04, 2.04], ['c', -1.17, 1.08, -1.65, 1.56, -2.07, 2.04], ['c', -0.84, 0.96, -1.38, 1.86, -1.68, 2.76], ['c', -0.21, 0.57, -0.27, 0.99, -0.30, 1.65], ['l', 0.00, 0.54], ['l', 0.66, -0.33], ['c', 3.57, -1.86, 5.49, -3.69, 5.94, -5.70], ['c', 0.06, -0.39, 0.06, -1.20, -0.03, -1.65], ['c', -0.06, -0.39, -0.24, -0.90, -0.36, -1.20], ['z'], ['m', -0.06, 7.20], ['c', -0.06, -0.15, -0.12, -0.33, -0.15, -0.45], ['l', -0.06, -0.18], ['l', -0.18, 0.21], ['l', -1.83, 1.83], ['c', -0.87, 0.90, -1.77, 1.80, -1.95, 2.01], ['c', -1.08, 1.29, -1.62, 2.31, -1.89, 3.51], ['c', -0.06, 0.30, -0.06, 0.51, -0.09, 0.93], ['l', 0.00, 0.57], ['l', 0.09, -0.06], ['c', 0.75, -0.45, 1.89, -1.26, 2.52, -1.74], ['c', 0.81, -0.66, 1.74, -1.53, 2.22, -2.16], ['c', 1.26, -1.53, 1.68, -3.06, 1.32, -4.47], ['z']], w: 8.385, h: 29.191 }, + 'flags.d64th': { d: [['M', 7.08, -32.88], ['c', 0.30, -0.12, 0.66, -0.03, 0.78, 0.24], ['c', 0.18, 0.33, 0.27, 2.10, 0.15, 2.64], ['c', -0.09, 0.39, -0.21, 0.78, -0.39, 1.08], ['l', -0.15, 0.30], ['l', 0.09, 0.27], ['c', 0.03, 0.12, 0.09, 0.45, 0.12, 0.69], ['c', 0.27, 1.44, 0.18, 2.55, -0.30, 3.60], ['l', -0.12, 0.33], ['l', 0.06, 0.42], ['c', 0.27, 1.35, 0.33, 2.82, 0.21, 3.63], ['c', -0.12, 0.60, -0.30, 1.23, -0.57, 1.80], ['l', -0.15, 0.27], ['l', 0.03, 0.42], ['c', 0.06, 1.02, 0.06, 2.70, 0.03, 3.06], ['c', -0.15, 1.47, -0.66, 2.76, -1.74, 4.41], ['c', -0.45, 0.69, -0.75, 1.11, -1.74, 2.37], ['c', -1.05, 1.38, -1.50, 1.98, -1.95, 2.73], ['c', -0.93, 1.50, -1.38, 2.82, -1.44, 4.20], ['l', 0.00, 0.42], ['l', -0.21, 0.00], ['l', -0.21, 0.00], ['l', 0.00, -15.00], ['l', 0.00, -15.00], ['l', 0.21, 0.00], ['l', 0.21, 0.00], ['l', 0.00, 1.86], ['l', 0.00, 1.89], ['c', 0.00, 0.00, 0.21, -0.03, 0.45, -0.09], ['c', 2.22, -0.39, 4.08, -1.11, 5.19, -2.01], ['c', 0.63, -0.54, 1.02, -1.14, 1.20, -1.80], ['c', 0.06, -0.30, 0.06, -1.14, -0.03, -1.65], ['c', -0.03, -0.18, -0.06, -0.39, -0.09, -0.48], ['c', -0.03, -0.24, 0.12, -0.48, 0.36, -0.60], ['z'], ['m', -0.45, 6.15], ['c', -0.03, -0.18, -0.06, -0.42, -0.06, -0.54], ['l', -0.03, -0.18], ['l', -0.33, 0.30], ['c', -0.42, 0.36, -0.87, 0.72, -1.68, 1.29], ['c', -1.98, 1.38, -2.25, 1.59, -2.85, 2.16], ['c', -0.75, 0.69, -1.23, 1.44, -1.47, 2.19], ['c', -0.15, 0.45, -0.18, 0.63, -0.21, 1.35], ['l', 0.00, 0.66], ['l', 0.39, -0.18], ['c', 1.83, -0.90, 3.45, -1.95, 4.47, -2.91], ['c', 0.93, -0.90, 1.53, -1.83, 1.74, -2.82], ['c', 0.06, -0.33, 0.06, -0.87, 0.03, -1.32], ['z'], ['m', -0.27, 4.86], ['c', -0.03, -0.21, -0.06, -0.36, -0.06, -0.36], ['c', 0.00, -0.03, -0.12, 0.09, -0.24, 0.24], ['c', -0.39, 0.48, -0.99, 1.08, -2.16, 2.19], ['c', -1.47, 1.38, -1.92, 1.83, -2.46, 2.49], ['c', -0.66, 0.87, -1.08, 1.74, -1.29, 2.58], ['c', -0.09, 0.42, -0.15, 0.87, -0.15, 1.44], ['l', 0.00, 0.54], ['l', 0.48, -0.33], ['c', 1.50, -1.02, 2.58, -1.89, 3.51, -2.82], ['c', 1.47, -1.47, 2.25, -2.85, 2.40, -4.26], ['c', 0.03, -0.39, 0.03, -1.17, -0.03, -1.71], ['z'], ['m', -0.66, 7.68], ['c', 0.03, -0.15, 0.03, -0.60, 0.03, -0.99], ['l', 0.00, -0.72], ['l', -0.27, 0.33], ['l', -1.74, 1.98], ['c', -1.77, 1.92, -2.43, 2.76, -2.97, 3.90], ['c', -0.51, 1.02, -0.72, 1.77, -0.75, 2.91], ['c', 0.00, 0.63, 0.00, 0.63, 0.06, 0.60], ['c', 0.03, -0.03, 0.30, -0.27, 0.63, -0.54], ['c', 0.66, -0.60, 1.86, -1.80, 2.31, -2.31], ['c', 1.65, -1.89, 2.52, -3.54, 2.70, -5.16], ['z']], w: 8.485, h: 32.932 }, + 'clefs.C': { d: [['M', 0.06, -14.94], ['l', 0.09, -0.06], ['l', 1.92, 0.00], ['l', 1.92, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 14.85], ['l', 0.00, 14.82], ['l', -0.06, 0.09], ['l', -0.09, 0.06], ['l', -1.92, 0.00], ['l', -1.92, 0.00], ['l', -0.09, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -14.82], ['l', 0.00, -14.85], ['z'], ['m', 5.37, 0.00], ['c', 0.09, -0.06, 0.09, -0.06, 0.57, -0.06], ['c', 0.45, 0.00, 0.45, 0.00, 0.54, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 7.14], ['l', 0.00, 7.11], ['l', 0.09, -0.06], ['c', 0.18, -0.18, 0.72, -0.84, 0.96, -1.20], ['c', 0.30, -0.45, 0.66, -1.17, 0.84, -1.65], ['c', 0.36, -0.90, 0.57, -1.83, 0.60, -2.79], ['c', 0.03, -0.48, 0.03, -0.54, 0.09, -0.63], ['c', 0.12, -0.18, 0.36, -0.21, 0.54, -0.12], ['c', 0.18, 0.09, 0.21, 0.15, 0.24, 0.66], ['c', 0.06, 0.87, 0.21, 1.56, 0.57, 2.22], ['c', 0.51, 1.02, 1.26, 1.68, 2.22, 1.92], ['c', 0.21, 0.06, 0.33, 0.06, 0.78, 0.06], ['c', 0.45, 0.00, 0.57, 0.00, 0.84, -0.06], ['c', 0.45, -0.12, 0.81, -0.33, 1.08, -0.60], ['c', 0.57, -0.57, 0.87, -1.41, 0.99, -2.88], ['c', 0.06, -0.54, 0.06, -3.00, 0.00, -3.57], ['c', -0.21, -2.58, -0.84, -3.87, -2.16, -4.50], ['c', -0.48, -0.21, -1.17, -0.36, -1.77, -0.36], ['c', -0.69, 0.00, -1.29, 0.27, -1.50, 0.72], ['c', -0.06, 0.15, -0.06, 0.21, -0.06, 0.42], ['c', 0.00, 0.24, 0.00, 0.30, 0.06, 0.45], ['c', 0.12, 0.24, 0.24, 0.39, 0.63, 0.66], ['c', 0.42, 0.30, 0.57, 0.48, 0.69, 0.72], ['c', 0.06, 0.15, 0.06, 0.21, 0.06, 0.48], ['c', 0.00, 0.39, -0.03, 0.63, -0.21, 0.96], ['c', -0.30, 0.60, -0.87, 1.08, -1.50, 1.26], ['c', -0.27, 0.06, -0.87, 0.06, -1.14, 0.00], ['c', -0.78, -0.24, -1.44, -0.87, -1.65, -1.68], ['c', -0.12, -0.42, -0.09, -1.17, 0.09, -1.71], ['c', 0.51, -1.65, 1.98, -2.82, 3.81, -3.09], ['c', 0.84, -0.09, 2.46, 0.03, 3.51, 0.27], ['c', 2.22, 0.57, 3.69, 1.80, 4.44, 3.75], ['c', 0.36, 0.93, 0.57, 2.13, 0.57, 3.36], ['c', 0.00, 1.44, -0.48, 2.73, -1.38, 3.81], ['c', -1.26, 1.50, -3.27, 2.43, -5.28, 2.43], ['c', -0.48, 0.00, -0.51, 0.00, -0.75, -0.09], ['c', -0.15, -0.03, -0.48, -0.21, -0.78, -0.36], ['c', -0.69, -0.36, -0.87, -0.42, -1.26, -0.42], ['c', -0.27, 0.00, -0.30, 0.00, -0.51, 0.09], ['c', -0.57, 0.30, -0.81, 0.90, -0.81, 2.10], ['c', 0.00, 1.23, 0.24, 1.83, 0.81, 2.13], ['c', 0.21, 0.09, 0.24, 0.09, 0.51, 0.09], ['c', 0.39, 0.00, 0.57, -0.06, 1.26, -0.42], ['c', 0.30, -0.15, 0.63, -0.33, 0.78, -0.36], ['c', 0.24, -0.09, 0.27, -0.09, 0.75, -0.09], ['c', 2.01, 0.00, 4.02, 0.93, 5.28, 2.40], ['c', 0.90, 1.11, 1.38, 2.40, 1.38, 3.84], ['c', 0.00, 1.50, -0.30, 2.88, -0.84, 3.96], ['c', -0.78, 1.59, -2.19, 2.64, -4.17, 3.15], ['c', -1.05, 0.24, -2.67, 0.36, -3.51, 0.27], ['c', -1.83, -0.27, -3.30, -1.44, -3.81, -3.09], ['c', -0.18, -0.54, -0.21, -1.29, -0.09, -1.74], ['c', 0.15, -0.60, 0.63, -1.20, 1.23, -1.47], ['c', 0.36, -0.18, 0.57, -0.21, 0.99, -0.21], ['c', 0.42, 0.00, 0.63, 0.03, 1.02, 0.21], ['c', 0.42, 0.21, 0.84, 0.63, 1.05, 1.05], ['c', 0.18, 0.36, 0.21, 0.60, 0.21, 0.96], ['c', 0.00, 0.30, 0.00, 0.36, -0.06, 0.51], ['c', -0.12, 0.24, -0.27, 0.42, -0.69, 0.72], ['c', -0.57, 0.42, -0.69, 0.63, -0.69, 1.08], ['c', 0.00, 0.24, 0.00, 0.30, 0.06, 0.45], ['c', 0.12, 0.21, 0.30, 0.39, 0.57, 0.54], ['c', 0.42, 0.18, 0.87, 0.21, 1.53, 0.15], ['c', 1.08, -0.15, 1.80, -0.57, 2.34, -1.32], ['c', 0.54, -0.75, 0.84, -1.83, 0.99, -3.51], ['c', 0.06, -0.57, 0.06, -3.03, 0.00, -3.57], ['c', -0.12, -1.47, -0.42, -2.31, -0.99, -2.88], ['c', -0.27, -0.27, -0.63, -0.48, -1.08, -0.60], ['c', -0.27, -0.06, -0.39, -0.06, -0.84, -0.06], ['c', -0.45, 0.00, -0.57, 0.00, -0.78, 0.06], ['c', -1.14, 0.27, -2.01, 1.17, -2.46, 2.49], ['c', -0.21, 0.57, -0.30, 0.99, -0.33, 1.65], ['c', -0.03, 0.51, -0.06, 0.57, -0.24, 0.66], ['c', -0.12, 0.06, -0.27, 0.06, -0.39, 0.00], ['c', -0.21, -0.09, -0.21, -0.15, -0.24, -0.75], ['c', -0.09, -1.92, -0.78, -3.72, -2.01, -5.19], ['c', -0.18, -0.21, -0.36, -0.42, -0.39, -0.45], ['l', -0.09, -0.06], ['l', 0.00, 7.11], ['l', 0.00, 7.14], ['l', -0.06, 0.09], ['c', -0.09, 0.06, -0.09, 0.06, -0.54, 0.06], ['c', -0.48, 0.00, -0.48, 0.00, -0.57, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -14.82], ['l', 0.00, -14.85], ['z']], w: 20.31, h: 29.97 }, + 'clefs.F': { d: [['M', 6.30, -7.80], ['c', 0.36, -0.03, 1.65, 0.00, 2.13, 0.03], ['c', 3.60, 0.42, 6.03, 2.10, 6.93, 4.86], ['c', 0.27, 0.84, 0.36, 1.50, 0.36, 2.58], ['c', 0.00, 0.90, -0.03, 1.35, -0.18, 2.16], ['c', -0.78, 3.78, -3.54, 7.08, -8.37, 9.96], ['c', -1.74, 1.05, -3.87, 2.13, -6.18, 3.12], ['c', -0.39, 0.18, -0.75, 0.33, -0.81, 0.36], ['c', -0.06, 0.03, -0.15, 0.06, -0.18, 0.06], ['c', -0.15, 0.00, -0.33, -0.18, -0.33, -0.33], ['c', 0.00, -0.15, 0.06, -0.21, 0.51, -0.48], ['c', 3.00, -1.77, 5.13, -3.21, 6.84, -4.74], ['c', 0.51, -0.45, 1.59, -1.50, 1.95, -1.95], ['c', 1.89, -2.19, 2.88, -4.32, 3.15, -6.78], ['c', 0.06, -0.42, 0.06, -1.77, 0.00, -2.19], ['c', -0.24, -2.01, -0.93, -3.63, -2.04, -4.71], ['c', -0.63, -0.63, -1.29, -1.02, -2.07, -1.20], ['c', -1.62, -0.39, -3.36, 0.15, -4.56, 1.44], ['c', -0.54, 0.60, -1.05, 1.47, -1.32, 2.22], ['l', -0.09, 0.21], ['l', 0.24, -0.12], ['c', 0.39, -0.21, 0.63, -0.24, 1.11, -0.24], ['c', 0.30, 0.00, 0.45, 0.00, 0.66, 0.06], ['c', 1.92, 0.48, 2.85, 2.55, 1.95, 4.38], ['c', -0.45, 0.99, -1.41, 1.62, -2.46, 1.71], ['c', -1.47, 0.09, -2.91, -0.87, -3.39, -2.25], ['c', -0.18, -0.57, -0.21, -1.32, -0.03, -2.28], ['c', 0.39, -2.25, 1.83, -4.20, 3.81, -5.19], ['c', 0.69, -0.36, 1.59, -0.60, 2.37, -0.69], ['z'], ['m', 11.58, 2.52], ['c', 0.84, -0.21, 1.71, 0.30, 1.89, 1.14], ['c', 0.30, 1.17, -0.72, 2.19, -1.89, 1.89], ['c', -0.99, -0.21, -1.50, -1.32, -1.02, -2.25], ['c', 0.18, -0.39, 0.60, -0.69, 1.02, -0.78], ['z'], ['m', 0.00, 7.50], ['c', 0.84, -0.21, 1.71, 0.30, 1.89, 1.14], ['c', 0.21, 0.87, -0.30, 1.71, -1.14, 1.89], ['c', -0.87, 0.21, -1.71, -0.30, -1.89, -1.14], ['c', -0.21, -0.84, 0.30, -1.71, 1.14, -1.89], ['z']], w: 20.153, h: 23.142 }, + 'clefs.G': { d: [['M', 9.69, -37.41], ['c', 0.09, -0.09, 0.24, -0.06, 0.36, 0.00], ['c', 0.12, 0.09, 0.57, 0.60, 0.96, 1.11], ['c', 1.77, 2.34, 3.21, 5.85, 3.57, 8.73], ['c', 0.21, 1.56, 0.03, 3.27, -0.45, 4.86], ['c', -0.69, 2.31, -1.92, 4.47, -4.23, 7.44], ['c', -0.30, 0.39, -0.57, 0.72, -0.60, 0.75], ['c', -0.03, 0.06, 0.00, 0.15, 0.18, 0.78], ['c', 0.54, 1.68, 1.38, 4.44, 1.68, 5.49], ['l', 0.09, 0.42], ['l', 0.39, 0.00], ['c', 1.47, 0.09, 2.76, 0.51, 3.96, 1.29], ['c', 1.83, 1.23, 3.06, 3.21, 3.39, 5.52], ['c', 0.09, 0.45, 0.12, 1.29, 0.06, 1.74], ['c', -0.09, 1.02, -0.33, 1.83, -0.75, 2.73], ['c', -0.84, 1.71, -2.28, 3.06, -4.02, 3.72], ['l', -0.33, 0.12], ['l', 0.03, 1.26], ['c', 0.00, 1.74, -0.06, 3.63, -0.21, 4.62], ['c', -0.45, 3.06, -2.19, 5.49, -4.47, 6.21], ['c', -0.57, 0.18, -0.90, 0.21, -1.59, 0.21], ['c', -0.69, 0.00, -1.02, -0.03, -1.65, -0.21], ['c', -1.14, -0.27, -2.13, -0.84, -2.94, -1.65], ['c', -0.99, -0.99, -1.56, -2.16, -1.71, -3.54], ['c', -0.09, -0.81, 0.06, -1.53, 0.45, -2.13], ['c', 0.63, -0.99, 1.83, -1.56, 3.00, -1.53], ['c', 1.50, 0.09, 2.64, 1.32, 2.73, 2.94], ['c', 0.06, 1.47, -0.93, 2.70, -2.37, 2.97], ['c', -0.45, 0.06, -0.84, 0.03, -1.29, -0.09], ['l', -0.21, -0.09], ['l', 0.09, 0.12], ['c', 0.39, 0.54, 0.78, 0.93, 1.32, 1.26], ['c', 1.35, 0.87, 3.06, 1.02, 4.35, 0.36], ['c', 1.44, -0.72, 2.52, -2.28, 2.97, -4.35], ['c', 0.15, -0.66, 0.24, -1.50, 0.30, -3.03], ['c', 0.03, -0.84, 0.03, -2.94, 0.00, -3.00], ['c', -0.03, 0.00, -0.18, 0.00, -0.36, 0.03], ['c', -0.66, 0.12, -0.99, 0.12, -1.83, 0.12], ['c', -1.05, 0.00, -1.71, -0.06, -2.61, -0.30], ['c', -4.02, -0.99, -7.11, -4.35, -7.80, -8.46], ['c', -0.12, -0.66, -0.12, -0.99, -0.12, -1.83], ['c', 0.00, -0.84, 0.00, -1.14, 0.15, -1.92], ['c', 0.36, -2.28, 1.41, -4.62, 3.30, -7.29], ['l', 2.79, -3.60], ['c', 0.54, -0.66, 0.96, -1.20, 0.96, -1.23], ['c', 0.00, -0.03, -0.09, -0.33, -0.18, -0.69], ['c', -0.96, -3.21, -1.41, -5.28, -1.59, -7.68], ['c', -0.12, -1.38, -0.15, -3.09, -0.06, -3.96], ['c', 0.33, -2.67, 1.38, -5.07, 3.12, -7.08], ['c', 0.36, -0.42, 0.99, -1.05, 1.17, -1.14], ['z'], ['m', 2.01, 4.71], ['c', -0.15, -0.30, -0.30, -0.54, -0.30, -0.54], ['c', -0.03, 0.00, -0.18, 0.09, -0.30, 0.21], ['c', -2.40, 1.74, -3.87, 4.20, -4.26, 7.11], ['c', -0.06, 0.54, -0.06, 1.41, -0.03, 1.89], ['c', 0.09, 1.29, 0.48, 3.12, 1.08, 5.22], ['c', 0.15, 0.42, 0.24, 0.78, 0.24, 0.81], ['c', 0.00, 0.03, 0.84, -1.11, 1.23, -1.68], ['c', 1.89, -2.73, 2.88, -5.07, 3.15, -7.53], ['c', 0.09, -0.57, 0.12, -1.74, 0.06, -2.37], ['c', -0.09, -1.23, -0.27, -1.92, -0.87, -3.12], ['z'], ['m', -2.94, 20.70], ['c', -0.21, -0.72, -0.39, -1.32, -0.42, -1.32], ['c', 0.00, 0.00, -1.20, 1.47, -1.86, 2.37], ['c', -2.79, 3.63, -4.02, 6.30, -4.35, 9.30], ['c', -0.03, 0.21, -0.03, 0.69, -0.03, 1.08], ['c', 0.00, 0.69, 0.00, 0.75, 0.06, 1.11], ['c', 0.12, 0.54, 0.27, 0.99, 0.51, 1.47], ['c', 0.69, 1.38, 1.83, 2.55, 3.42, 3.42], ['c', 0.96, 0.54, 2.07, 0.90, 3.21, 1.08], ['c', 0.78, 0.12, 2.04, 0.12, 2.94, -0.03], ['c', 0.51, -0.06, 0.45, -0.03, 0.42, -0.30], ['c', -0.24, -3.33, -0.72, -6.33, -1.62, -10.08], ['c', -0.09, -0.39, -0.18, -0.75, -0.18, -0.78], ['c', -0.03, -0.03, -0.42, 0.00, -0.81, 0.09], ['c', -0.90, 0.18, -1.65, 0.57, -2.22, 1.14], ['c', -0.72, 0.72, -1.08, 1.65, -1.05, 2.64], ['c', 0.06, 0.96, 0.48, 1.83, 1.23, 2.58], ['c', 0.36, 0.36, 0.72, 0.63, 1.17, 0.90], ['c', 0.33, 0.18, 0.36, 0.21, 0.42, 0.33], ['c', 0.18, 0.42, -0.18, 0.90, -0.60, 0.87], ['c', -0.18, -0.03, -0.84, -0.36, -1.26, -0.63], ['c', -0.78, -0.51, -1.38, -1.11, -1.86, -1.83], ['c', -1.77, -2.70, -0.99, -6.42, 1.71, -8.19], ['c', 0.30, -0.21, 0.81, -0.48, 1.17, -0.63], ['c', 0.30, -0.09, 1.02, -0.30, 1.14, -0.30], ['c', 0.06, 0.00, 0.09, 0.00, 0.09, -0.03], ['c', 0.03, -0.03, -0.51, -1.92, -1.23, -4.26], ['z'], ['m', 3.78, 7.41], ['c', -0.18, -0.03, -0.36, -0.06, -0.39, -0.06], ['c', -0.03, 0.00, 0.00, 0.21, 0.18, 1.02], ['c', 0.75, 3.18, 1.26, 6.30, 1.50, 9.09], ['c', 0.06, 0.72, 0.00, 0.69, 0.51, 0.42], ['c', 0.78, -0.36, 1.44, -0.96, 1.98, -1.77], ['c', 1.08, -1.62, 1.20, -3.69, 0.30, -5.55], ['c', -0.81, -1.62, -2.31, -2.79, -4.08, -3.15], ['z']], w: 19.051, h: 57.057 }, + 'clefs.perc': { d: [['M', 5.07, -7.44], ['l', 0.09, -0.06], ['l', 1.53, 0.00], ['l', 1.53, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 7.35], ['l', 0.00, 7.32], ['l', -0.06, 0.09], ['l', -0.09, 0.06], ['l', -1.53, 0.00], ['l', -1.53, 0.00], ['l', -0.09, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -7.32], ['l', 0.00, -7.35], ['z'], ['m', 6.63, 0.00], ['l', 0.09, -0.06], ['l', 1.53, 0.00], ['l', 1.53, 0.00], ['l', 0.09, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 7.35], ['l', 0.00, 7.32], ['l', -0.06, 0.09], ['l', -0.09, 0.06], ['l', -1.53, 0.00], ['l', -1.53, 0.00], ['l', -0.09, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -7.32], ['l', 0.00, -7.35], ['z']], w: 21, h: 14.97 }, + 'tab.big': { d: [['M', 20.16, -21.66], ['c', 0.24, -0.09, 0.66, 0.09, 0.78, 0.36], ['c', 0.09, 0.21, 0.09, 0.24, -0.18, 0.54], ['c', -0.78, 0.81, -1.86, 1.44, -2.94, 1.71], ['c', -0.87, 0.24, -1.71, 0.24, -2.55, 0.03], ['l', -0.06, -0.03], ['l', -0.18, 0.99], ['c', -0.33, 1.98, -0.75, 4.26, -0.96, 5.04], ['c', -0.42, 1.65, -1.26, 3.18, -2.28, 4.14], ['c', -0.57, 0.57, -1.17, 0.90, -1.86, 1.08], ['c', -0.18, 0.06, -0.33, 0.06, -0.66, 0.06], ['c', -0.54, 0.00, -0.78, -0.03, -1.23, -0.27], ['c', -0.39, -0.18, -0.66, -0.39, -1.38, -0.99], ['c', -0.30, -0.24, -0.66, -0.51, -0.75, -0.57], ['c', -0.21, -0.15, -0.27, -0.24, -0.24, -0.45], ['c', 0.06, -0.27, 0.36, -0.60, 0.60, -0.66], ['c', 0.18, -0.03, 0.33, 0.06, 0.90, 0.57], ['c', 0.48, 0.42, 0.72, 0.57, 0.93, 0.69], ['c', 0.66, 0.33, 1.38, 0.21, 1.95, -0.36], ['c', 0.63, -0.60, 1.05, -1.62, 1.23, -3.00], ['c', 0.03, -0.18, 0.09, -0.66, 0.09, -1.11], ['c', 0.09, -1.56, 0.33, -3.81, 0.57, -5.49], ['c', 0.06, -0.33, 0.09, -0.63, 0.09, -0.63], ['c', -0.03, -0.03, -0.81, -0.12, -1.02, -0.12], ['c', -0.57, 0.00, -1.32, 0.12, -1.80, 0.33], ['c', -0.87, 0.30, -1.35, 0.78, -1.50, 1.41], ['c', -0.18, 0.63, 0.09, 1.26, 0.66, 1.65], ['c', 0.12, 0.06, 0.15, 0.12, 0.18, 0.24], ['c', 0.09, 0.27, 0.06, 0.57, -0.09, 0.75], ['c', -0.03, 0.06, -0.12, 0.09, -0.27, 0.15], ['c', -0.72, 0.21, -1.44, 0.15, -2.10, -0.18], ['c', -0.54, -0.27, -0.96, -0.66, -1.20, -1.14], ['c', -0.39, -0.75, -0.33, -1.74, 0.15, -2.52], ['c', 0.27, -0.42, 0.84, -0.93, 1.41, -1.23], ['c', 1.17, -0.57, 2.88, -0.90, 4.80, -0.90], ['c', 0.69, 0.00, 0.78, 0.00, 1.08, 0.06], ['c', 0.45, 0.09, 1.11, 0.30, 2.07, 0.60], ['c', 1.47, 0.48, 1.83, 0.57, 2.55, 0.54], ['c', 1.02, -0.06, 2.04, -0.45, 2.94, -1.11], ['c', 0.12, -0.09, 0.24, -0.18, 0.27, -0.18], ['z'], ['m', -5.88, 13.05], ['c', 0.21, -0.03, 0.81, 0.00, 1.08, 0.06], ['c', 0.48, 0.12, 0.90, 0.42, 0.99, 0.69], ['c', 0.03, 0.09, 0.03, 0.15, 0.00, 0.27], ['c', 0.00, 0.09, -0.03, 0.57, -0.06, 1.08], ['c', -0.09, 2.19, -0.24, 5.76, -0.39, 8.28], ['c', -0.06, 1.53, -0.06, 1.77, 0.03, 2.01], ['c', 0.09, 0.18, 0.15, 0.24, 0.30, 0.30], ['c', 0.24, 0.12, 0.54, 0.06, 1.23, -0.27], ['c', 0.57, -0.27, 0.66, -0.30, 0.75, -0.24], ['c', 0.09, 0.06, 0.18, 0.30, 0.18, 0.45], ['c', 0.00, 0.33, -0.15, 0.51, -0.45, 0.63], ['c', -0.12, 0.03, -0.39, 0.15, -0.60, 0.27], ['c', -1.17, 0.60, -1.38, 0.69, -1.80, 0.72], ['c', -0.45, 0.03, -0.78, -0.09, -1.08, -0.39], ['c', -0.39, -0.42, -0.66, -1.20, -1.02, -3.12], ['c', -0.24, -1.23, -0.36, -2.07, -0.54, -3.75], ['l', 0.00, -0.18], ['l', -0.36, 0.45], ['c', -0.60, 0.75, -1.32, 1.59, -1.95, 2.25], ['c', -0.15, 0.18, -0.27, 0.30, -0.27, 0.33], ['c', 0.00, 0.00, 0.06, 0.09, 0.15, 0.18], ['c', 0.24, 0.33, 0.60, 0.57, 1.05, 0.69], ['c', 0.18, 0.06, 0.30, 0.06, 0.69, 0.06], ['l', 0.48, 0.03], ['l', 0.06, 0.12], ['c', 0.15, 0.27, 0.03, 0.72, -0.21, 0.90], ['c', -0.18, 0.12, -0.93, 0.27, -1.41, 0.27], ['c', -0.84, 0.00, -1.59, -0.30, -1.98, -0.84], ['l', -0.12, -0.15], ['l', -0.45, 0.42], ['c', -0.99, 0.87, -1.53, 1.32, -2.16, 1.74], ['c', -0.78, 0.51, -1.50, 0.84, -2.10, 0.93], ['c', -0.69, 0.12, -1.20, 0.03, -1.95, -0.42], ['c', -0.21, -0.12, -0.51, -0.27, -0.66, -0.36], ['c', -0.24, -0.12, -0.30, -0.18, -0.33, -0.24], ['c', -0.12, -0.27, 0.15, -0.78, 0.45, -0.93], ['c', 0.24, -0.12, 0.33, -0.09, 0.90, 0.18], ['c', 0.60, 0.30, 0.84, 0.39, 1.20, 0.36], ['c', 0.87, -0.09, 1.77, -0.69, 3.24, -2.31], ['c', 2.67, -2.85, 4.59, -5.94, 5.70, -9.15], ['c', 0.15, -0.45, 0.24, -0.63, 0.42, -0.81], ['c', 0.21, -0.24, 0.60, -0.45, 0.99, -0.51], ['z'], ['m', -3.99, 16.05], ['c', 0.18, 0.00, 0.69, -0.03, 1.17, 0.00], ['c', 3.27, 0.03, 5.37, 0.75, 6.00, 2.07], ['c', 0.45, 0.99, 0.12, 2.40, -0.81, 3.42], ['c', -0.24, 0.27, -0.57, 0.57, -0.84, 0.75], ['c', -0.09, 0.06, -0.18, 0.09, -0.18, 0.12], ['c', 0.00, 0.00, 0.18, 0.03, 0.42, 0.09], ['c', 1.23, 0.30, 2.01, 0.81, 2.37, 1.59], ['c', 0.27, 0.54, 0.30, 1.32, 0.09, 2.10], ['c', -0.12, 0.36, -0.45, 1.05, -0.69, 1.35], ['c', -0.87, 1.17, -2.10, 1.92, -3.54, 2.25], ['c', -0.36, 0.06, -0.48, 0.06, -0.96, 0.06], ['c', -0.45, 0.00, -0.66, 0.00, -0.84, -0.03], ['c', -0.84, -0.18, -1.47, -0.51, -2.07, -1.11], ['c', -0.33, -0.33, -0.45, -0.51, -0.45, -0.63], ['c', 0.00, -0.06, 0.03, -0.15, 0.06, -0.24], ['c', 0.18, -0.33, 0.69, -0.60, 0.93, -0.48], ['c', 0.03, 0.03, 0.15, 0.12, 0.27, 0.24], ['c', 0.39, 0.42, 0.99, 0.57, 1.62, 0.45], ['c', 1.05, -0.21, 1.98, -1.02, 2.31, -2.01], ['c', 0.48, -1.53, -0.48, -2.55, -2.58, -2.67], ['c', -0.21, 0.00, -0.36, -0.03, -0.42, -0.06], ['c', -0.15, -0.09, -0.21, -0.51, -0.06, -0.78], ['c', 0.12, -0.27, 0.24, -0.33, 0.60, -0.36], ['c', 0.57, -0.06, 1.11, -0.42, 1.50, -0.99], ['c', 0.48, -0.72, 0.54, -1.59, 0.18, -2.31], ['c', -0.12, -0.21, -0.45, -0.54, -0.69, -0.69], ['c', -0.33, -0.21, -0.93, -0.45, -1.35, -0.51], ['l', -0.12, -0.03], ['l', -0.06, 0.48], ['c', -0.54, 2.94, -1.14, 6.24, -1.29, 6.75], ['c', -0.33, 1.35, -0.93, 2.61, -1.65, 3.60], ['c', -0.30, 0.36, -0.81, 0.90, -1.14, 1.14], ['c', -0.30, 0.24, -0.84, 0.48, -1.14, 0.57], ['c', -0.33, 0.09, -0.96, 0.09, -1.26, 0.03], ['c', -0.45, -0.12, -0.87, -0.39, -1.53, -0.96], ['c', -0.24, -0.15, -0.51, -0.39, -0.63, -0.48], ['c', -0.30, -0.21, -0.33, -0.33, -0.21, -0.63], ['c', 0.12, -0.18, 0.27, -0.36, 0.42, -0.45], ['c', 0.27, -0.12, 0.36, -0.09, 0.87, 0.33], ['c', 0.78, 0.60, 1.08, 0.75, 1.65, 0.72], ['c', 0.45, -0.03, 0.81, -0.21, 1.17, -0.54], ['c', 0.87, -0.90, 1.38, -2.85, 1.38, -5.37], ['c', 0.00, -0.60, 0.03, -1.11, 0.12, -2.04], ['c', 0.06, -0.69, 0.24, -2.01, 0.33, -2.58], ['c', 0.06, -0.24, 0.06, -0.42, 0.06, -0.42], ['c', 0.00, 0.00, -0.12, 0.03, -0.21, 0.09], ['c', -1.44, 0.57, -2.16, 1.65, -1.74, 2.55], ['c', 0.09, 0.15, 0.18, 0.24, 0.27, 0.33], ['c', 0.24, 0.21, 0.30, 0.27, 0.33, 0.39], ['c', 0.06, 0.24, 0.00, 0.63, -0.15, 0.78], ['c', -0.09, 0.12, -0.54, 0.21, -0.96, 0.24], ['c', -1.02, 0.03, -2.01, -0.48, -2.43, -1.32], ['c', -0.21, -0.45, -0.27, -0.90, -0.15, -1.44], ['c', 0.06, -0.27, 0.21, -0.66, 0.39, -0.93], ['c', 0.87, -1.29, 3.00, -2.22, 5.64, -2.43], ['z']], w: 19.643, h: 43.325 }, + 'tab.tiny': { d: [['M', 16.02, -17.25], ['c', 0.12, -0.09, 0.15, -0.09, 0.27, -0.09], ['c', 0.21, 0.03, 0.51, 0.30, 0.51, 0.45], ['c', 0.00, 0.06, -0.12, 0.18, -0.30, 0.36], ['c', -1.11, 1.08, -2.55, 1.59, -3.84, 1.41], ['c', -0.15, -0.03, -0.33, -0.06, -0.39, -0.09], ['c', -0.06, -0.03, -0.09, -0.03, -0.12, -0.03], ['c', 0.00, 0.00, -0.06, 0.42, -0.15, 0.93], ['c', -0.33, 2.01, -0.66, 3.69, -0.84, 4.26], ['c', -0.42, 1.41, -1.23, 2.67, -2.16, 3.33], ['c', -0.27, 0.18, -0.75, 0.42, -0.99, 0.48], ['c', -0.30, 0.09, -0.72, 0.09, -1.02, 0.06], ['c', -0.45, -0.09, -0.84, -0.33, -1.53, -0.90], ['c', -0.21, -0.18, -0.51, -0.39, -0.63, -0.48], ['c', -0.27, -0.21, -0.30, -0.24, -0.30, -0.36], ['c', 0.00, -0.12, 0.09, -0.36, 0.18, -0.45], ['c', 0.09, -0.09, 0.27, -0.18, 0.36, -0.18], ['c', 0.12, 0.00, 0.30, 0.12, 0.66, 0.45], ['c', 0.57, 0.51, 0.87, 0.69, 1.23, 0.72], ['c', 0.93, 0.06, 1.68, -0.78, 1.98, -2.37], ['c', 0.09, -0.39, 0.15, -0.75, 0.18, -1.53], ['c', 0.06, -0.99, 0.24, -2.79, 0.42, -4.05], ['c', 0.03, -0.30, 0.06, -0.57, 0.06, -0.60], ['c', 0.00, -0.06, -0.03, -0.09, -0.15, -0.12], ['c', -0.90, -0.18, -2.13, 0.06, -2.76, 0.57], ['c', -0.36, 0.30, -0.51, 0.60, -0.51, 1.02], ['c', 0.00, 0.45, 0.15, 0.75, 0.48, 0.99], ['c', 0.06, 0.06, 0.15, 0.18, 0.18, 0.24], ['c', 0.12, 0.24, 0.03, 0.63, -0.15, 0.69], ['c', -0.24, 0.12, -0.60, 0.15, -0.90, 0.15], ['c', -0.36, -0.03, -0.57, -0.09, -0.87, -0.24], ['c', -0.78, -0.36, -1.23, -1.11, -1.20, -1.92], ['c', 0.12, -1.53, 1.74, -2.49, 4.62, -2.70], ['c', 1.20, -0.09, 1.47, -0.03, 3.33, 0.57], ['c', 0.90, 0.30, 1.14, 0.36, 1.56, 0.39], ['c', 0.45, 0.00, 0.93, -0.06, 1.38, -0.21], ['c', 0.51, -0.18, 0.81, -0.33, 1.41, -0.75], ['z'], ['m', -4.68, 10.38], ['c', 0.39, -0.06, 0.84, 0.00, 1.20, 0.15], ['c', 0.24, 0.12, 0.36, 0.21, 0.45, 0.36], ['l', 0.09, 0.09], ['l', -0.06, 1.41], ['c', -0.09, 2.19, -0.18, 3.96, -0.27, 5.49], ['c', -0.03, 0.78, -0.06, 1.59, -0.06, 1.86], ['c', 0.00, 0.42, 0.00, 0.48, 0.06, 0.57], ['c', 0.06, 0.18, 0.18, 0.24, 0.36, 0.27], ['c', 0.18, 0.00, 0.39, -0.06, 0.84, -0.27], ['c', 0.45, -0.21, 0.54, -0.24, 0.63, -0.18], ['c', 0.12, 0.12, 0.15, 0.54, 0.03, 0.69], ['c', -0.03, 0.03, -0.15, 0.12, -0.27, 0.18], ['c', -0.15, 0.03, -0.30, 0.12, -0.36, 0.15], ['c', -0.87, 0.45, -1.02, 0.51, -1.26, 0.57], ['c', -0.33, 0.09, -0.60, 0.06, -0.84, -0.06], ['c', -0.42, -0.18, -0.63, -0.60, -0.87, -1.44], ['c', -0.30, -1.23, -0.57, -2.97, -0.66, -4.08], ['c', 0.00, -0.18, -0.03, -0.30, -0.03, -0.33], ['l', -0.06, 0.06], ['c', -0.18, 0.27, -1.11, 1.38, -1.68, 2.01], ['l', -0.33, 0.33], ['l', 0.06, 0.09], ['c', 0.06, 0.15, 0.27, 0.33, 0.48, 0.42], ['c', 0.27, 0.18, 0.51, 0.24, 0.96, 0.27], ['l', 0.39, 0.00], ['l', 0.03, 0.12], ['c', 0.12, 0.21, 0.03, 0.57, -0.15, 0.69], ['c', -0.03, 0.03, -0.21, 0.09, -0.36, 0.15], ['c', -0.27, 0.06, -0.39, 0.06, -0.75, 0.06], ['c', -0.48, 0.00, -0.75, -0.03, -1.08, -0.21], ['c', -0.21, -0.12, -0.51, -0.36, -0.57, -0.48], ['l', -0.03, -0.09], ['l', -0.39, 0.36], ['c', -1.47, 1.35, -2.49, 1.98, -3.42, 2.13], ['c', -0.54, 0.09, -0.96, -0.03, -1.62, -0.39], ['c', -0.21, -0.15, -0.45, -0.27, -0.54, -0.30], ['c', -0.18, -0.09, -0.21, -0.21, -0.12, -0.45], ['c', 0.06, -0.27, 0.33, -0.48, 0.54, -0.48], ['c', 0.03, 0.00, 0.27, 0.09, 0.48, 0.21], ['c', 0.48, 0.24, 0.69, 0.27, 0.99, 0.27], ['c', 0.60, -0.06, 1.17, -0.42, 2.10, -1.35], ['c', 2.22, -2.22, 4.02, -4.98, 4.95, -7.59], ['c', 0.21, -0.57, 0.30, -0.78, 0.48, -0.93], ['c', 0.15, -0.15, 0.42, -0.27, 0.66, -0.33], ['z'], ['m', -3.06, 12.84], ['c', 0.27, -0.03, 1.68, 0.00, 2.01, 0.03], ['c', 1.92, 0.18, 3.15, 0.69, 3.63, 1.50], ['c', 0.18, 0.33, 0.24, 0.51, 0.21, 0.93], ['c', 0.00, 0.45, -0.06, 0.72, -0.24, 1.11], ['c', -0.24, 0.51, -0.69, 1.02, -1.17, 1.35], ['c', -0.21, 0.15, -0.21, 0.15, -0.12, 0.18], ['c', 0.72, 0.15, 1.11, 0.30, 1.50, 0.57], ['c', 0.39, 0.24, 0.63, 0.57, 0.75, 0.96], ['c', 0.09, 0.30, 0.09, 0.96, 0.00, 1.29], ['c', -0.15, 0.57, -0.39, 1.05, -0.78, 1.50], ['c', -0.66, 0.75, -1.62, 1.32, -2.61, 1.53], ['c', -0.27, 0.06, -0.42, 0.06, -0.84, 0.06], ['c', -0.48, 0.00, -0.57, 0.00, -0.81, -0.06], ['c', -0.60, -0.18, -1.05, -0.42, -1.47, -0.81], ['c', -0.36, -0.39, -0.42, -0.51, -0.30, -0.75], ['c', 0.12, -0.21, 0.39, -0.39, 0.60, -0.39], ['c', 0.09, 0.00, 0.15, 0.03, 0.33, 0.18], ['c', 0.12, 0.12, 0.27, 0.24, 0.36, 0.27], ['c', 0.96, 0.48, 2.46, -0.33, 2.82, -1.50], ['c', 0.24, -0.81, -0.03, -1.44, -0.69, -1.77], ['c', -0.39, -0.21, -1.02, -0.33, -1.53, -0.33], ['c', -0.18, 0.00, -0.21, 0.00, -0.27, -0.09], ['c', -0.06, -0.09, -0.06, -0.30, -0.03, -0.48], ['c', 0.06, -0.18, 0.18, -0.36, 0.33, -0.36], ['c', 0.39, -0.06, 0.51, -0.09, 0.72, -0.18], ['c', 0.69, -0.36, 1.11, -1.23, 0.99, -2.01], ['c', -0.09, -0.51, -0.42, -0.90, -0.93, -1.17], ['c', -0.24, -0.12, -0.60, -0.27, -0.87, -0.30], ['c', -0.09, -0.03, -0.09, -0.03, -0.12, 0.12], ['c', 0.00, 0.09, -0.21, 1.11, -0.42, 2.25], ['c', -0.66, 3.75, -0.72, 3.99, -1.26, 5.07], ['c', -0.90, 1.89, -2.25, 2.85, -3.48, 2.61], ['c', -0.39, -0.09, -0.69, -0.27, -1.38, -0.84], ['c', -0.63, -0.51, -0.63, -0.48, -0.63, -0.60], ['c', 0.00, -0.18, 0.18, -0.48, 0.39, -0.57], ['c', 0.21, -0.12, 0.30, -0.09, 0.81, 0.33], ['c', 0.15, 0.15, 0.39, 0.30, 0.54, 0.36], ['c', 0.18, 0.12, 0.27, 0.12, 0.48, 0.15], ['c', 0.99, 0.06, 1.71, -0.78, 2.04, -2.46], ['c', 0.12, -0.66, 0.18, -1.14, 0.21, -2.22], ['c', 0.03, -1.23, 0.12, -2.25, 0.36, -3.63], ['c', 0.03, -0.24, 0.06, -0.45, 0.06, -0.48], ['c', -0.06, -0.03, -0.66, 0.27, -0.90, 0.42], ['c', -0.06, 0.06, -0.21, 0.18, -0.33, 0.30], ['c', -0.57, 0.57, -0.60, 1.35, -0.06, 1.74], ['c', 0.18, 0.12, 0.24, 0.24, 0.21, 0.51], ['c', -0.03, 0.30, -0.15, 0.42, -0.57, 0.48], ['c', -1.11, 0.24, -2.22, -0.42, -2.43, -1.38], ['c', -0.09, -0.45, 0.03, -1.02, 0.30, -1.47], ['c', 0.18, -0.24, 0.60, -0.63, 0.90, -0.84], ['c', 0.90, -0.60, 2.28, -1.02, 3.69, -1.11], ['z']], w: 15.709, h: 34.656 }, + 'timesig.common': { d: [['M', 6.66, -7.83], ['c', 0.72, -0.06, 1.41, -0.03, 1.98, 0.09], ['c', 1.20, 0.27, 2.34, 0.96, 3.09, 1.92], ['c', 0.63, 0.81, 1.08, 1.86, 1.14, 2.73], ['c', 0.06, 1.02, -0.51, 1.92, -1.44, 2.22], ['c', -0.24, 0.09, -0.30, 0.09, -0.63, 0.09], ['c', -0.33, 0.00, -0.42, 0.00, -0.63, -0.06], ['c', -0.66, -0.24, -1.14, -0.63, -1.41, -1.20], ['c', -0.15, -0.30, -0.21, -0.51, -0.24, -0.90], ['c', -0.06, -1.08, 0.57, -2.04, 1.56, -2.37], ['c', 0.18, -0.06, 0.27, -0.06, 0.63, -0.06], ['l', 0.45, 0.00], ['c', 0.06, 0.03, 0.09, 0.03, 0.09, 0.00], ['c', 0.00, 0.00, -0.09, -0.12, -0.24, -0.27], ['c', -1.02, -1.11, -2.55, -1.68, -4.08, -1.50], ['c', -1.29, 0.15, -2.04, 0.69, -2.40, 1.74], ['c', -0.36, 0.93, -0.42, 1.89, -0.42, 5.37], ['c', 0.00, 2.97, 0.06, 3.96, 0.24, 4.77], ['c', 0.24, 1.08, 0.63, 1.68, 1.41, 2.07], ['c', 0.81, 0.39, 2.16, 0.45, 3.18, 0.09], ['c', 1.29, -0.45, 2.37, -1.53, 3.03, -2.97], ['c', 0.15, -0.33, 0.33, -0.87, 0.39, -1.17], ['c', 0.09, -0.24, 0.15, -0.36, 0.30, -0.39], ['c', 0.21, -0.03, 0.42, 0.15, 0.39, 0.36], ['c', -0.06, 0.39, -0.42, 1.38, -0.69, 1.89], ['c', -0.96, 1.80, -2.49, 2.94, -4.23, 3.18], ['c', -0.99, 0.12, -2.58, -0.06, -3.63, -0.45], ['c', -0.96, -0.36, -1.71, -0.84, -2.40, -1.50], ['c', -1.11, -1.11, -1.80, -2.61, -2.04, -4.56], ['c', -0.06, -0.60, -0.06, -2.01, 0.00, -2.61], ['c', 0.24, -1.95, 0.90, -3.45, 2.01, -4.56], ['c', 0.69, -0.66, 1.44, -1.11, 2.37, -1.47], ['c', 0.63, -0.24, 1.47, -0.42, 2.22, -0.48], ['z']], w: 13.038, h: 15.689 }, + 'timesig.cut': { d: [['M', 6.24, -10.44], ['c', 0.09, -0.06, 0.09, -0.06, 0.48, -0.06], ['c', 0.36, 0.00, 0.36, 0.00, 0.45, 0.06], ['l', 0.06, 0.09], ['l', 0.00, 1.23], ['l', 0.00, 1.26], ['l', 0.27, 0.00], ['c', 1.26, 0.00, 2.49, 0.45, 3.48, 1.29], ['c', 1.05, 0.87, 1.80, 2.28, 1.89, 3.48], ['c', 0.06, 1.02, -0.51, 1.92, -1.44, 2.22], ['c', -0.24, 0.09, -0.30, 0.09, -0.63, 0.09], ['c', -0.33, 0.00, -0.42, 0.00, -0.63, -0.06], ['c', -0.66, -0.24, -1.14, -0.63, -1.41, -1.20], ['c', -0.15, -0.30, -0.21, -0.51, -0.24, -0.90], ['c', -0.06, -1.08, 0.57, -2.04, 1.56, -2.37], ['c', 0.18, -0.06, 0.27, -0.06, 0.63, -0.06], ['l', 0.45, 0.00], ['c', 0.06, 0.03, 0.09, 0.03, 0.09, 0.00], ['c', 0.00, -0.03, -0.45, -0.51, -0.66, -0.69], ['c', -0.87, -0.69, -1.83, -1.05, -2.94, -1.11], ['l', -0.42, 0.00], ['l', 0.00, 7.17], ['l', 0.00, 7.14], ['l', 0.42, 0.00], ['c', 0.69, -0.03, 1.23, -0.18, 1.86, -0.51], ['c', 1.05, -0.51, 1.89, -1.47, 2.46, -2.70], ['c', 0.15, -0.33, 0.33, -0.87, 0.39, -1.17], ['c', 0.09, -0.24, 0.15, -0.36, 0.30, -0.39], ['c', 0.21, -0.03, 0.42, 0.15, 0.39, 0.36], ['c', -0.03, 0.24, -0.21, 0.78, -0.39, 1.20], ['c', -0.96, 2.37, -2.94, 3.90, -5.13, 3.90], ['l', -0.30, 0.00], ['l', 0.00, 1.26], ['l', 0.00, 1.23], ['l', -0.06, 0.09], ['c', -0.09, 0.06, -0.09, 0.06, -0.45, 0.06], ['c', -0.39, 0.00, -0.39, 0.00, -0.48, -0.06], ['l', -0.06, -0.09], ['l', 0.00, -1.29], ['l', 0.00, -1.29], ['l', -0.21, -0.03], ['c', -1.23, -0.21, -2.31, -0.63, -3.21, -1.29], ['c', -0.15, -0.09, -0.45, -0.36, -0.66, -0.57], ['c', -1.11, -1.11, -1.80, -2.61, -2.04, -4.56], ['c', -0.06, -0.60, -0.06, -2.01, 0.00, -2.61], ['c', 0.24, -1.95, 0.93, -3.45, 2.04, -4.59], ['c', 0.42, -0.39, 0.78, -0.66, 1.26, -0.93], ['c', 0.75, -0.45, 1.65, -0.75, 2.61, -0.90], ['l', 0.21, -0.03], ['l', 0.00, -1.29], ['l', 0.00, -1.29], ['z'], ['m', -0.06, 10.44], ['c', 0.00, -5.58, 0.00, -6.99, -0.03, -6.99], ['c', -0.15, 0.00, -0.63, 0.27, -0.87, 0.45], ['c', -0.45, 0.36, -0.75, 0.93, -0.93, 1.77], ['c', -0.18, 0.81, -0.24, 1.80, -0.24, 4.74], ['c', 0.00, 2.97, 0.06, 3.96, 0.24, 4.77], ['c', 0.24, 1.08, 0.66, 1.68, 1.41, 2.07], ['c', 0.12, 0.06, 0.30, 0.12, 0.33, 0.15], ['l', 0.09, 0.00], ['l', 0.00, -6.96], ['z']], w: 13.038, h: 20.97 }, + 'timesig.imperfectum': { d: [['M', 13, -5], ['a', 8, 8, 0, 1, 0, 0, 10]], w: 13.038, h: 20.97 }, + 'timesig.imperfectum2': { d: [['M', 13, -5], ['a', 8, 8, 0, 1, 0, 0, 10]], w: 13.038, h: 20.97 }, + 'timesig.perfectum': { d: [['M', 13, -5], ['a', 8, 8, 0, 1, 0, 0, 10]], w: 13.038, h: 20.97 }, + 'timesig.perfectum2': { d: [['M', 13, -5], ['a', 8, 8, 0, 1, 0, 0, 10]], w: 13.038, h: 20.97 }, + 'f': { d: [['M', 9.93, -14.28], ['c', 1.53, -0.18, 2.88, 0.45, 3.12, 1.50], ['c', 0.12, 0.51, 0.00, 1.32, -0.27, 1.86], ['c', -0.15, 0.30, -0.42, 0.57, -0.63, 0.69], ['c', -0.69, 0.36, -1.56, 0.03, -1.83, -0.69], ['c', -0.09, -0.24, -0.09, -0.69, 0.00, -0.87], ['c', 0.06, -0.12, 0.21, -0.24, 0.45, -0.42], ['c', 0.42, -0.24, 0.57, -0.45, 0.60, -0.72], ['c', 0.03, -0.33, -0.09, -0.39, -0.63, -0.42], ['c', -0.30, 0.00, -0.45, 0.00, -0.60, 0.03], ['c', -0.81, 0.21, -1.35, 0.93, -1.74, 2.46], ['c', -0.06, 0.27, -0.48, 2.25, -0.48, 2.31], ['c', 0.00, 0.03, 0.39, 0.03, 0.90, 0.03], ['c', 0.72, 0.00, 0.90, 0.00, 0.99, 0.06], ['c', 0.42, 0.15, 0.45, 0.72, 0.03, 0.90], ['c', -0.12, 0.06, -0.24, 0.06, -1.17, 0.06], ['l', -1.05, 0.00], ['l', -0.78, 2.55], ['c', -0.45, 1.41, -0.87, 2.79, -0.96, 3.06], ['c', -0.87, 2.37, -2.37, 4.74, -3.78, 5.91], ['c', -1.05, 0.90, -2.04, 1.23, -3.09, 1.08], ['c', -1.11, -0.18, -1.89, -0.78, -2.04, -1.59], ['c', -0.12, -0.66, 0.15, -1.71, 0.54, -2.19], ['c', 0.69, -0.75, 1.86, -0.54, 2.22, 0.39], ['c', 0.06, 0.15, 0.09, 0.27, 0.09, 0.48], ['c', 0.00, 0.24, -0.03, 0.27, -0.12, 0.42], ['c', -0.03, 0.09, -0.15, 0.18, -0.27, 0.27], ['c', -0.09, 0.06, -0.27, 0.21, -0.36, 0.27], ['c', -0.24, 0.18, -0.36, 0.36, -0.39, 0.60], ['c', -0.03, 0.33, 0.09, 0.39, 0.63, 0.42], ['c', 0.42, 0.00, 0.63, -0.03, 0.90, -0.15], ['c', 0.60, -0.30, 0.96, -0.96, 1.38, -2.64], ['c', 0.09, -0.42, 0.63, -2.55, 1.17, -4.77], ['l', 1.02, -4.08], ['c', 0.00, -0.03, -0.36, -0.03, -0.81, -0.03], ['c', -0.72, 0.00, -0.81, 0.00, -0.93, -0.06], ['c', -0.42, -0.18, -0.39, -0.75, 0.03, -0.90], ['c', 0.09, -0.06, 0.27, -0.06, 1.05, -0.06], ['l', 0.96, 0.00], ['l', 0.00, -0.09], ['c', 0.06, -0.18, 0.30, -0.72, 0.51, -1.17], ['c', 1.20, -2.46, 3.30, -4.23, 5.34, -4.50], ['z']], w: 16.155, h: 19.445 }, + 'm': { d: [['M', 2.79, -8.91], ['c', 0.09, 0.00, 0.30, -0.03, 0.45, -0.03], ['c', 0.24, 0.03, 0.30, 0.03, 0.45, 0.12], ['c', 0.36, 0.15, 0.63, 0.54, 0.75, 1.02], ['l', 0.03, 0.21], ['l', 0.33, -0.30], ['c', 0.69, -0.69, 1.38, -1.02, 2.07, -1.02], ['c', 0.27, 0.00, 0.33, 0.00, 0.48, 0.06], ['c', 0.21, 0.09, 0.48, 0.36, 0.63, 0.60], ['c', 0.03, 0.09, 0.12, 0.27, 0.18, 0.42], ['c', 0.03, 0.15, 0.09, 0.27, 0.12, 0.27], ['c', 0.00, 0.00, 0.09, -0.09, 0.18, -0.21], ['c', 0.33, -0.39, 0.87, -0.81, 1.29, -0.99], ['c', 0.78, -0.33, 1.47, -0.21, 2.01, 0.33], ['c', 0.30, 0.33, 0.48, 0.69, 0.60, 1.14], ['c', 0.09, 0.42, 0.06, 0.54, -0.54, 3.06], ['c', -0.33, 1.29, -0.57, 2.40, -0.57, 2.43], ['c', 0.00, 0.12, 0.09, 0.21, 0.21, 0.21], ['c', 0.24, 0.00, 0.75, -0.30, 1.20, -0.72], ['c', 0.45, -0.39, 0.60, -0.45, 0.78, -0.27], ['c', 0.18, 0.18, 0.09, 0.36, -0.45, 0.87], ['c', -1.05, 0.96, -1.83, 1.47, -2.58, 1.71], ['c', -0.93, 0.33, -1.53, 0.21, -1.80, -0.33], ['c', -0.06, -0.15, -0.06, -0.21, -0.06, -0.45], ['c', 0.00, -0.24, 0.03, -0.48, 0.60, -2.82], ['c', 0.42, -1.71, 0.60, -2.64, 0.63, -2.79], ['c', 0.03, -0.57, -0.30, -0.75, -0.84, -0.48], ['c', -0.24, 0.12, -0.54, 0.39, -0.66, 0.63], ['c', -0.03, 0.09, -0.42, 1.38, -0.90, 3.00], ['c', -0.90, 3.15, -0.84, 3.00, -1.14, 3.15], ['l', -0.15, 0.09], ['l', -0.78, 0.00], ['c', -0.60, 0.00, -0.78, 0.00, -0.84, -0.06], ['c', -0.09, -0.03, -0.18, -0.18, -0.18, -0.27], ['c', 0.00, -0.03, 0.36, -1.38, 0.84, -2.97], ['c', 0.57, -2.04, 0.81, -2.97, 0.84, -3.12], ['c', 0.03, -0.54, -0.30, -0.72, -0.84, -0.45], ['c', -0.24, 0.12, -0.57, 0.42, -0.66, 0.63], ['c', -0.06, 0.09, -0.51, 1.44, -1.05, 2.97], ['c', -0.51, 1.56, -0.99, 2.85, -0.99, 2.91], ['c', -0.06, 0.12, -0.21, 0.24, -0.36, 0.30], ['c', -0.12, 0.06, -0.21, 0.06, -0.90, 0.06], ['c', -0.60, 0.00, -0.78, 0.00, -0.84, -0.06], ['c', -0.09, -0.03, -0.18, -0.18, -0.18, -0.27], ['c', 0.00, -0.03, 0.45, -1.38, 0.99, -2.97], ['c', 1.05, -3.18, 1.05, -3.18, 0.93, -3.45], ['c', -0.12, -0.27, -0.39, -0.30, -0.72, -0.15], ['c', -0.54, 0.27, -1.14, 1.17, -1.56, 2.40], ['c', -0.06, 0.15, -0.15, 0.30, -0.18, 0.36], ['c', -0.21, 0.21, -0.57, 0.27, -0.72, 0.09], ['c', -0.09, -0.09, -0.06, -0.21, 0.06, -0.63], ['c', 0.48, -1.26, 1.26, -2.46, 2.01, -3.21], ['c', 0.57, -0.54, 1.20, -0.87, 1.83, -1.02], ['z']], w: 14.687, h: 9.126 }, + 'p': { d: [['M', 1.92, -8.70], ['c', 0.27, -0.09, 0.81, -0.06, 1.11, 0.03], ['c', 0.54, 0.18, 0.93, 0.51, 1.17, 0.99], ['c', 0.09, 0.15, 0.15, 0.33, 0.18, 0.36], ['l', 0.00, 0.12], ['l', 0.30, -0.27], ['c', 0.66, -0.60, 1.35, -1.02, 2.13, -1.20], ['c', 0.21, -0.06, 0.33, -0.06, 0.78, -0.06], ['c', 0.45, 0.00, 0.51, 0.00, 0.84, 0.09], ['c', 1.29, 0.33, 2.07, 1.32, 2.25, 2.79], ['c', 0.09, 0.81, -0.09, 2.01, -0.45, 2.79], ['c', -0.54, 1.26, -1.86, 2.55, -3.18, 3.03], ['c', -0.45, 0.18, -0.81, 0.24, -1.29, 0.24], ['c', -0.69, -0.03, -1.35, -0.18, -1.86, -0.45], ['c', -0.30, -0.15, -0.51, -0.18, -0.69, -0.09], ['c', -0.09, 0.03, -0.18, 0.09, -0.18, 0.12], ['c', -0.09, 0.12, -1.05, 2.94, -1.05, 3.06], ['c', 0.00, 0.24, 0.18, 0.48, 0.51, 0.63], ['c', 0.18, 0.06, 0.54, 0.15, 0.75, 0.15], ['c', 0.21, 0.00, 0.36, 0.06, 0.42, 0.18], ['c', 0.12, 0.18, 0.06, 0.42, -0.12, 0.54], ['c', -0.09, 0.03, -0.15, 0.03, -0.78, 0.00], ['c', -1.98, -0.15, -3.81, -0.15, -5.79, 0.00], ['c', -0.63, 0.03, -0.69, 0.03, -0.78, 0.00], ['c', -0.24, -0.15, -0.24, -0.57, 0.03, -0.66], ['c', 0.06, -0.03, 0.48, -0.09, 0.99, -0.12], ['c', 0.87, -0.06, 1.11, -0.09, 1.35, -0.21], ['c', 0.18, -0.06, 0.33, -0.18, 0.39, -0.30], ['c', 0.06, -0.12, 3.24, -9.42, 3.27, -9.60], ['c', 0.06, -0.33, 0.03, -0.57, -0.15, -0.69], ['c', -0.09, -0.06, -0.12, -0.06, -0.30, -0.06], ['c', -0.69, 0.06, -1.53, 1.02, -2.28, 2.61], ['c', -0.09, 0.21, -0.21, 0.45, -0.27, 0.51], ['c', -0.09, 0.12, -0.33, 0.24, -0.48, 0.24], ['c', -0.18, 0.00, -0.36, -0.15, -0.36, -0.30], ['c', 0.00, -0.24, 0.78, -1.83, 1.26, -2.55], ['c', 0.72, -1.11, 1.47, -1.74, 2.28, -1.92], ['z'], ['m', 5.37, 1.47], ['c', -0.27, -0.12, -0.75, -0.03, -1.14, 0.21], ['c', -0.75, 0.48, -1.47, 1.68, -1.89, 3.15], ['c', -0.45, 1.47, -0.42, 2.34, 0.00, 2.70], ['c', 0.45, 0.39, 1.26, 0.21, 1.83, -0.36], ['c', 0.51, -0.51, 0.99, -1.68, 1.38, -3.27], ['c', 0.30, -1.17, 0.33, -1.74, 0.15, -2.13], ['c', -0.09, -0.15, -0.15, -0.21, -0.33, -0.30], ['z']], w: 14.689, h: 13.127 }, + 'r': { d: [['M', 6.33, -9.12], ['c', 0.27, -0.03, 0.93, 0.00, 1.20, 0.06], ['c', 0.84, 0.21, 1.23, 0.81, 1.02, 1.53], ['c', -0.24, 0.75, -0.90, 1.17, -1.56, 0.96], ['c', -0.33, -0.09, -0.51, -0.30, -0.66, -0.75], ['c', -0.03, -0.12, -0.09, -0.24, -0.12, -0.30], ['c', -0.09, -0.15, -0.30, -0.24, -0.48, -0.24], ['c', -0.57, 0.00, -1.38, 0.54, -1.65, 1.08], ['c', -0.06, 0.15, -0.33, 1.17, -0.90, 3.27], ['c', -0.57, 2.31, -0.81, 3.12, -0.87, 3.21], ['c', -0.03, 0.06, -0.12, 0.15, -0.18, 0.21], ['l', -0.12, 0.06], ['l', -0.81, 0.03], ['c', -0.69, 0.00, -0.81, 0.00, -0.90, -0.03], ['c', -0.09, -0.06, -0.18, -0.21, -0.18, -0.30], ['c', 0.00, -0.06, 0.39, -1.62, 0.90, -3.51], ['c', 0.84, -3.24, 0.87, -3.45, 0.87, -3.72], ['c', 0.00, -0.21, 0.00, -0.27, -0.03, -0.36], ['c', -0.12, -0.15, -0.21, -0.24, -0.42, -0.24], ['c', -0.24, 0.00, -0.45, 0.15, -0.78, 0.42], ['c', -0.33, 0.36, -0.45, 0.54, -0.72, 1.14], ['c', -0.03, 0.12, -0.21, 0.24, -0.36, 0.27], ['c', -0.12, 0.00, -0.15, 0.00, -0.24, -0.06], ['c', -0.18, -0.12, -0.18, -0.21, -0.06, -0.54], ['c', 0.21, -0.57, 0.42, -0.93, 0.78, -1.32], ['c', 0.54, -0.51, 1.20, -0.81, 1.95, -0.87], ['c', 0.81, -0.03, 1.53, 0.30, 1.92, 0.87], ['l', 0.12, 0.18], ['l', 0.09, -0.09], ['c', 0.57, -0.45, 1.41, -0.84, 2.19, -0.96], ['z']], w: 9.41, h: 9.132 }, + 's': { d: [['M', 4.47, -8.73], ['c', 0.09, 0.00, 0.36, -0.03, 0.57, -0.03], ['c', 0.75, 0.03, 1.29, 0.24, 1.71, 0.63], ['c', 0.51, 0.54, 0.66, 1.26, 0.36, 1.83], ['c', -0.24, 0.42, -0.63, 0.57, -1.11, 0.42], ['c', -0.33, -0.09, -0.60, -0.36, -0.60, -0.57], ['c', 0.00, -0.03, 0.06, -0.21, 0.15, -0.39], ['c', 0.12, -0.21, 0.15, -0.33, 0.18, -0.48], ['c', 0.00, -0.24, -0.06, -0.48, -0.15, -0.60], ['c', -0.15, -0.21, -0.42, -0.24, -0.75, -0.15], ['c', -0.27, 0.06, -0.48, 0.18, -0.69, 0.36], ['c', -0.39, 0.39, -0.51, 0.96, -0.33, 1.38], ['c', 0.09, 0.21, 0.42, 0.51, 0.78, 0.72], ['c', 1.11, 0.69, 1.59, 1.11, 1.89, 1.68], ['c', 0.21, 0.39, 0.24, 0.78, 0.15, 1.29], ['c', -0.18, 1.20, -1.17, 2.16, -2.52, 2.52], ['c', -1.02, 0.24, -1.95, 0.12, -2.70, -0.42], ['c', -0.72, -0.51, -0.99, -1.47, -0.60, -2.19], ['c', 0.24, -0.48, 0.72, -0.63, 1.17, -0.42], ['c', 0.33, 0.18, 0.54, 0.45, 0.57, 0.81], ['c', 0.00, 0.21, -0.03, 0.30, -0.33, 0.51], ['c', -0.33, 0.24, -0.39, 0.42, -0.27, 0.69], ['c', 0.06, 0.15, 0.21, 0.27, 0.45, 0.33], ['c', 0.30, 0.09, 0.87, 0.09, 1.20, 0.00], ['c', 0.75, -0.21, 1.23, -0.72, 1.29, -1.35], ['c', 0.03, -0.42, -0.15, -0.81, -0.54, -1.20], ['c', -0.24, -0.24, -0.48, -0.42, -1.41, -1.02], ['c', -0.69, -0.42, -1.05, -0.93, -1.05, -1.47], ['c', 0.00, -0.39, 0.12, -0.87, 0.30, -1.23], ['c', 0.27, -0.57, 0.78, -1.05, 1.38, -1.35], ['c', 0.24, -0.12, 0.63, -0.27, 0.90, -0.30], ['z']], w: 6.632, h: 8.758 }, + 'z': { d: [['M', 2.64, -7.95], ['c', 0.36, -0.09, 0.81, -0.03, 1.71, 0.27], ['c', 0.78, 0.21, 0.96, 0.27, 1.74, 0.30], ['c', 0.87, 0.06, 1.02, 0.03, 1.38, -0.21], ['c', 0.21, -0.15, 0.33, -0.15, 0.48, -0.06], ['c', 0.15, 0.09, 0.21, 0.30, 0.15, 0.45], ['c', -0.03, 0.06, -1.26, 1.26, -2.76, 2.67], ['l', -2.73, 2.55], ['l', 0.54, 0.03], ['c', 0.54, 0.03, 0.72, 0.03, 2.01, 0.15], ['c', 0.36, 0.03, 0.90, 0.06, 1.20, 0.09], ['c', 0.66, 0.00, 0.81, -0.03, 1.02, -0.24], ['c', 0.30, -0.30, 0.39, -0.72, 0.27, -1.23], ['c', -0.06, -0.27, -0.06, -0.27, -0.03, -0.39], ['c', 0.15, -0.30, 0.54, -0.27, 0.69, 0.03], ['c', 0.15, 0.33, 0.27, 1.02, 0.27, 1.50], ['c', 0.00, 1.47, -1.11, 2.70, -2.52, 2.79], ['c', -0.57, 0.03, -1.02, -0.09, -2.01, -0.51], ['c', -1.02, -0.42, -1.23, -0.48, -2.13, -0.54], ['c', -0.81, -0.06, -0.96, -0.03, -1.26, 0.18], ['c', -0.12, 0.06, -0.24, 0.12, -0.27, 0.12], ['c', -0.27, 0.00, -0.45, -0.30, -0.36, -0.51], ['c', 0.03, -0.06, 1.32, -1.32, 2.91, -2.79], ['l', 2.88, -2.73], ['c', -0.03, 0.00, -0.21, 0.03, -0.42, 0.06], ['c', -0.21, 0.03, -0.78, 0.09, -1.23, 0.12], ['c', -1.11, 0.12, -1.23, 0.15, -1.95, 0.27], ['c', -0.72, 0.15, -1.17, 0.18, -1.29, 0.09], ['c', -0.27, -0.18, -0.21, -0.75, 0.12, -1.26], ['c', 0.39, -0.60, 0.93, -1.02, 1.59, -1.20], ['z']], w: 8.573, h: 8.743 }, + '+': { d: [['M', 3.48, -9.3], ['c', 0.18, -0.09, 0.36, -0.09, 0.54, 0.00], ['c', 0.18, 0.09, 0.24, 0.15, 0.33, 0.30], ['l', 0.06, 0.15], ['l', 0.00, 1.29], ['l', 0.00, 1.29], ['l', 1.29, 0.00], ['c', 1.23, 0.00, 1.29, 0.00, 1.41, 0.06], ['c', 0.06, 0.03, 0.15, 0.09, 0.18, 0.12], ['c', 0.12, 0.09, 0.21, 0.33, 0.21, 0.48], ['c', 0.00, 0.15, -0.09, 0.39, -0.21, 0.48], ['c', -0.03, 0.03, -0.12, 0.09, -0.18, 0.12], ['c', -0.12, 0.06, -0.18, 0.06, -1.41, 0.06], ['l', -1.29, 0.00], ['l', 0.00, 1.29], ['c', 0.00, 1.23, 0.00, 1.29, -0.06, 1.41], ['c', -0.09, 0.18, -0.15, 0.24, -0.30, 0.33], ['c', -0.21, 0.09, -0.39, 0.09, -0.57, 0.00], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['c', -0.06, -0.12, -0.06, -0.18, -0.06, -1.41], ['l', 0.00, -1.29], ['l', -1.29, 0.00], ['c', -1.23, 0.00, -1.29, 0.00, -1.41, -0.06], ['c', -0.18, -0.09, -0.24, -0.15, -0.33, -0.33], ['c', -0.09, -0.18, -0.09, -0.36, 0.00, -0.54], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['l', 0.15, -0.06], ['l', 1.26, 0.00], ['l', 1.29, 0.00], ['l', 0.00, -1.29], ['c', 0.00, -1.23, 0.00, -1.29, 0.06, -1.41], ['c', 0.09, -0.18, 0.15, -0.24, 0.33, -0.33], ['z']], w: 7.507, h: 7.515 }, + ',': { d: [['M', 1.85, -3.36], ['c', 0.57, -0.15, 1.17, 0.03, 1.59, 0.45], ['c', 0.45, 0.45, 0.60, 0.96, 0.51, 1.89], ['c', -0.09, 1.23, -0.42, 2.46, -0.99, 3.93], ['c', -0.30, 0.72, -0.72, 1.62, -0.78, 1.68], ['c', -0.18, 0.21, -0.51, 0.18, -0.66, -0.06], ['c', -0.03, -0.06, -0.06, -0.15, -0.06, -0.18], ['c', 0.00, -0.06, 0.12, -0.33, 0.24, -0.63], ['c', 0.84, -1.80, 1.02, -2.61, 0.69, -3.24], ['c', -0.12, -0.24, -0.27, -0.36, -0.75, -0.60], ['c', -0.36, -0.15, -0.42, -0.21, -0.60, -0.39], ['c', -0.69, -0.69, -0.69, -1.71, 0.00, -2.40], ['c', 0.21, -0.21, 0.51, -0.39, 0.81, -0.45], ['z']], w: 3.452, h: 8.143 }, + '-': { d: [['M', 0.18, -5.34], ['c', 0.09, -0.06, 0.15, -0.06, 2.31, -0.06], ['c', 2.46, 0.00, 2.37, 0.00, 2.46, 0.21], ['c', 0.12, 0.21, 0.03, 0.42, -0.15, 0.54], ['c', -0.09, 0.06, -0.15, 0.06, -2.28, 0.06], ['c', -2.16, 0.00, -2.22, 0.00, -2.31, -0.06], ['c', -0.27, -0.15, -0.27, -0.54, -0.03, -0.69], ['z']], w: 5.001, h: 0.81 }, + '.': { d: [['M', 1.32, -3.36], ['c', 1.05, -0.27, 2.10, 0.57, 2.10, 1.65], ['c', 0.00, 1.08, -1.05, 1.92, -2.10, 1.65], ['c', -0.90, -0.21, -1.50, -1.14, -1.26, -2.04], ['c', 0.12, -0.63, 0.63, -1.11, 1.26, -1.26], ['z']], w: 3.413, h: 3.402 }, + 'scripts.wedge': { d: [['M', -3.66, -7.44], ['c', 0.06, -0.09, 0.00, -0.09, 0.81, 0.03], ['c', 1.86, 0.30, 3.84, 0.30, 5.73, 0.00], ['c', 0.78, -0.12, 0.72, -0.12, 0.78, -0.03], ['c', 0.15, 0.15, 0.12, 0.24, -0.24, 0.60], ['c', -0.93, 0.93, -1.98, 2.76, -2.67, 4.62], ['c', -0.30, 0.78, -0.51, 1.71, -0.51, 2.13], ['c', 0.00, 0.15, 0.00, 0.18, -0.06, 0.27], ['c', -0.12, 0.09, -0.24, 0.09, -0.36, 0.00], ['c', -0.06, -0.09, -0.06, -0.12, -0.06, -0.27], ['c', 0.00, -0.42, -0.21, -1.35, -0.51, -2.13], ['c', -0.69, -1.86, -1.74, -3.69, -2.67, -4.62], ['c', -0.36, -0.36, -0.39, -0.45, -0.24, -0.60], ['z']], w: 7.49, h: 7.752 }, + 'scripts.thumb': { d: [['M', -0.54, -3.69], ['c', 0.15, -0.03, 0.36, -0.06, 0.51, -0.06], ['c', 1.44, 0.00, 2.58, 1.11, 2.94, 2.85], ['c', 0.09, 0.48, 0.09, 1.32, 0.00, 1.80], ['c', -0.27, 1.41, -1.08, 2.43, -2.16, 2.73], ['l', -0.18, 0.06], ['l', 0.00, 0.12], ['c', 0.03, 0.06, 0.06, 0.45, 0.09, 0.87], ['c', 0.03, 0.57, 0.03, 0.78, 0.00, 0.84], ['c', -0.09, 0.27, -0.39, 0.48, -0.66, 0.48], ['c', -0.27, 0.00, -0.57, -0.21, -0.66, -0.48], ['c', -0.03, -0.06, -0.03, -0.27, 0.00, -0.84], ['c', 0.03, -0.42, 0.06, -0.81, 0.09, -0.87], ['l', 0.00, -0.12], ['l', -0.18, -0.06], ['c', -1.08, -0.30, -1.89, -1.32, -2.16, -2.73], ['c', -0.09, -0.48, -0.09, -1.32, 0.00, -1.80], ['c', 0.15, -0.84, 0.51, -1.53, 1.02, -2.04], ['c', 0.39, -0.39, 0.84, -0.63, 1.35, -0.75], ['z'], ['m', 1.05, 0.90], ['c', -0.15, -0.09, -0.21, -0.09, -0.45, -0.12], ['c', -0.15, 0.00, -0.30, 0.03, -0.39, 0.03], ['c', -0.57, 0.18, -0.90, 0.72, -1.08, 1.74], ['c', -0.06, 0.48, -0.06, 1.80, 0.00, 2.28], ['c', 0.15, 0.90, 0.42, 1.44, 0.90, 1.65], ['c', 0.18, 0.09, 0.21, 0.09, 0.51, 0.09], ['c', 0.30, 0.00, 0.33, 0.00, 0.51, -0.09], ['c', 0.48, -0.21, 0.75, -0.75, 0.90, -1.65], ['c', 0.03, -0.27, 0.03, -0.54, 0.03, -1.14], ['c', 0.00, -0.60, 0.00, -0.87, -0.03, -1.14], ['c', -0.15, -0.90, -0.45, -1.44, -0.90, -1.65], ['z']], w: 5.955, h: 9.75 }, + 'scripts.open': { d: [['M', -0.54, -3.69], ['c', 0.15, -0.03, 0.36, -0.06, 0.51, -0.06], ['c', 1.44, 0.00, 2.58, 1.11, 2.94, 2.85], ['c', 0.09, 0.48, 0.09, 1.32, 0.00, 1.80], ['c', -0.33, 1.74, -1.47, 2.85, -2.91, 2.85], ['c', -1.44, 0.00, -2.58, -1.11, -2.91, -2.85], ['c', -0.09, -0.48, -0.09, -1.32, 0.00, -1.80], ['c', 0.15, -0.84, 0.51, -1.53, 1.02, -2.04], ['c', 0.39, -0.39, 0.84, -0.63, 1.35, -0.75], ['z'], ['m', 1.11, 0.90], ['c', -0.21, -0.09, -0.27, -0.09, -0.51, -0.12], ['c', -0.30, 0.00, -0.42, 0.03, -0.66, 0.15], ['c', -0.24, 0.12, -0.51, 0.39, -0.66, 0.63], ['c', -0.54, 0.93, -0.63, 2.64, -0.21, 3.81], ['c', 0.21, 0.54, 0.51, 0.90, 0.93, 1.11], ['c', 0.21, 0.09, 0.24, 0.09, 0.54, 0.09], ['c', 0.30, 0.00, 0.33, 0.00, 0.54, -0.09], ['c', 0.42, -0.21, 0.72, -0.57, 0.93, -1.11], ['c', 0.36, -0.99, 0.36, -2.37, 0.00, -3.36], ['c', -0.21, -0.54, -0.51, -0.90, -0.90, -1.11], ['z']], w: 5.955, h: 7.5 }, + 'scripts.longphrase': { d: [['M', 1.47, -15.09], ['c', 0.36, -0.09, 0.66, -0.18, 0.69, -0.18], ['c', 0.06, 0.00, 0.06, 0.54, 0.06, 11.25], ['l', 0.00, 11.25], ['l', -0.63, 0.15], ['c', -0.66, 0.18, -1.44, 0.39, -1.50, 0.39], ['c', -0.03, 0.00, -0.03, -3.39, -0.03, -11.25], ['l', 0.00, -11.25], ['l', 0.36, -0.09], ['c', 0.21, -0.06, 0.66, -0.18, 1.05, -0.27], ['z']], w: 2.16, h: 23.04 }, + 'scripts.mediumphrase': { d: [['M', 1.47, -7.59], ['c', 0.36, -0.09, 0.66, -0.18, 0.69, -0.18], ['c', 0.06, 0.00, 0.06, 0.39, 0.06, 7.50], ['l', 0.00, 7.50], ['l', -0.63, 0.15], ['c', -0.66, 0.18, -1.44, 0.39, -1.50, 0.39], ['c', -0.03, 0.00, -0.03, -2.28, -0.03, -7.50], ['l', 0.00, -7.50], ['l', 0.36, -0.09], ['c', 0.21, -0.06, 0.66, -0.18, 1.05, -0.27], ['z']], w: 2.16, h: 15.54 }, + 'scripts.shortphrase': { d: [['M', 1.47, -7.59], ['c', 0.36, -0.09, 0.66, -0.18, 0.69, -0.18], ['c', 0.06, 0.00, 0.06, 0.21, 0.06, 3.75], ['l', 0.00, 3.75], ['l', -0.42, 0.09], ['c', -0.57, 0.18, -1.65, 0.45, -1.71, 0.45], ['c', -0.03, 0.00, -0.03, -0.72, -0.03, -3.75], ['l', 0.00, -3.75], ['l', 0.36, -0.09], ['c', 0.21, -0.06, 0.66, -0.18, 1.05, -0.27], ['z']], w: 2.16, h: 8.04 }, + 'scripts.snap': { d: [['M', 4.50, -3.39], ['c', 0.36, -0.03, 0.96, -0.03, 1.35, 0.00], ['c', 1.56, 0.15, 3.15, 0.90, 4.20, 2.01], ['c', 0.24, 0.27, 0.33, 0.42, 0.33, 0.60], ['c', 0.00, 0.27, 0.03, 0.24, -2.46, 2.22], ['c', -1.29, 1.02, -2.40, 1.86, -2.49, 1.92], ['c', -0.18, 0.09, -0.30, 0.09, -0.48, 0.00], ['c', -0.09, -0.06, -1.20, -0.90, -2.49, -1.92], ['c', -2.49, -1.98, -2.46, -1.95, -2.46, -2.22], ['c', 0.00, -0.18, 0.09, -0.33, 0.33, -0.60], ['c', 1.05, -1.08, 2.64, -1.86, 4.17, -2.01], ['z'], ['m', 1.29, 1.17], ['c', -1.47, -0.15, -2.97, 0.30, -4.14, 1.20], ['l', -0.18, 0.15], ['l', 0.06, 0.09], ['c', 0.15, 0.12, 3.63, 2.85, 3.66, 2.85], ['c', 0.03, 0.00, 3.51, -2.73, 3.66, -2.85], ['l', 0.06, -0.09], ['l', -0.18, -0.15], ['c', -0.84, -0.66, -1.89, -1.08, -2.94, -1.20], ['z']], w: 10.38, h: 6.84 } +}; + +// Custom characters that weren't generated from the font: +glyphs['noteheads.slash.whole'] = { d: [['M', 5, -5], ['l', 1, 1], ['l', -5, 5], ['l', -1, -1], ['z'], ['m', 4, 6], ['l', -5, -5], ['l', 2, -2], ['l', 5, 5], ['z'], ['m', 0, -2], ['l', 1, 1], ['l', -5, 5], ['l', -1, -1], ['z'], ['m', -4, 6], ['l', -5, -5], ['l', 2, -2], ['l', 5, 5], ['z']], w: 10.81, h: 15.63 }; + +glyphs['noteheads.slash.quarter'] = { d: [['M', 9, -6], ['l', 0, 4], ['l', -9, 9], ['l', 0, -4], ['z']], w: 9, h: 9 }; + +glyphs['noteheads.harmonic.quarter'] = { d: [['M', 3.63, -4.02], ['c', 0.09, -0.06, 0.18, -0.09, 0.24, -0.03], ['c', 0.03, 0.03, 0.87, 0.93, 1.83, 2.01], ['c', 1.50, 1.65, 1.80, 1.98, 1.80, 2.04], ['c', 0.00, 0.06, -0.30, 0.39, -1.80, 2.04], ['c', -0.96, 1.08, -1.80, 1.98, -1.83, 2.01], ['c', -0.06, 0.06, -0.15, 0.03, -0.24, -0.03], ['c', -0.12, -0.09, -3.54, -3.84, -3.60, -3.93], ['c', -0.03, -0.03, -0.03, -0.09, -0.03, -0.15], ['c', 0.03, -0.06, 3.45, -3.84, 3.63, -3.96], ['z']], w: 7.5, h: 8.165 }; + +glyphs['noteheads.triangle.quarter'] = { d: [['M', 0, 4], ['l', 9, 0], ['l', -4.5, -9], ['z']], w: 9, h: 9 }; + +var pathClone = function (pathArray) { + var res = []; + for (var i = 0, ii = pathArray.length; i < ii; i++) { + res[i] = []; + for (var j = 0, jj = pathArray[i].length; j < jj; j++) { + res[i][j] = pathArray[i][j]; + } + } + return res; +}; + +var pathScale = function (pathArray, kx, ky) { + for (var i = 0, ii = pathArray.length; i < ii; i++) { + var p = pathArray[i]; + var j, jj; + for (j = 1, jj = p.length; j < jj; j++) { + p[j] *= (j % 2) ? kx : ky; + } + } +}; + +var Glyphs = { + printSymbol: function (x, y, symb, paper, attrs) { + if (!glyphs[symb]) return null; + var pathArray = pathClone(glyphs[symb].d); + pathArray[0][1] += x; + pathArray[0][2] += y; + var path = ""; + for (var i = 0; i < pathArray.length; i++) + path += pathArray[i].join(" "); + attrs.path = path; + return paper.path(attrs); + }, + + getPathForSymbol: function (x, y, symb, scalex, scaley) { + scalex = scalex || 1; + scaley = scaley || 1; + if (!glyphs[symb]) return null; + var pathArray = pathClone(glyphs[symb].d); + if (scalex !== 1 || scaley !== 1) pathScale(pathArray, scalex, scaley); + pathArray[0][1] += x; + pathArray[0][2] += y; + + return pathArray; + }, + + getSymbolWidth: function (symbol) { + if (glyphs[symbol]) return glyphs[symbol].w; + return 0; + }, + + symbolHeightInPitches: function (symbol) { + var height = glyphs[symbol] ? glyphs[symbol].h : 0; + return height / spacing.STEP; + }, + + getSymbolAlign: function (symbol) { + if (symbol.substring(0, 7) === "scripts" && + symbol !== "scripts.roll") { + return "center"; + } + return "left"; + }, + + getYCorr: function (symbol) { + switch (symbol) { + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + case "+": return -2; + case "timesig.common": + case "timesig.cut": return 0; + case "flags.d32nd": return -1; + case "flags.d64th": return -2; + case "flags.u32nd": return 1; + case "flags.u64th": return 3; + case "rests.whole": return 1; + case "rests.half": return -1; + case "rests.8th": return -1; + case "rests.quarter": return -1; + case "rests.16th": return -1; + case "rests.32nd": return -1; + case "rests.64th": return -1; + case "f": + case "m": + case "p": + case "s": + case "z": + return -4; + case "scripts.trill": + case "scripts.upbow": + case "scripts.downbow": + return -2; + case "scripts.ufermata": + case "scripts.wedge": + case "scripts.roll": + case "scripts.shortphrase": + case "scripts.longphrase": + return -1; + case "scripts.dfermata": + return 1; + default: return 0; + } + }, + setSymbol: function (name, path) { + glyphs[name] = path; + } +}; + +module.exports = Glyphs; // we need the glyphs for layout information diff --git a/src/write/creation/translate-chord.js b/src/write/creation/translate-chord.js new file mode 100644 index 0000000000000000000000000000000000000000..5394ab568f9718837d733ae769b37e5495a1ffb4 --- /dev/null +++ b/src/write/creation/translate-chord.js @@ -0,0 +1,37 @@ +function germanNote(note) { + switch (note) { + case "B#": return "H#"; + case "B♯": return "H♯"; + case "B": return "H"; + case "Bb": return "B"; + case "B♭": return "B"; + } + return note; +} + +function translateChord(chordString, jazzchords, germanAlphabet) { + var lines = chordString.split("\n"); + for (let i = 0; i < lines.length; i++) { + let chord = lines[i]; + // If the chord isn't in a recognizable format then just skip it. + let reg = chord.match(/^([ABCDEFG][♯♭]?)?([^\/]+)?(\/([ABCDEFG][#b♯♭]?))?/); + if (!reg) { + continue; + } + let baseChord = reg[1] || ""; + let modifier = reg[2] || ""; + let bassNote = reg[4] || ""; + if (germanAlphabet) { + baseChord = germanNote(baseChord); + bassNote = germanNote(bassNote); + } + // This puts markers in the pieces of the chord that are read by the svg creator. + // After the main part of the chord (the letter, a sharp or flat, and "m") a marker is added. Before a slash a marker is added. + const marker = jazzchords ? "\x03" : ""; + const bass = bassNote ? "/" + bassNote : ""; + lines[i] = [baseChord, modifier, bass].join(marker); + } + return lines.join("\n"); +} + +module.exports = translateChord; diff --git a/src/write/draw/absolute.js b/src/write/draw/absolute.js new file mode 100644 index 0000000000000000000000000000000000000000..7edad60cbc23ece61a26b60f351763900e328238 --- /dev/null +++ b/src/write/draw/absolute.js @@ -0,0 +1,79 @@ +var drawTempo = require('./tempo'); +var drawRelativeElement = require('./relative'); +var spacing = require('../helpers/spacing'); +var setClass = require('../helpers/set-class'); +var elementGroup = require('./group-elements'); + +function drawAbsolute(renderer, params, bartop, selectables, staffPos) { + if (params.invisible) return; + var isTempo = params.children.length > 0 && params.children[0].type === "TempoElement"; + params.elemset = []; + elementGroup.beginGroup(renderer.paper, renderer.controller); + for (var i = 0; i < params.children.length; i++) { + var child = params.children[i]; + switch (child.type) { + case "TempoElement": + drawTempo(renderer, child); + break; + default: + var el = drawRelativeElement(renderer, child, bartop); + if (child.type === "symbol" && child.c && child.c.indexOf('notehead') >= 0) { + el.setAttribute('class', 'abcjs-notehead') + } + } + } + var klass = params.type; + if (params.type === 'note' || params.type === 'rest') { + params.counters = renderer.controller.classes.getCurrent(); + klass += ' d' + Math.round(params.durationClass * 1000) / 1000; + klass = klass.replace(/\./g, '-'); + if (params.abcelem.pitches) { + for (var j = 0; j < params.abcelem.pitches.length; j++) { + klass += ' p' + params.abcelem.pitches[j].pitch; + } + } + } + var g = elementGroup.endGroup(klass, params.type); + if (g) { + // TODO-PER-HACK! This corrects the classes because the tablature is not being created at the right time. + if (params.cloned) { + params.cloned.overrideClasses = g.className.baseVal + } + if (params.overrideClasses) { + var type = g.classList && g.classList.length > 0 ? g.classList[0] + ' ' : '' + g.setAttribute("class", type + params.overrideClasses) + } + if (isTempo) { + params.startChar = params.abcelem.startChar; + params.endChar = params.abcelem.endChar; + selectables.add(params, g, false, staffPos); + } else { + params.elemset.push(g); + var isSelectable = false; + if (params.type === 'note' || params.type === 'tabNumber') { + isSelectable = true; + } + selectables.add(params, g, isSelectable, staffPos); + } + } else if (params.elemset.length > 0) + selectables.add(params, params.elemset[0], params.type === 'note', staffPos); + // If there was no output, then don't add to the selectables. This happens when using the "y" spacer, for instance. + + if (params.klass) + setClass(params.elemset, "mark", "", "#00ff00"); + if (params.hint) + setClass(params.elemset, "abcjs-hint", "", null); + params.abcelem.abselem = params; + + if (params.heads && params.heads.length > 0) { + params.notePositions = []; + for (var jj = 0; jj < params.heads.length; jj++) { + params.notePositions.push({ + x: params.heads[jj].x + params.heads[jj].w / 2, + y: staffPos.zero - params.heads[jj].pitch * spacing.STEP + }); + } + } +} + +module.exports = drawAbsolute; diff --git a/src/write/draw/beam.js b/src/write/draw/beam.js new file mode 100644 index 0000000000000000000000000000000000000000..5d39aa4ed515732635b5bb57217997ca3195e3de --- /dev/null +++ b/src/write/draw/beam.js @@ -0,0 +1,56 @@ +var printPath = require('./print-path'); +var roundNumber = require("./round-number"); + +function drawBeam(renderer, params) { + if (params.beams.length === 0) return; + + var pathString = ""; + for (var i = 0; i < params.beams.length; i++) { + var beam = params.beams[i]; + if (beam.split) { + var slope = getSlope(renderer, beam.startX, beam.startY, beam.endX, beam.endY); + var xes = []; + for (var j = 0; j < beam.split.length; j += 2) { + xes.push([beam.split[j], beam.split[j + 1]]); + } + for (j = 0; j < xes.length; j++) { + var y1 = getY(beam.startX, beam.startY, slope, xes[j][0]); + var y2 = getY(beam.startX, beam.startY, slope, xes[j][1]); + pathString += draw(renderer, xes[j][0], y1, xes[j][1], y2, beam.dy); + } + } else + pathString += draw(renderer, beam.startX, beam.startY, beam.endX, beam.endY, beam.dy); + } + var durationClass = ("abcjs-d" + params.duration).replace(/\./g, "-"); + var klasses = renderer.controller.classes.generate('beam-elem ' + durationClass); + var el = printPath(renderer, { + path: pathString, + stroke: "none", + fill: renderer.foregroundColor, + 'class': klasses + }); + return [el]; +} + +function draw(renderer, startX, startY, endX, endY, dy) { + // the X coordinates are actual coordinates, but the Y coordinates are in pitches. + startY = roundNumber(renderer.calcY(startY)); + endY = roundNumber(renderer.calcY(endY)); + startX = roundNumber(startX); + endX = roundNumber(endX); + var startY2 = roundNumber(startY + dy); + var endY2 = roundNumber(endY + dy); + return "M" + startX + " " + startY + " L" + endX + " " + endY + + "L" + endX + " " + endY2 + " L" + startX + " " + startY2 + "z"; +} + +function getSlope(renderer, startX, startY, endX, endY) { + return (endY - startY) / (endX - startX); +} + +function getY(startX, startY, slope, currentX) { + var x = currentX - startX; + return startY + x * slope; +} + +module.exports = drawBeam; diff --git a/src/write/draw/brace.js b/src/write/draw/brace.js new file mode 100644 index 0000000000000000000000000000000000000000..d0a2b220e0fb459d5e30ea63730d039f8c154a97 --- /dev/null +++ b/src/write/draw/brace.js @@ -0,0 +1,106 @@ +var sprintf = require('./sprintf'); +var spacing = require('../helpers/spacing'); +var renderText = require('./text'); + +function drawBrace(renderer, params, selectables) { + // The absoluteY number is the spot where the note on the first ledger line is drawn (i.e. middle C if treble clef) + // The STEP offset here moves it to the top and bottom lines + var startY = params.startVoice.staff.absoluteY - spacing.STEP * 10; + if (params.endVoice && params.endVoice.staff) + params.endY = params.endVoice.staff.absoluteY - spacing.STEP * 2; + else if (params.lastContinuedVoice && params.lastContinuedVoice.staff) + params.endY = params.lastContinuedVoice.staff.absoluteY - spacing.STEP * 2; + else + params.endY = params.startVoice.staff.absoluteY - spacing.STEP * 2; + return draw(renderer, params.x, startY, params.endY, params.type, params.header, selectables); +} + +function straightPath(renderer, xLeft, yTop, yBottom, type) { + xLeft += spacing.STEP; + var xLineWidth = spacing.STEP * 0.75; + var yOverlap = spacing.STEP * 0.75; + var height = yBottom - yTop; + // Straight line + var pathString = sprintf("M %f %f l %f %f l %f %f l %f %f z", + xLeft, yTop - yOverlap, // top left line + 0, height + yOverlap * 2, // bottom left line + xLineWidth, 0, // bottom right line + 0, - (height + yOverlap * 2) // top right line + ); + // Top arm + var wCurve = spacing.STEP * 2; + var hCurve = spacing.STEP; + pathString += sprintf("M %f %f q %f %f %f %f q %f %f %f %f z", + xLeft + xLineWidth, yTop - yOverlap, // top left arm + wCurve * 0.6, hCurve * 0.2, + wCurve, -hCurve, // right point + -wCurve * 0.1, hCurve * 0.3, + -wCurve, hCurve + spacing.STEP // left bottom + ); + // Bottom arm + pathString += sprintf("M %f %f q %f %f %f %f q %f %f %f %f z", + xLeft + xLineWidth, yTop + yOverlap + height, // bottom left arm + wCurve * 0.6, -hCurve * 0.2, + wCurve, hCurve, // right point + -wCurve * 0.1, -hCurve * 0.3, + -wCurve, -hCurve - spacing.STEP // left bottom + ); + return renderer.paper.path({ path: pathString, stroke: renderer.foregroundColor, fill: renderer.foregroundColor, 'class': renderer.controller.classes.generate(type), "data-name": type }); +} + +function curvyPath(renderer, xLeft, yTop, yBottom, type) { + var yHeight = yBottom - yTop; + + var pathString = curve(xLeft, + yTop, + [7.5, -8, 21, 0, 18.5, -10.5, 7.5], + [0, yHeight / 5.5, yHeight / 3.14, yHeight / 2, yHeight / 2.93, yHeight / 4.88, 0]); + + pathString += curve(xLeft, + yTop, + [0, 17.5, -7.5, 6.6, -5, 20, 0], + [yHeight / 2, yHeight / 1.46, yHeight / 1.22, yHeight, yHeight / 1.19, yHeight / 1.42, yHeight / 2]); + + return renderer.paper.path({ path: pathString, stroke: renderer.foregroundColor, fill: renderer.foregroundColor, 'class': renderer.controller.classes.generate(type), "data-name": type }); +} + +function curve(xLeft, yTop, xCurve, yCurve) { + return sprintf("M %f %f C %f %f %f %f %f %f C %f %f %f %f %f %f z", + xLeft + xCurve[0], yTop + yCurve[0], + xLeft + xCurve[1], yTop + yCurve[1], + xLeft + xCurve[2], yTop + yCurve[2], + xLeft + xCurve[3], yTop + yCurve[3], + xLeft + xCurve[4], yTop + yCurve[4], + xLeft + xCurve[5], yTop + yCurve[5], + xLeft + xCurve[6], yTop + yCurve[6]); +} + +var draw = function (renderer, xLeft, yTop, yBottom, type, header, selectables) {//Tony + var ret; + if (header) { + renderer.paper.openGroup({ klass: renderer.controller.classes.generate("staff-extra voice-name"), "data-name": type }); + var position = yTop + (yBottom - yTop) / 2; + position = position - renderer.controller.getTextSize.baselineToCenter(header, "voicefont", 'staff-extra voice-name', 0, 1); + + renderText(renderer, { + x: renderer.padding.left, + y: position, + text: header, + type: 'voicefont', + klass: 'staff-extra voice-name', + anchor: 'start', + centerVertically: true + }); + } + if (type === "brace") + ret = curvyPath(renderer, xLeft, yTop, yBottom, type); + else if (type === "bracket") + ret = straightPath(renderer, xLeft, yTop, yBottom, type); + if (header) { + ret = renderer.paper.closeGroup(); + } + selectables.wrapSvgEl({ el_type: type, startChar: -1, endChar: -1 }, ret); + + return ret; +}; +module.exports = drawBrace; diff --git a/src/write/draw/crescendo.js b/src/write/draw/crescendo.js new file mode 100644 index 0000000000000000000000000000000000000000..8c7cb1a94a90bd41cacd935167bd5c6af6b3abfc --- /dev/null +++ b/src/write/draw/crescendo.js @@ -0,0 +1,38 @@ +var sprintf = require('./sprintf'); +var printPath = require('./print-path'); +var roundNumber = require("./round-number"); + +function drawCrescendo(renderer, params, selectables) { + if (params.pitch === undefined) + window.console.error("Crescendo Element y-coordinate not set."); + var y = renderer.calcY(params.pitch) + 4; // This is the top pixel to use (it is offset a little so that it looks good with the volume marks.) + var height = 8; + + // TODO-PER: This is just a quick hack to make the dynamic marks not crash if they are mismatched. See the slur treatment for the way to get the beginning and end. + var left = params.anchor1 ? params.anchor1.x : 0; + var right = params.anchor2 ? params.anchor2.x : 800; + + var el; + if (params.dir === "<") { + el = drawLine(renderer, y + height / 2, y, y + height / 2, y + height, left, right); + } else { + el = drawLine(renderer, y, y + height / 2, y + height, y + height / 2, left, right); + } + selectables.wrapSvgEl({ el_type: "dynamicDecoration", startChar: -1, endChar: -1 }, el); + return [el]; +} + +var drawLine = function (renderer, y1, y2, y3, y4, left, right) { + y1 = roundNumber(y1); + y2 = roundNumber(y2); + y3 = roundNumber(y3); + y4 = roundNumber(y4); + left = roundNumber(left); + right = roundNumber(right); + + var pathString = sprintf("M %f %f L %f %f M %f %f L %f %f", + left, y1, right, y2, left, y3, right, y4); + return printPath(renderer, { path: pathString, highlight: "stroke", stroke: renderer.foregroundColor, 'class': renderer.controller.classes.generate('dynamics decoration'), "data-name": "dynamics" }); +}; + +module.exports = drawCrescendo; diff --git a/src/write/draw/debug-box.js b/src/write/draw/debug-box.js new file mode 100644 index 0000000000000000000000000000000000000000..3fb416ad2094451bb64df81c3f0181c0b93653d0 --- /dev/null +++ b/src/write/draw/debug-box.js @@ -0,0 +1,8 @@ +function printDebugBox(renderer, attr, comment) { + var box = renderer.paper.rectBeneath(attr); + if (comment) + renderer.paper.text(comment, { x: 0, y: attr.y + 7, "text-anchor": "start", "font-size": "14px", fill: "rgba(0,0,255,.4)", stroke: "rgba(0,0,255,.4)" }); + return box; +} + +module.exports = printDebugBox; diff --git a/src/write/draw/draw.js b/src/write/draw/draw.js new file mode 100644 index 0000000000000000000000000000000000000000..69d8997df367cd8816e1eacce9f39af73aeb2884 --- /dev/null +++ b/src/write/draw/draw.js @@ -0,0 +1,73 @@ +var drawStaffGroup = require('./staff-group'); +var setPaperSize = require('./set-paper-size'); +var nonMusic = require('./non-music'); +var spacing = require('../helpers/spacing'); +var Selectables = require('./selectables'); + +function draw(renderer, classes, abcTune, width, maxWidth, responsive, scale, selectTypes, tuneNumber, lineOffset) { + var selectables = new Selectables(renderer.paper, selectTypes, tuneNumber); + var groupClasses = {} + if (classes.shouldAddClasses) + groupClasses.klass = "abcjs-meta-top" + renderer.paper.openGroup(groupClasses) + renderer.moveY(renderer.padding.top); + nonMusic(renderer, abcTune.topText, selectables); + renderer.paper.closeGroup() + renderer.moveY(renderer.spacing.music); + var staffgroups = []; + for (var line = 0; line < abcTune.lines.length; line++) { + classes.incrLine(); + var abcLine = abcTune.lines[line]; + if (abcLine.staff) { + if (classes.shouldAddClasses) + groupClasses.klass = "abcjs-staff-wrapper abcjs-l" + classes.lineNumber + renderer.paper.openGroup(groupClasses) + if (abcLine.vskip) { + renderer.moveY(abcLine.vskip); + } + if (staffgroups.length >= 1) + addStaffPadding(renderer, renderer.spacing.staffSeparation, staffgroups[staffgroups.length - 1], abcLine.staffGroup); + var staffgroup = engraveStaffLine(renderer, abcLine.staffGroup, selectables, line); + staffgroup.line = lineOffset + line; // If there are non-music lines then the staffgroup array won't line up with the line array, so this keeps track. + staffgroups.push(staffgroup); + renderer.paper.closeGroup() + } else if (abcLine.nonMusic) { + if (classes.shouldAddClasses) + groupClasses.klass = "abcjs-non-music" + renderer.paper.openGroup(groupClasses) + nonMusic(renderer, abcLine.nonMusic, selectables); + renderer.paper.closeGroup() + } + } + + classes.reset(); + if (abcTune.bottomText && abcTune.bottomText.rows && abcTune.bottomText.rows.length > 0) { + if (classes.shouldAddClasses) + groupClasses.klass = "abcjs-meta-bottom" + renderer.paper.openGroup(groupClasses) + renderer.moveY(24); // TODO-PER: Empirically discovered. What variable should this be? + nonMusic(renderer, abcTune.bottomText, selectables); + renderer.paper.closeGroup() + } + setPaperSize(renderer, maxWidth, scale, responsive); + return { staffgroups: staffgroups, selectables: selectables.getElements() }; +} + +function engraveStaffLine(renderer, staffGroup, selectables, lineNumber) { + drawStaffGroup(renderer, staffGroup, selectables, lineNumber); + var height = staffGroup.height * spacing.STEP; + renderer.y += height; + return staffGroup; +} + +function addStaffPadding(renderer, staffSeparation, lastStaffGroup, thisStaffGroup) { + var lastStaff = lastStaffGroup.staffs[lastStaffGroup.staffs.length - 1]; + var lastBottomLine = -(lastStaff.bottom - 2); // The 2 is because the scale goes to 2 below the last line. + var nextTopLine = thisStaffGroup.staffs[0].top - 10; // Because 10 represents the top line. + var naturalSeparation = nextTopLine + lastBottomLine; // This is how far apart they'd be without extra spacing + var separationInPixels = naturalSeparation * spacing.STEP; + if (separationInPixels < staffSeparation) + renderer.moveY(staffSeparation - separationInPixels); +} + +module.exports = draw; diff --git a/src/write/draw/dynamics.js b/src/write/draw/dynamics.js new file mode 100644 index 0000000000000000000000000000000000000000..81b41fb4fecd0fa8abfded9d6fa4f35ab03b4482 --- /dev/null +++ b/src/write/draw/dynamics.js @@ -0,0 +1,20 @@ +var printSymbol = require('./print-symbol'); + +function drawDynamics(renderer, params, selectables) { + if (params.pitch === undefined) + window.console.error("Dynamic Element y-coordinate not set."); + var scalex = 1; + var scaley = 1; + var el = printSymbol(renderer, params.anchor.x, params.pitch, params.dec, { + scalex: scalex, + scaley: scaley, + klass: renderer.controller.classes.generate('decoration dynamics'), + fill: renderer.foregroundColor, + stroke: "none", + name: "dynamics" + }); + selectables.wrapSvgEl({ el_type: "dynamicDecoration", startChar: -1, endChar: -1, decoration: params.dec }, el); + return [el]; +} + +module.exports = drawDynamics; diff --git a/src/write/draw/ending.js b/src/write/draw/ending.js new file mode 100644 index 0000000000000000000000000000000000000000..e6283893587c02844179c94d772183799532edf3 --- /dev/null +++ b/src/write/draw/ending.js @@ -0,0 +1,46 @@ +var sprintf = require('./sprintf'); +var renderText = require('./text'); +var printPath = require('./print-path'); +var roundNumber = require("./round-number"); + +function drawEnding(renderer, params, linestartx, lineendx, selectables) { + if (params.pitch === undefined) + window.console.error("Ending Element y-coordinate not set."); + var y = roundNumber(renderer.calcY(params.pitch)); + var height = 20; + var pathString = ''; + + if (params.anchor1) { + linestartx = roundNumber(params.anchor1.x + params.anchor1.w); + pathString += sprintf("M %f %f L %f %f ", + linestartx, y, linestartx, roundNumber(y + height)); + } + + if (params.anchor2) { + lineendx = roundNumber(params.anchor2.x); + pathString += sprintf("M %f %f L %f %f ", + lineendx, y, lineendx, roundNumber(y + height)); + } + + pathString += sprintf("M %f %f L %f %f ", + linestartx, y, lineendx, y); + + renderer.paper.openGroup({ klass: renderer.controller.classes.generate("ending"), "data-name": "ending" }); + printPath(renderer, { path: pathString, stroke: renderer.foregroundColor, fill: renderer.foregroundColor, "data-name": "line" }); + if (params.anchor1) + renderText(renderer, { + x: roundNumber(linestartx + 5), + y: roundNumber(renderer.calcY(params.pitch - 0.5)), + text: params.text, + type: 'repeatfont', + klass: 'ending', + anchor: "start", + noClass: true, + name: params.text + }); + var g = renderer.paper.closeGroup(); + selectables.wrapSvgEl({ el_type: "ending", startChar: -1, endChar: -1 }, g); + return [g]; +} + +module.exports = drawEnding; diff --git a/src/write/draw/glissando.js b/src/write/draw/glissando.js new file mode 100644 index 0000000000000000000000000000000000000000..884f17f5e6638e4290aa72896b4eed749389ded4 --- /dev/null +++ b/src/write/draw/glissando.js @@ -0,0 +1,76 @@ +var sprintf = require('./sprintf'); +var printPath = require('./print-path'); +var roundNumber = require("./round-number"); + +function drawGlissando(renderer, params, selectables) { + if (!params.anchor1 || !params.anchor2 || !params.anchor1.heads || !params.anchor2.heads || params.anchor1.heads.length === 0 || params.anchor2.heads.length === 0) + window.console.error("Glissando Element not set."); + + var margin = 4; + var leftY = renderer.calcY(params.anchor1.heads[0].pitch) + var rightY = renderer.calcY(params.anchor2.heads[0].pitch) + var leftX = params.anchor1.x + params.anchor1.w / 2 + var rightX = params.anchor2.x + params.anchor2.w / 2 + + var len = lineLength(leftX, leftY, rightX, rightY) + var marginLeft = params.anchor1.w / 2 + margin + var marginRight = params.anchor2.w / 2 + margin + var s = slope(leftX, leftY, rightX, rightY) + var leftYAdj = getY(leftY, s, marginLeft) + var rightYAdj = getY(rightY, s, -marginRight) + var num = numSquigglies(len - marginLeft - marginRight) + + var el = drawSquiggly(renderer, leftX + marginLeft, leftYAdj, num, s) + selectables.wrapSvgEl({ el_type: "glissando", startChar: -1, endChar: -1 }, el); + return [el]; +} + +function lineLength(leftX, leftY, rightX, rightY) { + // The length from notehead center to notehead center. + var w = rightX - leftX + var h = rightY - leftY + return Math.sqrt(w * w + h * h) +} + +function slope(leftX, leftY, rightX, rightY) { + return (rightY - leftY) / (rightX - leftX) +} + +function getY(y, slope, xOfs) { + return roundNumber(y + (xOfs) * slope); +} + +function numSquigglies(length) { + var endLen = 5; // The width of the end - that is, the non repeating part + return Math.max(2, Math.floor((length - endLen * 2) / 6)); +} + +var leftStart = [[3.5, -4.8]] +var right = [[1.5, -1], [.3, -.3], [-3.5, 3.8]] +var leftEnd = [[-1.5, 2]] +var top = [[3, 4], [3, -4]] +var bottom = [[-3, 4], [-3, -4]] + +function segment(arr, slope) { + var ret = ""; + for (var i = 0; i < arr.length; i++) { + ret += 'l' + arr[i][0] + ' ' + getY(arr[i][1], slope, arr[i][0]) + } + return ret +} + +var drawSquiggly = function (renderer, x, y, num, slope) { + var p = sprintf("M %f %f", x, y); + p += segment(leftStart, slope) + var i + for (i = 0; i < num; i++) { + p += segment(top, slope) + } + p += segment(right, slope) + for (i = 0; i < num; i++) + p += segment(bottom, slope) + p += segment(leftEnd, slope) + 'z' + return printPath(renderer, { path: p, highlight: "stroke", stroke: renderer.foregroundColor, 'class': renderer.controller.classes.generate('decoration'), "data-name": "glissando" }); +} + +module.exports = drawGlissando; diff --git a/src/write/draw/group-elements.js b/src/write/draw/group-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..e6d7a2d88d76d4c91330e0cbcd1ff0733fae7cf0 --- /dev/null +++ b/src/write/draw/group-elements.js @@ -0,0 +1,66 @@ +/** + * Begin a group of glyphs that will always be moved, scaled and highlighted together + */ + +var roundNumber = require("./round-number"); + +function Group() { + this.ingroup = false; +} + +Group.prototype.beginGroup = function (paper, controller) { + this.paper = paper; + this.controller = controller; + this.path = []; + this.lastM = [0, 0]; + this.ingroup = true; + this.paper.openGroup(); +}; + +Group.prototype.isInGroup = function () { + return this.ingroup; +} + +Group.prototype.addPath = function (path) { + path = path || []; + if (path.length === 0) return; + path[0][0] = "m"; + path[0][1] = roundNumber(path[0][1] - this.lastM[0]); + path[0][2] = roundNumber(path[0][2] - this.lastM[1]); + this.lastM[0] += path[0][1]; + this.lastM[1] += path[0][2]; + this.path.push(path[0]); + for (var i = 1, ii = path.length; i < ii; i++) { + if (path[i][0] === "m") { + this.lastM[0] += path[i][1]; + this.lastM[1] += path[i][2]; + } + this.path.push(path[i]); + } +}; + +/** + * End a group of glyphs that will always be moved, scaled and highlighted together + */ +Group.prototype.endGroup = function (klass, name) { + this.ingroup = false; + //if (this.path.length === 0) return null; + var path = ""; + for (var i = 0; i < this.path.length; i++) + path += this.path[i].join(" "); + this.path = []; + + var ret = this.paper.closeGroup(); + if (ret) { + ret.setAttribute("class", this.controller.classes.generate(klass)) + ret.setAttribute("fill", this.controller.renderer.foregroundColor) + ret.setAttribute("stroke", "none") + ret.setAttribute("data-name", name) + } + return ret; +}; + +// There is just a singleton of this object. +var elementGroup = new Group(); + +module.exports = elementGroup; diff --git a/src/write/draw/horizontal-line.js b/src/write/draw/horizontal-line.js new file mode 100644 index 0000000000000000000000000000000000000000..90c2129091706b6a9ce1c4dd8bff6c0b25332c2a --- /dev/null +++ b/src/write/draw/horizontal-line.js @@ -0,0 +1,25 @@ +// For debugging, it is sometimes useful to know where you are vertically. +var sprintf = require("./sprintf"); + +function printHorizontalLine(renderer, width, vertical, comment) { + var dy = 0.35; + var fill = "rgba(0,0,255,.4)"; + var y = renderer.y; + if (vertical) y = vertical; + y = Math.round(y); + renderer.paper.text("" + Math.round(y), { x: 10, y: y, "text-anchor": "start", "font-size": "18px", fill: fill, stroke: fill }); + var x1 = 50; + var x2 = width; + var pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y - dy, x1 + x2, y - dy, + x2, y + dy, x1, y + dy); + renderer.paper.pathToBack({ path: pathString, stroke: "none", fill: fill, 'class': renderer.controller.classes.generate('staff') }); + for (var i = 1; i < width / 100; i++) { + pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", i * 100 - dy, y - 5, i * 100 - dy, y + 5, + i * 100 + dy, y - 5, i * 100 + dy, y + 5); + renderer.paper.pathToBack({ path: pathString, stroke: "none", fill: fill, 'class': renderer.controller.classes.generate('staff') }); + } + if (comment) + renderer.paper.text(comment, { x: width + 70, y: y, "text-anchor": "start", "font-size": "18px", fill: fill, stroke: fill }); +} + +module.exports = printHorizontalLine; diff --git a/src/write/draw/non-music.js b/src/write/draw/non-music.js new file mode 100644 index 0000000000000000000000000000000000000000..4cd4e356e1313b0367459cfd35499eacdcd70dcc --- /dev/null +++ b/src/write/draw/non-music.js @@ -0,0 +1,52 @@ +var drawSeparator = require('./separator'); +var renderText = require('./text'); + +function nonMusic(renderer, obj, selectables) { + for (var i = 0; i < obj.rows.length; i++) { + var row = obj.rows[i]; + if (row.absmove) { + renderer.absolutemoveY(row.absmove); + } else if (row.move) { + renderer.moveY(row.move); + } else if (row.text || row.phrases) { + var x = row.left ? row.left : 0; + var el = renderText(renderer, { + x: x, + y: renderer.y, + text: row.text, + phrases: row.phrases, + 'dominant-baseline': row['dominant-baseline'], + type: row.font, + klass: row.klass, + name: row.name, + anchor: row.anchor + }); + if (row.absElemType) { + selectables.wrapSvgEl({ + el_type: row.absElemType, + name: row.name, + startChar: row.startChar, + endChar: row.endChar, + text: row.text + }, el); + } + } else if (row.separator) { + drawSeparator(renderer, row.separator) + } else if (row.startGroup) { + renderer.paper.openGroup({ klass: row.klass, "data-name": row.name }); + } else if (row.endGroup) { + // TODO-PER: also create a history element with the title "row.endGroup" + var g = renderer.paper.closeGroup(); + if (row.absElemType) + selectables.wrapSvgEl({ + el_type: row.absElemType, + name: row.name, + startChar: row.startChar, + endChar: row.endChar, + text: "" + }, g); + } + } +} + +module.exports = nonMusic; diff --git a/src/write/draw/print-line.js b/src/write/draw/print-line.js new file mode 100644 index 0000000000000000000000000000000000000000..04706277cbde9a770a74e69e339ddff0cd52303f --- /dev/null +++ b/src/write/draw/print-line.js @@ -0,0 +1,42 @@ +var sprintf = require('./sprintf'); +var roundNumber = require("./round-number"); + +function printLine(renderer, x1, x2, y, klass, name, dy) { + var fill = renderer.foregroundColor; + x1 = roundNumber(x1); + x2 = roundNumber(x2); + var y1 = roundNumber(y - dy); + var y2 = roundNumber(y + dy); + // TODO-PER: This fixes a firefox bug where it isn't displayed + if (renderer.firefox112) { + y += dy / 2; // Because the y coordinate is the edge of where the line goes but the width widens from the middle. + var attr = { + x1: x1, + x2: x2, + y1: y, + y2: y, + stroke: renderer.foregroundColor, + 'stroke-width': Math.abs(dy*2) + } + if (klass) + attr['class'] = klass; + if (name) + attr['data-name'] = name; + + return renderer.paper.lineToBack(attr); + } + + var pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y1, x2, y1, + x2, y2, x1, y2); + var options = { path: pathString, stroke: "none", fill: fill }; + if (name) + options['data-name'] = name; + if (klass) + options['class'] = klass; + var ret = renderer.paper.pathToBack(options); + + return ret; +} + +module.exports = printLine; + diff --git a/src/write/draw/print-path.js b/src/write/draw/print-path.js new file mode 100644 index 0000000000000000000000000000000000000000..a295485b1fa3bf98972d08b9346074eabbf9d4cd --- /dev/null +++ b/src/write/draw/print-path.js @@ -0,0 +1,7 @@ +function printPath(renderer, attrs, params) { + var ret = renderer.paper.path(attrs); + + return ret; +} + +module.exports = printPath; diff --git a/src/write/draw/print-stem.js b/src/write/draw/print-stem.js new file mode 100644 index 0000000000000000000000000000000000000000..4aa20338613fb49e1bf953c34ed53eef2c95adcb --- /dev/null +++ b/src/write/draw/print-stem.js @@ -0,0 +1,48 @@ +var elementGroup = require('./group-elements'); +var roundNumber = require("./round-number"); + +function printStem(renderer, x, dx, y1, y2, klass, name) { + if (dx < 0 || y1 < y2) { // correct path "handedness" for intersection with other elements + var tmp = roundNumber(y2); + y2 = roundNumber(y1); + y1 = tmp; + } else { + y1 = roundNumber(y1); + y2 = roundNumber(y2); + } + x = roundNumber(x); + var x2 = roundNumber(x + dx); + // TODO-PER: This fixes a firefox bug where it isn't displayed + if (renderer.firefox112) { + x += dx / 2; // Because the x coordinate is the edge of where the line goes but the width widens from the middle. + var attr = { + x1: x, + x2: x, + y1: y1, + y2: y2, + stroke: renderer.foregroundColor, + 'stroke-width': Math.abs(dx) + } + if (klass) + attr['class'] = klass; + if (name) + attr['data-name'] = name; + + return renderer.paper.lineToBack(attr); + } + var pathArray = [["M", x, y1], ["L", x, y2], ["L", x2, y2], ["L", x2, y1], ["z"]]; + var attr = { path: "" }; + for (var i = 0; i < pathArray.length; i++) + attr.path += pathArray[i].join(" "); + if (klass) + attr['class'] = klass; + if (name) + attr['data-name'] = name; + if (!elementGroup.isInGroup()) { + attr.stroke = "none"; + attr.fill = renderer.foregroundColor; + } + return renderer.paper.pathToBack(attr); +} + +module.exports = printStem; diff --git a/src/write/draw/print-symbol.js b/src/write/draw/print-symbol.js new file mode 100644 index 0000000000000000000000000000000000000000..81d97604140b53ca616f67e8347707eb78b1a1eb --- /dev/null +++ b/src/write/draw/print-symbol.js @@ -0,0 +1,59 @@ +var renderText = require('./text'); +var glyphs = require('../creation/glyphs'); +var elementGroup = require('./group-elements'); + +/** + * assumes this.y is set appropriately + * if symbol is a multichar string without a . (as in scripts.staccato) 1 symbol per char is assumed + * not scaled if not in printgroup + */ +function printSymbol(renderer, x, offset, symbol, options) { + // TODO-PER: what happened to scalex, and scaley? That might have been a bug introduced in refactoring + var el; + var ycorr; + if (!symbol) return null; + if (symbol.length > 1 && symbol.indexOf(".") < 0) { + var groupClass = elementGroup.isInGroup() ? '' : options.klass // If this is already in a group then don't repeat the classes for the sub-group) + renderer.paper.openGroup({ "data-name": options.name, klass: groupClass }); + var dx = 0; + for (var i = 0; i < symbol.length; i++) { + var s = symbol[i]; + ycorr = glyphs.getYCorr(s); + el = glyphs.printSymbol(x + dx, renderer.calcY(offset + ycorr), s, renderer.paper, { stroke: options.stroke, fill: options.fill }); + if (el) { + if (i < symbol.length - 1) + dx += kernSymbols(s, symbol[i + 1], glyphs.getSymbolWidth(s)); + } else { + renderText(renderer, { x: x, y: renderer.y, text: "no symbol:" + symbol, type: "debugfont", klass: 'debug-msg', anchor: 'start' }, false); + } + } + var g = renderer.paper.closeGroup(); + return g; + } else { + ycorr = glyphs.getYCorr(symbol); + if (elementGroup.isInGroup()) { + el = glyphs.printSymbol(x, renderer.calcY(offset + ycorr), symbol, renderer.paper, { "data-name": options.name }); + } else { + el = glyphs.printSymbol(x, renderer.calcY(offset + ycorr), symbol, renderer.paper, { klass: options.klass, stroke: options.stroke, fill: options.fill, "data-name": options.name }); + } + if (el) { + return el; + } + renderText(renderer, { x: x, y: renderer.y, text: "no symbol:" + symbol, type: "debugfont", klass: 'debug-msg', anchor: 'start' }, false); + return null; + } +} + +function kernSymbols(lastSymbol, thisSymbol, lastSymbolWidth) { + // This is just some adjustments to make it look better. + var width = lastSymbolWidth; + if (lastSymbol === 'f' && thisSymbol === 'f') + width = width * 2 / 3; + if (lastSymbol === 'p' && thisSymbol === 'p') + width = width * 5 / 6; + if (lastSymbol === 'f' && thisSymbol === 'z') + width = width * 5 / 8; + return width; +} + +module.exports = printSymbol; diff --git a/src/write/draw/print-vertical-line.js b/src/write/draw/print-vertical-line.js new file mode 100644 index 0000000000000000000000000000000000000000..fb1c66089619941cfb358263dd222f5245e17184 --- /dev/null +++ b/src/write/draw/print-vertical-line.js @@ -0,0 +1,18 @@ +var sprintf = require("./sprintf"); + +function printVerticalLine(renderer, x, y1, y2) { + var dy = 0.35; + var fill = "#00aaaa"; + var pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x - dy, y1, x - dy, y2, + x + dy, y1, x + dy, y2); + renderer.paper.pathToBack({ path: pathString, stroke: "none", fill: fill, 'class': renderer.controller.classes.generate('staff') }); + pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x - 20, y1, x - 20, y1 + 3, + x, y1, x, y1 + 3); + renderer.paper.pathToBack({ path: pathString, stroke: "none", fill: fill, 'class': renderer.controller.classes.generate('staff') }); + pathString = sprintf("M %f %f L %f %f L %f %f L %f %f z", x + 20, y2, x + 20, y2 + 3, + x, y2, x, y2 + 3); + renderer.paper.pathToBack({ path: pathString, stroke: "none", fill: fill, 'class': renderer.controller.classes.generate('staff') }); + +} + +module.exports = printVerticalLine; diff --git a/src/write/draw/relative.js b/src/write/draw/relative.js new file mode 100644 index 0000000000000000000000000000000000000000..283d81acbb00d1a677a0723b999580dfd9d57bc4 --- /dev/null +++ b/src/write/draw/relative.js @@ -0,0 +1,78 @@ +var renderText = require('./text'); +var printStem = require('./print-stem'); +var printStaffLine = require('./staff-line'); +var printSymbol = require('./print-symbol'); + +function drawRelativeElement(renderer, params, bartop) { + if (params.pitch === undefined) + window.console.error(params.type + " Relative Element y-coordinate not set."); + var y = renderer.calcY(params.pitch); + switch (params.type) { + case "symbol": + if (params.c === null) return null; + var klass = "symbol"; + if (params.klass) klass += " " + params.klass; + params.graphelem = printSymbol(renderer, params.x, params.pitch, params.c, { + scalex: params.scalex, + scaley: params.scaley, + klass: renderer.controller.classes.generate(klass), + // fill:"none", + // stroke: renderer.foregroundColor, + name: params.name + }); + break; + case "debug": + params.graphelem = renderText(renderer, { x: params.x, y: renderer.calcY(15), text: "" + params.c, type: "debugfont", klass: renderer.controller.classes.generate('debug-msg'), anchor: 'start', centerVertically: false, dim: params.dim }, false); + break; + case "tabNumber": + var hAnchor = "middle"; + var tabFont = "tabnumberfont"; + var tabClass = 'abcjs-tab-number'; + if (params.isGrace) { + tabFont = "tabgracefont"; + y += 2.5; + tabClass = 'tab-grace' + } + params.graphelem = renderText(renderer, { x: params.x, y: y, text: "" + params.c, type: tabFont, klass: renderer.controller.classes.generate(tabClass), anchor: hAnchor, centerVertically: false, dim: params.dim, cursor: 'default' }, false); + break; + case "barNumber": + params.graphelem = renderText(renderer, { x: params.x, y: y, text: "" + params.c, type: "measurefont", klass: renderer.controller.classes.generate('bar-number'), anchor: "middle", dim: params.dim, name: "bar-number" }, true); + break; + case "lyric": + params.graphelem = renderText(renderer, { x: params.x, y: y, text: params.c, type: "vocalfont", klass: renderer.controller.classes.generate('lyric'), anchor: "middle", dim: params.dim, name: "lyric" }, false); + break; + case "chord": + params.graphelem = renderText(renderer, { x: params.x, y: y, text: params.c, type: 'gchordfont', klass: renderer.controller.classes.generate("chord"), anchor: "middle", dim: params.dim, lane: params.getLane(), name: "chord" }, false); + break; + case "decoration": + // The +6 is to compensate for the placement of text in svg: to be on the same row as symbols, the y-coord needs to compensate for the center line. + params.graphelem = renderText(renderer, { x: params.x, y: y + 6, text: params.c, type: 'annotationfont', klass: renderer.controller.classes.generate("annotation"), anchor: params.anchor, centerVertically: true, dim: params.dim }, false); + break; + case "text": + params.graphelem = renderText(renderer, { x: params.x, y: y, text: params.c, type: 'annotationfont', klass: renderer.controller.classes.generate("annotation"), anchor: "start", centerVertically: params.centerVertically, dim: params.dim, lane: params.getLane(), name: "annotation" }, false); + break; + case "multimeasure-text": + params.graphelem = renderText(renderer, { x: params.x + params.w / 2, y: y, text: params.c, type: 'tempofont', klass: renderer.controller.classes.generate("rest"), anchor: "middle", centerVertically: false, dim: params.dim }, false); + break; + case "part": + params.graphelem = renderText(renderer, { x: params.x, y: y, text: params.c, type: 'partsfont', klass: renderer.controller.classes.generate("part"), anchor: "start", dim: params.dim, name: params.c }, true); + break; + case "bar": + params.graphelem = printStem(renderer, params.x, params.linewidth + renderer.lineThickness, y, (bartop) ? bartop : renderer.calcY(params.pitch2), null, "bar"); break; // bartop can't be 0 + case "stem": + var stemWidth = params.linewidth > 0 ? params.linewidth + renderer.lineThickness : params.linewidth - renderer.lineThickness + params.graphelem = printStem(renderer, params.x, stemWidth, y, renderer.calcY(params.pitch2), 'abcjs-stem', 'stem'); break; + case "ledger": + params.graphelem = printStaffLine(renderer, params.x, params.x + params.w, params.pitch, "abcjs-ledger", "ledger", 0.35 + renderer.lineThickness); break; + } + if (params.scalex !== 1 && params.graphelem) { + scaleExistingElem(renderer.paper, params.graphelem, params.scalex, params.scaley, params.x, y); + } + return params.graphelem; +} + +function scaleExistingElem(paper, elem, scaleX, scaleY, x, y) { + paper.setAttributeOnElement(elem, { style: "transform:scale(" + scaleX + "," + scaleY + ");transform-origin:" + x + "px " + y + "px;" }); +} + +module.exports = drawRelativeElement; diff --git a/src/write/draw/round-number.js b/src/write/draw/round-number.js new file mode 100644 index 0000000000000000000000000000000000000000..a8da92ad86a6d21b37595fcaaee4b0d03b56e3c9 --- /dev/null +++ b/src/write/draw/round-number.js @@ -0,0 +1,5 @@ +function roundNumber(x) { + return parseFloat(x.toFixed(2)); +} + +module.exports = roundNumber; diff --git a/src/write/draw/selectables.js b/src/write/draw/selectables.js new file mode 100644 index 0000000000000000000000000000000000000000..4c2cd3b0c882dcbd1f46b03f40ccc759227e7746 --- /dev/null +++ b/src/write/draw/selectables.js @@ -0,0 +1,59 @@ +var highlight = require('../interactive/highlight'); +var unhighlight = require('../interactive/unhighlight'); + +function Selectables(paper, selectTypes, tuneNumber) { + this.elements = []; + this.paper = paper; + this.tuneNumber = tuneNumber; + this.selectTypes = selectTypes; +} + +Selectables.prototype.getElements = function () { + return this.elements; +}; + +Selectables.prototype.add = function (absEl, svgEl, isNoteOrTabNumber, staffPos) { + if (!this.canSelect(absEl)) + return; + var params; + if (this.selectTypes === undefined) + params = { selectable: false, "data-index": this.elements.length }; // This is the old behavior. + else + params = { selectable: true, tabindex: 0, "data-index": this.elements.length }; + this.paper.setAttributeOnElement(svgEl, params); + var sel = { absEl: absEl, svgEl: svgEl, isDraggable: isNoteOrTabNumber }; + if (staffPos !== undefined) + sel.staffPos = staffPos; + this.elements.push(sel); + +}; + +Selectables.prototype.canSelect = function (absEl) { + if (this.selectTypes === false) + return false; + if (!absEl || !absEl.abcelem) + return false; + if (this.selectTypes === true) + return true; + if (this.selectTypes === undefined) { + // by default, only notes and tab numbers can be clicked. + if (absEl.abcelem.el_type === 'note' || absEl.abcelem.el_type === 'tabNumber') { + return true; + } + return false; + } + return this.selectTypes.indexOf(absEl.abcelem.el_type) >= 0; +}; + +Selectables.prototype.wrapSvgEl = function (abcelem, el) { + var absEl = { + tuneNumber: this.tuneNumber, + abcelem: abcelem, + elemset: [el], + highlight: highlight, + unhighlight: unhighlight + }; + this.add(absEl, el, false); +}; + +module.exports = Selectables; diff --git a/src/write/draw/separator.js b/src/write/draw/separator.js new file mode 100644 index 0000000000000000000000000000000000000000..d34597cbb6a2bada27fc2b32c197dfcdfabf3dcb --- /dev/null +++ b/src/write/draw/separator.js @@ -0,0 +1,16 @@ +function drawSeparator(renderer, width) { + var fill = "rgba(0,0,0,255)"; + var stroke = "rgba(0,0,0,0)"; + var y = Math.round(renderer.y); + var staffWidth = renderer.controller.width; + var x1 = (staffWidth - width) / 2; + var x2 = x1 + width; + var pathString = 'M ' + x1 + ' ' + y + + ' L ' + x2 + ' ' + y + + ' L ' + x2 + ' ' + (y + 1) + + ' L ' + x1 + ' ' + (y + 1) + + ' L ' + x1 + ' ' + y + ' z'; + renderer.paper.pathToBack({ path: pathString, stroke: stroke, fill: fill, 'class': renderer.controller.classes.generate('defined-text') }); +} + +module.exports = drawSeparator; diff --git a/src/write/draw/set-paper-size.js b/src/write/draw/set-paper-size.js new file mode 100644 index 0000000000000000000000000000000000000000..689b472f3fde05fc2930bfc6f64845272f7e1e8d --- /dev/null +++ b/src/write/draw/set-paper-size.js @@ -0,0 +1,45 @@ +function setPaperSize(renderer, maxwidth, scale, responsive) { + var w = (maxwidth + renderer.padding.left + renderer.padding.right) * scale; + var h = (renderer.y + renderer.padding.bottom) * scale; + if (renderer.isPrint) + h = Math.max(h, 1056); // 11in x 72pt/in x 1.33px/pt + // TODO-PER: We are letting the page get as long as it needs now, but eventually that should go to a second page. + + // for accessibility + if (renderer.ariaLabel !== '') { + var text = "Sheet Music"; + if (renderer.abctune && renderer.abctune.metaText && renderer.abctune.metaText.title) + text += " for \"" + renderer.abctune.metaText.title + '"'; + renderer.paper.setTitle(text); + var label = renderer.ariaLabel ? renderer.ariaLabel : text; + renderer.paper.setAttribute("aria-label", label); + } + + // for dragging - don't select during drag + var styles = [ + "-webkit-touch-callout: none;", + "-webkit-user-select: none;", + "-khtml-user-select: none;", + "-moz-user-select: none;", + "-ms-user-select: none;", + "user-select: none;" + ]; + renderer.paper.insertStyles(".abcjs-dragging-in-progress text, .abcjs-dragging-in-progress tspan {" + styles.join(" ") + "}"); + + var parentStyles = { overflow: "hidden" }; + if (responsive === 'resize') { + renderer.paper.setResponsiveWidth(w, h); + } else { + parentStyles.width = ""; + parentStyles.height = h + "px"; + if (scale < 1) { + parentStyles.width = w + "px"; + renderer.paper.setSize(w / scale, h / scale); + } else + renderer.paper.setSize(w, h); + } + renderer.paper.setScale(scale); + renderer.paper.setParentStyles(parentStyles); +} + +module.exports = setPaperSize; diff --git a/src/write/draw/sprintf.js b/src/write/draw/sprintf.js new file mode 100644 index 0000000000000000000000000000000000000000..a250a98cafd47e9dc4ede2af786f1452ae2961a7 --- /dev/null +++ b/src/write/draw/sprintf.js @@ -0,0 +1,65 @@ +/** + * sprintf() for JavaScript v.0.4 + * + Copyright (c) 2007-present, Alexandru Mărășteanu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of this software nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); } + +var sprintf = function () { + var i = 0, a, f = arguments[i++], o = [], m, p, c, x; + while (f) { + if (m = /^[^\x25]+/.exec(f)) o.push(m[0]); + else if (m = /^\x25{2}/.exec(f)) o.push('%'); + else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { + if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw ("Too few arguments."); + if (/[^s]/.test(m[7]) && (typeof (a) != 'number')) + throw ("Expecting number but found " + typeof (a)); + switch (m[7]) { + case 'b': a = a.toString(2); break; + case 'c': a = String.fromCharCode(a); break; + case 'd': a = parseInt(a); break; + case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; + case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; + case 'o': a = a.toString(8); break; + case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; + case 'u': a = Math.abs(a); break; + case 'x': a = a.toString(16); break; + case 'X': a = a.toString(16).toUpperCase(); break; + } + a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a); + c = m[3] ? m[3] == '0' ? '0' : m[3][1] : ' '; + x = m[5] - String(a).length; + p = m[5] ? str_repeat(c, x) : ''; + o.push(m[4] ? a + p : p + a); + } + else throw ("Huh ?!"); + f = f.substring(m[0].length); + } + return o.join(''); +}; + +module.exports = sprintf; diff --git a/src/write/draw/staff-group.js b/src/write/draw/staff-group.js new file mode 100644 index 0000000000000000000000000000000000000000..68a762d080349338d7429786db1c778639d1651e --- /dev/null +++ b/src/write/draw/staff-group.js @@ -0,0 +1,232 @@ +var spacing = require('../helpers/spacing'); +var drawBrace = require('./brace'); +var drawVoice = require('./voice'); +var printStaff = require('./staff'); +var printDebugBox = require('./debug-box'); +var printStem = require('./print-stem'); +var nonMusic = require('./non-music'); + +function drawStaffGroup(renderer, params, selectables, lineNumber) { + // We enter this method with renderer.y pointing to the topmost coordinate that we're allowed to draw. + // All of the children that will be drawn have a relative "pitch" set, where zero is the first ledger line below the staff. + // renderer.y will be offset at the beginning of each staff by the amount required to make the relative pitch work. + // If there are multiple staves, then renderer.y will be incremented for each new staff. + + var colorIndex; + + // An invisible marker is useful to be able to find where each system starts. + //addInvisibleMarker(renderer, "abcjs-top-of-system"); + + var startY = renderer.y; // So that it can be restored after we're done. + // Set the absolute Y position for each staff here, so the voice drawing below can just use if. + for (var j = 0; j < params.staffs.length; j++) { + var staff1 = params.staffs[j]; + //renderer.printHorizontalLine(50, renderer.y, "start"); + renderer.moveY(spacing.STEP, staff1.top); + staff1.absoluteY = renderer.y; + if (renderer.showDebug) { + if (renderer.showDebug.indexOf("box") >= 0 && staff1.voices) { + boxAllElements(renderer, params.voices, staff1.voices); + } + if (renderer.showDebug.indexOf("grid") >= 0) { + renderer.paper.dottedLine({ x1: renderer.padding.left, x2: renderer.padding.left + renderer.controller.width, y1: startY, y2: startY, stroke: "#0000ff" }); + printDebugBox(renderer, + { + x: renderer.padding.left, + y: renderer.calcY(staff1.originalTop), + width: renderer.controller.width, + height: renderer.calcY(staff1.originalBottom) - renderer.calcY(staff1.originalTop), + fill: renderer.foregroundColor, + stroke: renderer.foregroundColor, + "fill-opacity": 0.1, + "stroke-opacity": 0.1 + }); + colorIndex = 0; + debugPrintGridItem(staff1, 'chordHeightAbove'); + debugPrintGridItem(staff1, 'chordHeightBelow'); + debugPrintGridItem(staff1, 'dynamicHeightAbove'); + debugPrintGridItem(staff1, 'dynamicHeightBelow'); + debugPrintGridItem(staff1, 'endingHeightAbove'); + debugPrintGridItem(staff1, 'lyricHeightAbove'); + debugPrintGridItem(staff1, 'lyricHeightBelow'); + debugPrintGridItem(staff1, 'partHeightAbove'); + debugPrintGridItem(staff1, 'tempoHeightAbove'); + debugPrintGridItem(staff1, 'volumeHeightAbove'); + debugPrintGridItem(staff1, 'volumeHeightBelow'); + } + } + renderer.moveY(spacing.STEP, -staff1.bottom); + if (renderer.showDebug) { + if (renderer.showDebug.indexOf("grid") >= 0) { + renderer.paper.dottedLine({ + x1: renderer.padding.left, + x2: renderer.padding.left + renderer.controller.width, + y1: renderer.y, + y2: renderer.y, + stroke: "#0000aa" + }); + } + } + } + var topLine; // these are to connect multiple staves. We need to remember where they are. + var bottomLine; + + var linePitch = 2; + var bartop = 0; + for (var i = 0; i < params.voices.length; i++) { + var staff = params.voices[i].staff; + var tabName = params.voices[i].tabNameInfos; + renderer.y = staff.absoluteY; + renderer.controller.classes.incrVoice(); + //renderer.y = staff.y; + // offset for starting the counting at middle C + if (!params.voices[i].duplicate) { + // renderer.moveY(spacing.STEP, staff.top); + if (!topLine) topLine = renderer.calcY(10); + bottomLine = renderer.calcY(linePitch); + if (staff.lines !== 0) { + if (staff.linePitch) { + linePitch = staff.linePitch; + } + renderer.controller.classes.newMeasure(); + var lines = printStaff(renderer, params.startx, params.w, staff.lines, staff.linePitch, 0.35); + bottomLine = lines[1]; + staff.bottomLine = bottomLine; + staff.topLine = lines[0]; + // rework bartop when tabs are present with current staff + if (staff.hasTab) { + // do not link to staff above (ugly looking) + bartop = staff.topLine; + } + if (staff.hasStaff) { + // this is a tab + bartop = staff.hasStaff.topLine; + params.voices[i].barto = true; + params.voices[i].topLine = topLine; + } + + } + printBrace(renderer, staff.absoluteY, params.brace, i, selectables); + printBrace(renderer, staff.absoluteY, params.bracket, i, selectables); + } + drawVoice(renderer, params.voices[i], bartop, selectables, { + top: startY, + zero: renderer.y, + height: params.height * spacing.STEP + }); + var tabNameHeight = 0; + if (tabName) { + // print tab infos on staffBottom + var r = { rows: [] }; + r.rows.push({ absmove: bottomLine + 2 }); + var leftMargin = 8; + r.rows.push({ left: params.startx + leftMargin, text: tabName.name, font: 'tablabelfont', klass: 'text instrument-name', anchor: 'start' }); + r.rows.push({ move: tabName.textSize.height }); + nonMusic(renderer, r); + tabNameHeight = tabName.textSize.height; + } + + renderer.controller.classes.newMeasure(); + if (!params.voices[i].duplicate) { + bartop = renderer.calcY(2 + tabNameHeight); // This connects the bar lines between two different staves. + // if (staff.bottom < 0) + // renderer.moveY(spacing.STEP, -staff.bottom); + } + } + renderer.controller.classes.newMeasure(); + + // connect all the staves together with a vertical line + var staffSize = params.staffs.length; + if (staffSize > 1) { + topLine = params.staffs[0].topLine; + bottomLine = params.staffs[staffSize - 1].bottomLine; + printStem(renderer, params.startx, 0.6, topLine, bottomLine, null); + } + renderer.y = startY; + + function debugPrintGridItem(staff, key) { + var colors = ["rgb(207,27,36)", "rgb(168,214,80)", "rgb(110,161,224)", "rgb(191,119,218)", "rgb(195,30,151)", + "rgb(31,170,177)", "rgb(220,166,142)"]; + if (staff.positionY && staff.positionY[key]) { + var height = staff.specialY[key] * spacing.STEP; + if (key === "chordHeightAbove" && staff.specialY.chordLines && staff.specialY.chordLines.above) + height *= staff.specialY.chordLines.above; + if (key === "chordHeightBelow" && staff.specialY.chordLines && staff.specialY.chordLines.below) + height *= staff.specialY.chordLines.below; + printDebugBox(renderer, + { + x: renderer.padding.left, + y: renderer.calcY(staff.positionY[key]), + width: renderer.controller.width, + height: height, + fill: colors[colorIndex], + stroke: colors[colorIndex], + "fill-opacity": 0.4, + "stroke-opacity": 0.4 + }, + key.substr(0, 4)); + colorIndex += 1; if (colorIndex > 6) colorIndex = 0; + } + } +} + +function printBrace(renderer, absoluteY, brace, index, selectables) { + if (brace) { + for (var i = 0; i < brace.length; i++) { + if (brace[i].isStartVoice(index)) { + brace[i].startY = absoluteY - spacing.STEP * 10; + brace[i].elemset = drawBrace(renderer, brace[i], selectables); + } + } + } +} + +// function addInvisibleMarker(renderer, className) { +// var y = Math.round(renderer.y); +// renderer.paper.pathToBack({path:"M 0 " + y + " L 0 0", stroke:"none", fill:"none", "stroke-opacity": 0, "fill-opacity": 0, 'class': renderer.controller.classes.generate(className), 'data-vertical': y }); +// } + +function boxAllElements(renderer, voices, which) { + for (var i = 0; i < which.length; i++) { + var children = voices[which[i]].children; + for (var j = 0; j < children.length; j++) { + var elem = children[j]; + var coords = elem.getFixedCoords(); + if (elem.invisible || coords.t === undefined || coords.b === undefined) + continue; + var height = (coords.t - coords.b) * spacing.STEP; + printDebugBox(renderer, + { + x: coords.x, + y: renderer.calcY(coords.t), + width: coords.w, + height: height, + fill: "#88e888", + "fill-opacity": 0.4, + stroke: "#4aa93d", + "stroke-opacity": 0.8 + }); + + for (var k = 0; k < elem.children.length; k++) { + var relElem = elem.children[k]; + var chord = relElem.getChordDim(); + if (chord) { + var y = renderer.calcY(relElem.pitch); + y += relElem.dim.font.size * relElem.getLane(); + printDebugBox(renderer, + { + x: chord.left, + y: y, + width: chord.right - chord.left, + height: relElem.dim.font.size, + fill: "none", + stroke: "#4aa93d", + "stroke-opacity": 0.8 + }); + } + } + } + } +} + +module.exports = drawStaffGroup; diff --git a/src/write/draw/staff-line.js b/src/write/draw/staff-line.js new file mode 100644 index 0000000000000000000000000000000000000000..ca81e5576b935b4cf1a96dd39415f84c3d0b9849 --- /dev/null +++ b/src/write/draw/staff-line.js @@ -0,0 +1,9 @@ +var printLine = require('./print-line'); + +function printStaffLine(renderer, x1, x2, pitch, klass, name, dy) { + var y = renderer.calcY(pitch); + return printLine(renderer, x1, x2, y, klass, name, dy); +} + +module.exports = printStaffLine; + diff --git a/src/write/draw/staff.js b/src/write/draw/staff.js new file mode 100644 index 0000000000000000000000000000000000000000..9fd19b435a5678ac80d59a3480ebd80c6ec5fc77 --- /dev/null +++ b/src/write/draw/staff.js @@ -0,0 +1,33 @@ +var printStaffLine = require('./staff-line'); + +function printStaff(renderer, startx, endx, numLines, linePitch, dy) { + var klass = "abcjs-top-line"; + var pitch = 2; + if (linePitch) { + pitch = linePitch; + } + renderer.paper.openGroup({ prepend: true, klass: renderer.controller.classes.generate("abcjs-staff") }); + // If there is one line, it is the B line. Otherwise, the bottom line is the E line. + var firstYLine = 0; + var lastYLine = 0; + if (numLines === 1) { + printStaffLine(renderer, startx, endx, 6, klass, null, dy + renderer.lineThickness); + firstYLine = renderer.calcY(10); + lastYLine = renderer.calcY(2); + } else { + + for (var i = numLines - 1; i >= 0; i--) { + var curpitch = (i + 1) * pitch; + lastYLine = renderer.calcY(curpitch); + if (firstYLine === 0) { + firstYLine = lastYLine; + } + printStaffLine(renderer, startx, endx, curpitch, klass, null, dy + renderer.lineThickness); + klass = undefined; + } + } + renderer.paper.closeGroup(); + return [firstYLine, lastYLine]; +} + +module.exports = printStaff; diff --git a/src/write/draw/tab-line.js b/src/write/draw/tab-line.js new file mode 100644 index 0000000000000000000000000000000000000000..061d1d8754d120c250063604bd78f1295e72e4cb --- /dev/null +++ b/src/write/draw/tab-line.js @@ -0,0 +1,40 @@ +var sprintf = require('./sprintf'); +var roundNumber = require('./round-number'); +var printStem = require('./print-stem'); + +function TabLine(renderer, klass, dx, name) { + this.renderer = renderer; + if (!dx) dx = 0.35; // default + this.dx = dx; + this.klass = klass; + this.name = name; + var fill = renderer.foregroundColor; + this.options = { stroke: "none", fill: fill }; + if (name) + this.options['data-name'] = name; + if (klass) + this.options['class'] = klass; +} + +TabLine.prototype.printVertical = function (y1, y2, x) { + return printStem(this.renderer, + x, + this.dx, + y1, + y2, + this.options.klass, + this.options.name); +} + +TabLine.prototype.printHorizontal = function (x1, x2, y) { + x1 = roundNumber(x1); + x2 = roundNumber(x2); + var y1 = roundNumber(y - this.dx); + var y2 = roundNumber(y + this.dx); + this.options.path = sprintf("M %f %f L %f %f L %f %f L %f %f z", x1, y1, x2, y1, + x2, y2, x1, y2); + return this.renderer.paper.pathToBack(this.options); +} + +module.exports = TabLine; + diff --git a/src/write/draw/tempo.js b/src/write/draw/tempo.js new file mode 100644 index 0000000000000000000000000000000000000000..9b4ba740b8db304982898950c226274d1144355e --- /dev/null +++ b/src/write/draw/tempo.js @@ -0,0 +1,45 @@ +var drawRelativeElement = require('./relative'); +var renderText = require('./text'); + +function drawTempo(renderer, params) { + var x = params.x; + if (params.pitch === undefined) + window.console.error("Tempo Element y-coordinate not set."); + + //var tempoGroup; + params.tempo.el_type = "tempo"; + // renderer.wrapInAbsElem(params.tempo, "abcjs-tempo", function () { + //renderer.paper.openGroup({klass: renderer.controller.classes.generate("tempo wha")}); + // The text is aligned with extra room for descenders but numbers look like they are a little too high, so bump it a little. + var descenderHeight = 2; + var y = renderer.calcY(params.pitch) + 2; + var text; + var size; + if (params.tempo.preString) { + text = renderText(renderer, { x: x, y: y, text: params.tempo.preString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "pre" }, true); + size = renderer.controller.getTextSize.calc(params.tempo.preString, 'tempofont', 'tempo', text); + var preWidth = size.width; + var charWidth = preWidth / params.tempo.preString.length; // Just get some average number to increase the spacing. + x += preWidth + charWidth; + } + if (params.note) { + params.note.setX(x); + for (var i = 0; i < params.note.children.length; i++) + drawRelativeElement(renderer, params.note.children[i], x); + x += (params.note.w + 5); + var str = "= " + params.tempo.bpm; + text = renderText(renderer, { x: x, y: y, text: str, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "beats" }); + size = renderer.controller.getTextSize.calc(str, 'tempofont', 'tempo', text); + var postWidth = size.width; + var charWidth2 = postWidth / str.length; // Just get some average number to increase the spacing. + x += postWidth + charWidth2; + } + if (params.tempo.postString) { + renderText(renderer, { x: x, y: y, text: params.tempo.postString, type: 'tempofont', klass: 'abcjs-tempo', anchor: "start", noClass: true, name: "post" }, true); + } + //tempoGroup = renderer.paper.closeGroup(); + // }); + //return [tempoGroup]; +} + +module.exports = drawTempo; diff --git a/src/write/draw/text.js b/src/write/draw/text.js new file mode 100644 index 0000000000000000000000000000000000000000..f018d92c2b8cacd99bfe77d8e63a51ba9d41c56e --- /dev/null +++ b/src/write/draw/text.js @@ -0,0 +1,81 @@ +var roundNumber = require("./round-number"); + +function renderText(renderer, params, alreadyInGroup) { + var y = params.y; + + // TODO-PER: Probably need to merge the regular text and rich text better. At the least, rich text loses the font box. + if (params.phrases) { + //richTextLine = function (phrases, x, y, klass, anchor, target) + var elem = renderer.paper.richTextLine(params.phrases, params.x, params.y, params.klass, params.anchor); + return elem; + } + + if (params.lane) { + var laneMargin = params.dim.font.size * 0.25; + y += (params.dim.font.size + laneMargin) * params.lane; + } + + var hash; + if (params.dim) { + hash = params.dim; + hash.attr.class = params.klass; + } else + hash = renderer.controller.getFontAndAttr.calc(params.type, params.klass); + if (params.anchor) + hash.attr["text-anchor"] = params.anchor; + if (params['dominant-baseline']) + hash.attr["dominant-baseline"] = params['dominant-baseline']; + hash.attr.x = params.x; + hash.attr.y = y; + if (!params.centerVertically) + hash.attr.y += hash.font.size; + if (params.type === 'debugfont') { + console.log("Debug msg: " + params.text); + hash.attr.stroke = "#ff0000"; + } + if (params.cursor) { + hash.attr.cursor = params.cursor; + } + + var text = params.text.replace(/\n\n/g, "\n \n"); + text = text.replace(/^\n/, "\xA0\n"); + + if (hash.font.box) { + if (!alreadyInGroup) + renderer.paper.openGroup({ klass: hash.attr['class'], fill: renderer.foregroundColor, "data-name": params.name }); + if (hash.attr["text-anchor"] === "end") { + hash.attr.x -= hash.font.padding; + } else if (hash.attr["text-anchor"] === "start") { + hash.attr.x += hash.font.padding; + } + hash.attr.y += hash.font.padding; + delete hash.attr['class']; + } + if (params.noClass) + delete hash.attr['class']; + hash.attr.x = roundNumber(hash.attr.x); + hash.attr.y = roundNumber(hash.attr.y); + if (params.name) + hash.attr["data-name"] = params.name; + var elem = renderer.paper.text(text, hash.attr); + if (hash.font.box) { + var size = elem.getBBox(); + + var delta = 0; + if (hash.attr["text-anchor"] === "middle") { + delta = size.width / 2 + hash.font.padding; + } else if (hash.attr["text-anchor"] === "end") { + delta = size.width + hash.font.padding * 2; + } + var deltaY = 0; + if (params.centerVertically) { + deltaY = size.height - hash.font.padding; + } + renderer.paper.rect({ "data-name": "box", x: Math.round(params.x - delta), y: Math.round(y - deltaY), width: Math.round(size.width + hash.font.padding * 2), height: Math.round(size.height + hash.font.padding * 2) }); + if (!alreadyInGroup) + elem = renderer.paper.closeGroup(); + } + return elem; +} + +module.exports = renderText; diff --git a/src/write/draw/tie.js b/src/write/draw/tie.js new file mode 100644 index 0000000000000000000000000000000000000000..0a498479e92329aa726be1dbf1568c90adecfb65 --- /dev/null +++ b/src/write/draw/tie.js @@ -0,0 +1,105 @@ +var sprintf = require('./sprintf'); +var roundNumber = require("./round-number"); + +function drawTie(renderer, params, linestartx, lineendx, selectables) { + layout(params, linestartx, lineendx); + + var klass = ''; + if (params.anchor1) { + klass += 'abcjs-start-m' + params.anchor1.parent.counters.measure + '-n' + params.anchor1.parent.counters.note; + } else + klass += 'abcjs-start-edge'; + if (params.anchor2) { + klass += ' abcjs-end-m' + params.anchor2.parent.counters.measure + '-n' + params.anchor2.parent.counters.note; + } else + klass += ' abcjs-end-edge'; + if (params.hint) + klass = "abcjs-hint"; + var fudgeY = params.fixedY ? 1.5 : 0; // TODO-PER: This just compensates for drawArc, which contains too much knowledge of ties and slurs. + var el = drawArc(renderer, params.startX, params.endX, params.startY + fudgeY, params.endY + fudgeY, params.above, klass, params.isTie, params.dotted); + var startChar = -1 + // This gets the start and end points of the contents of the slur. We assume that the parenthesis are just to the outside of that. + if (params.anchor1 && !params.isTie) + startChar = params.anchor1.parent.abcelem.startChar - 1 + var endChar = -1 + if (params.anchor2 && !params.isTie) + endChar = params.anchor2.parent.abcelem.endChar + 1 + + selectables.wrapSvgEl({ el_type: "slur", startChar: startChar, endChar: endChar }, el); + return [el]; +} + +// TODO-PER: I think params part should have been done earlier in the layout pass. +var layout = function (params, lineStartX, lineEndX) { + // We now have all of the input variables set, so we can figure out the start and ending x,y coordinates, and finalize the direction of the arc. + + // Ties and slurs are handled a little differently, so do calculations for them separately. + if (!params.anchor1 || !params.anchor2) + params.isTie = true; // if the slur goes off the end of the line, then draw it like a tie + else if (params.anchor1.pitch === params.anchor2.pitch && params.internalNotes.length === 0) + params.isTie = true; + else + params.isTie = false; + + if (params.isTie) { + params.calcTieDirection(); + params.calcX(lineStartX, lineEndX); + params.calcTieY(); + + } else { + params.calcSlurDirection(); + params.calcX(lineStartX, lineEndX); + params.calcSlurY(); + } + params.avoidCollisionAbove(); +}; + +var drawArc = function (renderer, x1, x2, pitch1, pitch2, above, klass, isTie, dotted) { + // If it is a tie vs. a slur, draw it shallower. + var spacing = isTie ? 1.2 : 1.5; + + x1 = roundNumber(x1 + 6); + x2 = roundNumber(x2 + 4); + pitch1 = pitch1 + ((above) ? spacing : -spacing); + pitch2 = pitch2 + ((above) ? spacing : -spacing); + var y1 = roundNumber(renderer.calcY(pitch1)); + var y2 = roundNumber(renderer.calcY(pitch2)); + + //unit direction vector + var dx = x2 - x1; + var dy = y2 - y1; + var norm = Math.sqrt(dx * dx + dy * dy); + var ux = dx / norm; + var uy = dy / norm; + + var flatten = norm / 3.5; + var maxFlatten = isTie ? 10 : 25; // If it is a tie vs. a slur, draw it shallower. + var curve = ((above) ? -1 : 1) * Math.min(maxFlatten, Math.max(4, flatten)); + + var controlx1 = roundNumber(x1 + flatten * ux - curve * uy); + var controly1 = roundNumber(y1 + flatten * uy + curve * ux); + var controlx2 = roundNumber(x2 - flatten * ux - curve * uy); + var controly2 = roundNumber(y2 - flatten * uy + curve * ux); + var thickness = 2; + if (klass) + klass += ' slur'; + else + klass = 'slur'; + klass += isTie ? ' tie' : ' legato'; + var ret; + if (dotted) { + klass += ' dotted'; + var pathString2 = sprintf("M %f %f C %f %f %f %f %f %f", x1, y1, + controlx1, controly1, controlx2, controly2, x2, y2); + ret = renderer.paper.path({ path: pathString2, stroke: renderer.foregroundColor, fill: "none", 'stroke-dasharray': "5 5", 'class': renderer.controller.classes.generate(klass), "data-name": isTie ? "tie" : "slur" }); + } else { + var pathString = sprintf("M %f %f C %f %f %f %f %f %f C %f %f %f %f %f %f z", x1, y1, + controlx1, controly1, controlx2, controly2, x2, y2, + roundNumber(controlx2 - thickness * uy), roundNumber(controly2 + thickness * ux), roundNumber(controlx1 - thickness * uy), roundNumber(controly1 + thickness * ux), x1, y1); + ret = renderer.paper.path({ path: pathString, stroke: "none", fill: renderer.foregroundColor, 'class': renderer.controller.classes.generate(klass), "data-name": isTie ? "tie" : "slur" }); + } + + return ret; +}; + +module.exports = drawTie; diff --git a/src/write/draw/triplet.js b/src/write/draw/triplet.js new file mode 100644 index 0000000000000000000000000000000000000000..35269d5d13d7716b770319ef34e2a7457ce7be09 --- /dev/null +++ b/src/write/draw/triplet.js @@ -0,0 +1,46 @@ +var sprintf = require('./sprintf'); +var renderText = require('./text'); +var printPath = require('./print-path'); +var roundNumber = require("./round-number"); + +function drawTriplet(renderer, params, selectables) { + renderer.paper.openGroup({ klass: renderer.controller.classes.generate('triplet ' + params.durationClass), "data-name": "triplet" }); + if (!params.hasBeam) { + drawBracket(renderer, params.anchor1.x, params.startNote, params.anchor2.x + params.anchor2.w, params.endNote); + } + // HACK: adjust the position of "3". It is too high in all cases so we fudge it by subtracting 1 here. + renderText(renderer, { x: params.xTextPos, y: renderer.calcY(params.yTextPos - 1), text: "" + params.number, type: 'tripletfont', anchor: "middle", centerVertically: true, noClass: true, name: "" + params.number }, true); + var g = renderer.paper.closeGroup(); + selectables.wrapSvgEl({ el_type: "triplet", startChar: -1, endChar: -1 }, g); + return g; +} + +function drawLine(l, t, r, b) { + return sprintf("M %f %f L %f %f", roundNumber(l), roundNumber(t), roundNumber(r), roundNumber(b)); +} + +function drawBracket(renderer, x1, y1, x2, y2) { + y1 = renderer.calcY(y1); + y2 = renderer.calcY(y2); + var bracketHeight = 5; + + // Draw vertical lines at the beginning and end + var pathString = ""; + pathString += drawLine(x1, y1, x1, y1 + bracketHeight); + pathString += drawLine(x2, y2, x2, y2 + bracketHeight); + + // figure out midpoints to draw the broken line. + var midX = x1 + (x2 - x1) / 2; + //var midY = y1 + (y2-y1)/2; + var gapWidth = 8; + var slope = (y2 - y1) / (x2 - x1); + var leftEndX = midX - gapWidth; + var leftEndY = y1 + (leftEndX - x1) * slope; + pathString += drawLine(x1, y1, leftEndX, leftEndY); + var rightStartX = midX + gapWidth; + var rightStartY = y1 + (rightStartX - x1) * slope; + pathString += drawLine(rightStartX, rightStartY, x2, y2); + printPath(renderer, { path: pathString, stroke: renderer.foregroundColor, "data-name": "triplet-bracket" }); +} + +module.exports = drawTriplet; diff --git a/src/write/draw/voice.js b/src/write/draw/voice.js new file mode 100644 index 0000000000000000000000000000000000000000..a4cee4ab30e1940992a68c4f80a7911a6a694f75 --- /dev/null +++ b/src/write/draw/voice.js @@ -0,0 +1,105 @@ +var drawGlissando = require('./glissando'); +var drawCrescendo = require('./crescendo'); +var drawDynamics = require('./dynamics'); +var drawTriplet = require('./triplet'); +var drawEnding = require('./ending'); +var drawTie = require('./tie'); +var drawBeam = require('./beam'); +var renderText = require('./text'); +var drawAbsolute = require('./absolute'); + +function drawVoice(renderer, params, bartop, selectables, staffPos) { + var width = params.w - 1; + renderer.staffbottom = params.staff.bottom; + var saveColor = renderer.foregroundColor + if (params.color) + renderer.foregroundColor = params.color + + if (params.header) { // print voice name + var textEl = renderText(renderer, { x: renderer.padding.left, y: renderer.calcY(params.headerPosition), text: params.header, type: 'voicefont', klass: 'staff-extra voice-name', anchor: 'start', centerVertically: true, name: "voice-name" }, true); + selectables.wrapSvgEl({ el_type: "voiceName", startChar: -1, endChar: -1, text: params.header }, textEl); + } + + var i; + var child; + var foundNote = false; + for (i = 0; i < params.children.length; i++) { + child = params.children[i]; + if (child.type === 'note' || child.type === 'rest') + foundNote = true; + var justInitializedMeasureNumber = false; + if (child.type !== 'staff-extra' && !renderer.controller.classes.isInMeasure()) { + renderer.controller.classes.startMeasure(); + justInitializedMeasureNumber = true; + } + if (params.staff.isTabStaff) { + child.invisible = false; + if (child.type == 'bar') { + if (child.abcelem.lastBar) { + bartop = params.topLine; + } + } + } + drawAbsolute(renderer, child, (params.barto || i === params.children.length - 1) ? bartop : 0, selectables, staffPos); + + if (child.type === 'note' || isNonSpacerRest(child)) + renderer.controller.classes.incrNote(); + if (child.type === 'bar' && !justInitializedMeasureNumber && foundNote) { + renderer.controller.classes.incrMeasure(); + } + } + + renderer.controller.classes.startMeasure(); + + for (i = 0; i < params.beams.length; i++) { + var beam = params.beams[i]; + if (beam === 'bar') { + renderer.controller.classes.incrMeasure(); + } else + drawBeam(renderer, beam, selectables); // beams must be drawn first for proper printing of triplets, slurs and ties. + } + + renderer.controller.classes.startMeasure(); + for (i = 0; i < params.otherchildren.length; i++) { + child = params.otherchildren[i]; + if (child === 'bar') { + renderer.controller.classes.incrMeasure(); + } else { + switch (child.type) { + case "GlissandoElem": + child.elemset = drawGlissando(renderer, child, selectables); + break; + case "CrescendoElem": + child.elemset = drawCrescendo(renderer, child, selectables); + break; + case "DynamicDecoration": + child.elemset = drawDynamics(renderer, child, selectables); + break; + case "TripletElem": + drawTriplet(renderer, child, selectables); + break; + case "EndingElem": + child.elemset = drawEnding(renderer, child, params.startx + 10, width, selectables); + break; + case "TieElem": + child.elemset = drawTie(renderer, child, params.startx + 10, width, selectables); + break; + default: + console.log(child); + drawAbsolute(renderer, child, params.startx + 10, width, selectables, staffPos); + } + } + } + renderer.foregroundColor = saveColor + +} + +function isNonSpacerRest(elem) { + if (elem.type !== 'rest') + return false; + if (elem.abcelem && elem.abcelem.rest && elem.abcelem.rest.type !== 'spacer') + return true; + return false; +} + +module.exports = drawVoice; diff --git a/src/write/engraver-controller.js b/src/write/engraver-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..c6c5363fa0bb8c65bc214d2c0bdd237b30ffcc36 --- /dev/null +++ b/src/write/engraver-controller.js @@ -0,0 +1,396 @@ +// abc_engraver_controller.js: Controls the engraving process of an ABCJS abstract syntax tree as produced by ABCJS/parse + +/*global Math */ + +var spacing = require('./helpers/spacing'); +var AbstractEngraver = require('./creation/abstract-engraver'); +var Renderer = require('./renderer'); +var FreeText = require('./creation/elements/free-text'); +var Separator = require('./creation/elements/separator'); +var Subtitle = require('./creation/elements/subtitle'); +var TopText = require('./creation/elements/top-text'); +var BottomText = require('./creation/elements/bottom-text'); +var setupSelection = require('./interactive/selection'); +var layout = require('./layout/layout'); +var Classes = require('./helpers/classes'); +var GetFontAndAttr = require('./helpers/get-font-and-attr'); +var GetTextSize = require('./helpers/get-text-size'); +var draw = require('./draw/draw'); +var tablatures = require('../tablatures/abc_tablatures'); +var findSelectableElement = require('./interactive/find-selectable-element'); + +/** + * @class + * Controls the engraving process, from ABCJS Abstract Syntax Tree (ABCJS AST) to rendered score sheet + * + * Call engraveABC to run the process. This creates a graphelems ABCJS Abstract Engraving Structure (ABCJS AES) that can be accessed through this.staffgroups + * this data structure is first laid out (giving the graphelems x and y coordinates) and then drawn onto the renderer + * each ABCJS AES represents a single staffgroup - all elements that are not in a staffgroup are rendered directly by the controller + * + * elements in ABCJS AES know their "source data" in the ABCJS AST, and their "target shape" + * in the renderer for highlighting purposes + * + */ +var EngraverController = function (paper, params) { + params = params || {}; + this.findSelectableElement = findSelectableElement; + this.oneSvgPerLine = params.oneSvgPerLine; + this.selectionColor = params.selectionColor; + this.dragColor = params.dragColor ? params.dragColor : params.selectionColor; + this.dragging = !!params.dragging; + this.selectTypes = params.selectTypes; + this.responsive = params.responsive; + this.space = 3 * spacing.SPACE; + this.initialClef = params.initialClef; + this.timeBasedLayout = params.timeBasedLayout; + this.expandToWidest = !!params.expandToWidest; + this.scale = params.scale ? parseFloat(params.scale) : 0; + this.classes = new Classes({ shouldAddClasses: params.add_classes }); + if (!(this.scale > 0.1)) + this.scale = undefined; + + if (params.staffwidth) { + // Note: Normally all measurements to the engraver are in POINTS. However, if a person is formatting for the + // screen and directly inputting the width, then it is more logical to have the measurement in pixels. + this.staffwidthScreen = params.staffwidth; + this.staffwidthPrint = params.staffwidth; + } else { + this.staffwidthScreen = 740; // TODO-PER: Not sure where this number comes from, but this is how it's always been. + this.staffwidthPrint = 680; // The number of pixels in 8.5", after 1cm of margin has been removed. + } + this.listeners = []; + if (params.clickListener) + this.addSelectListener(params.clickListener); + + this.renderer = new Renderer(paper); + this.renderer.setPaddingOverride(params); + if (params.showDebug) + this.renderer.showDebug = params.showDebug; + if (params.jazzchords) + this.jazzchords = params.jazzchords; + if (params.accentAbove) + this.accentAbove = params.accentAbove; + if (params.germanAlphabet) + this.germanAlphabet = params.germanAlphabet; + if (params.lineThickness) + this.lineThickness = params.lineThickness; + this.renderer.controller = this; // TODO-GD needed for highlighting + this.renderer.foregroundColor = params.foregroundColor ? params.foregroundColor : "currentColor"; + if (params.ariaLabel !== undefined) + this.renderer.ariaLabel = params.ariaLabel; + this.renderer.minPadding = params.minPadding ? params.minPadding : 0; + + this.reset(); +}; + +EngraverController.prototype.reset = function () { + this.selected = []; + this.staffgroups = []; + if (this.engraver) + this.engraver.reset(); + this.engraver = null; + this.renderer.reset(); + this.dragTarget = null; + this.dragIndex = -1; + this.dragMouseStart = { x: -1, y: -1 }; + this.dragYStep = 0; + if (this.lineThickness) + this.renderer.setLineThickness(this.lineThickness) +}; + +/** + * run the engraving process + */ +EngraverController.prototype.engraveABC = function (abctunes, tuneNumber, lineOffset) { + if (abctunes[0] === undefined) { + abctunes = [abctunes]; + } + this.reset(); + + for (var i = 0; i < abctunes.length; i++) { + if (tuneNumber === undefined) + tuneNumber = i; + this.getFontAndAttr = new GetFontAndAttr(abctunes[i].formatting, this.classes); + this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper); + this.engraveTune(abctunes[i], tuneNumber, lineOffset); + } +}; + +/** + * Some of the items on the page are not scaled, so adjust them in the opposite direction of scaling to cancel out the scaling. + */ +EngraverController.prototype.adjustNonScaledItems = function (scale) { + this.width /= scale; + this.renderer.adjustNonScaledItems(scale); +}; + +EngraverController.prototype.getMeasureWidths = function (abcTune) { + this.reset(); + this.getFontAndAttr = new GetFontAndAttr(abcTune.formatting, this.classes); + this.getTextSize = new GetTextSize(this.getFontAndAttr, this.renderer.paper); + var origJazzChords = this.jazzchords + + this.setupTune(abcTune, 0); + this.constructTuneElements(abcTune); + // layout() sets the x-coordinate of the abcTune element here: + // abcTune.lines[0].staffGroup.voices[0].children[0].x + layout(this.renderer, abcTune, 0, this.space, this.timeBasedLayout); + + var ret = []; + var section; + + var needNewSection = true; + for (var i = 0; i < abcTune.lines.length; i++) { + var abcLine = abcTune.lines[i]; + if (abcLine.staff) { + if (needNewSection) { + section = { + left: 0, + measureWidths: [], + //height: this.renderer.padding.top + this.renderer.spacing.music + this.renderer.padding.bottom + 24, // the 24 is the empirical value added to the bottom of all tunes. + total: 0 + }; + ret.push(section); + needNewSection = false; + } + // At this point, the voices are laid out so that the bar lines are even with each other. So we just need to get the placement of the first voice. + if (abcLine.staffGroup.voices.length > 0) { + var voice = abcLine.staffGroup.voices[0]; + var foundNotStaffExtra = false; + var lastXPosition = 0; + for (var k = 0; k < voice.children.length; k++) { + var child = voice.children[k]; + if (!foundNotStaffExtra && !child.isClef && !child.isKeySig) { + foundNotStaffExtra = true; + section.left = child.x; + lastXPosition = child.x; + } + if (child.type === 'bar') { + section.measureWidths.push(child.x - lastXPosition); + section.total += (child.x - lastXPosition); + lastXPosition = child.x; + } + } + } + //section.height += calcHeight(abcLine.staffGroup) * spacing.STEP; + } else + needNewSection = true; + } + this.jazzchords = origJazzChords + return ret; +}; + +EngraverController.prototype.setupTune = function (abcTune, tuneNumber) { + this.classes.reset(); + + if (abcTune.formatting.jazzchords !== undefined) + this.jazzchords = abcTune.formatting.jazzchords; + if (abcTune.formatting.accentAbove !== undefined) + this.accentAbove = abcTune.formatting.accentAbove; + + this.renderer.newTune(abcTune); + this.engraver = new AbstractEngraver(this.getTextSize, tuneNumber, { + bagpipes: abcTune.formatting.bagpipes, + flatbeams: abcTune.formatting.flatbeams, + graceSlurs: abcTune.formatting.graceSlurs !== false, // undefined is the default, which is true + percmap: abcTune.formatting.percmap, + initialClef: this.initialClef, + jazzchords: this.jazzchords, + timeBasedLayout: this.timeBasedLayout, + accentAbove: this.accentAbove, + germanAlphabet: this.germanAlphabet + }); + this.engraver.setStemHeight(this.renderer.spacing.stemHeight); + this.engraver.measureLength = abcTune.getMeterFraction().num / abcTune.getMeterFraction().den; + if (abcTune.formatting.staffwidth) { + this.width = abcTune.formatting.staffwidth * 1.33; // The width is expressed in pt; convert to px. + } else { + this.width = this.renderer.isPrint ? this.staffwidthPrint : this.staffwidthScreen; + } + + var scale = abcTune.formatting.scale ? abcTune.formatting.scale : this.scale; + if (this.responsive === "resize") // The resizing will mess with the scaling, so just don't do it explicitly. + scale = undefined; + if (scale === undefined) scale = this.renderer.isPrint ? 0.75 : 1; + this.adjustNonScaledItems(scale); + return scale; +}; + +EngraverController.prototype.constructTuneElements = function (abcTune) { + abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); + + // Generate the raw staff line data + var i; + var abcLine; + var hasPrintedTempo = false; + var hasSeenNonSubtitle = false; + for (i = 0; i < abcTune.lines.length; i++) { + abcLine = abcTune.lines[i]; + if (abcLine.staff) { + hasSeenNonSubtitle = true; + abcLine.staffGroup = this.engraver.createABCLine(abcLine.staff, !hasPrintedTempo ? abcTune.metaText.tempo : null, i); + hasPrintedTempo = true; + } else if (abcLine.subtitle) { + // If the subtitle is at the top, then it was already accounted for. So skip all subtitles until the first non-subtitle line. + if (hasSeenNonSubtitle) { + var center = this.width / 2 + this.renderer.padding.left; + abcLine.nonMusic = new Subtitle(this.renderer.spacing.subtitle, abcTune.formatting, abcLine.subtitle, center, this.renderer.padding.left, this.getTextSize); + } + } else if (abcLine.text !== undefined) { + hasSeenNonSubtitle = true; + abcLine.nonMusic = new FreeText(abcLine.text, abcLine.vskip, this.getFontAndAttr, this.renderer.padding.left, this.width, this.getTextSize); + } else if (abcLine.separator !== undefined && abcLine.separator.lineLength) { + hasSeenNonSubtitle = true; + abcLine.nonMusic = new Separator(abcLine.separator.spaceAbove, abcLine.separator.lineLength, abcLine.separator.spaceBelow); + } + } + abcTune.bottomText = new BottomText(abcTune.metaText, this.width, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); +}; + +EngraverController.prototype.engraveTune = function (abcTune, tuneNumber, lineOffset) { + + var origJazzChords = this.jazzchords + var scale = this.setupTune(abcTune, tuneNumber); + + // Create all of the element objects that will appear on the page. + this.constructTuneElements(abcTune); + + //Set the top text now that we know the width + + // Do all the positioning, both horizontally and vertically + var maxWidth = layout(this.renderer, abcTune, this.width, this.space, this.expandToWidest, this.timeBasedLayout); + + //Set the top text now that we know the width + if (this.expandToWidest && maxWidth > this.width + 1) { + + abcTune.topText = new TopText(abcTune.metaText, abcTune.metaTextInfo, abcTune.formatting, abcTune.lines, maxWidth, this.renderer.isPrint, this.renderer.padding.left, this.renderer.spacing, this.classes.shouldAddClasses, this.getTextSize); + + if ((abcTune.lines)&&(abcTune.lines.length > 0)){ + var nlines = abcTune.lines.length; + + for (var i=0;i 0)){ + var nRows = entry.nonMusic.rows.length; + for (var j=0;j0)){ + if (entry.text[0].center){ + thisRow.left = (maxWidth/2) + this.renderer.padding.left; + } + } + } + } + } + } + } + } + } + } + + // Deal with tablature for staff + if (abcTune.tablatures) { + tablatures.layoutTablatures(this.renderer, abcTune); + } + + // Do all the writing to the SVG + var ret = draw(this.renderer, this.classes, abcTune, this.width, maxWidth, this.responsive, scale, this.selectTypes, tuneNumber, lineOffset); + this.staffgroups = ret.staffgroups; + this.selectables = ret.selectables; + if (this.oneSvgPerLine) { + var div = this.renderer.paper.svg.parentNode; + this.svgs = splitSvgIntoLines(this.renderer, div, abcTune.metaText.title, this.responsive, scale); + } else { + this.svgs = [this.renderer.paper.svg]; + } + setupSelection(this, this.svgs); + + this.jazzchords = origJazzChords +}; + +function splitSvgIntoLines(renderer, output, title, responsive, scale) { + // Each line is a top level in the svg. To split it into separate + // svgs iterate through each of those and put them in a new svg. Since + // they are placed absolutely, the viewBox needs to be manipulated to + // get the correct vertical positioning. + // We copy all the attributes from the original svg except for the aria-label + // since we want that to include a count. And the height is now a fraction of the original svg. + if (!title) title = "Untitled" + var source = output.querySelector("svg") + if (responsive === 'resize') + output.style.paddingBottom = '' + var style = source.querySelector("style") + var width = responsive === 'resize' ? source.viewBox.baseVal.width : source.getAttribute("width") + var sections = output.querySelectorAll("svg > g") // each section is a line, or the top matter or the bottom matter, or text that has been inserted. + var nextTop = 0 // There are often gaps between the elements for spacing, so the actual top and height needs to be inferred. + var wrappers = [] // Create all the elements and place them at once because we use the current svg to get data. It would disappear after placing the first line. + var svgs = [] + for (var i = 0; i < sections.length; i++) { + var section = sections[i] + var box = section.getBBox() + var gapBetweenLines = box.y - nextTop // take the margin into account + var height = box.height + gapBetweenLines; + var wrapper = document.createElement("div"); + var divStyles = "overflow: hidden;" + if (responsive !== 'resize') + divStyles += "height:" + (height * scale) + "px;" + wrapper.setAttribute("style", divStyles) + var svg = duplicateSvg(source) + var fullTitle = "Sheet Music for \"" + title + "\" section " + (i + 1) + svg.setAttribute("aria-label", fullTitle) + if (responsive !== 'resize') + svg.setAttribute("height", height) + if (responsive === 'resize') + svg.style.position = '' + // TODO-PER: Hack! Not sure why this is needed. + var viewBoxHeight = renderer.firefox112 ? height+1 : height + svg.setAttribute("viewBox", "0 " + nextTop + " " + width + " " + viewBoxHeight) + svg.appendChild(style.cloneNode(true)) + var titleEl = document.createElement("title") + titleEl.innerText = fullTitle + svg.appendChild(titleEl) + svg.appendChild(section) + + wrapper.appendChild(svg) + svgs.push(svg) + output.appendChild(wrapper) + //wrappers.push(wrapper) + nextTop = box.y + box.height + } + // for (i = 0; i < wrappers.length; i++) + // output.appendChild(wrappers[i]) + output.removeChild(source) + return svgs; +} + +function duplicateSvg(source) { + var svgNS = "http://www.w3.org/2000/svg"; + var svg = document.createElementNS(svgNS, "svg"); + for (var i = 0; i < source.attributes.length; i++) { + var attr = source.attributes[i]; + if (attr.name !== "height" && attr.name != "aria-label") + svg.setAttribute(attr.name, attr.value) + } + return svg; +} + +EngraverController.prototype.getDim = function (historyEl) { + // Get the dimensions on demand because the getBBox call is expensive. + if (!historyEl.dim) { + var box = historyEl.svgEl.getBBox(); + historyEl.dim = { left: Math.round(box.x), top: Math.round(box.y), right: Math.round(box.x + box.width), bottom: Math.round(box.y + box.height) }; + } + return historyEl.dim; +}; + +EngraverController.prototype.addSelectListener = function (clickListener) { + this.listeners[this.listeners.length] = clickListener; +}; + +module.exports = EngraverController; diff --git a/src/write/helpers/classes.js b/src/write/helpers/classes.js new file mode 100644 index 0000000000000000000000000000000000000000..2a3003e78f29f5186ce5ac87df8ad78c4de83e73 --- /dev/null +++ b/src/write/helpers/classes.js @@ -0,0 +1,104 @@ +var Classes = function Classes(options) { + this.shouldAddClasses = options.shouldAddClasses; + this.reset(); +} + +Classes.prototype.reset = function () { + this.lineNumber = null; + this.voiceNumber = null; + this.measureNumber = null; + this.measureTotalPerLine = []; + this.noteNumber = null; +} + +Classes.prototype.incrLine = function () { + if (this.lineNumber === null) + this.lineNumber = 0; + else + this.lineNumber++; + this.voiceNumber = null; + this.measureNumber = null; + this.noteNumber = null; +}; + +Classes.prototype.incrVoice = function () { + if (this.voiceNumber === null) + this.voiceNumber = 0; + else + this.voiceNumber++; + this.measureNumber = null; + this.noteNumber = null; +}; + +Classes.prototype.isInMeasure = function () { + return this.measureNumber !== null; +}; + +Classes.prototype.newMeasure = function () { + if (this.measureNumber) + this.measureTotalPerLine[this.lineNumber] = this.measureNumber; + this.measureNumber = null; + this.noteNumber = null; +}; + +Classes.prototype.startMeasure = function () { + this.measureNumber = 0; + this.noteNumber = 0; +}; + +Classes.prototype.incrMeasure = function () { + this.measureNumber++; + this.noteNumber = 0; +}; + +Classes.prototype.incrNote = function () { + this.noteNumber++; +}; + +Classes.prototype.measureTotal = function () { + var total = 0; + for (var i = 0; i < this.lineNumber; i++) + total += this.measureTotalPerLine[i] ? this.measureTotalPerLine[i] : 0; // This can be null when non-music things are present. + if (this.measureNumber) + total += this.measureNumber; + return total; +}; + +Classes.prototype.getCurrent = function (c) { + return { + line: this.lineNumber, + measure: this.measureNumber, + measureTotal: this.measureTotal(), + voice: this.voiceNumber, + note: this.noteNumber + }; +}; + +Classes.prototype.generate = function (c) { + if (!this.shouldAddClasses) + return ""; + var ret = []; + if (c && c.length > 0) ret.push(c); + if (c === "abcjs-tab-number") // TODO-PER-HACK! straighten out the tablature + return ret.join(' ') + if (c === "text instrument-name") + return "abcjs-text abcjs-instrument-name" + if (this.lineNumber !== null) ret.push("l" + this.lineNumber); + if (this.measureNumber !== null) ret.push("m" + this.measureNumber); + if (this.measureNumber !== null) ret.push("mm" + this.measureTotal()); // measureNumber is null between measures so this is still the test for measureTotal + if (this.voiceNumber !== null) ret.push("v" + this.voiceNumber); + if (c && (c.indexOf('note') >= 0 || c.indexOf('rest') >= 0 || c.indexOf('lyric') >= 0) && this.noteNumber !== null) ret.push("n" + this.noteNumber); + // add a prefix to all classes that abcjs adds. + if (ret.length > 0) { + ret = ret.join(' '); // Some strings are compound classes - that is, specify more than one class in a string. + ret = ret.split(' '); + for (var i = 0; i < ret.length; i++) { + if (ret[i].indexOf('abcjs-') !== 0 && ret[i].length > 0) // if the prefix doesn't already exist and the class is not blank. + ret[i] = 'abcjs-' + ret[i]; + } + } + return ret.join(' '); +}; + + +module.exports = Classes; diff --git a/src/write/helpers/get-font-and-attr.js b/src/write/helpers/get-font-and-attr.js new file mode 100644 index 0000000000000000000000000000000000000000..f511a232d61457d1e37eb76a37311d9a6c09e2f9 --- /dev/null +++ b/src/write/helpers/get-font-and-attr.js @@ -0,0 +1,46 @@ +var GetFontAndAttr = function GetFontAndAttr(formatting, classes) { + this.formatting = formatting; + this.classes = classes; +}; + +GetFontAndAttr.prototype.updateFonts = function (fontOverrides) { + if (fontOverrides.gchordfont) + this.formatting.gchordfont = fontOverrides.gchordfont; + if (fontOverrides.tripletfont) + this.formatting.tripletfont = fontOverrides.tripletfont; + if (fontOverrides.annotationfont) + this.formatting.annotationfont = fontOverrides.annotationfont; + if (fontOverrides.vocalfont) + this.formatting.vocalfont = fontOverrides.vocalfont; +}; + +GetFontAndAttr.prototype.getFamily = function (type) { + if (type[0] === '"' && type[type.length-1] === '"') { + return type.substring(1, type.length-1) + } + return type +}; + +GetFontAndAttr.prototype.calc = function (type, klass) { + var font; + if (typeof type === 'string') { + font = this.formatting[type]; + // Raphael deliberately changes the font units to pixels for some reason, so we need to change points to pixels here. + if (font) + font = { face: font.face, size: Math.round(font.size * 4 / 3), decoration: font.decoration, style: font.style, weight: font.weight, box: font.box }; + else + font = { face: "Arial", size: Math.round(12 * 4 / 3), decoration: "underline", style: "normal", weight: "normal" }; + } else + font = { face: type.face, size: Math.round(type.size * 4 / 3), decoration: type.decoration, style: type.style, weight: type.weight, box: type.box }; + var paddingPercent = this.formatting.fontboxpadding ? this.formatting.fontboxpadding : 0.1 + font.padding = font.size * paddingPercent; + + var attr = { + "font-size": font.size, 'font-style': font.style, + "font-family": this.getFamily(font.face), 'font-weight': font.weight, 'text-decoration': font.decoration, + 'class': this.classes.generate(klass) + }; + return { font: font, attr: attr }; +}; + +module.exports = GetFontAndAttr; diff --git a/src/write/helpers/get-text-size.js b/src/write/helpers/get-text-size.js new file mode 100644 index 0000000000000000000000000000000000000000..82c13754f0fc51a77429b2b8bac4bb77b6bc6ebe --- /dev/null +++ b/src/write/helpers/get-text-size.js @@ -0,0 +1,63 @@ +var GetTextSize = function GetTextSize(getFontAndAttr, svg) { + this.getFontAndAttr = getFontAndAttr; + this.svg = svg; +}; + +GetTextSize.prototype.updateFonts = function (fontOverrides) { + this.getFontAndAttr.updateFonts(fontOverrides); +}; + +GetTextSize.prototype.attr = function (type, klass) { + return this.getFontAndAttr.calc(type, klass); +}; + +GetTextSize.prototype.getFamily = function (type) { + if (type[0] === '"' && type[type.length-1] === '"') { + return type.substring(1, type.length-1) + } + return type +}; + +GetTextSize.prototype.calc = function (text, type, klass, el) { + var hash; + // This can be passed in either a string or a font. If it is a string it names one of the standard fonts. + if (typeof type === 'string') + hash = this.attr(type, klass); + else { + hash = { + font: { + face: type.face, + size: type.size, + decoration: type.decoration, + style: type.style, + weight: type.weight + }, + attr: { + "font-size": type.size, + "font-style": type.style, + "font-family": this.getFamily(type.face), + "font-weight": type.weight, + "text-decoration": type.decoration, + "class": this.getFontAndAttr.classes.generate(klass) + } + } + } + var size = this.svg.getTextSize(text, hash.attr, el); + if (hash.font.box) { + // Add padding and an equal margin to each side. + return { height: size.height + hash.font.padding * 4, width: size.width + hash.font.padding * 4 }; + } + return size; +}; + +GetTextSize.prototype.baselineToCenter = function (text, type, klass, index, total) { + // This is for the case where SVG wants to use the baseline of the first line as the Y coordinate. + // If there are multiple lines of text or there is an array of text then that will not be centered so this adjusts it. + var height = this.calc(text, type, klass).height; + var fontHeight = this.attr(type, klass).font.size; + + return height * 0.5 + (total - index - 2) * fontHeight; +}; + + +module.exports = GetTextSize; diff --git a/src/write/helpers/set-class.js b/src/write/helpers/set-class.js new file mode 100644 index 0000000000000000000000000000000000000000..ff9c099d7666c6c3b4f8f336c6253bf70905c7d5 --- /dev/null +++ b/src/write/helpers/set-class.js @@ -0,0 +1,21 @@ +var setClass = function (elemset, addClass, removeClass, color) { + if (!elemset) + return; + for (var i = 0; i < elemset.length; i++) { + var el = elemset[i]; + var attr = el.getAttribute("highlight"); + if (!attr) attr = "fill"; + el.setAttribute(attr, color); + var kls = el.getAttribute("class"); + if (!kls) kls = ""; + kls = kls.replace(removeClass, ""); + kls = kls.replace(addClass, ""); + if (addClass.length > 0) { + if (kls.length > 0 && kls[kls.length - 1] !== ' ') kls += " "; + kls += addClass; + } + el.setAttribute("class", kls); + } +}; + +module.exports = setClass; diff --git a/src/write/helpers/spacing.js b/src/write/helpers/spacing.js new file mode 100644 index 0000000000000000000000000000000000000000..113cb161a7b81cbf229897e5c793a448dd2a70a0 --- /dev/null +++ b/src/write/helpers/spacing.js @@ -0,0 +1,11 @@ +var spacing = {}; + +spacing.FONTEM = 360; +spacing.FONTSIZE = 30; +spacing.STEP = spacing.FONTSIZE * 93 / 720; +spacing.SPACE = 10; +spacing.TOPNOTE = 15; +spacing.STAVEHEIGHT = 100; +spacing.INDENT = 50; + +module.exports = spacing; diff --git a/src/write/interactive/create-analysis.js b/src/write/interactive/create-analysis.js new file mode 100644 index 0000000000000000000000000000000000000000..aa786e441ada69b285192278567142611529b9fc --- /dev/null +++ b/src/write/interactive/create-analysis.js @@ -0,0 +1,50 @@ +function findNumber(klass, match, target, name) { + if (klass.indexOf(match) === 0) { + var value = klass.replace(match, ''); + var num = parseInt(value, 10); + if ('' + num === value) + target[name] = num; + } +} + +function createAnalysis(target, ev) { + var classes = []; + if (target.absEl.elemset) { + var classObj = {}; + for (var j = 0; j < target.absEl.elemset.length; j++) { + var es = target.absEl.elemset[j]; + if (es) { + var klass = es.getAttribute("class").split(' '); + for (var k = 0; k < klass.length; k++) + classObj[klass[k]] = true; + } + } + for (var kk = 0; kk < Object.keys(classObj).length; kk++) + classes.push(Object.keys(classObj)[kk]); + } + var analysis = {}; + for (var ii = 0; ii < classes.length; ii++) { + findNumber(classes[ii], "abcjs-v", analysis, "voice"); + findNumber(classes[ii], "abcjs-l", analysis, "line"); + findNumber(classes[ii], "abcjs-m", analysis, "measure"); + } + if (target.staffPos) + analysis.staffPos = target.staffPos; + var closest = ev.target; + while (closest && closest.dataset && !closest.dataset.name && closest.tagName.toLowerCase() !== 'svg') + closest = closest.parentNode; + var parent = ev.target; + while (parent && parent.dataset && !parent.dataset.index && parent.tagName.toLowerCase() !== 'svg') + parent = parent.parentNode; + if (parent && parent.dataset) { + analysis.name = parent.dataset.name; + analysis.clickedName = closest.dataset.name; + analysis.parentClasses = parent.classList; + } + if (closest && closest.classList) + analysis.clickedClasses = closest.classList; + analysis.selectableElement = target.svgEl; + return {classes: classes, analysis: analysis} +} + +module.exports = createAnalysis; diff --git a/src/write/interactive/find-selectable-element.js b/src/write/interactive/find-selectable-element.js new file mode 100644 index 0000000000000000000000000000000000000000..5893125bd3d63a6da47046f3672ccd6715ff6883 --- /dev/null +++ b/src/write/interactive/find-selectable-element.js @@ -0,0 +1,24 @@ +var createAnalysis = require('./create-analysis'); + +function findSelectableElement(event) { + var selectable = event + while (selectable && selectable.attributes && selectable.tagName.toLowerCase() !== 'svg' && !selectable.attributes.selectable) { + selectable = selectable.parentNode + } + if (selectable && selectable.attributes && selectable.attributes.selectable) { + var index = selectable.attributes['data-index'].nodeValue + if (index) { + index = parseInt(index, 10) + if (index >= 0 && index < this.selectables.length) { + var element = this.selectables[index] + var ret = createAnalysis(element, event) + ret.index = index + ret.element = element + return ret + } + } + } + return null +} + +module.exports = findSelectableElement; diff --git a/src/write/interactive/highlight.js b/src/write/interactive/highlight.js new file mode 100644 index 0000000000000000000000000000000000000000..ec888078434d9e14c7fe3c5aaef8e2d19078c2c2 --- /dev/null +++ b/src/write/interactive/highlight.js @@ -0,0 +1,11 @@ +var setClass = require('../helpers/set-class'); + +var highlight = function (klass, color) { + if (klass === undefined) + klass = "abcjs-note_selected"; + if (color === undefined) + color = "#ff0000"; + setClass(this.elemset, klass, "", color); +}; + +module.exports = highlight; diff --git a/src/write/interactive/selection.js b/src/write/interactive/selection.js new file mode 100644 index 0000000000000000000000000000000000000000..1dd8ad26a86e8d373364f3f593f128ff699a9c98 --- /dev/null +++ b/src/write/interactive/selection.js @@ -0,0 +1,418 @@ +var spacing = require('../helpers/spacing'); +var createAnalysis = require('./create-analysis'); + +function setupSelection(engraver, svgs) { + engraver.rangeHighlight = rangeHighlight; + if (engraver.dragging) { + for (var h = 0; h < engraver.selectables.length; h++) { + var hist = engraver.selectables[h]; + if (hist.svgEl.getAttribute("selectable") === "true") { + hist.svgEl.setAttribute("tabindex", 0); + hist.svgEl.setAttribute("data-index", h); + hist.svgEl.addEventListener("keydown", keyboardDown.bind(engraver)); + hist.svgEl.addEventListener("keyup", keyboardSelection.bind(engraver)); + hist.svgEl.addEventListener("focus", elementFocused.bind(engraver)); + } + } + } + for (var i = 0; i < svgs.length; i++) { + svgs[i].addEventListener('touchstart', mouseDown.bind(engraver), { passive: true }); + svgs[i].addEventListener('touchmove', mouseMove.bind(engraver), { passive: true }); + svgs[i].addEventListener('touchend', mouseUp.bind(engraver), { passive: true }); + svgs[i].addEventListener('mousedown', mouseDown.bind(engraver)); + svgs[i].addEventListener('mousemove', mouseMove.bind(engraver)); + svgs[i].addEventListener('mouseup', mouseUp.bind(engraver)); + } +} + +function getCoord(ev) { + var scaleX = 1; + var scaleY = 1; + var svg = ev.target.closest('svg') + var yOffset = 0 + + // when renderer.options.responsive === 'resize' the click coords are in relation to the HTML + // element, we need to convert to the SVG viewBox coords + if (svg && svg.viewBox && svg.viewBox.baseVal) { // Firefox passes null to this when no viewBox is given + // Chrome makes these values null when no viewBox is given. + if (svg.viewBox.baseVal.width !== 0) + scaleX = svg.viewBox.baseVal.width / svg.clientWidth + if (svg.viewBox.baseVal.height !== 0) + scaleY = svg.viewBox.baseVal.height / svg.clientHeight + yOffset = svg.viewBox.baseVal.y + } + + var svgClicked = ev.target && ev.target.tagName === "svg"; + var x; + var y; + if (svgClicked) { + x = ev.offsetX; + y = ev.offsetY; + } else { + x = ev.layerX; + y = ev.layerY; + } + + x = x * scaleX; + y = y * scaleY; + //console.log(x, y) + + return [x, y + yOffset]; +} + +function elementFocused(ev) { + // If there had been another element focused and is being dragged, then report that before setting the new element up. + if (this.dragMechanism === "keyboard" && this.dragYStep !== 0 && this.dragTarget) + notifySelect.bind(this)(this.dragTarget, this.dragYStep, this.selectables.length, this.dragIndex, ev); + + this.dragYStep = 0; +} + +function keyboardDown(ev) { + // Swallow the up and down arrow events - they will be used for dragging with the keyboard + switch (ev.keyCode) { + case 38: + case 40: + ev.preventDefault(); + } +} + +function keyboardSelection(ev) { + // "this" is the EngraverController because of the bind(this) when setting the event listener. + var handled = false; + var index = ev.target.dataset.index; + switch (ev.keyCode) { + case 13: + case 32: + handled = true; + this.dragTarget = this.selectables[index]; + this.dragIndex = index; + this.dragMechanism = "keyboard"; + mouseUp.bind(this)(ev); + break; + case 38: // arrow up + handled = true; + this.dragTarget = this.selectables[index]; + this.dragIndex = index; + if (this.dragTarget && this.dragTarget.isDraggable) { + if (this.dragging && this.dragTarget.isDraggable) + this.dragTarget.absEl.highlight(undefined, this.dragColor); + this.dragYStep--; + this.dragTarget.svgEl.setAttribute("transform", "translate(0," + (this.dragYStep * spacing.STEP) + ")"); + } + break; + case 40: // arrow down + handled = true; + this.dragTarget = this.selectables[index]; + this.dragIndex = index; + this.dragMechanism = "keyboard"; + if (this.dragTarget && this.dragTarget.isDraggable) { + if (this.dragging && this.dragTarget.isDraggable) + this.dragTarget.absEl.highlight(undefined, this.dragColor); + this.dragYStep++; + this.dragTarget.svgEl.setAttribute("transform", "translate(0," + (this.dragYStep * spacing.STEP) + ")"); + } + break; + case 9: // tab + // This is losing focus - if there had been dragging, then do the callback + if (this.dragYStep !== 0) { + mouseUp.bind(this)(ev); + } + break; + default: + //console.log(ev); + break; + } + if (handled) + ev.preventDefault(); +} + +function findElementInHistory(selectables, el) { + if (!el) + return -1; + for (var i = 0; i < selectables.length; i++) { + if (el.dataset.index === selectables[i].svgEl.dataset.index) + return i; + } + return -1; +} + +function findElementByCoord(self, x, y) { + var minDistance = 9999999; + var closestIndex = -1; + for (var i = 0; i < self.selectables.length && minDistance > 0; i++) { + var el = self.selectables[i]; + + self.getDim(el); + if (el.dim.left < x && el.dim.right > x && el.dim.top < y && el.dim.bottom > y) { + // See if it is a direct hit on an element - if so, definitely take it (there are no overlapping elements) + closestIndex = i; + minDistance = 0; + } else if (el.dim.top < y && el.dim.bottom > y) { + // See if it is the same vertical as the element. Then the distance is the x difference + var horiz = Math.min(Math.abs(el.dim.left - x), Math.abs(el.dim.right - x)); + if (horiz < minDistance) { + minDistance = horiz; + closestIndex = i; + } + } else if (el.dim.left < x && el.dim.right > x) { + // See if it is the same horizontal as the element. Then the distance is the y difference + var vert = Math.min(Math.abs(el.dim.top - y), Math.abs(el.dim.bottom - y)); + if (vert < minDistance) { + minDistance = vert; + closestIndex = i; + } + } else { + // figure out the distance to this element. + var dx = Math.abs(x - el.dim.left) > Math.abs(x - el.dim.right) ? Math.abs(x - el.dim.right) : Math.abs(x - el.dim.left); + var dy = Math.abs(y - el.dim.top) > Math.abs(y - el.dim.bottom) ? Math.abs(y - el.dim.bottom) : Math.abs(y - el.dim.top); + var hypotenuse = Math.sqrt(dx * dx + dy * dy); + if (hypotenuse < minDistance) { + minDistance = hypotenuse; + closestIndex = i; + } + } + } + return (closestIndex >= 0 && minDistance <= 12) ? closestIndex : -1; +} + +function getBestMatchCoordinates(dim, ev, scale) { + // Different browsers have conflicting meanings for the coordinates that are returned. + // If the item we want is clicked on directly, then we will just see what is the best match. + // This seems like less of a hack than browser sniffing. + if (dim.x <= ev.offsetX && dim.x + dim.width >= ev.offsetX && + dim.y <= ev.offsetY && dim.y + dim.height >= ev.offsetY) + return [ev.offsetX, ev.offsetY]; + // Firefox returns a weird value for offset, but layer is correct. + // Safari and Chrome return the correct value for offset, but layer is multiplied by the scale (that is, if it were rendered with { scale: 2 }) + // For instance (if scale is 2): + // Firefox: { offsetY: 5, layerY: 335 } + // Others: {offsetY: 335, layerY: 670} (there could be a little rounding, so the number might not be exactly 2x) + // So, if layerY/scale is approx. offsetY, then use offsetY, otherwise use layerY + var epsilon = Math.abs(ev.layerY / scale - ev.offsetY); + if (epsilon < 3) + return [ev.offsetX, ev.offsetY]; + else + return [ev.layerX, ev.layerY]; +} + +function getTarget(target) { + // This searches up the dom for the first item containing the attribute "selectable", or stopping at the SVG. + if (!target) + return null; + if (target.tagName === "svg") + return target; + + if (!target.getAttribute) + return null; + var found = target.getAttribute("selectable"); + while (!found) { + if (!target.parentElement) + found = true; + else { + target = target.parentElement; + if (target.tagName === "svg") + found = true; + else + found = target.getAttribute("selectable"); + } + } + return target; +} + +function getMousePosition(self, ev) { + // if the user clicked exactly on an element that we're interested in, then we already have the answer. + // This is more reliable than the calculations because firefox returns different coords for offsetX, offsetY + var x; + var y; + var box; + var clickedOn = findElementInHistory(self.selectables, getTarget(ev.target)); + if (clickedOn >= 0) { + // There was a direct hit on an element. + box = getBestMatchCoordinates(self.selectables[clickedOn].svgEl.getBBox(), ev, self.scale); + x = box[0]; + y = box[1]; + //console.log("clicked on", clickedOn, x, y, self.selectables[clickedOn].svgEl.getBBox(), ev.target.getBBox()); + } else { + // See if they clicked close to an element. + box = getCoord(ev); + x = box[0]; + y = box[1]; + clickedOn = findElementByCoord(self, x, y); + //console.log("clicked near", clickedOn, x, y, printEl(ev.target)); + } + return { x: x, y: y, clickedOn: clickedOn }; +} + +function attachMissingTouchEventAttributes(touchEv) { + if (!touchEv || !touchEv.target || !touchEv.touches || touchEv.touches.length < 1) + return + var rect = touchEv.target.getBoundingClientRect(); + var offsetX = touchEv.touches[0].pageX - rect.left; + var offsetY = touchEv.touches[0].pageY - rect.top; + + touchEv.touches[0].offsetX = offsetX; + touchEv.touches[0].offsetY = offsetY; + + touchEv.touches[0].layerX = touchEv.touches[0].pageX; + touchEv.touches[0].layerY = touchEv.touches[0].pageY; +} + +function mouseDown(ev) { + // "this" is the EngraverController because of the bind(this) when setting the event listener. + var _ev = ev; + if (ev.type === 'touchstart') { + attachMissingTouchEventAttributes(ev); + if (ev.touches.length > 0) + _ev = ev.touches[0]; + } + + var positioning = getMousePosition(this, _ev); + + // Only start dragging if the user clicked close enough to an element and clicked with the main mouse button. + if (positioning.clickedOn >= 0 && (ev.type === 'touchstart' || ev.button === 0) && this.selectables[positioning.clickedOn]) { + this.dragTarget = this.selectables[positioning.clickedOn]; + this.dragIndex = positioning.clickedOn; + this.dragMechanism = "mouse"; + this.dragMouseStart = { x: positioning.x, y: positioning.y }; + if (this.dragging && this.dragTarget.isDraggable) { + addGlobalClass(this.renderer.paper, "abcjs-dragging-in-progress"); + this.dragTarget.absEl.highlight(undefined, this.dragColor); + } + } +} + +function mouseMove(ev) { + var _ev = ev; + if (ev.type === 'touchmove') { + attachMissingTouchEventAttributes(ev); + if (ev.touches.length > 0) + _ev = ev.touches[0]; + } + this.lastTouchMove = ev; + // "this" is the EngraverController because of the bind(this) when setting the event listener. + + if (!this.dragTarget || !this.dragging || !this.dragTarget.isDraggable || this.dragMechanism !== 'mouse' || !this.dragMouseStart) + return; + + var positioning = getMousePosition(this, _ev); + + var yDist = Math.round((positioning.y - this.dragMouseStart.y) / spacing.STEP); + if (yDist !== this.dragYStep) { + this.dragYStep = yDist; + this.dragTarget.svgEl.setAttribute("transform", "translate(0," + (yDist * spacing.STEP) + ")"); + } +} + +function mouseUp(ev) { + // "this" is the EngraverController because of the bind(this) when setting the event listener. + var _ev = ev; + if (ev.type === 'touchend' && this.lastTouchMove) { + attachMissingTouchEventAttributes(this.lastTouchMove); + if (this.lastTouchMove && this.lastTouchMove.touches && this.lastTouchMove.touches.length > 0) + _ev = this.lastTouchMove.touches[0]; + } + + if (!this.dragTarget) + return; + + clearSelection.bind(this)(); + if (this.dragTarget.absEl && this.dragTarget.absEl.highlight) { + this.selected = [this.dragTarget.absEl]; + this.dragTarget.absEl.highlight(undefined, this.selectionColor); + } + + notifySelect.bind(this)(this.dragTarget, this.dragYStep, this.selectables.length, this.dragIndex, _ev); + if (this.dragTarget.svgEl && this.dragTarget.svgEl.focus) { + this.dragTarget.svgEl.focus(); + this.dragTarget = null; + this.dragIndex = -1; + } + removeGlobalClass(this.renderer.svg, "abcjs-dragging-in-progress"); +} + +function setSelection(dragIndex) { + if (dragIndex >= 0 && dragIndex < this.selectables.length) { + this.dragTarget = this.selectables[dragIndex]; + this.dragIndex = dragIndex; + this.dragMechanism = "keyboard"; + mouseUp.bind(this)({ target: this.dragTarget.svgEl }); + } +} + + +function notifySelect(target, dragStep, dragMax, dragIndex, ev) { + var ret = createAnalysis(target, ev) + var classes = ret.classes + var analysis = ret.analysis + + for (var i = 0; i < this.listeners.length; i++) { + this.listeners[i](target.absEl.abcelem, target.absEl.tuneNumber, classes.join(' '), analysis, { step: dragStep, max: dragMax, index: dragIndex, setSelection: setSelection.bind(this) }, ev); + } +} + +function clearSelection() { + for (var i = 0; i < this.selected.length; i++) { + this.selected[i].unhighlight(undefined, this.renderer.foregroundColor); + } + this.selected = []; +} + +function rangeHighlight(start, end) { + clearSelection.bind(this)(); + for (var line = 0; line < this.staffgroups.length; line++) { + var voices = this.staffgroups[line].voices; + for (var voice = 0; voice < voices.length; voice++) { + var elems = voices[voice].children; + for (var elem = 0; elem < elems.length; elem++) { + // Since the user can highlight more than an element, or part of an element, a hit is if any of the endpoints + // is inside the other range. + var elStart = elems[elem].abcelem.startChar; + var elEnd = elems[elem].abcelem.endChar; + if ((end > elStart && start < elEnd) || ((end === start) && end === elEnd)) { + // if (elems[elem].abcelem.startChar>=start && elems[elem].abcelem.endChar<=end) { + this.selected[this.selected.length] = elems[elem]; + elems[elem].highlight(undefined, this.selectionColor); + } + } + } + } +} + +function getClassSet(el) { + var oldClass = el.getAttribute('class'); + if (!oldClass) + oldClass = ""; + var klasses = oldClass.split(" "); + var obj = {}; + for (var i = 0; i < klasses.length; i++) + obj[klasses[i]] = true; + return obj; +} + +function setClassSet(el, klassSet) { + var klasses = []; + for (var key in klassSet) { + if (klassSet.hasOwnProperty(key)) + klasses.push(key); + } + el.setAttribute('class', klasses.join(' ')); +} + +function addGlobalClass(svg, klass) { + if (svg) { + var obj = getClassSet(svg.svg); + obj[klass] = true; + setClassSet(svg.svg, obj); + } +} + +function removeGlobalClass(svg, klass) { + if (svg) { + var obj = getClassSet(svg.svg); + delete obj[klass]; + setClassSet(svg.svg, obj); + } +} + +module.exports = setupSelection; diff --git a/src/write/interactive/unhighlight.js b/src/write/interactive/unhighlight.js new file mode 100644 index 0000000000000000000000000000000000000000..41f340b83aaf715c900b42c1f15a79066b0de6d7 --- /dev/null +++ b/src/write/interactive/unhighlight.js @@ -0,0 +1,11 @@ +var setClass = require('../helpers/set-class'); + +var unhighlight = function (klass, color) { + if (klass === undefined) + klass = "abcjs-note_selected"; + if (color === undefined) + color = "#000000"; + setClass(this.elemset, "", klass, color); +}; + +module.exports = unhighlight; diff --git a/src/write/layout/beam.js b/src/write/layout/beam.js new file mode 100644 index 0000000000000000000000000000000000000000..dc23d0cb54b833abd6d1913511540e902444caec --- /dev/null +++ b/src/write/layout/beam.js @@ -0,0 +1,213 @@ +var RelativeElement = require('../creation/elements/relative-element'); +var spacing = require('../helpers/spacing'); +var getBarYAt = require('./get-bar-y-at'); + +var layoutBeam = function (beam) { + if (beam.elems.length === 0 || beam.allrests) return; + + var dy = calcDy(beam.stemsUp, beam.isgrace); // This is the width of the beam line. + + // create the main beam + var firstElement = beam.elems[0]; + var lastElement = beam.elems[beam.elems.length - 1]; + var minStemHeight = 0; // The following is to leave space for "!///!" marks. + var referencePitch = beam.stemsUp ? firstElement.abcelem.maxpitch : firstElement.abcelem.minpitch; + minStemHeight = minStem(firstElement, beam.stemsUp, referencePitch, minStemHeight); + minStemHeight = minStem(lastElement, beam.stemsUp, referencePitch, minStemHeight); + minStemHeight = Math.max(beam.stemHeight, minStemHeight + 3); // TODO-PER: The 3 is the width of a 16th beam. The actual height of the beam should be used instead. + var yPos = calcYPos(beam.average, beam.elems.length, minStemHeight, beam.stemsUp, firstElement.abcelem.averagepitch, lastElement.abcelem.averagepitch, beam.isflat, beam.min, beam.max, beam.isgrace); + var xPos = calcXPos(beam.stemsUp, firstElement, lastElement); + beam.addBeam({ startX: xPos[0], endX: xPos[1], startY: yPos[0], endY: yPos[1], dy: dy }); + + // create the rest of the beams (in the case of 1/16th notes, etc. + var beams = createAdditionalBeams(beam.elems, beam.stemsUp, beam.beams[0], beam.isgrace, dy); + for (var i = 0; i < beams.length; i++) + beam.addBeam(beams[i]); + + // Now that the main beam is defined, we know how tall the stems should be, so create them and attach them to the original notes. + createStems(beam.elems, beam.stemsUp, beam.beams[0], dy, beam.mainNote); +}; + +var getDurlog = function (duration) { + // TODO-PER: This is a hack to prevent a Chrome lockup. Duration should have been defined already, + // but there's definitely a case where it isn't. [Probably something to do with triplets.] + if (duration === undefined) { + return 0; + } + // console.log("getDurlog: " + duration); + return Math.floor(Math.log(duration) / Math.log(2)); +}; + +// +// private functions +// +function minStem(element, stemsUp, referencePitch, minStemHeight) { + if (!element.children) + return minStemHeight; + for (var i = 0; i < element.children.length; i++) { + var elem = element.children[i]; + if (stemsUp && elem.top !== undefined && elem.c === "flags.ugrace") + minStemHeight = Math.max(minStemHeight, elem.top - referencePitch); + else if (!stemsUp && elem.bottom !== undefined && elem.c === "flags.ugrace") + minStemHeight = Math.max(minStemHeight, referencePitch - elem.bottom + 7); // The extra 7 is because we are measuring the slash from the top. + } + return minStemHeight; +} + +function calcSlant(leftAveragePitch, rightAveragePitch, numStems, isFlat) { + if (isFlat) + return 0; + var slant = leftAveragePitch - rightAveragePitch; + var maxSlant = numStems / 2; + + if (slant > maxSlant) slant = maxSlant; + if (slant < -maxSlant) slant = -maxSlant; + return slant; +} + +function calcDy(asc, isGrace) { + var dy = (asc) ? spacing.STEP : -spacing.STEP; + if (isGrace) dy = dy * 0.4; + return dy; +} + +function calcXPos(asc, firstElement, lastElement) { + var starthead = firstElement.heads[asc ? 0 : firstElement.heads.length - 1]; + var endhead = lastElement.heads[asc ? 0 : lastElement.heads.length - 1]; + var startX = starthead.x; + if (asc) startX += starthead.w - 0.6; + var endX = endhead.x; + endX += (asc) ? endhead.w : 0.6; + return [startX, endX]; +} + +function calcYPos(average, numElements, stemHeight, asc, firstAveragePitch, lastAveragePitch, isFlat, minPitch, maxPitch, isGrace) { + var barpos = stemHeight - 2; // (isGrace)? 5:7; + var barminpos = stemHeight - 2; + var pos = Math.round(asc ? Math.max(average + barpos, maxPitch + barminpos) : Math.min(average - barpos, minPitch - barminpos)); + + var slant = calcSlant(firstAveragePitch, lastAveragePitch, numElements, isFlat); + var startY = pos + Math.floor(slant / 2); + var endY = pos + Math.floor(-slant / 2); + + // If the notes are too high or too low, make the beam go down to the middle + if (!isGrace) { + if (asc && pos < 6) { + startY = 6; + endY = 6; + } else if (!asc && pos > 6) { + startY = 6; + endY = 6; + } + } + + return [startY, endY]; +} + +function createStems(elems, asc, beam, dy, mainNote) { + for (var i = 0; i < elems.length; i++) { + var elem = elems[i]; + if (elem.abcelem.rest) + continue; + // TODO-PER: This is odd. If it is a regular beam then elems is an array of AbsoluteElements, if it is a grace beam then it is an array of objects , so we directly attach the element to the parent. We tell it if is a grace note because they are passed in as a generic object instead of an AbsoluteElement. + var isGrace = elem.addExtra ? false : true; + var parent = isGrace ? mainNote : elem; + var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1]; + var ovalDelta = 1 / 5;//(isGrace)?1/3:1/5; + var pitch = furthestHead.pitch + ((asc) ? ovalDelta : -ovalDelta); + var dx = asc ? furthestHead.w : 0; // down-pointing stems start on the left side of the note, up-pointing stems start on the right side, so we offset by the note width. + if (!isGrace) + dx += furthestHead.dx; + var x = furthestHead.x + dx; // this is now the actual x location in pixels. + var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x); + var lineWidth = (asc) ? -0.6 : 0.6; + if (!asc) + bary -= (dy / 2) / spacing.STEP; // TODO-PER: This is just a fudge factor so the down-pointing stems don't overlap. + if (isGrace) + dx += elem.heads[0].dx; + // TODO-PER-HACK: One type of note head has a different placement of the stem. This should be more generically calculated: + if (furthestHead.c === 'noteheads.slash.quarter') { + if (asc) + pitch += 1; + else + pitch -= 1; + } + var stem = new RelativeElement(null, dx, 0, pitch, { + "type": "stem", + "pitch2": bary, + linewidth: lineWidth + }); + stem.setX(parent.x); // This is after the x coordinates were set, so we have to set it directly. + parent.addRight(stem); + } + +} + +function createAdditionalBeams(elems, asc, beam, isGrace, dy) { + var beams = []; + var auxBeams = []; // auxbeam will be {x, y, durlog, single} auxbeam[0] should match with durlog=-4 (16th) (j=-4-durlog) + for (var i = 0; i < elems.length; i++) { + var elem = elems[i]; + if (elem.abcelem.rest) + continue; + var furthestHead = elem.heads[(asc) ? 0 : elem.heads.length - 1]; + var x = furthestHead.x + ((asc) ? furthestHead.w : 0); + var bary = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, x); + + var sy = (asc) ? -1.5 : 1.5; + if (isGrace) sy = sy * 2 / 3; // This makes the second beam on grace notes closer to the first one. + var duration = elem.abcelem.duration; // get the duration via abcelem because of triplets + if (duration === 0) duration = 0.25; // if this is stemless, then we use quarter note as the duration. + for (var durlog = getDurlog(duration); durlog < -3; durlog++) { + var index = -4 - durlog; + if (auxBeams[index]) { + auxBeams[index].single = false; + } else { + auxBeams[index] = { + x: x + ((asc) ? -0.6 : 0), y: bary + sy * (index + 1), + durlog: durlog, single: true + }; + } + if (i > 0 && elem.abcelem.beambr && elem.abcelem.beambr <= (index + 1)) { + if (!auxBeams[index].split) + auxBeams[index].split = [auxBeams[index].x]; + var xPos = calcXPos(asc, elems[i - 1], elem); + if (auxBeams[index].split[auxBeams[index].split.length - 1] >= xPos[0]) { + // the reduction in beams leaves a note unattached so create a small flag for it. + xPos[0] += elem.w; + } + auxBeams[index].split.push(xPos[0]); + auxBeams[index].split.push(xPos[1]); + } + } + + for (var j = auxBeams.length - 1; j >= 0; j--) { + if (i === elems.length - 1 || getDurlog(elems[i + 1].abcelem.duration) > (-j - 4)) { + + var auxBeamEndX = x; + var auxBeamEndY = bary + sy * (j + 1); + + + if (auxBeams[j].single) { + auxBeamEndX = (i === 0) ? x + 5 : x - 5; + auxBeamEndY = getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, auxBeamEndX) + sy * (j + 1); + } + var b = { startX: auxBeams[j].x, endX: auxBeamEndX, startY: auxBeams[j].y, endY: auxBeamEndY, dy: dy } + if (auxBeams[j].split !== undefined) { + var split = auxBeams[j].split; + if (b.endX <= split[split.length - 1]) { + // the reduction in beams leaves the last note by itself, so create a little flag for it + split[split.length - 1] -= elem.w; + } + split.push(b.endX); + b.split = auxBeams[j].split; + } + beams.push(b); + auxBeams = auxBeams.slice(0, j); + } + } + } + return beams; +} + +module.exports = layoutBeam; diff --git a/src/write/layout/get-bar-y-at.js b/src/write/layout/get-bar-y-at.js new file mode 100644 index 0000000000000000000000000000000000000000..db2066290156979b7f400214f2aaeaa498846699 --- /dev/null +++ b/src/write/layout/get-bar-y-at.js @@ -0,0 +1,6 @@ +function getBarYAt(startx, starty, endx, endy, x) { + return starty + (endy - starty) / (endx - startx) * (x - startx); +} + +module.exports = getBarYAt; + diff --git a/src/write/layout/get-left-edge-of-staff.js b/src/write/layout/get-left-edge-of-staff.js new file mode 100644 index 0000000000000000000000000000000000000000..4cf805ac93f850ffec60be070fb4b35cb21a9c53 --- /dev/null +++ b/src/write/layout/get-left-edge-of-staff.js @@ -0,0 +1,56 @@ +function getLeftEdgeOfStaff(renderer, getTextSize, voices, brace, bracket) { + var x = renderer.padding.left; + + // find out how much space will be taken up by voice headers + var voiceheaderw = 0; + var i; + var size; + for (i = 0; i < voices.length; i++) { + if (voices[i].header) { + size = getTextSize.calc(voices[i].header, 'voicefont', ''); + voiceheaderw = Math.max(voiceheaderw, size.width); + } + } + voiceheaderw = addBraceSize(voiceheaderw, brace, getTextSize); + voiceheaderw = addBraceSize(voiceheaderw, bracket, getTextSize); + + if (voiceheaderw) { + // Give enough spacing to the right - we use the width of an A for the amount of spacing. + var sizeW = getTextSize.calc("A", 'voicefont', ''); + voiceheaderw += sizeW.width; + } + x += voiceheaderw; + + var ofs = 0; + ofs = setBraceLocation(brace, x, ofs); + ofs = setBraceLocation(bracket, x, ofs); + return x + ofs; +} + +function addBraceSize(voiceheaderw, brace, getTextSize) { + if (brace) { + for (var i = 0; i < brace.length; i++) { + if (brace[i].header) { + var size = getTextSize.calc(brace[i].header, 'voicefont', ''); + voiceheaderw = Math.max(voiceheaderw, size.width); + } + } + } + return voiceheaderw; +} + +function setBraceLocation(brace, x, ofs) { + if (brace) { + for (var i = 0; i < brace.length; i++) { + setLocation(x, brace[i]); + ofs = Math.max(ofs, brace[i].getWidth()); + } + } + return ofs; +} + +function setLocation(x, element) { + element.x = x; +} + +module.exports = getLeftEdgeOfStaff; diff --git a/src/write/layout/layout-in-grid.js b/src/write/layout/layout-in-grid.js new file mode 100644 index 0000000000000000000000000000000000000000..6f8da6781cde509e18ff6063ac4e6c18fb68c8f6 --- /dev/null +++ b/src/write/layout/layout-in-grid.js @@ -0,0 +1,83 @@ +var getLeftEdgeOfStaff = require('./get-left-edge-of-staff'); + +function layoutInGrid(renderer, staffGroup, timeBasedLayout) { + var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket); + var ret = getTotalDuration(staffGroup, timeBasedLayout.minPadding) + var totalDuration = ret.totalDuration + var minSpacing = ret.minSpacing + var totalWidth = minSpacing * totalDuration + if (timeBasedLayout.minWidth) + totalWidth = Math.max(totalWidth, timeBasedLayout.minWidth) + var leftAlignPadding = timeBasedLayout.minPadding ? timeBasedLayout.minPadding/2 : 2 // If the padding isn't specified still give it some + + staffGroup.startx = leftEdge + staffGroup.w = totalWidth + leftEdge + for (var i = 0; i < staffGroup.voices.length; i++) { + var voice = staffGroup.voices[i] + voice.startx = leftEdge + voice.w = totalWidth + leftEdge + + var x = leftEdge + var afterFixedLeft = false + var durationUnit = 0 + for (var j = 0; j < voice.children.length; j++) { + var child = voice.children[j] + if (!afterFixedLeft) { + if (child.duration !== 0) { + // We got to the first music element on the line + afterFixedLeft = true + durationUnit = (totalWidth + leftEdge - x) / totalDuration + staffGroup.gridStart = x + } else { + // We are still doing the preliminary stuff - clef, time sig, etc. + child.x = x + x += child.w + child.minspacing + } + } + if (afterFixedLeft) { + if (timeBasedLayout.align === 'center') + child.x = x + (child.duration * durationUnit) / 2 - child.w / 2 + else { + // left align with padding - but no padding for barlines, they should be right aligned. + // TODO-PER: it looks better to move bar lines one pixel to right. Not sure why. + if (child.duration === 0) { + child.x = x + 1 - child.w + } else { + // child.extraw has the width of the accidentals - push the note to the right to take that into consideration. It will be 0 if there is nothing to the left. + child.x = x + leftAlignPadding - child.extraw + } + } + x += child.duration * durationUnit + } + for (var k = 0; k < child.children.length; k++) { + var grandchild = child.children[k] + // some elements don't have a dx - Tempo, for instance + var dx = grandchild.dx ? grandchild.dx : 0 + grandchild.x = child.x + dx + } + } + staffGroup.gridEnd = x + } + return totalWidth +} + +function getTotalDuration(staffGroup, timeBasedLayout) { + var maxSpacing = 0 + var maxCount = 0 + for (var i = 0; i < staffGroup.voices.length; i++) { + var count = 0 + var voice = staffGroup.voices[i] + for (var j = 0; j < voice.children.length; j++) { + var element = voice.children[j] + count += element.duration + if (element.duration) { + var width = (element.w+timeBasedLayout) / element.duration + maxSpacing = Math.max(maxSpacing, width) + } + } + maxCount = Math.max(maxCount, count) + } + return { totalDuration: maxCount, minSpacing: maxSpacing} +} + +module.exports = layoutInGrid; diff --git a/src/write/layout/layout.js b/src/write/layout/layout.js new file mode 100644 index 0000000000000000000000000000000000000000..a0da69502c4f59df68e94762cf2c9eb990e77a6b --- /dev/null +++ b/src/write/layout/layout.js @@ -0,0 +1,129 @@ +var layoutVoice = require('./voice'); +var setUpperAndLowerElements = require('./set-upper-and-lower-elements'); +var layoutStaffGroup = require('./staff-group'); +var getLeftEdgeOfStaff = require('./get-left-edge-of-staff'); +var layoutInGrid = require('./layout-in-grid'); + +// This sets the "x" attribute on all the children in abctune.lines +// It also sets the "w" and "startx" attributes on "voices" +// It also sets the "w" and "startx" attributes on "voices.children" +var layout = function (renderer, abctune, width, space, expandToWidest, timeBasedLayout) { + var i; + var abcLine; + // Adjust the x-coordinates to their absolute positions + var maxWidth = width; + for (i = 0; i < abctune.lines.length; i++) { + abcLine = abctune.lines[i]; + if (abcLine.staff) { + // console.log("=== line", i) + var thisWidth; + if (timeBasedLayout !== undefined) + thisWidth = layoutInGrid(renderer, abcLine.staffGroup, timeBasedLayout); + else + thisWidth = setXSpacing(renderer, maxWidth, space, abcLine.staffGroup, abctune.formatting, i === abctune.lines.length - 1, false); + // console.log(thisWidth, maxWidth) + if (Math.round(thisWidth) > Math.round(maxWidth)) { // to take care of floating point weirdness + maxWidth = thisWidth + if (expandToWidest) + i = -1 // do the calculations over with the new width + } + } + } + + // Layout the beams and add the stems to the beamed notes. + for (i = 0; i < abctune.lines.length; i++) { + abcLine = abctune.lines[i]; + if (abcLine.staffGroup && abcLine.staffGroup.voices) { + for (var j = 0; j < abcLine.staffGroup.voices.length; j++) + layoutVoice(abcLine.staffGroup.voices[j]); + setUpperAndLowerElements(renderer, abcLine.staffGroup); + } + } + + // Set the staff spacing + // TODO-PER: we should have been able to do this by the time we called setUpperAndLowerElements, but for some reason the "bottom" element seems to be set as a side effect of setting the X spacing. + for (i = 0; i < abctune.lines.length; i++) { + abcLine = abctune.lines[i]; + if (abcLine.staffGroup) { + abcLine.staffGroup.setHeight(); + } + } + return maxWidth; +} +// Do the x-axis positioning for a single line (a group of related staffs) +var setXSpacing = function (renderer, width, space, staffGroup, formatting, isLastLine, debug) { + var leftEdge = getLeftEdgeOfStaff(renderer, staffGroup.getTextSize, staffGroup.voices, staffGroup.brace, staffGroup.bracket); + var newspace = space; + //dumpGroup("before", staffGroup) + for (var it = 0; it < 8; it++) { // TODO-PER: shouldn't need multiple passes, but each pass gets it closer to the right spacing. (Only affects long lines: normal lines break out of this loop quickly.) + // console.log("iteration", it) + var ret = layoutStaffGroup(newspace, renderer.minPadding, debug, staffGroup, leftEdge); + newspace = calcHorizontalSpacing(isLastLine, formatting.stretchlast, width + renderer.padding.left, staffGroup.w, newspace, ret.spacingUnits, ret.minSpace, renderer.padding.left + renderer.padding.right); + if (debug) + console.log("setXSpace", it, staffGroup.w, newspace, staffGroup.minspace); + if (newspace === null) break; + } + //dumpGroup("after",staffGroup) + centerWholeRests(staffGroup.voices); + return staffGroup.w - leftEdge +}; + +function replacer(key, value) { + // Filtering out properties + if (key === 'parent') { + return 'parent'; + } + if (key === 'beam') { + return 'beam'; + } + return value; +} + +function dumpGroup(label, staffGroup) { + console.log("=================== " + label + " =========================") + console.log(staffGroup) + console.log(JSON.stringify(staffGroup, replacer, "\t")) +} + +function calcHorizontalSpacing(isLastLine, stretchLast, targetWidth, lineWidth, spacing, spacingUnits, minSpace, padding) { + if (isLastLine) { + if (stretchLast === undefined) { + if (lineWidth / targetWidth < 0.66) return null; // keep this for backward compatibility. The break isn't quite the same for some reason. + } else { + // "Stretch the last music line of a tune when it lacks less than the float fraction of the page width." + var lack = 1 - (lineWidth + padding) / targetWidth; + var stretch = lack < stretchLast; + if (!stretch) return null; // don't stretch last line too much + } + } + if (Math.abs(targetWidth - lineWidth) < 2) return null; // if we are already near the target width, we're done. + var relSpace = spacingUnits * spacing; + var constSpace = lineWidth - relSpace; + if (spacingUnits > 0) { + spacing = (targetWidth - constSpace) / spacingUnits; + if (spacing * minSpace > 50) { + spacing = 50 / minSpace; + } + return spacing; + } + return null; +} + +function centerWholeRests(voices) { + // whole rests are a special case: if they are by themselves in a measure, then they should be centered. + // (If they are not by themselves, that is probably a user error, but we'll just center it between the two items to either side of it.) + for (var i = 0; i < voices.length; i++) { + var voice = voices[i]; + // Look through all of the elements except for the first and last. If the whole note appears there then there isn't anything to center it between anyway. + for (var j = 1; j < voice.children.length - 1; j++) { + var absElem = voice.children[j]; + if (absElem.abcelem.rest && (absElem.abcelem.rest.type === 'whole' || absElem.abcelem.rest.type === 'multimeasure')) { + var before = voice.children[j - 1]; + var after = voice.children[j + 1]; + absElem.center(before, after); + } + } + } +} + +module.exports = layout; diff --git a/src/write/layout/set-upper-and-lower-elements.js b/src/write/layout/set-upper-and-lower-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..822f7eba4ab5e647e992d1434cc3956878f7c60b --- /dev/null +++ b/src/write/layout/set-upper-and-lower-elements.js @@ -0,0 +1,242 @@ +var spacing = require('../helpers/spacing'); + +var setUpperAndLowerElements = function (renderer, staffGroup) { + // Each staff already has the top and bottom set, now we see if there are elements that are always on top and bottom, and resolve their pitch. + // Also, get the overall height of all the staves in this group. + var lastStaffBottom; + for (var i = 0; i < staffGroup.staffs.length; i++) { + var staff = staffGroup.staffs[i]; + // the vertical order of elements that are above is: tempo, part, volume/dynamic, ending/chord, lyric + // the vertical order of elements that are below is: lyric, chord, volume/dynamic + var positionY = { + tempoHeightAbove: 0, + partHeightAbove: 0, + volumeHeightAbove: 0, + dynamicHeightAbove: 0, + endingHeightAbove: 0, + chordHeightAbove: 0, + lyricHeightAbove: 0, + + lyricHeightBelow: 0, + chordHeightBelow: 0, + volumeHeightBelow: 0, + dynamicHeightBelow: 0 + }; + + if (renderer.showDebug && renderer.showDebug.indexOf("box") >= 0) { + staff.originalTop = staff.top; // This is just being stored for debugging purposes. + staff.originalBottom = staff.bottom; // This is just being stored for debugging purposes. + } + + incTop(staff, positionY, 'lyricHeightAbove'); + incTop(staff, positionY, 'chordHeightAbove', staff.specialY.chordLines.above); + if (staff.specialY.endingHeightAbove) { + if (staff.specialY.chordHeightAbove) + staff.top += 2; + else + staff.top += staff.specialY.endingHeightAbove + margin; + positionY.endingHeightAbove = staff.top; + } + if (staff.specialY.dynamicHeightAbove && staff.specialY.volumeHeightAbove) { + staff.top += Math.max(staff.specialY.dynamicHeightAbove, staff.specialY.volumeHeightAbove) + margin; + positionY.dynamicHeightAbove = staff.top; + positionY.volumeHeightAbove = staff.top; + } else { + incTop(staff, positionY, 'dynamicHeightAbove'); + incTop(staff, positionY, 'volumeHeightAbove'); + } + incTop(staff, positionY, 'partHeightAbove'); + incTop(staff, positionY, 'tempoHeightAbove'); + + if (staff.specialY.lyricHeightBelow) { + staff.specialY.lyricHeightBelow += renderer.spacing.vocal / spacing.STEP; + positionY.lyricHeightBelow = staff.bottom; + staff.bottom -= (staff.specialY.lyricHeightBelow + margin); + } + if (staff.specialY.chordHeightBelow) { + positionY.chordHeightBelow = staff.bottom; + var hgt = staff.specialY.chordHeightBelow; + if (staff.specialY.chordLines.below) + hgt *= staff.specialY.chordLines.below; + staff.bottom -= (hgt + margin); + } + if (staff.specialY.volumeHeightBelow && staff.specialY.dynamicHeightBelow) { + positionY.volumeHeightBelow = staff.bottom; + positionY.dynamicHeightBelow = staff.bottom; + staff.bottom -= (Math.max(staff.specialY.volumeHeightBelow, staff.specialY.dynamicHeightBelow) + margin); + } else if (staff.specialY.volumeHeightBelow) { + positionY.volumeHeightBelow = staff.bottom; staff.bottom -= (staff.specialY.volumeHeightBelow + margin); + } else if (staff.specialY.dynamicHeightBelow) { + positionY.dynamicHeightBelow = staff.bottom; staff.bottom -= (staff.specialY.dynamicHeightBelow + margin); + } + + if (renderer.showDebug && renderer.showDebug.indexOf("box") >= 0) + staff.positionY = positionY; // This is just being stored for debugging purposes. + + for (var j = 0; j < staff.voices.length; j++) { + var voice = staffGroup.voices[staff.voices[j]]; + setUpperAndLowerVoiceElements(positionY, voice, renderer.spacing); + } + // We might need a little space in between staves if the staves haven't been pushed far enough apart by notes or extra vertical stuff. + // Only try to put in extra space if this isn't the top staff. + if (lastStaffBottom !== undefined) { + var thisStaffTop = staff.top - 10; + var forcedSpacingBetween = lastStaffBottom + thisStaffTop; + var minSpacingInPitches = renderer.spacing.systemStaffSeparation / spacing.STEP; + var addedSpace = minSpacingInPitches - forcedSpacingBetween; + if (addedSpace > 0) + staff.top += addedSpace; + } + staff.top += renderer.spacing.staffTopMargin / spacing.STEP + + lastStaffBottom = 2 - staff.bottom; // the staff starts at position 2 and the bottom variable is negative. Therefore to find out how large the bottom is, we reverse the sign of the bottom, and add the 2 in. + + // Now we need a little margin on the top, so we'll just throw that in. + //staff.top += 4; + //console.log("Staff Y: ",i,heightInPitches,staff.top,staff.bottom); + } + //console.log("Staff Height: ",heightInPitches,this.height); +}; + +var margin = 1; +function incTop(staff, positionY, item, count) { + if (staff.specialY[item]) { + var height = staff.specialY[item]; + if (count) + height *= count; + staff.top += height + margin; + positionY[item] = staff.top; + } +} + +function setUpperAndLowerVoiceElements(positionY, voice, spacing) { + var i; + var abselem; + for (i = 0; i < voice.children.length; i++) { + abselem = voice.children[i]; + setUpperAndLowerAbsoluteElements(positionY, abselem, spacing); + } + for (i = 0; i < voice.otherchildren.length; i++) { + abselem = voice.otherchildren[i]; + switch (abselem.type) { + case 'CrescendoElem': + setUpperAndLowerCrescendoElements(positionY, abselem); + break; + case 'DynamicDecoration': + setUpperAndLowerDynamicElements(positionY, abselem); + break; + case 'EndingElem': + setUpperAndLowerEndingElements(positionY, abselem); + break; + case 'TieElem': + // If a tie element is the highest or lowest thing then space might need to make room for it. + var yBounds = abselem.getYBounds() + voice.staff.top = Math.max(voice.staff.top, yBounds[0]) + voice.staff.top = Math.max(voice.staff.top, yBounds[1]) + voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[0]) + voice.staff.bottom = Math.min(voice.staff.bottom, yBounds[1]) + break; + } + } +} + +// For each of the relative elements that can't be placed in advance (because their vertical placement depends on everything +// else on the line), this iterates through them and sets their pitch. By the time this is called, specialYResolved contains a +// hash with the vertical placement (in pitch units) for each type. +// TODO-PER: I think this needs to be separated by "above" and "below". How do we know that for dynamics at the point where they are being defined, though? We need a pass through all the relative elements to set "above" and "below". +function setUpperAndLowerAbsoluteElements(specialYResolved, element, spacing) { + // specialYResolved contains the actual pitch for each of the classes of elements. + for (var i = 0; i < element.children.length; i++) { + var child = element.children[i]; + for (var key in element.specialY) { // for each class of element that needs to be placed vertically + if (element.specialY.hasOwnProperty(key)) { + if (child[key]) { // If this relative element has defined a height for this class of element + child.pitch = specialYResolved[key]; + if (child.top === undefined) { // TODO-PER: HACK! Not sure this is the right place to do this. + if (child.type === 'TempoElement') { + setUpperAndLowerTempoElement(specialYResolved, child); + } else { + setUpperAndLowerRelativeElements(specialYResolved, child, spacing); + } + element.pushTop(child.top); + element.pushBottom(child.bottom); + } + } + } + } + } +} + +function setUpperAndLowerCrescendoElements(positionY, element) { + if (element.dynamicHeightAbove) + element.pitch = positionY.dynamicHeightAbove; + else + element.pitch = positionY.dynamicHeightBelow; +} + +function setUpperAndLowerDynamicElements(positionY, element) { + if (element.volumeHeightAbove) + element.pitch = positionY.volumeHeightAbove; + else + element.pitch = positionY.volumeHeightBelow; +} + +function setUpperAndLowerEndingElements(positionY, element) { + element.pitch = positionY.endingHeightAbove - 2; +} + +function setUpperAndLowerTempoElement(positionY, element) { + element.pitch = positionY.tempoHeightAbove; + element.top = positionY.tempoHeightAbove; + element.bottom = positionY.tempoHeightAbove; + if (element.note) { + var tempoPitch = element.pitch - element.totalHeightInPitches + 1; // The pitch we receive is the top of the allotted area: change that to practically the bottom. + element.note.top = tempoPitch; + element.note.bottom = tempoPitch; + for (var i = 0; i < element.note.children.length; i++) { + var child = element.note.children[i]; + child.top += tempoPitch; + child.bottom += tempoPitch; + child.pitch += tempoPitch; + if (child.pitch2 !== undefined) + child.pitch2 += tempoPitch; + } + } +} + +function setUpperAndLowerRelativeElements(positionY, element, renderSpacing) { + switch (element.type) { + case "part": + element.top = positionY.partHeightAbove + element.height; + element.bottom = positionY.partHeightAbove; + break; + case "text": + case "chord": + if (element.chordHeightAbove) { + element.top = positionY.chordHeightAbove; + element.bottom = positionY.chordHeightAbove; + } else { + element.top = positionY.chordHeightBelow; + element.bottom = positionY.chordHeightBelow; + } + break; + case "lyric": + if (element.lyricHeightAbove) { + element.top = positionY.lyricHeightAbove; + element.bottom = positionY.lyricHeightAbove; + } else { + element.top = positionY.lyricHeightBelow + renderSpacing.vocal / spacing.STEP; + element.bottom = positionY.lyricHeightBelow + renderSpacing.vocal / spacing.STEP; + element.pitch -= renderSpacing.vocal / spacing.STEP; + } + break; + case "debug": + element.top = positionY.chordHeightAbove; + element.bottom = positionY.chordHeightAbove; + break; + } + if (element.pitch === undefined || element.top === undefined) + console.error("RelativeElement position not set.", element.type, element.pitch, element.top, positionY); +} + +module.exports = setUpperAndLowerElements; diff --git a/src/write/layout/staff-group.js b/src/write/layout/staff-group.js new file mode 100644 index 0000000000000000000000000000000000000000..3b7678ebaa5f3d0e650873520d922e6e9bc3f94d --- /dev/null +++ b/src/write/layout/staff-group.js @@ -0,0 +1,146 @@ +var layoutVoiceElements = require('./voice-elements'); + +function checkLastBarX(voices) { + var maxX = 0; + for (var i = 0; i < voices.length; i++) { + var curVoice = voices[i]; + if (curVoice.children.length > 0) { + var lastChild = curVoice.children.length - 1; + var maxChild = curVoice.children[lastChild]; + if (maxChild.abcelem.el_type === 'bar') { + var barX = maxChild.children[0].x; + if (barX > maxX) { + maxX = barX; + } else { + maxChild.children[0].x = maxX; + } + } + } + } +} + +var layoutStaffGroup = function (spacing, minPadding, debug, staffGroup, leftEdge) { + var epsilon = 0.0000001; // Fudging for inexactness of floating point math. + var spacingunits = 0; // number of times we will have ended up using the spacing distance (as opposed to fixed width distances) + var minspace = 1000; // a big number to start off with - used to find out what the smallest space between two notes is -- GD 2014.1.7 + + var x = leftEdge; + staffGroup.startx = x; + var i; + + var currentduration = 0; + if (debug) console.log("init layout", spacing); + for (i = 0; i < staffGroup.voices.length; i++) { + layoutVoiceElements.beginLayout(x, staffGroup.voices[i]); + } + + var spacingunit = 0; // number of spacingunits coming from the previously laid out element to this one + while (!finished(staffGroup.voices)) { + // find first duration level to be laid out among candidates across voices + currentduration = null; // candidate smallest duration level + for (i = 0; i < staffGroup.voices.length; i++) { + if (!layoutVoiceElements.layoutEnded(staffGroup.voices[i]) && (!currentduration || getDurationIndex(staffGroup.voices[i]) < currentduration)) + currentduration = getDurationIndex(staffGroup.voices[i]); + } + + + // isolate voices at current duration level + var currentvoices = []; + var othervoices = []; + for (i = 0; i < staffGroup.voices.length; i++) { + var durationIndex = getDurationIndex(staffGroup.voices[i]); + // PER: Because of the inexactness of JS floating point math, we just get close. + if (durationIndex - currentduration > epsilon) { + othervoices.push(staffGroup.voices[i]); + //console.log("out: voice ",i); + } else { + currentvoices.push(staffGroup.voices[i]); + //if (debug) console.log("in: voice ",i); + } + } + + // among the current duration level find the one which needs starting furthest right + spacingunit = 0; // number of spacingunits coming from the previously laid out element to this one + var spacingduration = 0; + for (i = 0; i < currentvoices.length; i++) { + //console.log("greatest spacing unit", x, layoutVoiceElements.getNextX(currentvoices[i]), layoutVoiceElements.getSpacingUnits(currentvoices[i]), currentvoices[i].spacingduration); + if (layoutVoiceElements.getNextX(currentvoices[i]) > x) { + x = layoutVoiceElements.getNextX(currentvoices[i]); + spacingunit = layoutVoiceElements.getSpacingUnits(currentvoices[i]); + spacingduration = currentvoices[i].spacingduration; + } + } + spacingunits += spacingunit; + minspace = Math.min(minspace, spacingunit); + if (debug) console.log("currentduration: ", currentduration, spacingunits, minspace); + + var lastTopVoice = undefined; + for (i = 0; i < currentvoices.length; i++) { + var v = currentvoices[i]; + if (v.voicenumber === 0) + lastTopVoice = i; + var topVoice = (lastTopVoice !== undefined && currentvoices[lastTopVoice].voicenumber !== v.voicenumber) ? currentvoices[lastTopVoice] : undefined; + if (!isSameStaff(v, topVoice)) + topVoice = undefined; + var voicechildx = layoutVoiceElements.layoutOneItem(x, spacing, v, minPadding, topVoice); + var dx = voicechildx - x; + if (dx > 0) { + x = voicechildx; //update x + for (var j = 0; j < i; j++) { // shift over all previously laid out elements + layoutVoiceElements.shiftRight(dx, currentvoices[j]); + } + } + } + + // remove the value of already counted spacing units in other voices (e.g. if a voice had planned to use up 5 spacing units but is not in line to be laid out at this duration level - where we've used 2 spacing units - then we must use up 3 spacing units, not 5) + for (i = 0; i < othervoices.length; i++) { + othervoices[i].spacingduration -= spacingduration; + layoutVoiceElements.updateNextX(x, spacing, othervoices[i]); // adjust other voices expectations + } + + // update indexes of currently laid out elems + for (i = 0; i < currentvoices.length; i++) { + var voice = currentvoices[i]; + layoutVoiceElements.updateIndices(voice); + } + } // finished laying out + + + // find the greatest remaining x as a base for the width + for (i = 0; i < staffGroup.voices.length; i++) { + if (layoutVoiceElements.getNextX(staffGroup.voices[i]) > x) { + x = layoutVoiceElements.getNextX(staffGroup.voices[i]); + spacingunit = layoutVoiceElements.getSpacingUnits(staffGroup.voices[i]); + } + } + + // adjust lastBar when needed (multi staves) + checkLastBarX(staffGroup.voices); + //console.log("greatest remaining",spacingunit,x); + spacingunits += spacingunit; + staffGroup.setWidth(x); + + return { spacingUnits: spacingunits, minSpace: minspace }; +}; + + +function finished(voices) { + for (var i = 0; i < voices.length; i++) { + if (!layoutVoiceElements.layoutEnded(voices[i])) return false; + } + return true; +} + +function getDurationIndex(element) { + return element.durationindex - (element.children[element.i] && (element.children[element.i].duration > 0) ? 0 : 0.0000005); // if the ith element doesn't have a duration (is not a note), its duration index is fractionally before. This enables CLEF KEYSIG TIMESIG PART, etc. to be laid out before we get to the first note of other voices +} + +function isSameStaff(voice1, voice2) { + if (!voice1 || !voice1.staff || !voice1.staff.voices || voice1.staff.voices.length === 0) + return false; + if (!voice2 || !voice2.staff || !voice2.staff.voices || voice2.staff.voices.length === 0) + return false; + return (voice1.staff.voices[0] === voice2.staff.voices[0]); +} + +module.exports = layoutStaffGroup; diff --git a/src/write/layout/triplet.js b/src/write/layout/triplet.js new file mode 100644 index 0000000000000000000000000000000000000000..9790a54bdeb659b2b76cce82f49525c3b54b423f --- /dev/null +++ b/src/write/layout/triplet.js @@ -0,0 +1,75 @@ +var getBarYAt = require('./get-bar-y-at'); + +function layoutTriplet(element) { + // TODO end and beginning of line (PER: P.S. I'm not sure this can happen: I think the parser will always specify both the start and end points.) + if (element.anchor1 && element.anchor2) { + element.hasBeam = !!element.anchor1.parent.beam && element.anchor1.parent.beam === element.anchor2.parent.beam; + var beam = element.anchor1.parent.beam; + // if hasBeam is true, then the first and last element in the triplet have the same beam. + // We also need to check if the beam doesn't contain other notes so that `(3 dcdcc` will do a bracket. + if (element.hasBeam && (beam.elems[0] !== element.anchor1.parent || beam.elems[beam.elems.length - 1] !== element.anchor2.parent)) + element.hasBeam = false; + + if (element.hasBeam) { + // If there is a beam then we don't need to draw anything except the text. The beam could either be above or below. + var left = isAbove(beam) ? element.anchor1.x + element.anchor1.w : element.anchor1.x; + element.yTextPos = heightAtMidpoint(left, element.anchor2.x, beam); + element.yTextPos += isAbove(beam) ? 3 : -2; // This creates some space between the beam and the number. + element.xTextPos = xAtMidpoint(left, element.anchor2.x); + element.top = element.yTextPos + 1; + element.bottom = element.yTextPos - 2; + if (isAbove(beam)) + element.endingHeightAbove = 4; + } else { + // If there isn't a beam, then we need to draw the bracket and the text. The bracket is always above. + // The bracket is never lower than the 'a' line, but is 4 pitches above the first and last notes. If there is + // a tall note in the middle, the bracket is horizontal and above the highest note. + element.startNote = Math.max(element.anchor1.parent.top, 9) + 4; + element.endNote = Math.max(element.anchor2.parent.top, 9) + 4; + // If it starts or ends on a rest, make the beam horizontal + if (element.anchor1.parent.type === "rest" && element.anchor2.parent.type !== "rest") + element.startNote = element.endNote; + else if (element.anchor2.parent.type === "rest" && element.anchor1.parent.type !== "rest") + element.endNote = element.startNote; + // See if the middle note is really high. + var max = 0; + for (var i = 0; i < element.middleElems.length; i++) { + max = Math.max(max, element.middleElems[i].top); + } + max += 4; + if (max > element.startNote || max > element.endNote) { + element.startNote = max; + element.endNote = max; + } + if (element.flatBeams) { + element.startNote = Math.max(element.startNote, element.endNote); + element.endNote = Math.max(element.startNote, element.endNote); + } + + element.yTextPos = element.startNote + (element.endNote - element.startNote) / 2; + element.xTextPos = element.anchor1.x + (element.anchor2.x + element.anchor2.w - element.anchor1.x) / 2; + element.top = element.yTextPos + 1; + } + } + delete element.middleElems; + delete element.flatBeams; +} + +function isAbove(beam) { + return beam.stemsUp; +} + +// We can't just use the entire beam for the calculation. The range has to be passed in, because the beam might extend into some unrelated notes. for instance, (3_a'f'e'f'2 when L:16 +function heightAtMidpoint(startX, endX, beam) { + if (beam.beams.length === 0) + return 0; + beam = beam.beams[0]; + var midPoint = startX + (endX - startX) / 2; + return getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, midPoint); +} + +function xAtMidpoint(startX, endX) { + return startX + (endX - startX) / 2; +} + +module.exports = layoutTriplet; diff --git a/src/write/layout/voice-elements.js b/src/write/layout/voice-elements.js new file mode 100644 index 0000000000000000000000000000000000000000..52d3033b803d87600f95f7674f377f15201a1872 --- /dev/null +++ b/src/write/layout/voice-elements.js @@ -0,0 +1,121 @@ +var VoiceElement = function VoiceElements() { } + +VoiceElement.beginLayout = function (startx, voice) { + voice.i = 0; + voice.durationindex = 0; + //this.ii=this.children.length; + voice.startx = startx; + voice.minx = startx; // furthest left to where negatively positioned elements are allowed to go + voice.nextx = startx; // x position where the next element of this voice should be placed assuming no other voices and no fixed width constraints + voice.spacingduration = 0; // duration left to be laid out in current iteration (omitting additional spacing due to other aspects, such as bars, dots, sharps and flats) +}; + +VoiceElement.layoutEnded = function (voice) { + return (voice.i >= voice.children.length); +}; + +VoiceElement.getNextX = function (voice) { + return Math.max(voice.minx, voice.nextx); +}; + +// number of spacing units expected for next positioning +VoiceElement.getSpacingUnits = function (voice) { + return Math.sqrt(voice.spacingduration * 8); +}; + +// Try to layout the element at index this.i +// x - position to try to layout the element at +// spacing - base spacing +// can't call this function more than once per iteration +VoiceElement.layoutOneItem = function (x, spacing, voice, minPadding, firstVoice) { + var child = voice.children[voice.i]; + if (!child) return 0; + var er = x - voice.minx; // available extrawidth to the left + var pad = voice.durationindex + child.duration > 0 ? minPadding : 0; // only add padding to the items that aren't fixed to the left edge. + // See if this item overlaps the item in the first voice. If firstVoice is undefined then there's nothing to compare. + if (child.abcelem.el_type === "note" && !child.abcelem.rest && voice.voicenumber !== 0 && firstVoice) { + var firstChild = firstVoice.children[firstVoice.i]; + // It overlaps if the either the child's top or bottom is inside the firstChild's or at least within 1 + // A special case is if the element is on the same line then it can share a note head, if the notehead is the same + var overlaps = firstChild && + ((child.abcelem.maxpitch <= firstChild.abcelem.maxpitch + 1 && child.abcelem.maxpitch >= firstChild.abcelem.minpitch - 1) || + (child.abcelem.minpitch <= firstChild.abcelem.maxpitch + 1 && child.abcelem.minpitch >= firstChild.abcelem.minpitch - 1)) + // See if they can share a note head + if (overlaps && child.abcelem.minpitch === firstChild.abcelem.minpitch && child.abcelem.maxpitch === firstChild.abcelem.maxpitch && + firstChild.heads && firstChild.heads.length > 0 && child.heads && child.heads.length > 0 && + firstChild.heads[0].c === child.heads[0].c) + overlaps = false; + // If this note overlaps the note in the first voice and we haven't moved the note yet (this can be called multiple times) + if (overlaps) { + // I think that firstChild should always have at least one note head, but defensively make sure. + // There was a problem with this being called more than once so if a value is adjusted then it is saved so it is only adjusted once. + var firstChildNoteWidth = firstChild.heads && firstChild.heads.length > 0 ? firstChild.heads[0].realWidth : firstChild.fixed.w; + if (!child.adjustedWidth) + child.adjustedWidth = firstChildNoteWidth + child.w; + child.w = child.adjustedWidth + for (var j = 0; j < child.children.length; j++) { + var relativeChild = child.children[j]; + if (relativeChild.name.indexOf("accidental") < 0) { + if (!relativeChild.adjustedWidth) + relativeChild.adjustedWidth = relativeChild.dx + firstChildNoteWidth; + relativeChild.dx = relativeChild.adjustedWidth + } + } + + } + } + var extraWidth = getExtraWidth(child, pad); + if (er < extraWidth) { // shift right by needed amount + // There's an exception if a bar element is after a Part element, there is no shift. + if (voice.i === 0 || child.type !== 'bar' || (voice.children[voice.i - 1].type !== 'part' && voice.children[voice.i - 1].type !== 'tempo')) + x += extraWidth - er; + } + child.setX(x); + + voice.spacingduration = child.duration; + //update minx + voice.minx = x + getMinWidth(child); // add necessary layout space + if (voice.i !== voice.children.length - 1) voice.minx += child.minspacing; // add minimumspacing except on last elem + + this.updateNextX(x, spacing, voice); + + // contribute to staff y position + //this.staff.top = Math.max(child.top,this.staff.top); + //this.staff.bottom = Math.min(child.bottom,this.staff.bottom); + + return x; // where we end up having placed the child +}; + +VoiceElement.shiftRight = function (dx, voice) { + var child = voice.children[voice.i]; + if (!child) return; + child.setX(child.x + dx); + voice.minx += dx; + voice.nextx += dx; +}; + +// call when spacingduration has been updated +VoiceElement.updateNextX = function (x, spacing, voice) { + voice.nextx = x + (spacing * this.getSpacingUnits(voice)); +}; + +VoiceElement.updateIndices = function (voice) { + if (!this.layoutEnded(voice)) { + voice.durationindex += voice.children[voice.i].duration; + if (voice.children[voice.i].type === 'bar') voice.durationindex = Math.round(voice.durationindex * 64) / 64; // everytime we meet a barline, do rounding to nearest 64th + voice.i++; + } +}; + +function getExtraWidth(child, minPadding) { // space needed to the left of the note + var padding = 0; + if (child.type === 'note' || child.type === 'bar') + padding = minPadding; + return -child.extraw + padding; +} + +function getMinWidth(child) { // absolute space taken to the right of the note + return child.w; +} + +module.exports = VoiceElement; diff --git a/src/write/layout/voice.js b/src/write/layout/voice.js new file mode 100644 index 0000000000000000000000000000000000000000..86a8a54c3c0ed2c7ebfb9190ac9212ae8f806efd --- /dev/null +++ b/src/write/layout/voice.js @@ -0,0 +1,137 @@ +var layoutBeam = require('./beam'); +var getBarYAt = require('./get-bar-y-at'); +var layoutTriplet = require('./triplet'); + +var layoutVoice = function (voice) { + for (var i = 0; i < voice.beams.length; i++) { + if (voice.beams[i].type === 'BeamElem') { + layoutBeam(voice.beams[i]); + moveDecorations(voice.beams[i]); + // The above will change the top and bottom of the abselem children, so see if we need to expand our range. + for (var j = 0; j < voice.beams[i].elems.length; j++) { + voice.adjustRange(voice.beams[i].elems[j]); + } + } + } + voice.staff.specialY.chordLines = setLaneForChord(voice.children); + + // Now we can layout the triplets + for (i = 0; i < voice.otherchildren.length; i++) { + var child = voice.otherchildren[i]; + if (child.type === 'TripletElem') { + layoutTriplet(child); + voice.adjustRange(child); + } + } + voice.staff.top = Math.max(voice.staff.top, voice.top); + voice.staff.bottom = Math.min(voice.staff.bottom, voice.bottom); +}; + +function moveDecorations(beam) { + var padding = 1.5; // This is the vertical padding between elements, in pitches. + for (var ch = 0; ch < beam.elems.length; ch++) { + var child = beam.elems[ch]; + if (child.top) { + // We now know where the ornaments should have been placed, so move them if they would overlap. + var top = yAtNote(child, beam); + for (var i = 0; i < child.children.length; i++) { + var el = child.children[i]; + if (el.klass === 'ornament' && el.position !== 'below') { + if (el.bottom - padding < top) { + var distance = top - el.bottom + padding; // Find the distance that it needs to move and add a little margin so the element doesn't touch the beam. + el.bottom += distance; + el.top += distance; + el.pitch += distance; + top = child.top = el.top; + } + } + } + } + } +} + +function placeInLane(rightMost, relElem) { + // These items are centered so figure the coordinates accordingly. + // The font reports some extra space so the margin is built in. + var xCoords = relElem.getChordDim(); + if (xCoords) { + for (var i = 0; i < rightMost.length; i++) { + var fits = rightMost[i] < xCoords.left; + if (fits) { + if (i > 0) + relElem.putChordInLane(i); + rightMost[i] = xCoords.right; + return; + } + } + // If we didn't return early, then we need a new row + rightMost.push(xCoords.right); + relElem.putChordInLane(rightMost.length - 1); + } +} + +function setLaneForChord(absElems) { + // Criteria: + // 1) lane numbers start from the bottom so that as many items as possible are in lane 0, closest to the music. + // 2) a chord can have more than one line (for instance "C\nD") each line is a lane. + // 3) if two adjoining items would touch then push the second one to the next lane. + // 4) use as many lanes as is necessary to get everything to not touch. + // 5) leave a margin between items, so use another lane if the chords would have less than a character's width. + // 6) if the chord only has one character, allow it to be closer than if the chord has more than one character. + var rightMostAbove = [0]; + var rightMostBelow = [0]; + var i; + var j; + var relElem; + for (i = 0; i < absElems.length; i++) { + for (j = 0; j < absElems[i].children.length; j++) { + relElem = absElems[i].children[j]; + if (relElem.chordHeightAbove) { + placeInLane(rightMostAbove, relElem); + } + } + for (j = absElems[i].children.length - 1; j >= 0; j--) { + relElem = absElems[i].children[j]; + if (relElem.chordHeightBelow) { + placeInLane(rightMostBelow, relElem); + } + } + } + // If we used a second line, then we need to go back and set the first lines. + // Also we need to flip the indexes of the names so that we can count from the top line. + if (rightMostAbove.length > 1 || rightMostBelow.length > 1) + setLane(absElems, rightMostAbove.length, rightMostBelow.length); + return { above: rightMostAbove.length, below: rightMostBelow.length }; +} + +function numAnnotationsBelow(absElem) { + var count = 0; + for (var j = 0; j < absElem.children.length; j++) { + var relElem = absElem.children[j]; + if (relElem.chordHeightBelow) + count++; + } + return count; +} + +function setLane(absElems, numLanesAbove, numLanesBelow) { + for (var i = 0; i < absElems.length; i++) { + var below = numAnnotationsBelow(absElems[i]); + for (var j = 0; j < absElems[i].children.length; j++) { + var relElem = absElems[i].children[j]; + if (relElem.chordHeightAbove) { + relElem.invertLane(numLanesAbove); + // } else if (relElem.chordHeightBelow) { + // relElem.invertLane(below); + } + } + } +} + +function yAtNote(element, beam) { + beam = beam.beams[0]; + return getBarYAt(beam.startX, beam.startY, beam.endX, beam.endY, element.x); +} + + +module.exports = layoutVoice; diff --git a/src/write/renderer.js b/src/write/renderer.js new file mode 100644 index 0000000000000000000000000000000000000000..dc4e430e8a5a21f26be53ab22a14d256aa3f91a7 --- /dev/null +++ b/src/write/renderer.js @@ -0,0 +1,191 @@ +// abc_renderer.js: API to render to SVG/Raphael/whatever rendering engine + +/*global Math */ + +var spacing = require('./helpers/spacing'); +var Svg = require('./svg'); + +/** + * Implements the API for rendering ABCJS Abstract Rendering Structure to a canvas/paper (e.g. SVG, Raphael, etc) + * @param {Object} paper + */ +var Renderer = function (paper) { + this.paper = new Svg(paper); + this.controller = null; + + this.space = 3 * spacing.SPACE; + this.padding = {}; // renderer's padding is managed by the controller + this.reset(); + this.firefox112 = navigator.userAgent.indexOf('Firefox/112.0') >= 0 +}; + +Renderer.prototype.reset = function () { + + this.paper.clear(); + this.y = 0; + this.abctune = null; + this.path = null; + this.isPrint = false; + this.lineThickness = 0; + this.initVerticalSpace(); +}; + +Renderer.prototype.newTune = function (abcTune) { + this.abctune = abcTune; // TODO-PER: this is just to get the font info. + this.setVerticalSpace(abcTune.formatting); + //this.measureNumber = null; + //this.noteNumber = null; + this.isPrint = abcTune.media === 'print'; + this.setPadding(abcTune); +}; + +Renderer.prototype.setLineThickness = function (lineThickness) { + this.lineThickness = lineThickness +}; + +Renderer.prototype.setPaddingOverride = function (params) { + this.paddingOverride = { + top: params.paddingtop, bottom: params.paddingbottom, + right: params.paddingright, left: params.paddingleft + }; +}; + +Renderer.prototype.setPadding = function (abctune) { + // If the padding is set in the tune, then use that. + // Otherwise, if the padding is set in the override, use that. + // Otherwise, use the defaults (there are a different set of defaults for screen and print.) + function setPaddingVariable(self, paddingKey, formattingKey, printDefault, screenDefault) { + if (abctune.formatting[formattingKey] !== undefined) + self.padding[paddingKey] = abctune.formatting[formattingKey]; + else if (self.paddingOverride[paddingKey] !== undefined) + self.padding[paddingKey] = self.paddingOverride[paddingKey]; + else if (self.isPrint) + self.padding[paddingKey] = printDefault; + else + self.padding[paddingKey] = screenDefault; + } + // 1cm x 0.393701in/cm x 72pt/in x 1.33px/pt = 38px + // 1.8cm x 0.393701in/cm x 72pt/in x 1.33px/pt = 68px + setPaddingVariable(this, 'top', 'topmargin', 38, 15); + setPaddingVariable(this, 'bottom', 'botmargin', 38, 15); + setPaddingVariable(this, 'left', 'leftmargin', 68, 15); + setPaddingVariable(this, 'right', 'rightmargin', 68, 15); +}; + +/** + * Some of the items on the page are not scaled, so adjust them in the opposite direction of scaling to cancel out the scaling. + * @param {float} scale + */ +Renderer.prototype.adjustNonScaledItems = function (scale) { + this.padding.top /= scale; + this.padding.bottom /= scale; + this.padding.left /= scale; + this.padding.right /= scale; + this.abctune.formatting.headerfont.size /= scale; + this.abctune.formatting.footerfont.size /= scale; +}; + +/** + * Set the the values for all the configurable vertical space options. + */ +Renderer.prototype.initVerticalSpace = function () { + // conversion: 37.7953 = conversion factor for cm to px. + // All of the following values are in px. + this.spacing = { + composer: 7.56, // Set the vertical space above the composer. + graceBefore: 8.67, // Define the space before, inside and after the grace notes. + graceInside: 10.67, + graceAfter: 16, + info: 0, // Set the vertical space above the infoline. + lineSkipFactor: 1.1, // Set the factor for spacing between lines of text. (multiply this by the font size) + music: 7.56, // Set the vertical space above the first staff. + paragraphSkipFactor: 0.4, // Set the factor for spacing between text paragraphs. (multiply this by the font size) + parts: 11.33, // Set the vertical space above a new part. + slurHeight: 1.0, // Set the slur height factor. + staffSeparation: 61.33, // Do not put a staff system closer than from the previous system. + staffTopMargin: 0, + stemHeight: 26.67 + 10, // Set the stem height. + subtitle: 3.78, // Set the vertical space above the subtitle. + systemStaffSeparation: 48, // Do not place the staves closer than inside a system. * This values applies to all staves when in the tune header. Otherwise, it applies to the next staff + text: 18.9, // Set the vertical space above the history. + title: 7.56, // Set the vertical space above the title. + top: 30.24, //Set the vertical space above the tunes and on the top of the continuation pages. + vocal: 0, // Set the vertical space above the lyrics under the staves. + words: 0 // Set the vertical space above the lyrics at the end of the tune. + }; + /* + TODO-PER: Handle the x-coordinate spacing items, too. +maxshrink Default: 0.65 +Set how much to compress horizontally when music line breaks +are automatic. + must be between 0 (natural spacing) +and 1 (max shrinking). + +// This next value is used to compute the natural spacing of +// the notes. The base spacing of the crotchet is always +// 40 pts. When the duration of a note type is twice the +// duration of an other note type, its spacing is multiplied +// by this factor. +// The default value causes the note spacing to be multiplied +// by 2 when its duration is multiplied by 4, i.e. the +// space of the semibreve is 80 pts and the space of the +// semiquaver is 20 pts. +// Setting this value to 1 sets all note spacing to 40 pts. +noteSpacingFactor: 1.414, // Set the note spacing factor to (range 1..2). + +scale Default: 0.75 Set the page scale factor. Note that the header and footer are not scaled. + +stretchlast Default: 0.8 +Stretch the last music line of a tune when it exceeds +the fraction of the page width. + range is 0.0 to 1.0. + */ +}; + +Renderer.prototype.setVerticalSpace = function (formatting) { + // conversion from pts to px 4/3 + if (formatting.staffsep !== undefined) + this.spacing.staffSeparation = formatting.staffsep * 4 / 3; + if (formatting.composerspace !== undefined) + this.spacing.composer = formatting.composerspace * 4 / 3; + if (formatting.partsspace !== undefined) + this.spacing.parts = formatting.partsspace * 4 / 3; + if (formatting.textspace !== undefined) + this.spacing.text = formatting.textspace * 4 / 3; + if (formatting.musicspace !== undefined) + this.spacing.music = formatting.musicspace * 4 / 3; + if (formatting.titlespace !== undefined) + this.spacing.title = formatting.titlespace * 4 / 3; + if (formatting.sysstaffsep !== undefined) + this.spacing.systemStaffSeparation = formatting.sysstaffsep * 4 / 3; + if (formatting.stafftopmargin !== undefined) + this.spacing.staffTopMargin = formatting.stafftopmargin * 4 / 3; + if (formatting.subtitlespace !== undefined) + this.spacing.subtitle = formatting.subtitlespace * 4 / 3; + if (formatting.topspace !== undefined) + this.spacing.top = formatting.topspace * 4 / 3; + if (formatting.vocalspace !== undefined) + this.spacing.vocal = formatting.vocalspace * 4 / 3; + if (formatting.wordsspace !== undefined) + this.spacing.words = formatting.wordsspace * 4 / 3; +}; + + +/** + * Calculates the y for a given pitch value (relative to the stave the renderer is currently printing) + * @param {number} ofs pitch value (bottom C on a G clef = 0, D=1, etc.) + */ +Renderer.prototype.calcY = function (ofs) { + return this.y - ofs * spacing.STEP; +}; + +Renderer.prototype.moveY = function (em, numLines) { + if (numLines === undefined) numLines = 1; + this.y += em * numLines; +}; + +Renderer.prototype.absolutemoveY = function (y) { + this.y = y; +}; + +module.exports = Renderer; diff --git a/src/write/svg.js b/src/write/svg.js new file mode 100644 index 0000000000000000000000000000000000000000..ff1499190d8ad997fe1255e28e2013b63a5dc8a3 --- /dev/null +++ b/src/write/svg.js @@ -0,0 +1,425 @@ +// abc_voice_element.js: Definition of the VoiceElement class. + +/*global module */ + +var svgNS = "http://www.w3.org/2000/svg"; + +function Svg(wrapper) { + this.svg = createSvg(); + this.currentGroup = []; + wrapper.appendChild(this.svg); +} + +Svg.prototype.clear = function () { + if (this.svg) { + var wrapper = this.svg.parentNode; + this.svg = createSvg(); + this.currentGroup = []; + if (wrapper) { + // TODO-PER: If the wrapper is not present, then the underlying div was pulled out from under this instance. It's possible that is still useful (for creating the music off page?) + wrapper.innerHTML = ""; + wrapper.appendChild(this.svg); + } + } +}; + +Svg.prototype.setTitle = function (title) { + var titleEl = document.createElement("title"); + var titleNode = document.createTextNode(title); + titleEl.appendChild(titleNode); + this.svg.insertBefore(titleEl, this.svg.firstChild); +}; + +Svg.prototype.setResponsiveWidth = function (w, h) { + // this technique is from: http://thenewcode.com/744/Make-SVG-Responsive, thx to https://github.com/iantresman + this.svg.setAttribute("viewBox", "0 0 " + w + " " + h); + this.svg.setAttribute("preserveAspectRatio", "xMinYMin meet"); + this.svg.removeAttribute("height"); + this.svg.removeAttribute("width"); + this.svg.style['display'] = "inline-block"; + this.svg.style['position'] = "absolute"; + this.svg.style['top'] = "0"; + this.svg.style['left'] = "0"; + + if (this.svg.parentNode) { + var cls = this.svg.parentNode.getAttribute("class"); + if (!cls) + this.svg.parentNode.setAttribute("class", "abcjs-container"); + else if (cls.indexOf("abcjs-container") < 0) + this.svg.parentNode.setAttribute("class", cls + " abcjs-container"); + this.svg.parentNode.style['display'] = "inline-block"; + this.svg.parentNode.style['position'] = "relative"; + this.svg.parentNode.style['width'] = "100%"; + // PER: I changed the padding from 100% to this through trial and error. + // The example was using a square image, but this music might be either wider or taller. + var padding = h / w * 100; + this.svg.parentNode.style['padding-bottom'] = padding + "%"; + this.svg.parentNode.style['vertical-align'] = "middle"; + this.svg.parentNode.style['overflow'] = "hidden"; + } +}; + +Svg.prototype.setSize = function (w, h) { + this.svg.setAttribute('width', w); + this.svg.setAttribute('height', h); +}; + +Svg.prototype.setAttribute = function (attr, value) { + this.svg.setAttribute(attr, value); +}; + +Svg.prototype.setScale = function (scale) { + if (scale !== 1) { + this.svg.style.transform = "scale(" + scale + "," + scale + ")"; + this.svg.style['-ms-transform'] = "scale(" + scale + "," + scale + ")"; + this.svg.style['-webkit-transform'] = "scale(" + scale + "," + scale + ")"; + this.svg.style['transform-origin'] = "0 0"; + this.svg.style['-ms-transform-origin-x'] = "0"; + this.svg.style['-ms-transform-origin-y'] = "0"; + this.svg.style['-webkit-transform-origin-x'] = "0"; + this.svg.style['-webkit-transform-origin-y'] = "0"; + } else { + this.svg.style.transform = ""; + this.svg.style['-ms-transform'] = ""; + this.svg.style['-webkit-transform'] = ""; + } +}; + +Svg.prototype.insertStyles = function (styles) { + var el = document.createElementNS(svgNS, "style"); + el.textContent = styles; + this.svg.insertBefore(el, this.svg.firstChild); // prepend is not available on older browsers. + // this.svg.prepend(el); +}; + +Svg.prototype.setParentStyles = function (attr) { + // This is needed to get the size right when there is scaling involved. + for (var key in attr) { + if (attr.hasOwnProperty(key)) { + if (this.svg.parentNode) + this.svg.parentNode.style[key] = attr[key]; + } + } + // This is the last thing that gets called, so delete the temporary SVG if one was created + if (this.dummySvg) { + var body = document.querySelector('body'); + body.removeChild(this.dummySvg); + this.dummySvg = null; + } + +}; + +function constructHLine(x1, y1, x2) { + var len = x2 - x1; + return "M " + x1 + " " + y1 + + " l " + len + ' ' + 0 + + " l " + 0 + " " + 1 + " " + + " l " + (-len) + " " + 0 + " " + " z "; +} + +function constructVLine(x1, y1, y2) { + var len = y2 - y1; + return "M " + x1 + " " + y1 + + " l " + 0 + ' ' + len + + " l " + 1 + " " + 0 + " " + + " l " + 0 + " " + (-len) + " " + " z "; +} + +Svg.prototype.rect = function (attr) { + // This uses path instead of rect so that it can be hollow and the color changes with "fill" instead of "stroke". + var lines = []; + var x1 = attr.x; + var y1 = attr.y; + var x2 = attr.x + attr.width; + var y2 = attr.y + attr.height; + lines.push(constructHLine(x1, y1, x2)); + lines.push(constructHLine(x1, y2, x2)); + lines.push(constructVLine(x2, y1, y2)); + lines.push(constructVLine(x1, y2, y1)); + + return this.path({ path: lines.join(" "), stroke: "none", "data-name": attr["data-name"] }); +}; + +Svg.prototype.dottedLine = function (attr) { + var el = document.createElementNS(svgNS, 'line'); + el.setAttribute("x1", attr.x1); + el.setAttribute("x2", attr.x2); + el.setAttribute("y1", attr.y1); + el.setAttribute("y2", attr.y2); + el.setAttribute("stroke", attr.stroke); + el.setAttribute("stroke-dasharray", "5,5"); + this.svg.insertBefore(el, this.svg.firstChild); +}; + +Svg.prototype.rectBeneath = function (attr) { + var el = document.createElementNS(svgNS, 'rect'); + el.setAttribute("x", attr.x); + el.setAttribute("width", attr.width); + el.setAttribute("y", attr.y); + el.setAttribute("height", attr.height); + if (attr.stroke) + el.setAttribute("stroke", attr.stroke); + if (attr['stroke-opacity']) + el.setAttribute("stroke-opacity", attr['stroke-opacity']); + if (attr.fill) + el.setAttribute("fill", attr.fill); + if (attr['fill-opacity']) + el.setAttribute("fill-opacity", attr['fill-opacity']); + this.svg.insertBefore(el, this.svg.firstChild); +}; + +Svg.prototype.text = function (text, attr, target) { + var el = document.createElementNS(svgNS, 'text'); + el.setAttribute("stroke", "none"); + for (var key in attr) { + if (attr.hasOwnProperty(key)) { + el.setAttribute(key, attr[key]); + } + } + var lines = ("" + text).split("\n"); + for (var i = 0; i < lines.length; i++) { + var line = document.createElementNS(svgNS, 'tspan'); + line.setAttribute("x", attr.x ? attr.x : 0); + if (i !== 0) + line.setAttribute("dy", "1.2em"); + if (lines[i].indexOf("\x03") !== -1) { + var parts = lines[i].split('\x03') + line.textContent = parts[0]; + if (parts[1]) { + var ts2 = document.createElementNS(svgNS, 'tspan'); + ts2.setAttribute("dy", "-0.3em"); + ts2.setAttribute("style", "font-size:0.7em"); + ts2.textContent = parts[1]; + line.appendChild(ts2); + } + if (parts[2]) { + var dist = parts[1] ? "0.4em" : "0.1em"; + var ts3 = document.createElementNS(svgNS, 'tspan'); + ts3.setAttribute("dy", dist); + ts3.setAttribute("style", "font-size:0.7em"); + ts3.textContent = parts[2]; + line.appendChild(ts3); + } + } else + line.textContent = lines[i]; + el.appendChild(line); + } + if (target) + target.appendChild(el); + else + this.append(el); + return el; +}; + +Svg.prototype.richTextLine = function (phrases, x, y, klass, anchor, target) { + var el = document.createElementNS(svgNS, 'text'); + el.setAttribute("stroke", "none"); + el.setAttribute("class", klass); + el.setAttribute("x", x); + el.setAttribute("y", y); + el.setAttribute("text-anchor", anchor); + el.setAttribute("dominant-baseline", "middle"); + + for (var i = 0; i < phrases.length; i++) { + var phrase = phrases[i] + var tspan = document.createElementNS(svgNS, 'tspan'); + var attrs = Object.keys(phrase.attrs) + for (var j = 0; j < attrs.length; j++) { + var value = phrase.attrs[attrs[j]] + if (value !== '') + tspan.setAttribute(attrs[j], value) + } + tspan.textContent = phrase.content; + + el.appendChild(tspan); + } + + if (target) + target.appendChild(el); + else + this.append(el); + return el; +} + +Svg.prototype.guessWidth = function (text, attr) { + var svg = this.createDummySvg(); + var el = this.text(text, attr, svg); + var size; + try { + size = el.getBBox(); + if (isNaN(size.height) || !size.height) // TODO-PER: I don't think this can happen unless there isn't a browser at all. + size = { width: attr['font-size'] / 2, height: attr['font-size'] + 2 }; // Just a wild guess. + else + size = { width: size.width, height: size.height }; + } catch (ex) { + size = { width: attr['font-size'] / 2, height: attr['font-size'] + 2 }; // Just a wild guess. + } + svg.removeChild(el); + return size; +}; + +Svg.prototype.createDummySvg = function () { + if (!this.dummySvg) { + this.dummySvg = createSvg(); + var styles = [ + "display: block !important;", + "height: 1px;", + "width: 1px;", + "position: absolute;" + ]; + this.dummySvg.setAttribute('style', styles.join("")); + var body = document.querySelector('body'); + body.appendChild(this.dummySvg); + } + + return this.dummySvg; +}; + +var sizeCache = {}; + +Svg.prototype.getTextSize = function (text, attr, el) { + if (typeof text === 'number') + text = '' + text; + if (!text || text.match(/^\s+$/)) + return { width: 0, height: 0 }; + var key; + if (text.length < 20) { + // The short text tends to be repetitive and getBBox is really slow, so lets cache. + key = text + JSON.stringify(attr); + if (sizeCache[key]) + return sizeCache[key]; + } + var removeLater = !el; + if (!el) + el = this.text(text, attr); + var size; + try { + size = el.getBBox(); + if (isNaN(size.height) || !size.height) + size = this.guessWidth(text, attr); + else + size = { width: size.width, height: size.height }; + } catch (ex) { + size = this.guessWidth(text, attr); + } + if (removeLater) { + if (this.currentGroup.length > 0) + this.currentGroup[0].removeChild(el); + else + this.svg.removeChild(el); + } + if (key) + sizeCache[key] = size; + return size; +}; + +Svg.prototype.openGroup = function (options) { + options = options ? options : {}; + var el = document.createElementNS(svgNS, "g"); + if (options.klass) + el.setAttribute("class", options.klass); + if (options.fill) + el.setAttribute("fill", options.fill); + if (options.stroke) + el.setAttribute("stroke", options.stroke); + if (options['data-name']) + el.setAttribute("data-name", options['data-name']); + + if (options.prepend) + this.prepend(el); + else + this.append(el); + this.currentGroup.unshift(el); + return el; +}; + +Svg.prototype.closeGroup = function () { + var g = this.currentGroup.shift(); + if (g && g.children.length === 0) { + // If nothing was added to the group it is because all the elements were invisible. We don't need the group, then. + g.parentElement.removeChild(g); + return null; + } + return g; +}; + +Svg.prototype.path = function (attr) { + var el = document.createElementNS(svgNS, "path"); + for (var key in attr) { + if (attr.hasOwnProperty(key)) { + if (key === 'path') + el.setAttributeNS(null, 'd', attr.path); + else if (key === 'klass') + el.setAttributeNS(null, "class", attr[key]); + else if (attr[key] !== undefined) + el.setAttributeNS(null, key, attr[key]); + } + } + this.append(el); + return el; +}; + +Svg.prototype.pathToBack = function (attr) { + var el = document.createElementNS(svgNS, "path"); + for (var key in attr) { + if (attr.hasOwnProperty(key)) { + if (key === 'path') + el.setAttributeNS(null, 'd', attr.path); + else if (key === 'klass') + el.setAttributeNS(null, "class", attr[key]); + else + el.setAttributeNS(null, key, attr[key]); + } + } + this.prepend(el); + return el; +}; + +Svg.prototype.lineToBack = function (attr) { + var el = document.createElementNS(svgNS, 'line'); + var keys = Object.keys(attr) + for (var i = 0; i < keys.length; i++) + el.setAttribute(keys[i], attr[keys[i]]); + this.prepend(el); + return el; +}; + + +Svg.prototype.append = function (el) { + if (this.currentGroup.length > 0) + this.currentGroup[0].appendChild(el); + else + this.svg.appendChild(el); +}; + +Svg.prototype.prepend = function (el) { + // The entire group is prepended, so don't prepend the individual elements. + if (this.currentGroup.length > 0) + this.currentGroup[0].appendChild(el); + else + this.svg.insertBefore(el, this.svg.firstChild); +}; + +Svg.prototype.setAttributeOnElement = function (el, attr) { + for (var key in attr) { + if (attr.hasOwnProperty(key)) { + el.setAttributeNS(null, key, attr[key]); + } + } +}; + +Svg.prototype.moveElementToChild = function (parent, child) { + parent.appendChild(child); +}; + +function createSvg() { + var svg = document.createElementNS(svgNS, "svg"); + svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); + svg.setAttribute('role', 'img'); // for accessibility + svg.setAttribute('fill', 'currentColor'); // for automatically picking up dark mode and high contrast + svg.setAttribute('stroke', 'currentColor'); // for automatically picking up dark mode and high contrast + return svg; +} + + +module.exports = Svg; diff --git a/start-localhost-p.bat b/start-localhost-p.bat new file mode 100644 index 0000000000000000000000000000000000000000..cf6fcdf411bf6d7338d623bae508175194f6d301 --- /dev/null +++ b/start-localhost-p.bat @@ -0,0 +1,5 @@ +start msedge http://localhost:8584/ + +http-server -c-1 --port 8584 %cd% + +exit diff --git a/start-version.sh b/start-version.sh new file mode 100644 index 0000000000000000000000000000000000000000..127d4c6930b97d61ab2927ea1f8f721cb61a915c --- /dev/null +++ b/start-version.sh @@ -0,0 +1,21 @@ +#!/bin/sh +die () { + echo >&2 "$@" + exit 1 +} + +[ "$#" -eq 1 ] || die "Call with a version number argument in the form x.y.z[-beta.n]" + +# switch to dev branch. +git checkout dev + +# change version number in package.json and version.js. +perl -pi -e "s/\'([^\']+)\'/\'$1\'/" version.js +perl -pi -e "s/\"version\": \"([^\"]+)\"/\"version\": \"$1\"/" package.json + +# build so dist has the right version numbers. +CMD="install" docker-compose up +CMD="run build" docker-compose up +#### If not running under Docker, do this instead: +# npm install +# npm run build diff --git a/test.js b/test.js new file mode 100644 index 0000000000000000000000000000000000000000..c6ae1375e1552b328bfc711ed25a22bd65d6d4a6 --- /dev/null +++ b/test.js @@ -0,0 +1,38 @@ +/**! +Copyright (c) 2009-2024 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 abcjs = require('./index'); +var version = require('./version'); +var Parse = require('./src/parse/abc_parse'); +var EngraverController = require('./src/write/engraver-controller') + +abcjs.signature = "abcjs-test v" + version; + +var parserLint = require('./src/test/abc_parser_lint'); +var verticalLint = require('./src/test/abc_vertical_lint'); +var midiLint = require('./src/test/abc_midi_lint'); +var midiSequencerLint = require('./src/test/abc_midi_sequencer_lint'); +var renderingLint = require('./src/test/rendering-lint'); +abcjs['test'] = { Parse: Parse, EngraverController: EngraverController, ParserLint: parserLint, verticalLint: verticalLint, midiLint: midiLint, midiSequencerLint: midiSequencerLint, renderingLint: renderingLint }; + +module.exports = abcjs; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8247e96977dbb67f76eed0259917a264a024abe4 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,24 @@ +# Automated Tests + +## Mocha + +Currently there are two sets of mocha tests. + +Either: + +Run with: +```shell script +npm run test +``` + +this will pass all `.js` files to mocha. + +or + +To run browser based tests, load the `.html` file into the browser. That will run +all of the tests associated with that file. To run just some of the tests, use the query parameter: +This will most like need a server to serve the `.html` files to solve CORS issues. +``` +?grep=test-name +``` +where `test-name` is any part of the test name. All the tests that match will be run. diff --git a/tests/all.html b/tests/all.html new file mode 100644 index 0000000000000000000000000000000000000000..fb558b6d02b112bb8ce5dfc78fe779cc16318ee5 --- /dev/null +++ b/tests/all.html @@ -0,0 +1,56 @@ + + + + + All Tests + + + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/api/tunebook_svg.test.js b/tests/api/tunebook_svg.test.js new file mode 100644 index 0000000000000000000000000000000000000000..05af7cc2bcc5340f725ffc4476bf702d96059905 --- /dev/null +++ b/tests/api/tunebook_svg.test.js @@ -0,0 +1,31 @@ +var assert = require('chai').assert +var render = require('../../src/api/abc_tunebook_svg') +var tunebook = require('../../src/api/abc_tunebook') + +// mock renderEngine Call +tunebook.renderEngine = function (...args) { + return args[3] +} + +describe("renderAbc", function () { + it("passes the correct params to renderEngine", () => { + let parserParams = { p: 'parserParams' } + let engraverParams = { e: 'engraverParams' } + let renderParams = { r: 'renderParams' } + let result = render('', '', parserParams, engraverParams, renderParams) + assert.deepEqual(result, { + ...parserParams, + ...engraverParams, + ...renderParams, + }) + }) + + it("passes click listener to renderEngine", () => { + let engraverParams = { listener: { highlight: 'clickListener' }} + let result = render('', '', {}, engraverParams, {}) + assert.deepEqual(result, { + clickListener: 'clickListener' + }) + }) +}) + diff --git a/tests/audio.html b/tests/audio.html new file mode 100644 index 0000000000000000000000000000000000000000..7288ba00e1651eea55b423bc8d103dde1f747236 --- /dev/null +++ b/tests/audio.html @@ -0,0 +1,34 @@ + + + + + Audio Tests + + + + + +
+
+
+ + + + + + + + + + + + + diff --git a/tests/browser-compatibility.html b/tests/browser-compatibility.html new file mode 100644 index 0000000000000000000000000000000000000000..6a1626f8550dab2b60db16cd989235816c3903a2 --- /dev/null +++ b/tests/browser-compatibility.html @@ -0,0 +1,74 @@ + + + + + + Browser Compatibility Test + + + + + +
+
+
+ + + + + + + + + + + + + + + diff --git a/tests/mouse-click.html b/tests/mouse-click.html new file mode 100644 index 0000000000000000000000000000000000000000..45e47c3951f016b1965a963197307283ca081b27 --- /dev/null +++ b/tests/mouse-click.html @@ -0,0 +1,30 @@ + + + + + Mouse Click Tests + + + + +
+
+ + + + + + + + + + + diff --git a/tests/options.html b/tests/options.html new file mode 100644 index 0000000000000000000000000000000000000000..58de6bb0ad197417853e4630942ed970bfdcfa18 --- /dev/null +++ b/tests/options.html @@ -0,0 +1,30 @@ + + + + + Options Tests + + + + +
+
+ + + + + + + + + + + diff --git a/tests/parse/book_parser.test.js b/tests/parse/book_parser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..950cd9ebf6dbdba508c2024703cfb7e5ff86627e --- /dev/null +++ b/tests/parse/book_parser.test.js @@ -0,0 +1,43 @@ +/** + * http://abcnotation.com/wiki/abc:standard:v2.1#xreference_number + * http://abcnotation.com/wiki/abc:standard:v2.1#ttune_title + * + */ + +describe("Book Parser function", function () { + it("parses a single tune", () => { + var tunebook = new abcjs.TuneBook("X:43\nT: example") + chai.assert.equal(tunebook.tunes.length, 1) + chai.assert.equal(tunebook.tunes[0].id, "43") + chai.assert.equal(tunebook.tunes[0].title, "example") + }) + + it("parses a single tune with no title", () => { + var tunebook = new abcjs.TuneBook("X:43\nT:") + chai.assert.equal(tunebook.tunes.length, 1) + chai.assert.equal(tunebook.tunes[0].id, "43") + chai.assert.equal(tunebook.tunes[0].title, "") + }) + + it("parses multiple tunes", () => { + var tunebook = new abcjs.TuneBook("X:1\nT: a\n\nX:2\n\nX:3\nT: c") + chai.assert.equal(tunebook.tunes.length, 3) + chai.assert.equal(tunebook.tunes[0].id, "1") + chai.assert.equal(tunebook.tunes[0].title, "a") + chai.assert.equal(tunebook.tunes[1].id, "2") + chai.assert.equal(tunebook.tunes[1].title, "") + chai.assert.equal(tunebook.tunes[2].id, "3") + chai.assert.equal(tunebook.tunes[2].title, "c") + }) + + it("collects directives in string header", () => { + var tunebook = new abcjs.TuneBook("%% example\nT: wed\n%%example\nX:1") + chai.assert.equal(tunebook.header, "%% example\n%%example\n") + }) + + it("trims whitespace from the end of a tune", () => { + var tunebook = new abcjs.TuneBook("%%example\nX:1\nT: a\n\n\n\n\n\nX:2\n\n") + chai.assert.equal(tunebook.tunes[0].abc, "%%example\nX:1\nT: a") + chai.assert.equal(tunebook.tunes[0].pure, "X:1\nT: a") + }) +}) diff --git a/tests/parse/note-id.test.js b/tests/parse/note-id.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a0dad8d66971a46ab6b00bf8fce6e9b53681b3ce --- /dev/null +++ b/tests/parse/note-id.test.js @@ -0,0 +1,45 @@ +describe("Parser", function() { + var abcOctave1 = "X:1\nK: bass\n" + + "[V:v1] C,D,E,F, |\n" + + "[V:v2 octave=-1] CDEF |\n" + + "[V:v3 octave=-2] cdef |\n" + + "[V:v4 octave=1] C,,D,,E,,F,, |\n" + + "K: octave=1\n" + + "[V:v1] C,,D,,E,,F,,|\n" + + "[V:v2] CDEF |\n" + + "[V:v3] cdef |\n" + + "[V:v4 octave=0] C,D,E,F, |\n" + + var expectedOctave1 = [ + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + [-7, -6, -5, -4], + ] + + it("octave1", function() { + doNoteIdTest(abcOctave1, expectedOctave1) + }) + + function doNoteIdTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var output = [] + for (var k = 0; k < visualObj[0].lines.length; k++) { + var line = visualObj[0].lines[k]; + for (var i = 0; i < line.staff.length; i++) { + var voice = line.staff[i].voices[0] + var out = [] + for (var j = 0; j < voice.length; j++) { + if (voice[j].pitches) + out.push(voice[j].pitches[0].pitch) + } + output.push(out) + } + } + chai.assert.deepStrictEqual(output, expected); + } +}) diff --git a/tests/parse/note.test.js b/tests/parse/note.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1ef76fb8904b21c056b3046af1b715b269e77f9b --- /dev/null +++ b/tests/parse/note.test.js @@ -0,0 +1,22 @@ +describe("Parser Note", function() { + var abcZeroLength = "X:1\n" + + "C0 D1 [EG]0 [FA]1\n" + + var expectedZeroLength = [ + 0, 0.125, 0, 0.125 + ] + + it("zero-length", function() { + doNoteLengthTest(abcZeroLength, expectedZeroLength) + }) + + function doNoteLengthTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var warnings = visualObj[0].warnings + var voice = visualObj[0].lines[0].staff[0].voices[0] + for (var i = 0; i < voice.length; i++) { + chai.assert.equal(voice[i].duration, expected[i], "element # "+i) + } + chai.assert.equal(warnings, undefined, "warnings") + } +}) diff --git a/tests/parse/start-char.test.js b/tests/parse/start-char.test.js new file mode 100644 index 0000000000000000000000000000000000000000..db9e13deecc45f7ebbacbebe71836ca675b029be --- /dev/null +++ b/tests/parse/start-char.test.js @@ -0,0 +1,58 @@ +describe("Start and End Char", function() { + var abcSlurs = 'M:4/4\n' + + 'K:C\n' + + 'L:1/16\n' + + 'V: V0 clef=treble name="Sop."\n' + + '[V: V0].("^🚩""_II7"F4.(e4).(F4)e4)|'; + + var expectedSlurs = [ + {"type":"clef"}, + {"type":"timeSignature"}, + {"type":"note","startChar":56,"endChar":69,"fragment":"\"^🚩\"\"_II7\"F4"}, + {"type":"note","startChar":69,"endChar":74,"fragment":".(e4)"}, + {"type":"note","startChar":74,"endChar":79,"fragment":".(F4)"}, + {"type":"note","startChar":79,"endChar":82,"fragment":"e4)"}, + {"type":"bar","startChar":82,"endChar":83,"fragment":"|"} + ]; + +////////////////////////////////////////////////////////// + + it("of slurs", function() { + doStartCharTest(abcSlurs, expectedSlurs); + }); +}); + +////////////////////////////////////////////////////////// + +function doStartCharTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc)[0]; + + // Remove all extraneous info so that just the start and end chars are considered. + var charPos = []; + for (var i = 0; i < visualObj.lines.length; i++) { + var line = visualObj.lines[i]; + if (line.staffGroup) { + for (var j = 0; j < line.staffGroup.voices.length; j++) { + var voice = line.staffGroup.voices[j]; + for (var k = 0; k < voice.children.length; k++) { + var element = voice.children[k]; + var obj = { type: element.abcelem.el_type }; + if (element.abcelem.startChar) + obj.startChar = element.abcelem.startChar; + if (element.abcelem.endChar) + obj.endChar = element.abcelem.endChar; + if (element.abcelem.startChar && element.abcelem.endChar) + obj.fragment = abc.substring(element.abcelem.startChar, element.abcelem.endChar) + charPos.push(obj); + } + } + } + } + + console.log(JSON.stringify(charPos)); + for (i = 0; i < expected.length; i++) { + var msg = "charPos\nRCV: " + + JSON.stringify(charPos[i]) + "\nEXP: " + JSON.stringify(expected[i]) + "\n"; + chai.assert.deepStrictEqual(charPos[i], expected[i], msg); + } +} diff --git a/tests/parse/tie-slur.test.js b/tests/parse/tie-slur.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dc8ec61a5acef8ecb1ff263c938117894d40d47c --- /dev/null +++ b/tests/parse/tie-slur.test.js @@ -0,0 +1,153 @@ +describe("Tie Slur", function () { + var abcMultipartTie = + "X:1\n" + + "%%staffwidth 200\n" + + "M:C\n" + + "K:E\n" + + "[V:T1] G8-|\n" + + "[V:B1] c8-|\n" + + "[V:T1] G8-|\n" + + "G8||\n" + + "[V:B1] c8-|\n" + + "c8||\n" + + var expectedMultipartTie = [ + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 1, startTie: {}, endTie: undefined}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 1, startTie: {}, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 1, startTie: undefined, endTie: true}, + ] + + var abcMultipartChordTie = + "X:1\n" + + "%%staffwidth 200\n" + + "M:C\n" + + "K:E\n" + + "[V:T1] [GB]8-|\n" + + "[V:B1] [ce]8-|\n" + + "[V:T1] [GB]8-|\n" + + "[GB]8||\n" + + "[V:B1] [ce]8-|\n" + + "[ce]8||\n" + + var expectedMultipartChordTie = [ + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 1, startTie: {}, endTie: undefined}, + {line: 0, staff: 1, startTie: {}, endTie: undefined}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 1, startTie: {}, endTie: true}, + {line: 1, staff: 1, startTie: {}, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 1, startTie: undefined, endTie: true}, + {line: 2, staff: 1, startTie: undefined, endTie: true}, + ] + + var abcMultipartOneStaffChordTie = + "X:1\n" + + "%%staffwidth 200\n" + + "%%staves (T1 B1)\n" + + "M:C\n" + + "K:E\n" + + "[V:T1] [GB]8-|\n" + + "[V:B1] [ce]8-|\n" + + "[V:T1] [GB]8-|\n" + + "[GB]8||\n" + + "[V:B1] [ce]8-|\n" + + "[ce]8||\n" + + var expectedMultipartOneStaffChordTie = [ + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 0, staff: 0, startTie: {}, endTie: undefined}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 1, staff: 0, startTie: {}, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + {line: 2, staff: 0, startTie: undefined, endTie: true}, + ] + + var abcMultipartTieHeight = + "X:1\n" + + "%%stretchlast 1\n" + + "L:1/8\n" + + "M:4/4\n" + + "K:D\n" + + "%%score (S A) (T B)\n" + + "V:S clef=treble middle=B stem=up\n" + + "V:A clef=treble middle=B stem=down\n" + + "V:T clef=bass,, stem=up\n" + + "V:B clef=bass,, stem=down\n" + + "[V:S]\n" + + "F2|F2 EF G2 E2|C6||\n" + + "[V:A]\n" + + "D2|D2 C=C B,2 B,2|A,6||\n" + + "[V:T]\n" + + "A,2|A,2(G,A,)G,2G,2|G,6||\n" + + "[V:B]\n" + + "D,2|D,2A,,A,,G,,2G,,2|A,,6||\n" + + var expectedMultipartTieHeight = [ 14, 20 ] + + it('multipart-tie', function() { + doParseTest(abcMultipartTie, expectedMultipartTie) + }) + + it('multipart-chord-tie', function() { + doParseTest(abcMultipartChordTie, expectedMultipartChordTie) + }) + + it('multipart-one-staff-chord-tie', function() { + doParseTest(abcMultipartOneStaffChordTie, expectedMultipartOneStaffChordTie) + }) + + it('multipart-tie-height', function() { + doHeightTest(abcMultipartTieHeight, expectedMultipartTieHeight) + }) + + function doHeightTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var actual = [] + for (var i = 0; i < visualObj[0].lines.length; i++) { + var line = visualObj[0].lines[i] + for (var j = 0; j < line.staffGroup.staffs.length; j++) { + var staff = line.staffGroup.staffs[j] + actual.push(Math.round(staff.top)) + } + } + chai.assert.deepStrictEqual(actual, expected); + } + + function doParseTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var actual = [] + for (var i = 0; i < visualObj[0].lines.length; i++) { + var line = visualObj[0].lines[i] + for (var j = 0; j < line.staff.length; j++) { + var staff = line.staff[j] + for (var k = 0; k < staff.voices.length; k++) { + var voice = staff.voices[k] + for (var ii = 0; ii < voice.length; ii++) { + var elem = voice[ii] + if (elem.pitches) { + for (var jj = 0; jj < elem.pitches.length; jj++) { + var pitch = elem.pitches[jj] + actual.push({line: i, staff: j, startTie: pitch.startTie, endTie: pitch.endTie}) + } + } + } + } + } + } + console.log(actual) + chai.assert.deepStrictEqual(actual, expected); + } +}) diff --git a/tests/selection.html b/tests/selection.html new file mode 100644 index 0000000000000000000000000000000000000000..acf245d40ea685fc0d26fdce91024465c8b18c9c --- /dev/null +++ b/tests/selection.html @@ -0,0 +1,30 @@ + + + + + Selection Tests + + + + +
+
+ + + + + + + + + + + diff --git a/tests/svg-per-line.html b/tests/svg-per-line.html new file mode 100644 index 0000000000000000000000000000000000000000..e9293286259721cdbf571863480db5e30730d280 --- /dev/null +++ b/tests/svg-per-line.html @@ -0,0 +1,53 @@ + + + + + SVG Per Line Tests + + + + + +
+
+
+
Click items above to be sure that the handler is called.
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/synth.html b/tests/synth.html new file mode 100644 index 0000000000000000000000000000000000000000..d59bb213bd5c228739cdb1799405e1d44702e7e6 --- /dev/null +++ b/tests/synth.html @@ -0,0 +1,32 @@ + + + + + Audio Tests + + + + + +
+
+
+ + + + + + + + + + + diff --git a/tests/synth/flattener.test.js b/tests/synth/flattener.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ce1d352bd4344ed4fef0981ea80d6ab522585d28 --- /dev/null +++ b/tests/synth/flattener.test.js @@ -0,0 +1,7770 @@ +describe("Audio flattener", function() { + var abcMultiple = 'K:C\n' + +'Q:1/4=60\n' + +'L:1/4\n' + +'V:1\n' + +'G/| (3c/d/c/ "C"z d .e| {f}g !tenuto!a [gb] !style=rhythm!B|"D"A2"E"^G2|\n' + +'V:2\n' + +'x/|C4|D4|^F2B2|\n'; + + var expectedMultiple = { + "tempo": 60, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 85, + "start": 0, + "duration": 0.125, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 72, + "volume": 105, + "start": 0.125, + "duration": 0.083333, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 74, + "volume": 85, + "start": 0.208333, + "duration": 0.083333, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 72, + "volume": 85, + "start": 0.291666, + "duration": 0.083334, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 74, + "volume": 95, + "start": 0.625, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 76, + "volume": 95, + "start": 0.875, + "duration": 0.25, + "instrument": 0, + "endType": "staccato", + "gap": 0.1 + }, + { + "cmd": "note", + "pitch": 77, + "volume": 70, + "start": 1.125, + "duration": 0.125, + "gap": 0, + "instrument": 0, + "style": "grace" + }, + { + "cmd": "note", + "pitch": 79, + "volume": 105, + "start": 1.25, + "duration": 0.125, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 81, + "volume": 95, + "start": 1.375, + "duration": 0.25, + "instrument": 0, + "endType": "tenuto", + "gap": -0.001 + }, + { + "cmd": "note", + "pitch": 79, + "volume": 95, + "start": 1.625, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 83, + "volume": 95, + "start": 1.625, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 95, + "start": 1.875, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 95, + "start": 1.875, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 95, + "start": 1.875, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 105, + "start": 2.125, + "duration": 0.5, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 68, + "volume": 95, + "start": 2.625, + "duration": 0.5, + "instrument": 0, + "gap": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 105, + "start": 0.125, + "duration": 1, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 105, + "start": 1.125, + "duration": 1, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 66, + "volume": 105, + "start": 2.125, + "duration": 0.5, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 95, + "start": 2.625, + "duration": 0.5, + "instrument": 0, + "gap": 0 + } + ], + [ + { + "cmd": "program", + "channel": 2, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 0.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 0.625, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 2.125, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 2.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 2.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 2.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 2.625, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 2.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 2.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 2.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + } + ] + ], + "totalDuration": 3.125 + } + + ////////////////////////////////////////////////////////// + + var abcDynamics = 'X:1\n' + +'M:4/4\n' + +'L:1/4\n' + +'Q:1/4=120\n' + +'K:C\n' + +'!crescendo(! EFGA| GAB !crescendo)!c | !diminuendo(! EFGA| GAB !diminuendo)!c |\n' + +'!pppp! A B !ppp!A B |!pp! A B !p!A B | !mp! AB !sfz! AB|\n' + +'!mf! AB !f! AB | !ff! AB !fff! AB | !ffff! ABAB|]\n'; + + var expectedDynamics = { + "tempo": 120, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 105, + "start": 0, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 98, + "start": 0.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 101, + "start": 0.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 104, + "start": 0.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 117, + "start": 1, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 110, + "start": 1.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 113, + "start": 1.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 72, + "volume": 116, + "start": 1.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 126, + "start": 2, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 108, + "start": 2.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 100, + "start": 2.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 92, + "start": 2.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 94, + "start": 3, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 76, + "start": 3.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 68, + "start": 3.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 72, + "volume": 60, + "start": 3.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 15, + "start": 4, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 10, + "start": 4.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 20, + "start": 4.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 20, + "start": 4.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 45, + "start": 5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 35, + "start": 5.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 50, + "start": 5.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 50, + "start": 5.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 75, + "start": 6, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 65, + "start": 6.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 65, + "start": 6.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 65, + "start": 6.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 90, + "start": 7, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 80, + "start": 7.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 95, + "start": 7.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 95, + "start": 7.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 120, + "start": 8, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 110, + "start": 8.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 125, + "start": 8.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 125, + "start": 8.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 127, + "start": 9, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 125, + "start": 9.25, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 125, + "start": 9.5, + "duration": 0.25, + "instrument": 0, + "gap": 0 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 125, + "start": 9.75, + "duration": 0.25, + "instrument": 0, + "gap": 0 + } + ] + ], + "totalDuration": 10 + } + + ////////////////////////////////////////////////////////// + + var abcDynamics2 = 'X: 1\n' + + 'M: 4/4\n' + + 'L: 1/8\n' + + 'K: C\n' + + '!p!C!<(!DEF GABc |d2 B2 G2 F2!<)! | !f!E!>(!FGA Bcde!>)! | !p!f2 d2 B2 A2 |\n' + + 'G2 c2 e2 g2 | a2 f2 d2 B2 |cdBc ABGA | FGEF DEFG |\n' + + 'E2 C2 D2 B,2 | C8 |\n'; + + var expectedDynamics2 = { + "tempo":180, + "instrument":0, + "totalDuration":10, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":60,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":35,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":54,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":43,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":62,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":51,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":70,"start":0.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":59,"start":0.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":88,"start":1,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":82,"start":1.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":86,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":90,"start":1.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":2,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":80,"start":2.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":87,"start":2.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":64,"start":2.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":71,"start":2.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":48,"start":2.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":55,"start":2.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":32,"start":2.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":60,"start":3,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":50,"start":3.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":50,"start":3.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":50,"start":3.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":60,"start":4,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":50,"start":4.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":50,"start":4.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":79,"volume":50,"start":4.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":60,"start":5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":50,"start":5.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":50,"start":5.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":50,"start":5.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":60,"start":6,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":35,"start":6.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":50,"start":6.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":35,"start":6.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":50,"start":6.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":35,"start":6.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":50,"start":6.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":35,"start":6.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":60,"start":7,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":35,"start":7.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":50,"start":7.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":35,"start":7.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":50,"start":7.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":35,"start":7.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":50,"start":7.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":35,"start":7.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":60,"start":8,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":50,"start":8.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":50,"start":8.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":59,"volume":50,"start":8.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":60,"start":9,"duration":1,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcDynamics3 = 'X: 1\n' + + 'M: 4/4\n' + + 'L: 1/4\n' + + 'K: C\n' + + '!pppp!CDEF | !<(!GABc| !<)!y!ffff!BcBA | !>(!GFED | !>)!y!pppp!C4 |]\n'; + + var expectedDynamics3 = { + "tempo":180, + "instrument":0, + "totalDuration":5, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":15,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":10,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":10,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":10,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":67,"volume":15,"start":1,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":38,"start":1.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":66,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":94,"start":1.75,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":71,"volume":127,"start":2,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":125,"start":2.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":125,"start":2.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":125,"start":2.75,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":67,"volume":127,"start":3,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":97,"start":3.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":69,"start":3.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":41,"start":3.75,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":60,"volume":15,"start":4,"duration":1,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcSixHuit = 'X:1\n' + +'M:6/8\n' + +'L:1/8\n' + +'Q:3/8=60\n' + +'K:G\n' + +'"G"GAB cde|"D7"fga DEF|\n'; + + var expectedSixHuit = { + "tempo": 60, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + }, + {"cmd":"note","pitch":67,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":105,"start":0.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":79,"volume":85,"start":0.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":85,"start":1,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":95,"start":1.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":1.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":66,"volume":85,"start":1.375,"duration":0.125,"instrument":0,"gap":0}, + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + {"cmd":"note","pitch":43,"volume":64,"start":0,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":1.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + ], + ], + "totalDuration": 1.5 + } + + ////////////////////////////////////////////////////////// + + var abcJigChords = 'X:1\n' + + 'L:1/8\n' + + 'Q:3/8=61\n' + + 'M:6/8\n' + + 'K:F\n' + + '"C"cde def|c2e d2f|"C"c2"D"d "G"d2"E"e|'; + + var expectedJigChords = { + "tempo":61, + "instrument":0, + "totalDuration":2.25, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":72,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":105,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":1,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1.125,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":85,"start":1.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":105,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":1.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1.875,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":2.125,"duration":0.125,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":0,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":31,"volume":64,"start":0.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":36,"volume":64,"start":0.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":31,"volume":64,"start":1.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":36,"volume":64,"start":1.5,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":43,"volume":64,"start":1.875,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":40,"volume":64,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":56,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcRepeat = 'X:1\n' + +'M:C\n' + +'L:1/8\n' + +'Q:1/2=50\n' + +'K:G\n' + +'cde|:"D7"f2 d2 e2 f2|1"G"g4 fedc:|"C"e4z4|]\n'; + + var expectedRepeat = { + "tempo":100, + "instrument":0, + "totalDuration":4.375, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":72,"volume":85,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":78,"volume":105,"start":0.375,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":0.625,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":0.875,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":1.125,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":79,"volume":105,"start":1.375,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":1.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":2,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":2.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":2.25,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":78,"volume":105,"start":2.375,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":2.625,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":2.875,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":3.125,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":105,"start":3.375,"duration":0.5,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0.375,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":0.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":0.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":0.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":0.875,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":1.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":1.125,"duration":0.125,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":43,"volume":64,"start":1.375,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":1.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":1.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":1.875,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":2.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":2.125,"duration":0.125,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":38,"volume":64,"start":2.375,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":2.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":2.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":2.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":2.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":2.875,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":3.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":3.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":3.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":3.125,"duration":0.125,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":36,"volume":64,"start":3.375,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":3.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":3.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":3.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":3.875,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":4.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":4.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":4.125,"duration":0.125,"gap":0,"instrument":0}, + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcDrum = 'X:1\n' + +'T: metronome\n' + +'L:1/4\n' + +'Q:1/4=60\n' + +'%%MIDI drum dddd 76 77 77 77 50 50 50 50\n' + +'N:The drum beat should start on the first full measure.\n' + +'N:The drum beat should drop out in the second line.\n' + +'N:The drum beat pattern should change for the third line.\n' + +'K:A\n' + +'V:1\n' + +'%%MIDI drumon\n' + +'e|a/g/ f/e/ c3/2 B/|Azzz|z4|z/c/ z/d/ z/e/ z/f/|\n' + +'%%MIDI drumoff\n' + +'|a/g/ f/e/ c3/2 B/|Azzz|[I:MIDI drumon]z4|z/c/ z/d/ z/e/ z/f/|\n' + +'%%MIDI drum d2z/d/d 35 38 38 100 50 50\n' + +'|a/g/ f/e/ c3/2 B/|Azzz|z4|z/c/ z/d/ z/e/ z/f/|\n'; + + var expectedDrum = { + "tempo":60, + "instrument":0, + "totalDuration":12.25, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":80,"volume":85,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":0.75,"duration":0.375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":1.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":1.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":85,"start":3.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":3.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":3.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":85,"start":4.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":4.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":80,"volume":85,"start":4.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":4.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":4.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":4.75,"duration":0.375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":5.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":5.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":85,"start":7.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":7.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":7.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":85,"start":8.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":8.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":80,"volume":85,"start":8.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":8.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":8.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":8.75,"duration":0.375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":9.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":9.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":85,"start":11.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":11.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":11.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":85,"start":12.125,"duration":0.125,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":2,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":0.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":0.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":0.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":1.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":2,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":2.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":2.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":2.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":3,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":3.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":3.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":3.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":4,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":6.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":6.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":6.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":7,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":7.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":7.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":7.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":8,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":35,"volume":100,"start":8.25,"duration":0.5,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":8.875,"duration":0.125,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":9,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":35,"volume":100,"start":9.25,"duration":0.5,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":9.875,"duration":0.125,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":10,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":35,"volume":100,"start":10.25,"duration":0.5,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":10.875,"duration":0.125,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":11,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":35,"volume":100,"start":11.25,"duration":0.5,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":11.875,"duration":0.125,"gap":0,"instrument":128}, + {"cmd":"note","pitch":38,"volume":50,"start":12,"duration":0.25,"gap":0,"instrument":128} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcTranspose = 'X: 1\n' + +'M: 4/4\n' + +'L: 1/4\n' + +'K: Em\n' + +'V: 1 transpose=-2\n' + +'"Em"EGAB|\n' + +'V: 2\n' + +'"Em"EGAB|\n' + +'V: 3 transpose=4\n' + +'"Em"EGAB|\n'; + + var expectedTranspose = { + "tempo":180,"instrument":0,"totalDuration":1,"tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":64,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":2,"instrument":0}, + {"cmd":"note","pitch":68,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":75,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":3,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcTempoChange = 'X:1\n' + +'L:1/4\n' + +'M:C|\n' + +'Q:1/2=60\n' + +'K:D\n' + +'"D"DEFG| [Q:1/2=90] DEFG |\n'; + + var expectedTempoChange = { + "tempo":60, + "instrument":0, + "totalDuration":1.666668, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":66,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":105,"start":1,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":1.166667,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":66,"volume":85,"start":1.333334,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":1.500001,"duration":0.166667,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0,"duration":0.25,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":0.5,"duration":0.25,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":0.5,"duration":0.25,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.5,"duration":0.25,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":1,"duration":0.166667,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1.333332,"duration":0.166667,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":1.333332,"duration":0.166667,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1.333332,"duration":0.166667,"gap":0,"instrument":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcTempoChange2 = 'X:1\n' + +'L:1/4\n' + +'M:4/4\n' + +'K:F\n' + +'[Q:1/4=129.0476605]CDEF |[Q:1/4=127]GABc | [Q:1/4=131] CDEF |[Q:1/4=130] GABc |[Q:1/4=127]CDEF |\n' ; + + var expectedTempoChange2 = { + "tempo":180, + "instrument":0, + "totalDuration":6.988656, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":105,"start":0,"duration":0.348837,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":0.348837,"duration":0.348837,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":0.697674,"duration":0.348837,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":1.046511,"duration":0.348837,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":1.395348,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":1.749679,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":85,"start":2.10401,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":2.458341,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":2.812672,"duration":0.343511,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":3.156183,"duration":0.343511,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":3.499694,"duration":0.343511,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":3.843205,"duration":0.343511,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":4.186716,"duration":0.346154,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":4.53287,"duration":0.346154,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":85,"start":4.879024,"duration":0.346154,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":5.225178,"duration":0.346154,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":5.571332,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":5.925663,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":6.279994,"duration":0.354331,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":6.634325,"duration":0.354331,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcDecoration = 'X:1\n' + +'M:4/4\n' + +'L:1/4\n' + +'Q:1/4=90\n' + +'K:C\n' + +'%%MIDI program 3\n' + +'!trill! e !lowermordent! d !uppermordent! c !mordent! B | !accent!A .G !turn! g !roll! a | !slide! d !/! G !//! G !///! G |\n' + +'[Q:1/4=180] !trill! e !lowermordent! d !uppermordent! c !mordent! B | !accent!A .G !turn! g !roll! a | !slide! d !/! G !//! G !///! G |\n' + +'[Q:1/4=60] !trill! e !lowermordent! d !uppermordent! c !mordent! B | !accent!A .G !turn! g !roll! a | !slide! d !/! G !//! G !///! G |\n'; + + var expectedDecoration = { + "tempo":90, + "instrument":3, + "totalDuration":9, + "tracks":[ + [ + // TODO-PER: also handle the slide and the drum rolls. + {"cmd":"program","channel":0,"instrument":3}, + {"cmd":"note","pitch":77,"volume":105,"start":0,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":0.03125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":0.0625,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":0.09375,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":0.125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":0.15625,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":0.1875,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":0.21875,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":95,"start":0.25,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":95,"start":0.28125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":95,"start":0.3125,"duration":0.1875,"gap":0,"instrument":3}, + {"cmd":"note","pitch":72,"volume":95,"start":0.5,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":95,"start":0.53125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":95,"start":0.5625,"duration":0.1875,"gap":0,"instrument":3}, + {"cmd":"note","pitch":71,"volume":95,"start":0.75,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":95,"start":0.78125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":71,"volume":95,"start":0.8125,"duration":0.1875,"gap":0,"instrument":3}, + {"cmd":"note","pitch":69,"volume":127,"start":1,"duration":0.25,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":1.25,"duration":0.25,"instrument":3,"endType":"staccato","gap":0.15000000000000002}, + {"cmd":"note","pitch":79,"volume":95,"start":1.5,"duration":0.05,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":1.55,"duration":0.05,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":1.6,"duration":0.05,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":1.65,"duration":0.05,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":1.7,"duration":0.05,"gap":0,"instrument":3}, + {"cmd":"note","pitch":81,"volume":95,"start":1.75,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":95,"start":1.8125,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":95,"start":1.875,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":95,"start":1.9375,"duration":0.03125,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":105,"start":2,"duration":0.25,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":2.25,"duration":0.25,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":2.5,"duration":0.25,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":2.75,"duration":0.25,"instrument":3,"gap":0}, + {"cmd":"note","pitch":77,"volume":105,"start":3,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":3.015625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":3.03125,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":3.046875,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":3.0625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":3.078125,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":3.09375,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":3.109375,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":85,"start":3.125,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":85,"start":3.140625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":85,"start":3.15625,"duration":0.09375,"gap":0,"instrument":3}, + {"cmd":"note","pitch":72,"volume":95,"start":3.25,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":95,"start":3.265625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":95,"start":3.28125,"duration":0.09375,"gap":0,"instrument":3}, + {"cmd":"note","pitch":71,"volume":85,"start":3.375,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":85,"start":3.390625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":71,"volume":85,"start":3.40625,"duration":0.09375,"gap":0,"instrument":3}, + {"cmd":"note","pitch":69,"volume":127,"start":3.5,"duration":0.125,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":3.625,"duration":0.125,"instrument":3,"endType":"staccato","gap":0.07500000000000001}, + {"cmd":"note","pitch":79,"volume":95,"start":3.75,"duration":0.025,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":3.775,"duration":0.025,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":3.8,"duration":0.025,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":3.825,"duration":0.025,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":3.85,"duration":0.025,"gap":0,"instrument":3}, + {"cmd":"note","pitch":81,"volume":85,"start":3.875,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":3.90625,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":3.9375,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":3.96875,"duration":0.015625,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":105,"start":4,"duration":0.125,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":4.125,"duration":0.125,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":4.25,"duration":0.125,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":4.375,"duration":0.125,"instrument":3,"gap":0}, + {"cmd":"note","pitch":77,"volume":105,"start":4.5,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":4.546875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":4.59375,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":4.640625,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":4.6875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":4.734375,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":77,"volume":105,"start":4.78125,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":76,"volume":105,"start":4.828125,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":85,"start":4.875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":85,"start":4.921875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":85,"start":4.96875,"duration":0.28125,"gap":0,"instrument":3}, + {"cmd":"note","pitch":72,"volume":95,"start":5.25,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":73,"volume":95,"start":5.296875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":95,"start":5.34375,"duration":0.28125,"gap":0,"instrument":3}, + {"cmd":"note","pitch":71,"volume":85,"start":5.625,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":72,"volume":85,"start":5.671875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":71,"volume":85,"start":5.71875,"duration":0.28125,"gap":0,"instrument":3}, + {"cmd":"note","pitch":69,"volume":127,"start":6,"duration":0.375,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":6.375,"duration":0.375,"instrument":3,"endType":"staccato","gap":0.22500000000000003}, + {"cmd":"note","pitch":79,"volume":95,"start":6.75,"duration":0.075,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":6.825,"duration":0.075,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":6.9,"duration":0.075,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":80,"volume":95,"start":6.975,"duration":0.075,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":79,"volume":95,"start":7.05,"duration":0.075,"gap":0,"instrument":3}, + {"cmd":"note","pitch":81,"volume":85,"start":7.125,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":7.21875,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":7.3125,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":81,"volume":85,"start":7.40625,"duration":0.046875,"gap":0,"instrument":3, "style": "decoration"}, + {"cmd":"note","pitch":74,"volume":105,"start":7.5,"duration":0.375,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":7.875,"duration":0.375,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":8.25,"duration":0.375,"instrument":3,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":8.625,"duration":0.375,"instrument":3,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcMeterChange = 'X:1\n' + +'T: chords meter change\n' + +'L:1/4\n' + +'Q:1/4=40\n' + +'M:3/4\n' + +'K:F\n' + +'"F"F2A|[M:4/4]"Bb"Bd2f|\n'; + + var expectedMeterChange = { + "tempo":40, + "instrument":0, + "totalDuration":1.75, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":65,"volume":105,"start":0,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":105,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":41,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":34,"volume":64,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":46,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":29,"volume":64,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":46,"volume":48,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcBreak = 'X:1\n' + +'L:1/4\n' + +'Q:1/4=40\n' + +'K:A\n' + +'"E7"Bcde|"A"f"^break"efe|"E7"Bc"^ignore"de|\n'; + + var expectedBreak = { + "tempo": 40, + "instrument": 0, + "totalDuration": 3, + "tracks": [ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":71,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":105,"start":2,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":2.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":2.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":2.75,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":40,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":56,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":35,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":56,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":40,"volume":64,"start":2,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":56,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":35,"volume":64,"start":2.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":56,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}] ] + }; + + ////////////////////////////////////////////////////////// + + var abcBreak2 = 'X:1\n' + +'L:1/8\n' + +'Q:135\n' + +'K:Ab\n' + +'"Eb7"zG2GA2A2|=A2AB-B4|"Ab"z"^break"c2cd2d2|=d2de2c2=c|\n'; + + var expectedBreak2 = { + "tempo":135, + "instrument":0, + "totalDuration":4, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":67,"volume":85,"start":0.125,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":68,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":68,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":95,"start":1.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":85,"start":1.375,"duration":0.625,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":2.125,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":2.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":2.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":2.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":105,"start":3,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":3.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":75,"volume":85,"start":3.375,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.625,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.875,"duration":0.125,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":39,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":51,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":61,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":34,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":51,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":61,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":39,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":51,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":61,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":34,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":51,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":61,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":32,"volume":64,"start":2,"duration":0.125,"gap":0,"instrument":0}] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcEndChord = 'X:1\n' + +'L:1/4\n' + +'Q:135\n' + +'K:C\n' + +'"C"c4-|c|]\n'; + + var expectedEndChord = { + "tempo":135, + "instrument":0, + "totalDuration":1.25, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":72,"volume":105,"start":0,"duration":1.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1,"duration":0.125,"gap":0,"instrument":0}, + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcMidMeasureChordChange = 'X:1\n' + +'K: Gmin\n' + +'|: "Gm" GFDF GFDF | GF D2 "F" C4 |\n'; + + var expectedMidMeasureChordChange = { + "tempo":180, + "instrument":0, + "totalDuration":2, + "tracks": + [ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":67,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":95,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":95,"start":0.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":0.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":1,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":1.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":95,"start":1.5,"duration":0.5,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":43,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":43,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":58,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":41,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":53,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcGrace = 'X:1\n' + +'T: midi-grace-notes\n' + +'L:1/4\n' + +'Q:1/4=40\n' + +'K:A\n' + +'{e}a|:{e}gz{e}ag|{efg}ag{ABcdefg}ag:|\n' + +'{B}e{B2c/d/}fef|[K:Bb]{Bcde}f2{Bcde}f2|]\n'; + + var expectedGrace = { + "tempo":40, + "instrument":0, + "totalDuration":6.25, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":76,"volume":57,"start":0,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":70,"start":0.25,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":105,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":63,"start":0.75,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":95,"start":0.875,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":1,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":70,"start":1.25,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":78,"volume":70,"start":1.2916666666666667,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":70,"start":1.3333333333333335,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":105,"start":1.375,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":69,"volume":63,"start":1.75,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":71,"volume":63,"start":1.7678571428571428,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":73,"volume":63,"start":1.7857142857142856,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":74,"volume":63,"start":1.8035714285714284,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":76,"volume":63,"start":1.8214285714285712,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":78,"volume":63,"start":1.839285714285714,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":63,"start":1.8571428571428568,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":95,"start":1.875,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":2,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":70,"start":2.25,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":105,"start":2.375,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":63,"start":2.75,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":95,"start":2.875,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":3,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":70,"start":3.25,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":78,"volume":70,"start":3.2916666666666667,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":70,"start":3.333333333333333,"duration":0.041666666666666664,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":105,"start":3.375,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":3.5,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":69,"volume":63,"start":3.75,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":71,"volume":63,"start":3.7678571428571428,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":73,"volume":63,"start":3.7857142857142856,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":74,"volume":63,"start":3.8035714285714284,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":76,"volume":63,"start":3.8214285714285712,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":78,"volume":63,"start":3.839285714285714,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":80,"volume":63,"start":3.8571428571428568,"duration":0.017857142857142856,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":95,"start":3.875,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":80,"volume":95,"start":4,"duration":0.25,"instrument":0,"gap":0}, +// + {"cmd":"note","pitch":71,"volume":70,"start":4.25,"duration":0.125,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":76,"volume":105,"start":4.375,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":71,"volume":63,"start":4.5,"duration":0.08333333333333333,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":73,"volume":63,"start":4.583333333333333,"duration":0.020833333333333332,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":74,"volume":63,"start":4.604166666666666,"duration":0.020833333333333332,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":78,"volume":95,"start":4.625,"duration":0.125,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":76,"volume":95,"start":4.75,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":78,"volume":95,"start":5,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":70,"volume":70,"start":5.25,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":72,"volume":70,"start":5.3125,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":74,"volume":70,"start":5.375,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":75,"volume":70,"start":5.4375,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":77,"volume":105,"start":5.5,"duration":0.25,"instrument":0,"gap":0}, + + {"cmd":"note","pitch":70,"volume":63,"start":5.75,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":72,"volume":63,"start":5.8125,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":74,"volume":63,"start":5.875,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":75,"volume":63,"start":5.9375,"duration":0.0625,"gap":0,"instrument":0, "style": "grace"}, + {"cmd":"note","pitch":77,"volume":95,"start":6,"duration":0.25,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcMidiOptions = 'X:1\n' + +'%%MIDI program 40\n' + +'%%MIDI channel 4\n' + +'%%MIDI transpose -2\n' + +'L:1/4\n' + +'Q:1/4=40\n' + +'K:A\n' + +'ABcd|\n'; + + var expectedMidiOptions = { + "tempo":40, + "instrument":40, + "totalDuration":1, + "tracks": + [ + [ + {"cmd":"program","channel":4,"instrument":40}, + {"cmd":"note","pitch":67,"volume":105,"start":0,"duration":0.25,"instrument":40,"gap":0}, + {"cmd":"note","pitch":69,"volume":95,"start":0.25,"duration":0.25,"instrument":40,"gap":0}, + {"cmd":"note","pitch":71,"volume":95,"start":0.5,"duration":0.25,"instrument":40,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":0.75,"duration":0.25,"instrument":40,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcMultiMeasureRest = 'X:1\n' + +'M:4/4\n' + +'L:1/4\n' + +'Q:1/4=130\n' + +'K:Bb\n' + +'cdef|Z4|fedc|\n'; + + var expectedMultiMeasureRest = { + "tempo":130, + "instrument":0, + "totalDuration":6, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":72,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":75,"volume":95,"start":0.50,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":77,"volume":105,"start":5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":75,"volume":95,"start":5.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":5.50,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":5.75,"duration":0.25,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcOctaveClefs = 'X:1\n' + +'M:4/4\n' + +'K:C\n' + +"[K: treble+8]{B}A4 [CE^F]4 | [K: treble-8]G8| G,2B,2 c'2e'2 | [K: bass-8]C8| [K: bass+8]B,8|\n"; + + var expectedOctaveClefs = { + "tempo": 180, + "instrument": 0, + "totalDuration": 5, + "tracks": [ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":83,"volume":70,"start":0,"duration":0.25,"instrument":0,"gap":0, "style": "grace"}, + {"cmd":"note","pitch":81,"volume":105,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":0.5,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":0.5,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":0.5,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":105,"start":1,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":43,"volume":105,"start":2,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":47,"volume":95,"start":2.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":2.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":2.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":48,"volume":105,"start":3,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":105,"start":4,"duration":1,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcOverlay = 'X:1\n' + +'M: 4/4\n' + +'L: 1/4\n' + +'K:C\n' + +'C4 | D4 |\n' + +'G4 & E4 | A4 & F4 |\n' + +'B4 & d4 & f4 | c4 & e4 & g4 |\n' + +"a4 | b4 & d'4 |\n" + +'C4 | D4 | E4 & G4 | A4 | B4 & d4 |\n'; + + var expectedOverlay = { + "tempo": 180, + "instrument": 0, + "totalDuration": 13, + "tracks": [ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":105,"start":0,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":105,"start":1,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":2,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":3,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":105,"start":4,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":105,"start":5,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":6,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":105,"start":7,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":8,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":105,"start":9,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":10,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":11,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":105,"start":12,"duration":1,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":64,"volume":95,"start":2,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":105,"start":3,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":105,"start":4,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":105,"start":5,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":86,"volume":105,"start":7,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":10,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":105,"start":12,"duration":1,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":2,"instrument":0}, + {"cmd":"note","pitch":77,"volume":95,"start":4,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":79,"volume":105,"start":5,"duration":1,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcPercMap = 'X:1\n' + +'%%percmap D pedal-hi-hat x\n' + +'%%percmap E bass-drum-1\n' + +'%%percmap F acoustic-bass-drum\n' + +'%%percmap G low-floor-tom\n' + +'%%percmap A high-floor-tom\n' + +'%%percmap B low-tom\n' + +'%%percmap ^B tambourine triangle\n' + +'%%percmap c acoustic-snare\n' + +'%%percmap _c electric-snare\n' + +'%%percmap ^c low-wood-block triangle\n' + +'%%percmap =c side-stick\n' + +'%%percmap d low-mid-tom\n' + +'%%percmap ^d hi-wood-block triangle\n' + +'%%percmap e hi-mid-tom\n' + +'%%percmap ^e cowbell triangle\n' + +'%%percmap f high-tom\n' + +'%%percmap ^f ride-cymbal-1\n' + +'%%percmap g closed-hi-hat\n' + +'%%percmap ^g open-hi-hat\n' + +'%%percmap a crash-cymbal-1 x\n' + +'%%percmap ^a open-triangle triangle\n' + +'Q:1/4=50\n' + +'K:C perc\n' + +'DEFG AB^Bc _c{^c}^c=cd ^de^ef ^fg^ga ^a\n'; + + var expectedPercMap = { + "tempo":50,"instrument":128,"totalDuration":2.625,"tracks":[ + [ + {"cmd":"program","channel":0,"instrument":128}, + {"cmd":"note","pitch":44,"volume":85,"start":0,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":36,"volume":85,"start":0.125,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":35,"volume":85,"start":0.25,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":41,"volume":85,"start":0.375,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":43,"volume":85,"start":0.5,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":45,"volume":85,"start":0.625,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":54,"volume":95,"start":0.75,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":85,"start":0.875,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":40,"volume":95,"start":1,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":77,"volume":57,"start":1.125,"duration":0.0625,"instrument":128,"gap":0,"style":"grace"}, + {"cmd":"note","pitch":77,"volume":85,"start":1.1875,"duration":0.0625,"instrument":128,"gap":0}, + {"cmd":"note","pitch":37,"volume":95,"start":1.25,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":47,"volume":85,"start":1.375,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":1.5,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":48,"volume":85,"start":1.625,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":56,"volume":95,"start":1.75,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":50,"volume":85,"start":1.875,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":51,"volume":95,"start":2,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":42,"volume":85,"start":2.125,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":46,"volume":95,"start":2.25,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":49,"volume":85,"start":2.375,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":81,"volume":95,"start":2.5,"duration":0.125,"instrument":128,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcPercMapHighC = 'X:1\n' + + "%%percmap ^c' high-tom x\n" + + "%%percmap c' high-tom x\n" + + "%%percmap b high-tom x\n" + + '%%percmap C high-tom x\n' + + 'Q:1/4=50\n' + + 'K:C perc\n' + + "b c' C ^c' C \n"; + + var expectedPercMapHighC = { + "tempo":50,"instrument":128,"totalDuration":0.625,"tracks":[ + [ + {"cmd":"program","channel":0,"instrument":128}, + {"cmd":"note","pitch":50,"volume":85,"start":0,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":50,"volume":85,"start":0.125,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":50,"volume":85,"start":0.25,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":50,"volume":85,"start":0.375,"duration":0.125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":50,"volume":85,"start":0.5,"duration":0.125,"instrument":128,"gap":0} + ] + ] + } + + ////////////////////////////////////////////////////////// + + var abcLongTie = 'X:1\n' + +'L:1/4\n' + +'Q:80\n' + +'K:A\n' + +'cd-d2-|d2-dz|\n'; + + var expectedLongTie = { + "tempo": 80, + "instrument": 0, + "totalDuration": 2, + "tracks": [ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":73,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":0.25,"duration":1.5,"instrument":0,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcRegularTie = 'X:1\n' + +'M:4/4\n' + +'L:1/8\n' + +'Q:1/4=150\n' + +'K:Bb\n' + +'GBcd-d4|zcdc dc3:|\n'; + + var expectedRegularTie = { + "tempo":150, + "instrument":0, + "totalDuration":4, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":67,"volume":105,"start":0,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":85,"start":0.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":0.375,"duration":0.625,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":1.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":1.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":1.625,"duration":0.375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":105,"start":2,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":70,"volume":85,"start":2.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":2.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":2.375,"duration":0.625,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":3.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":3.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.625,"duration":0.375,"instrument":0,"gap":0}, + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcTripletChords = 'X:1\n' + +'T:triplets-and-chord-rhythm\n' + +'M: 4/4\n' + +'L: 1/8\n' + +'Q: 80\n' + +'"C" (3 C2 D2 E2 "G/G" (3 F2 E2 D2 |\n' + +'"C" (3 CA,G, (3 CDE "C" (3 EEE (3 GGG |\n' + +'"C" (3 CDE (3 FED (3 CDE (3 FED |\n' + +'"C" (3 CDC (3 EFE "D" (3 GAG (3 cdc |\n'; + + var expectedTripletChords = { + "tempo":80, + "instrument":0, + "totalDuration":4, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":60,"volume":105,"start":0,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":0.166667,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":0.333334,"duration":0.166666,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":95,"start":0.5,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":0.666667,"duration":0.166667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":0.833334,"duration":0.166666,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":1,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":85,"start":1.083333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":1.166666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":95,"start":1.25,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":1.333333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":1.416666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":95,"start":1.5,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":1.583333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":1.666666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":1.75,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":1.833333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":1.916666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":2,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":2.083333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":2.166666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":95,"start":2.25,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":2.333333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":2.416666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":95,"start":2.5,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":2.583333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":2.666666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":95,"start":2.75,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":2.833333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":2.916666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":105,"start":3,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":62,"volume":85,"start":3.083333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":60,"volume":85,"start":3.166666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":95,"start":3.25,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":85,"start":3.333333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":85,"start":3.416666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":3.5,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":3.583333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":85,"start":3.666666,"duration":0.083334,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":95,"start":3.75,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":3.833333,"duration":0.083333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":72,"volume":85,"start":3.916666,"duration":0.083334,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":43,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":2,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":2.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":3,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":3.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":3.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":3.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":3.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":50,"volume":48,"start":3.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":54,"volume":48,"start":3.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":57,"volume":48,"start":3.75,"duration":0.125,"gap":0,"instrument":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcSnare = 'X:1\n' + +'V:SnareDrum stem=up stafflines=1\n' + +'K:C clef=perc\n' + +'%%MIDI channel 10\n' + +'%%MIDI drummap B 38\n' + +'!f!B2 z2 B/4B/4B/4B/4 B/4B/4B/4B/4 B/4B/4B/4B/4 B/4B/4B/4B/4 B/4B/4B/4B/4 | B/4B/4B/4B/4 !>!B2 {/B}B4|\n'; + + var expectedSnare = { + "tempo":180, + "instrument":128, + "totalDuration":2, + "tracks":[ + [ + {"cmd":"program","channel":10,"instrument":128}, + {"cmd":"note","pitch":38,"volume":80,"start":0,"duration":0.25,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":95,"start":0.5,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.53125,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.5625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.59375,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.65625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.6875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.71875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":95,"start":0.75,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.78125,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.8125,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.84375,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.90625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.9375,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":0.96875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":95,"start":1,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.03125,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.0625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.09375,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":105,"start":1.125,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.15625,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.1875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":80,"start":1.21875,"duration":0.03125,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":120,"start":1.25,"duration":0.25,"instrument":128,"gap":0}, + {"cmd":"note","pitch":38,"volume":53,"start":1.5,"duration":0.25,"gap":0,"instrument":128, "style": "grace"}, + {"cmd":"note","pitch":38,"volume":80,"start":1.75,"duration":0.25,"instrument":128,"gap":0} + ] + ] + }; + + ////////////////////////////////////////////////////////// + + var abcMetronome = 'X:1\n' + +'L:1/4\n' + +'Q:1/4=60\n' + +'%%MIDI drum dddd 76 77 77 77 50 50 50 50\n' + +'M:4/4\n' + +'K:A\n' + +'V:1\n' + +'%%MIDI drumon\n' + +'e|a/g/ f/e/ c3/2 B/|Azzz|\n'; + + var expectedMetronome = { + "tempo":60, + "instrument":0, + "totalDuration":2.25, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":80,"volume":85,"start":0.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":78,"volume":95,"start":0.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":73,"volume":95,"start":0.75,"duration":0.375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":1.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":1.25,"duration":0.25,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":2,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":0.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":0.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":0.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":76,"volume":50,"start":1.25,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1.5,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":1.75,"duration":0.25,"gap":0,"instrument":128}, + {"cmd":"note","pitch":77,"volume":50,"start":2,"duration":0.25,"gap":0,"instrument":128} + ] + ] + }; + ////////////////////////////////////////////////////////// + + var abcTwelveEight = 'X: 1\n' + + 'M: 12/8\n' + + 'K: Ador\n' + + '|:"Am" A2e e2d "G" BAB d2B | "Am" A2e e2d "G" B2A GAB |\n'; + + var expectedTwelveEight = { + "tempo":180, + "instrument":0, + "totalDuration":3, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":69,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":0.25,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":0.375,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":0.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":95,"start":0.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":0.875,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":1,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":95,"start":1.125,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":1.375,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":1.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":85,"start":1.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":76,"volume":95,"start":1.875,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":74,"volume":85,"start":2.125,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":95,"start":2.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":2.5,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":67,"volume":95,"start":2.625,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":85,"start":2.75,"duration":0.125,"instrument":0,"gap":0}, + {"cmd":"note","pitch":71,"volume":85,"start":2.875,"duration":0.125,"instrument":0,"gap":0} + ], + [ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":33,"volume":64,"start":0,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":45,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":28,"volume":64,"start":0.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":45,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":0.625,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":43,"volume":64,"start":0.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":1,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":1.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":1.375,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":33,"volume":64,"start":1.5,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":45,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.75,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":28,"volume":64,"start":1.875,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":45,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":2.125,"duration":0.0625,"gap":0,"instrument":0}, + + {"cmd":"note","pitch":43,"volume":64,"start":2.25,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":2.5,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.5,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":2.5,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":38,"volume":64,"start":2.625,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":2.875,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":59,"volume":48,"start":2.875,"duration":0.0625,"gap":0,"instrument":0}, + {"cmd":"note","pitch":62,"volume":48,"start":2.875,"duration":0.0625,"gap":0,"instrument":0} + ] + ] + +}; + + ////////////////////////////////////////////////////////// + + var abcTempoThreeVoices = 'X: 1\n' + + 'T: tempo-change-three-voices\n' + + '%%score { ( 1 3 ) | 2 }\n' + + 'Q:1/4=70\n' + + 'M: 4/4\n' + + 'L: 1/8\n' + + 'K: C\n' + + 'V:1 clef=treble\n' + + 'V:3 clef=treble\n' + + 'V:2 clef=bass\n' + + 'V:1\n' + + 'a2b2c\'2b2 | [Q:1/4=80] a2b2c\'2b2 | a2b2c\'2b2 |\n' + + 'a2b2c\'2b2 | a2b2c\'2b2 | a2b2c\'2b2 :|\n' + + 'V:3\n' + + 'E8 | F8 | A8 |\n' + + 'E8 | [Q:1/4=100] F8 | A8 :|\n' + + 'V:2\n' + + 'A,4 G,4 | A,4 G,4 |[Q:1/4=90] F,4 E,4 |\n' + + 'A,4 G,4 | A,4 G,4 | [Q:1/4=120] F,4 E,4 :|\n'; + + var expectedTempoThreeVoices = {"tempo":70,"instrument":0,"tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0}, + {"cmd":"note","pitch":81,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":1,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":1.21875,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":1.4375,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":1.65625,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":1.875,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":2.069444,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":2.263888,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":2.458332,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":2.652776,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":2.84722,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":3.041664,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":3.236108,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":3.430552,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":3.605552,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":3.780552,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":3.955552,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":4.130552,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":4.276385,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":4.422218,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":4.568051,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":4.713884,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":95,"start":4.963884,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":95,"start":5.213884,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":95,"start":5.463884,"duration":0.25,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":5.713884,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":5.932634,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":6.151384,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":6.370134,"duration":0.21875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":6.588884,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":6.783328,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":6.977772,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":7.172216,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":7.36666,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":7.561104,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":7.755548,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":7.949992,"duration":0.194444,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":8.144436,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":8.319436,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":8.494436,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":8.669436,"duration":0.175,"instrument":0,"gap":0}, + {"cmd":"note","pitch":81,"volume":105,"start":8.844436,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":8.990269,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":84,"volume":85,"start":9.136102,"duration":0.145833,"instrument":0,"gap":0}, + {"cmd":"note","pitch":83,"volume":85,"start":9.281935,"duration":0.145833,"instrument":0,"gap":0} + ],[ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":64,"volume":105,"start":0,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":105,"start":1,"duration":0.875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":1.875,"duration":0.777778,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":2.652778,"duration":0.777778,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":105,"start":3.430556,"duration":0.7,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":4.130556,"duration":0.583333,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":4.713889,"duration":1,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":105,"start":5.713889,"duration":0.875,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":6.588889,"duration":0.777778,"instrument":0,"gap":0}, + {"cmd":"note","pitch":64,"volume":105,"start":7.366667,"duration":0.777778,"instrument":0,"gap":0}, + {"cmd":"note","pitch":65,"volume":105,"start":8.144445,"duration":0.7,"instrument":0,"gap":0}, + {"cmd":"note","pitch":69,"volume":105,"start":8.844445,"duration":0.583333,"instrument":0,"gap":0} + ],[ + {"cmd":"program","channel":2,"instrument":0}, + {"cmd":"note","pitch":57,"volume":105,"start":0,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":95,"start":0.5,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":1,"duration":0.4375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":1.4375,"duration":0.4375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":53,"volume":105,"start":1.875,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":52,"volume":85,"start":2.263889,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":2.652778,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":3.041667,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":3.430556,"duration":0.35,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":3.780556,"duration":0.35,"instrument":0,"gap":0}, + {"cmd":"note","pitch":53,"volume":105,"start":4.130556,"duration":0.291667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":52,"volume":85,"start":4.422223,"duration":0.291667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":4.71389,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":95,"start":5.21389,"duration":0.5,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":5.71389,"duration":0.4375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":6.15139,"duration":0.4375,"instrument":0,"gap":0}, + {"cmd":"note","pitch":53,"volume":105,"start":6.58889,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":52,"volume":85,"start":6.977779,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":7.366668,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":7.755557,"duration":0.388889,"instrument":0,"gap":0}, + {"cmd":"note","pitch":57,"volume":105,"start":8.144446,"duration":0.35,"instrument":0,"gap":0}, + {"cmd":"note","pitch":55,"volume":85,"start":8.494446,"duration":0.35,"instrument":0,"gap":0}, + {"cmd":"note","pitch":53,"volume":105,"start":8.844446,"duration":0.291667,"instrument":0,"gap":0}, + {"cmd":"note","pitch":52,"volume":85,"start":9.136113,"duration":0.291667,"instrument":0,"gap":0} +]],"totalDuration":9.42778}; + + var expectedTempoThreeVoicesTiming = [ + {"ms":0,"ln":0,"x1":81.948,"ch":[136,224,271],"x2":136.119,"midiPitches":["81 0.25","64 1","57 0.5"]}, + {"ms":857,"ln":0,"x1":136.119,"ch":[138],"x2":190.29,"midiPitches":["83 0.25"]}, + {"ms":1714,"ln":0,"x1":190.29,"ch":[140,275],"x2":244.46099999999998,"midiPitches":["84 0.25","55 0.5"]}, + {"ms":2571,"ln":0,"x1":244.46099999999998,"ch":[143],"x2":309.632,"midiPitches":["83 0.25"]}, + {"ms":3429,"ln":0,"x1":309.632,"ch":[158,228,280],"x2":363.803,"midiPitches":["81 0.21875","65 0.875","57 0.4375"]}, + {"ms":4179,"ln":0,"x1":363.803,"ch":[161],"x2":417.974,"midiPitches":["83 0.21875"]}, + {"ms":4929,"ln":0,"x1":417.974,"ch":[163,285],"x2":472.145,"midiPitches":["84 0.21875","55 0.4375"]}, + {"ms":5679,"ln":0,"x1":472.145,"ch":[166],"x2":537.316,"midiPitches":["83 0.21875"]}, + {"ms":6429,"ln":0,"x1":537.316,"ch":[170,233,300],"x2":591.4870000000001,"midiPitches":["81 0.194444","69 0.777778","53 0.388889"]}, + {"ms":7095,"ln":0,"x1":591.4870000000001,"ch":[173],"x2":645.6580000000001,"midiPitches":["83 0.194444"]}, + {"ms":7762,"ln":0,"x1":645.6580000000001,"ch":[175,305],"x2":699.8290000000002,"midiPitches":["84 0.194444","52 0.388889"]}, + {"ms":8429,"ln":0,"x1":699.8290000000002,"ch":[178],"x2":755.0000000000002,"midiPitches":["83 0.194444"]}, + {"ms":9095,"ln":1,"x1":60.153,"ch":[183,239,311],"x2":115.05691666666667,"midiPitches":["81 0.194444","64 0.777778","57 0.388889"]}, + {"ms":9762,"ln":1,"x1":115.05691666666667,"ch":[185],"x2":169.96083333333334,"midiPitches":["83 0.194444"]}, + {"ms":10429,"ln":1,"x1":169.96083333333334,"ch":[187,315],"x2":224.86475000000002,"midiPitches":["84 0.194444","55 0.388889"]}, + {"ms":11095,"ln":1,"x1":224.86475000000002,"ch":[190],"x2":290.76866666666666,"midiPitches":["83 0.194444"]}, + {"ms":11762,"ln":1,"x1":290.76866666666666,"ch":[194,255,320],"x2":345.6725833333333,"midiPitches":["81 0.175","65 0.7","57 0.35"]}, + {"ms":12362,"ln":1,"x1":345.6725833333333,"ch":[197],"x2":400.57649999999995,"midiPitches":["83 0.175"]}, + {"ms":12962,"ln":1,"x1":400.57649999999995,"ch":[199,326],"x2":455.4804166666666,"midiPitches":["84 0.175","55 0.35"]}, + {"ms":13562,"ln":1,"x1":455.4804166666666,"ch":[202],"x2":521.3843333333332,"midiPitches":["83 0.175"]}, + {"ms":14162,"ln":1,"x1":521.3843333333332,"ch":[206,260,343],"x2":576.2882499999998,"midiPitches":["81 0.145833","69 0.583333","53 0.291667"]}, + {"ms":14662,"ln":1,"x1":576.2882499999998,"ch":[209],"x2":631.1921666666665,"midiPitches":["83 0.145833"]}, + {"ms":15162,"ln":1,"x1":631.1921666666665,"ch":[211,348],"x2":686.0960833333331,"midiPitches":["84 0.145833","52 0.291667"]}, + {"ms":15662,"ln":1,"x1":686.0960833333331,"ch":[214],"x2":740.9999999999998,"midiPitches":["83 0.145833"]}, + + + {"ms":16162,"ln":0,"x1":81.948,"ch":[136,224,271],"x2":136.119,"midiPitches":["81 0.25","64 1","57 0.5"]}, + {"ms":17019,"ln":0,"x1":136.119,"ch":[138],"x2":190.29,"midiPitches":["83 0.25"]}, + {"ms":17876,"ln":0,"x1":190.29,"ch":[140,275],"x2":244.46099999999998,"midiPitches":["84 0.25","55 0.5"]}, + {"ms":18733,"ln":0,"x1":244.46099999999998,"ch":[143],"x2":309.632,"midiPitches":["83 0.25"]}, + {"ms":19590,"ln":0,"x1":309.632,"ch":[158,228,280],"x2":363.803,"midiPitches":["81 0.21875","65 0.875","57 0.4375"]}, + {"ms":20340,"ln":0,"x1":363.803,"ch":[161],"x2":417.974,"midiPitches":["83 0.21875"]}, + {"ms":21090,"ln":0,"x1":417.974,"ch":[163,285],"x2":472.145,"midiPitches":["84 0.21875","55 0.4375"]}, + {"ms":21840,"ln":0,"x1":472.145,"ch":[166],"x2":537.316,"midiPitches":["83 0.21875"]}, + {"ms":22590,"ln":0,"x1":537.316,"ch":[170,233,300],"x2":591.4870000000001,"midiPitches":["81 0.194444","69 0.777778","53 0.388889"]}, + {"ms":23257,"ln":0,"x1":591.4870000000001,"ch":[173],"x2":645.6580000000001,"midiPitches":["83 0.194444"]}, + {"ms":23924,"ln":0,"x1":645.6580000000001,"ch":[175,305],"x2":699.8290000000002,"midiPitches":["84 0.194444","52 0.388889"]}, + {"ms":24590,"ln":0,"x1":699.8290000000002,"ch":[178],"x2":755.0000000000002,"midiPitches":["83 0.194444"]}, + {"ms":25257,"ln":1,"x1":60.153,"ch":[183,239,311],"x2":115.05691666666667,"midiPitches":["81 0.194444","64 0.777778","57 0.388889"]}, + {"ms":25924,"ln":1,"x1":115.05691666666667,"ch":[185],"x2":169.96083333333334,"midiPitches":["83 0.194444"]}, + {"ms":26590,"ln":1,"x1":169.96083333333334,"ch":[187,315],"x2":224.86475000000002,"midiPitches":["84 0.194444","55 0.388889"]}, + {"ms":27257,"ln":1,"x1":224.86475000000002,"ch":[190],"x2":290.76866666666666,"midiPitches":["83 0.194444"]}, + {"ms":27924,"ln":1,"x1":290.76866666666666,"ch":[194,255,320],"x2":345.6725833333333,"midiPitches":["81 0.175","65 0.7","57 0.35"]}, + {"ms":28524,"ln":1,"x1":345.6725833333333,"ch":[197],"x2":400.57649999999995,"midiPitches":["83 0.175"]}, + {"ms":29124,"ln":1,"x1":400.57649999999995,"ch":[199,326],"x2":455.4804166666666,"midiPitches":["84 0.175","55 0.35"]}, + {"ms":29724,"ln":1,"x1":455.4804166666666,"ch":[202],"x2":521.3843333333332,"midiPitches":["83 0.175"]}, + {"ms":30324,"ln":1,"x1":521.3843333333332,"ch":[206,260,343],"x2":576.2882499999998,"midiPitches":["81 0.145833","69 0.583333","53 0.291667"]}, + {"ms":30824,"ln":1,"x1":576.2882499999998,"ch":[209],"x2":631.1921666666665,"midiPitches":["83 0.145833"]}, + {"ms":31324,"ln":1,"x1":631.1921666666665,"ch":[211,348],"x2":686.0960833333331,"midiPitches":["84 0.145833","52 0.291667"]}, + {"ms":31824,"ln":1,"x1":686.0960833333331,"ch":[214],"x2":754.9999999999998,"midiPitches":["83 0.145833"]} + ]; + + ////////////////////////////////////////////////////////// + var abcQuarterTones = "X:1\n" + + "T:quarter-tone2\n" + + "M:12/8\n" + + "Q:1/8=120\n" + + "N: all combinations of accidentals in key sigs and in music\n" + + "K: C ^/f _/B _A ^D\n" + + "ABc def|_A_B_c _d_e_f ABc def|\n" + + "_/A_/B_/c _/d_/e_/f ABc def|=A=B=c =d=e=f ABc def|\n" + + "^/A^/B^/c ^/d^/e^/f ABc def|^A^B^c ^d^e^f ABc def|\n" + + "^^A^^B^^c ^^d^^e^^f ABc def|__A__B__c __d__e__f ABc def|\n"; + + var expectedQuarterTones = { + "tempo": 40, "instrument": 0, "tracks": [ + [ + {"cmd": "program", "channel": 0, "instrument": 0}, + {"cmd": "note", "pitch": 68, "volume": 85, "start": 0, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 0.125, "duration": 0.125, "instrument": 0, "gap": 0, cents: -50 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 0.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 85, "start": 0.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 0.5, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 0.625, "duration": 0.125, "instrument": 0, "gap": 0, cents: 50 }, + + // all flats + {"cmd": "note", "pitch": 68, "volume": 105, "start": 0.75, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 70, "volume": 85, "start": 0.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 1, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 73, "volume": 95, "start": 1.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 85, "start": 1.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 1.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + + {"cmd": "note", "pitch": 68, "volume": 95, "start": 1.5, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 70, "volume": 85, "start": 1.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 1.75, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 73, "volume": 95, "start": 1.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 85, "start": 2, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 2.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + + // all quarter flats + {"cmd": "note", "pitch": 69, "volume": 105, "start": 2.25, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 2.375, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 2.5, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 2.625, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 2.75, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 2.875, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + + {"cmd": "note", "pitch": 69, "volume": 95, "start": 3, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 3.125, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 3.25, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 3.375, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 3.5, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 3.625, "duration": 0.125, "instrument": 0, cents: -50, "gap": 0 }, + + // all natural + {"cmd": "note", "pitch": 69, "volume": 105, "start": 3.75, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 3.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 4, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 4.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 4.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 4.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + + {"cmd": "note", "pitch": 69, "volume": 95, "start": 4.5, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 4.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 4.75, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 4.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 5, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 5.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + + // all quarter sharp + {"cmd": "note", "pitch": 69, "volume": 105, "start": 5.25, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 5.375, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 5.5, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 5.625, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 5.75, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 5.875, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + + {"cmd": "note", "pitch": 69, "volume": 95, "start": 6, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0}, + {"cmd": "note", "pitch": 71, "volume": 85, "start": 6.125, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 6.25, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 95, "start": 6.375, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 85, "start": 6.5, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 6.625, "duration": 0.125, "instrument": 0, cents: 50, "gap": 0 }, + + // all sharp + {"cmd": "note", "pitch": 70, "volume": 105, "start": 6.75, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 6.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 73, "volume": 85, "start": 7, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 95, "start": 7.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 7.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 78, "volume": 85, "start": 7.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + + {"cmd": "note", "pitch": 70, "volume": 95, "start": 7.5, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 72, "volume": 85, "start": 7.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 73, "volume": 85, "start": 7.75, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 95, "start": 7.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 77, "volume": 85, "start": 8, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 78, "volume": 85, "start": 8.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + + // all double sharp + {"cmd": "note", "pitch": 71, "volume": 105, "start": 8.25, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 73, "volume": 85, "start": 8.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 85, "start": 8.5, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 95, "start": 8.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 78, "volume": 85, "start": 8.75, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 79, "volume": 85, "start": 8.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + + {"cmd": "note", "pitch": 71, "volume": 95, "start": 9, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 73, "volume": 85, "start": 9.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 85, "start": 9.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 76, "volume": 95, "start": 9.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 78, "volume": 85, "start": 9.5, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 79, "volume": 85, "start": 9.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + + // all double flat + {"cmd": "note", "pitch": 67, "volume": 105, "start": 9.75, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 69, "volume": 85, "start": 9.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 70, "volume": 85, "start": 10, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 95, "start": 10.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 85, "start": 10.25, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 85, "start": 10.375, "duration": 0.125, "instrument": 0, "gap": 0 }, + + {"cmd": "note", "pitch": 67, "volume": 95, "start": 10.5, "duration": 0.125, "instrument": 0, "gap": 0}, + {"cmd": "note", "pitch": 69, "volume": 85, "start": 10.625, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 70, "volume": 85, "start": 10.75, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 72, "volume": 95, "start": 10.875, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 74, "volume": 85, "start": 11, "duration": 0.125, "instrument": 0, "gap": 0 }, + {"cmd": "note", "pitch": 75, "volume": 85, "start": 11.125, "duration": 0.125, "instrument": 0, "gap": 0 }, + ] + ],"totalDuration":11.25 + }; + + ////////////////////////////////////////////////////////// + + var abcTempoOverride = 'X:1\n' + + 'T:tempo-override\n' + + 'L:1/4\n' + + 'Q:1/4=150\n' + + 'M:4/4\n' + + 'K:G\n' + + 'C D E F|\n'; + + var expectedTempoOverride = {"tempo":60,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":60,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":62,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":64,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":66,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0}]],"totalDuration":1}; + + var expectedTempoOverrideTiming = [ + {"ms":0,"ln":0,"x1":89.096,"ch":[47],"x2":131.52240687119286,"midiPitches":["60 0.25"]}, + {"ms":1000,"ln":0,"x1":131.52240687119286,"ch":[49],"x2":173.9488137423857,"midiPitches":["62 0.25"]}, + {"ms":2000,"ln":0,"x1":173.9488137423857,"ch":[51],"x2":216.37522061357856,"midiPitches":["64 0.25"]}, + {"ms":3000,"ln":0,"x1":216.37522061357856,"ch":[53],"x2":259.8016274847714,"midiPitches":["66 0.25"]} + ]; + + ////////////////////////////////////////////////////////// + + var abcNoChordVoice = 'X: 1\n' + + 'M: 4/4\n' + + '%%score (S A) (T B)\n' + + 'V:S clef=treble middle=B stem=up\n' + + 'V:A clef=treble middle=B stem=down\n' + + 'V:T clef=bass,, stem=up\n' + + 'V:B clef=bass,, stem=down\n' + + 'L: 1/4\n' + + 'K: C\n' + + 'V: S\n' + + '|"Am"e4 |\n' + + 'V: A\n' + + '|E4 |\n' + + 'V: T\n' + + '|B,4 |\n' + + 'V: B\n' + + '|E,4 |\n'; + + var expectedNoChordVoice = { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + {"cmd": "program", "channel": 0, "instrument": 0}, {"cmd": "note", "pitch": 76, "volume": 105, "start": 0, "duration": 1, "instrument": 0, "gap": 0}, + ], [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + {"cmd": "note", "pitch": 64, "volume": 105, "start": 0, "duration": 1, "instrument": 0, "gap": 0}, + ], [ + {"cmd": "program", "channel": 2, "instrument": 0}, + { + "cmd": "note", + "pitch": 59, + "volume": 105, + "start": 0, + "duration": 1, + "instrument": 0, + "gap": 0 + }, + ], [ + {"cmd": "program", "channel": 3, "instrument": 0}, + {"cmd": "note", "pitch": 52, "volume": 105, "start": 0, "duration": 1, "instrument": 0, "gap": 0}, + ] + ], + "totalDuration": 1 + } + + ////////////////////////////////////////////////////////// + + var abcGuitarChordParams = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + '%%MIDI bassprog 10\n' + + '%%MIDI bassvol 125\n' + + '%%MIDI chordprog 72\n' + + '%%MIDI chordvol 23\n' + + 'K:C\n' + + '"C"z4|"G7"z4|\n' + + var expectedGuitarChordParams = + { + "tempo":180,"instrument":0,"tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0} + ], + [ + {"cmd":"program","channel":1,"instrument":72}, + {"cmd":"note","pitch":36,"volume":125,"start":0,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":48,"volume":23,"start":0.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":52,"volume":23,"start":0.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":55,"volume":23,"start":0.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":31,"volume":125,"start":0.5,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":48,"volume":23,"start":0.75,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":52,"volume":23,"start":0.75,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":55,"volume":23,"start":0.75,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":43,"volume":125,"start":1,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":55,"volume":23,"start":1.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":59,"volume":23,"start":1.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":62,"volume":23,"start":1.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":65,"volume":23,"start":1.25,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":38,"volume":125,"start":1.5,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":55,"volume":23,"start":1.75,"duration":0.125,"gap":0,"instrument":72},{"cmd":"note","pitch":59,"volume":23,"start":1.75,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":62,"volume":23,"start":1.75,"duration":0.125,"gap":0,"instrument":72}, + {"cmd":"note","pitch":65,"volume":23,"start":1.75,"duration":0.125,"gap":0,"instrument":72} + ] + ], + "totalDuration":2 + } + + ////////////////////////////////////////////////////////// + + var abcChordArpeggio = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + '%%MIDI gchord fHIHfhih\n' + + '%%MIDI bassprog 10\n' + + '%%MIDI bassvol 125\n' + + '%%MIDI chordprog 72\n' + + '%%MIDI chordvol 23\n' + + 'K:C\n' + + '"C"z4|"G7"z2z"C"z|\n' + + '%%MIDI gchord GHIHghih\n' + + '"C"z4|"G7"z2z"C"z|\n' + + var expectedChordArpeggio = + { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 125, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0.125, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0.375, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 125, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 0.625, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 23, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 0.875, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 125, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1.125, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1.375, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 125, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 23, + "start": 1.625, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 125, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 1.875, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.125, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.375, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 2.625, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 2.875, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 3.125, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 3.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 3.375, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 23, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 71, + "volume": 23, + "start": 3.625, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 67, + "volume": 23, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 64, + "volume": 23, + "start": 3.875, + "duration": 0.125, + "gap": 0, + "instrument": 72 + } + ] + ], + "totalDuration": 4 + } + ////////////////////////////////////////////////////////// + + var abcChordSwing = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + '%%MIDI gchord bzczbzcz\n' + + '%%MIDI bassprog 10\n' + + '%%MIDI bassvol 125\n' + + '%%MIDI chordprog 72\n' + + '%%MIDI chordvol 23\n' + + 'K:C\n' + + '"C"z4|"G7"z4|"C"z"C#°7"z"Dm7"z"G7"z|"F"z3"F#°7"z|\n' + + var expectedChordSwing = + { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 125, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 125, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 125, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 23, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 125, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 23, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 125, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 37, + "volume": 125, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 58, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 125, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 53, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 125, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 65, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 41, + "volume": 125, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 53, + "volume": 23, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 53, + "volume": 23, + "start": 3.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 3.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 3.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 125, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 53, + "volume": 23, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 42, + "volume": 125, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 23, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 23, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 63, + "volume": 23, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + } + ] + ], + "totalDuration": 4 + } + + ////////////////////////////////////////////////////////// + + var abcAllTimeSigs = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + 'K:C\n' + + 'M:2/4\n' + + '"C"zz|"D"z"E"z|\n' + + 'M:3/4\n' + + '"C"zzz|"D"z"E"z"F"z|z"G"zz|\n' + + 'M:5/4\n' + + '"C"zzzzz|"D"zzz"E"zz|z"G"zzzz|\n' + + 'M:6/4\n' + + '"C"zzzzzz|"D"zzz"E"zzz|"G"zz"A"zz"B"zz|\n' + + 'M:2/2\n' + + '"C"zzzz|"D"zz"E"zz|"G"z"A"zz"B"z|\n' + + 'M:3/2\n' + + '"C"zzzzzz|"D"zzz"E"zzz|"G"zz"A"zz"B"zz|\n' + + 'M:4/2\n' + + '"C"zzzzzzzz|"D"zzzz"E"zzzz|"G"zz"A"zz"B"zz"C"zz|\n' + + 'M:3/8\n' + + '"C"z/z/z/|"D"z/"E"z/z/|"D"z/z/"E"z/|"G"z/"A"z/"B"z/|\n' + + 'M:6/8\n' + + '"C"z/z/z/z/z/z/|"D"z/"E"z/z/"D"z/z/"E"z/|"G"z"A"z"B"z|\n' + + 'M:9/8\n' + + '"C"z/z/z/z/z/z/z/z/z/|"D"z/z/"E"z/z/z/"D"z/z/z/"E"z/|"G"zz/"A"zz/"B"zz/|\n' + + 'M:12/8\n' + + '"C"z/z/z/z/z/z/z/z/z/z/z/z/|"D"z/z/z/"E"z/z/z/z/"D"z/z/z/z/"E"z/|"G"zz"A"zz"B"zz|\n' + + 'M:7/8\n' + + '"C"z/z/z/z/z/z/z/|"D"z/z/z/"E"z/z/z/z/|"G"z/z/z/z/"A"z/z/z/|\n' + + var expectedAllTimeSigs = + { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 41, + "volume": 64, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 53, + "volume": 48, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 60, + "volume": 48, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 41, + "volume": 64, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 3, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 3.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 3.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 3.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 4, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 4.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 4.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 4.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 4.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 4.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 4.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 4.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 5.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 5.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 5.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 5.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 5.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 6, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 6, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 6, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 6, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 6.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 6.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 6.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 6.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 6.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 6.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 6.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 7, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 7.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 7.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 7.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 7.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 7.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 7.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 7.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 8, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 8.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 8.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 8.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 8.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 8.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 8.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 8.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 9, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 9.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 9.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 9.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 9.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 9.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 9.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 9.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 9.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 10, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 10.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 10.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 10.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 10.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 10.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 10.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 10.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 11, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 11.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 11.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 11.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 11.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 12, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 12, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 12, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 12.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 13, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 13, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 13, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 13, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 13.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 13.75, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 14, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 14, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 14, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 14.25, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 14.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 15, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 15, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 15, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 15.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 15.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 15.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 16, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 16.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 16.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 16.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 16.75, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 17, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 17, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 17, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 17.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 18, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 18, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 18, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 18, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 18.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 18.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 18.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 18.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 19, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 19.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 19.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 19.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 20, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 20.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 20.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 20.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 21, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 21.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 21.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 21.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 22, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 22.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 22.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 22.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 23, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 23.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 23.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 23.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 23.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 24, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 24.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 24.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 24.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 24.5, + "duration": 0.25, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 25.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 25.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 25.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 25.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 25.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 25.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 25.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 25.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 25.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 26, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 26, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 26, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 26, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 26.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 26.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 26.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 26.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 26.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 26.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 26.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 26.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 26.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 26.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 26.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 27.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 27.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 27.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 27.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 27.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 27.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 27.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 27.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 27.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 27.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 27.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 27.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 27.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 28, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 28.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 28.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 28.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 28.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 28.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 28.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 28.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 28.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 28.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 28.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 29, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 29, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 29, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 29.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 29.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 29.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 29.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 29.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 29.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 29.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 29.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 29.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 30.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 30.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 30.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 30.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 30.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 30.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 30.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 30.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 30.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 30.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 30.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 30.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 30.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 30.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 31, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 31.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 31.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 31.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 31.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 31.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 31.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 31.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 31.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 32, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 32, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 32, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 32.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 32.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 32.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 32.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 32.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 32.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 32.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 32.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 32.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 33.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 33.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 33.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 33.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 33.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 33.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 33.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 33.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 33.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 33.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 33.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 34, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 34.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 34.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 34.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 34.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 34.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 34.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 34.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 34.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 34.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 35, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 35, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 35, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 35, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 35.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 35.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 35.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 35.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 35.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 35.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 35.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 35.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 35.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 35.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 36.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 36.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 36.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 36.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 36.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 47, + "volume": 48, + "start": 36.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 51, + "volume": 48, + "start": 36.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 36.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 36.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 36.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 36.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 36.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 36.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 36.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 36.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 37, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 37.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 37.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 37.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 37.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 37.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 37.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 37.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 37.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 37.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 37.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 37.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 37.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 48, + "start": 37.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 37.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 64, + "start": 37.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 38, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 38, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 38, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 64, + "start": 38.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 38.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 48, + "start": 38.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 38.25, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 43, + "volume": 64, + "start": 38.375, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 38.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 38.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 38.5, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 38.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 48, + "start": 38.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 38.625, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 64, + "start": 38.75, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 38.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 38.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 38.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 38.875, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 64, + "start": 39, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 45, + "volume": 48, + "start": 39.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 49, + "volume": 48, + "start": 39.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 39.125, + "duration": 0.0625, + "gap": 0, + "instrument": 0 + } + ] + ], + "totalDuration": 39.25 + } + + ////////////////////////////////////////////////////////// + + var abcChangeGChord = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + '%%MIDI gchord bzczbzcz\n' + + 'K:C\n' + + '"C"z4|\n' + + '%%MIDI gchord fHIHfhih\n' + + '%%MIDI bassprog 10\n' + + '%%MIDI bassvol 125\n' + + '%%MIDI chordprog 72\n' + + '%%MIDI chordvol 23\n' + + '"D"z4|\n' + + '%%MIDI gchord bzczbzcz\n' + + '"E"z4|\n' + + var expectedChangeGChord = + { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 38, + "volume": 125, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 23, + "start": 1.125, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 23, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 54, + "volume": 23, + "start": 1.375, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 33, + "volume": 125, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 66, + "volume": 23, + "start": 1.625, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 23, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 66, + "volume": 23, + "start": 1.875, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 40, + "volume": 125, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 2, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 2.25, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 35, + "volume": 125, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 10 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 2.5, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 52, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 56, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + }, + { + "cmd": "note", + "pitch": 59, + "volume": 23, + "start": 2.75, + "duration": 0.125, + "gap": 0, + "instrument": 72 + } + ] + ], + "totalDuration": 3 + } + + ////////////////////////////////////////////////////////// + + var abcPowerChord = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + 'K:C\n' + + '"C5"z4|\n' + + '%%MIDI gchord GHIJKghi\n' + + '"D5"z4|\n' + + var expectedPowerChord = { + "tempo": 180, + "instrument": 0, + "tracks": [ + [ + { + "cmd": "program", + "channel": 0, + "instrument": 0 + } + ], + [ + { + "cmd": "program", + "channel": 1, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 36, + "volume": 64, + "start": 0, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 31, + "volume": 64, + "start": 0.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 48, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 55, + "volume": 48, + "start": 0.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 50, + "volume": 48, + "start": 1, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 57, + "volume": 48, + "start": 1.125, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 1.25, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 48, + "start": 1.375, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 74, + "volume": 48, + "start": 1.5, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 62, + "volume": 48, + "start": 1.625, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 69, + "volume": 48, + "start": 1.75, + "duration": 0.125, + "gap": 0, + "instrument": 0 + }, + { + "cmd": "note", + "pitch": 74, + "volume": 48, + "start": 1.875, + "duration": 0.125, + "gap": 0, + "instrument": 0 + } + ] + ], + "totalDuration": 2 + } + + ////////////////////////////////////////////////////////// + + var abcChordOctave = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + 'K:C\n' + + '%%MIDI bassprog 10 octave=-1\n' + + '%%MIDI chordprog 4 octave=1\n' + + '"C"z4|\n' + + var expectedChordOctave = { + "tempo":180, + "instrument":0, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0} + ], + [ + {"cmd":"program","channel":1,"instrument":4}, + {"cmd":"note","pitch":24,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":60,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":4}, + {"cmd":"note","pitch":64,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":4}, + {"cmd":"note","pitch":67,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":4}, + {"cmd":"note","pitch":19,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":10}, + {"cmd":"note","pitch":60,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":4}, + {"cmd":"note","pitch":64,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":4}, + {"cmd":"note","pitch":67,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":4} + ] + ], + "totalDuration":1 + } + + ////////////////////////////////////////////////////////// + + var abcCancelGChord = 'X: 1\n' + + 'L: 1/4\n' + + 'M: 4/4\n' + + 'K:C\n' + + '%%MIDI gchord ffffffff\n' + + '"C"z4|\n' + + '%%MIDI gchord\n' + + '"C"z4|\n' + + var expectedCancelGChord = { + "tempo":180, + "instrument":0, + "tracks":[ + [ + {"cmd":"program","channel":0,"instrument":0} + ],[ + {"cmd":"program","channel":1,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.125,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.375,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.625,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":0.875,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":36,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":31,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":48,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":52,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}, + {"cmd":"note","pitch":55,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0} + ] + ], + "totalDuration":2 + } + + ////////////////////////////////////////////////////////// + + it("flatten-pickup-triplet-chords-rhythmhead", function() { + doFlattenTest(abcMultiple, expectedMultiple); + }) + + it("flatten-dynamics", function() { + doFlattenTest(abcDynamics, expectedDynamics); + }) + + it("flatten-dynamics2", function() { + doFlattenTest(abcDynamics2, expectedDynamics2); + }) + + it("flatten-dynamics3", function() { + doFlattenTest(abcDynamics3, expectedDynamics3); + }) + + it("flatten-six-huit", function() { + doFlattenTest(abcSixHuit, expectedSixHuit); + }) + + it("flatten-jig-chords", function() { + doFlattenTest(abcJigChords, expectedJigChords); + }) + + it("flatten-repeat", function() { + doFlattenTest(abcRepeat, expectedRepeat); + }) + + it("flatten-drum", function() { + doFlattenTest(abcDrum, expectedDrum); + }) + + it("flatten-transpose", function() { + doFlattenTest(abcTranspose, expectedTranspose); + }) + + it("flatten-tempo-change", function() { + //console.log("flatten-tempo-change") + doFlattenTest(abcTempoChange, expectedTempoChange); + }) + + it("flatten-tempo-change2", function() { + //console.log("flatten-tempo-change2") + doFlattenTest(abcTempoChange2, expectedTempoChange2); + }) + + it("flatten-decorations", function() { + doFlattenTest(abcDecoration, expectedDecoration); + }) + + it("flatten-meter-change", function() { + doFlattenTest(abcMeterChange, expectedMeterChange); + }) + + it("flatten-break", function() { + doFlattenTest(abcBreak, expectedBreak); + }) + + it("flatten-break2", function() { + doFlattenTest(abcBreak2, expectedBreak2); + }) + + it("flatten-end-chord", function() { + doFlattenTest(abcEndChord, expectedEndChord); + }) + + it("flatten-mid-measure", function() { + doFlattenTest(abcMidMeasureChordChange, expectedMidMeasureChordChange); + }) + + it("flatten-grace", function() { + doFlattenTest(abcGrace, expectedGrace); + }) + + it("flatten-midi-options", function() { + doFlattenTest(abcMidiOptions, expectedMidiOptions); + }) + + it("flatten-multi-measure-rest", function() { + doFlattenTest(abcMultiMeasureRest, expectedMultiMeasureRest); + }) + + it("flatten-octave-clefs", function() { + doFlattenTest(abcOctaveClefs, expectedOctaveClefs); + }) + + it("flatten-overlay", function() { + doFlattenTest(abcOverlay, expectedOverlay); + }) + + it("flatten-perc-map", function() { + doFlattenTest(abcPercMap, expectedPercMap); + }) + + it("flatten-perc-map-high-c", function() { + doFlattenTest(abcPercMapHighC, expectedPercMapHighC); + }) + + it("flatten-long-tie", function() { + doFlattenTest(abcLongTie, expectedLongTie); + }) + + it("flatten-triplet-chords", function() { + doFlattenTest(abcTripletChords, expectedTripletChords); + }) + + it("flatten-regular-tie", function() { + doFlattenTest(abcRegularTie, expectedRegularTie); + }) + + it("flatten-snare", function() { + doFlattenTest(abcSnare, expectedSnare); + }) + + it("flatten-metronome", function() { + doFlattenTest(abcMetronome, expectedMetronome); + }) + + it("flatten-twelve-eight", function() { + doFlattenTest(abcTwelveEight, expectedTwelveEight); + }) + + it("flatten-tempo-3-voices", function() { + doFlattenTest(abcTempoThreeVoices, expectedTempoThreeVoices); + doTimingObjTest(abcTempoThreeVoices, expectedTempoThreeVoicesTiming); + }) + + it("flatten-quarter-tone", function() { + doFlattenTest(abcQuarterTones, expectedQuarterTones); + }) + + it("flatten-tempo-override", function() { + doFlattenTest(abcTempoOverride, expectedTempoOverride, { qpm: 60 }); + doTimingObjTest(abcTempoOverride, expectedTempoOverrideTiming, { qpm: 60 }); + }) + + it("flatten-no-chord-voice", function() { + doFlattenTest(abcNoChordVoice, expectedNoChordVoice, {chordsOff: true}); + }) + + it("flatten-chord-params", function() { + doFlattenTest(abcGuitarChordParams, expectedGuitarChordParams); + }) + + it("flatten-chord-arpeggio", function() { + doFlattenTest(abcChordArpeggio, expectedChordArpeggio); + }) + + it("flatten-chord-swing", function() { + doFlattenTest(abcChordSwing, expectedChordSwing); + }) + + it("flatten-all-time-sigs", function() { + doFlattenTest(abcAllTimeSigs, expectedAllTimeSigs); + }) + + it("flatten-change-gchord", function() { + doFlattenTest(abcChangeGChord, expectedChangeGChord); + }) + + it("flatten-power-chord", function() { + doFlattenTest(abcPowerChord, expectedPowerChord); + }) + + it("bass-and-chord-octave", function() { + doFlattenTest(abcChordOctave, expectedChordOctave); + }) + + it("cancel-gchord", function() { + doFlattenTest(abcCancelGChord, expectedCancelGChord); + }) + +}) + +////////////////////////////////////////////////////////// + +function doFlattenTest(abc, expected, options) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var flatten = visualObj[0].setUpAudio(options); + //play(visualObj[0]) + console.log(JSON.stringify(flatten)) + chai.assert.equal(flatten.tempo, expected.tempo, "Tempo") + chai.assert.equal(flatten.tracks.length, expected.tracks.length, "Number of Tracks") + chai.assert.equal(flatten.totalDuration, expected.totalDuration, "Total Duration") + for (var i = 0; i < expected.tracks.length; i++) { + chai.assert.equal(flatten.tracks[i].length, expected.tracks[i].length, "length of track "+i) + for (var j = 0; j < expected.tracks[i].length; j++) { + var msg = "trk: " + i + " ev: " + j + "\nrcv: " + JSON.stringify(flatten.tracks[i][j]) + "\n" + + "exp: " + JSON.stringify(expected.tracks[i][j]) + "\n"; + // TODO-PER: There are too many changes from adding start and end char, so just ignore them at least for now - they aren't what needs to be tested here anyway. + var t = flatten.tracks[i][j] + if (t.startChar) + delete t.startChar; + if (t.endChar) + delete t.endChar; + chai.assert.deepStrictEqual(t,expected.tracks[i][j], msg) + } + } +} + +function play(visualObj) { + var midiBuffer = new abcjs.synth.CreateSynth(); + midiBuffer.init({ + visualObj: visualObj, + options: { + } + }).then(function (response) { + console.log(response); + midiBuffer.prime().then(function (response) { + midiBuffer.start(); + }); + }).catch(function (error) { + console.warn("Audio problem:", error); + }); +} + +function doTimingObjTest(abc, expected, options) { + if (!options) options = {}; + var visualObj = abcjs.renderAbc("paper", abc, {}); + visualObj[0].setUpAudio(options); + var timing = visualObj[0].setTiming(options.qpm); + var output = []; + for (var i = 0; i < timing.length; i++) { + var t = timing[i]; + if (t.type === "event") { + var midiPitches = []; + if (!t.midiPitches) + debugger; + for (var j = 0; j < t.midiPitches.length; j++) + midiPitches.push(t.midiPitches[j].pitch + ' ' + t.midiPitches[j].duration); + output.push({ + ms: t.milliseconds, + ln: t.line, + x1: t.left, + ch: t.startCharArray, + x2: t.endX, + midiPitches: midiPitches + }); + } + } + console.log(JSON.stringify(output)); + chai.assert.deepStrictEqual(output,expected, "timing error") +} diff --git a/tests/synth/midi.test.js b/tests/synth/midi.test.js new file mode 100644 index 0000000000000000000000000000000000000000..fa39b259aac313e013256cc78cb16230c6886b44 --- /dev/null +++ b/tests/synth/midi.test.js @@ -0,0 +1,80 @@ +describe("MIDI file creation", function() { + var abcMidi = "X:1\n" + +'%%MIDI program 4\n' + +'%%MIDI channel 4\n' + +'%%MIDI transpose -2\n' + +'T: midi options\n' + +'%score {RH LH}\n' + +'L:1/4\n' + +'Q:1/4=89\n' + +'K:A\n' + +'V:RH name="Right Hand"\n' + +'A[Bd]c2|\n' + +'V:LH clef=bass name="Left Hand"\n' + +'A,B,A,2|\n'; + + var expectedMidi = "data:audio/midi,MThd%00%00%00%06%00%01%00%03%01%e0" + + "MTrk%00%00%00%29%00%FF%51%03%0a%49%6d%00%FF%59%02%03%00%00%FF%58%04%04%02%18%08%00%FF%01%0c%6d%69%64%69%20%6f%70%74%69%6f%6e%73%00%FF%2F%00" + + "MTrk%00%00%00%4c%00%FF%03%0a%52%69%67%68%74%20%48%61%6e%64%00%C0%04%00%B4%79%00%00%B4%40%00%00%B4%5B%30%00%B4%0A%20%00%B4%07%64" + + "%00%94%43%69%83%60%84%43%00%00%94%45%5f%00%94%48%5f%83%60%84%45%00%00%84%48%00%00%94%47%5f%87%40%84%47%00%00%FF%2F%00" + + "MTrk%00%00%00%43%00%FF%03%09%4c%65%66%74%20%48%61%6e%64%00%C0%04%00%B4%79%00%00%B4%40%00%00%B4%5B%30%00%B4%0A%60%00%B4%07%64" + + "%00%94%37%69%83%60%84%37%00%00%94%39%5f%83%60%84%39%00%00%94%37%5f%87%40%84%37%00%00%FF%2F%00"; + + var abcStaccato = "X:1\n" + + 'T: staccato\n' + + 'L:1/4\n' + + 'Q:1/4=59\n' + + 'K:cm\n' + + '.A.Bcd(ef)|\n'; + + var expectedStaccato = "data:audio/midi,MThd%00%00%00%06%00%01%00%02%01%e0" + + "MTrk%00%00%00%25%00%FF%51%03%0f%84%75%00%FF%59%02%00%00%00%FF%58%04%04%02%18%08%00%FF%01%08%73%74%61%63%63%61%74%6f%00%FF%2F%00" + + "MTrk%00%00%00%53%00%C0%00%00%B0%79%00%00%B0%40%00%00%B0%5B%30%00%B0%0A%40%00%B0%07%64%00%90%45%55%82%26%80%45%00%81%3a%90%47%55%82%26%80%47%00%81%3a%90%48%5f%83%60%80%48%00%00%90%4a%5f%83%60%80%4a%00%00%90%4c%5f%83%60%90%4d%5f%02%80%4c%00%83%5e%80%4d%00%00%FF%2F%00" + + var abcDrums = "X:1\n" + + "T:percmap\n" + + "%%MIDI drummap F 36 %bass drum 1\n" + + "%%MIDI drummap c 38 %acoustic snare\n" + + "%%MIDI drummap g 42 %closed hi hat\n" + + "Q:1/4=50\n" + + "K:C perc\n" + + "[gF] g [gc] g [gF] g [gc] g | c c c c\n" + + var expectedDrums = "data:audio/midi,MThd%00%00%00%06%00%01%00%02%01%e0MTrk%00%00%00%24%00%FF%51%03%12%4f%80%00%FF%59%02%00%00%00%FF%58%04%04%02%18%08%00%FF%01%07%70%65%72%63%6d%61%70%00%FF%2F%00MTrk%00%00%00%a7%00%C0%00%00%B9%79%00%00%B9%40%00%00%B9%5B%30%00%B9%0A%40%00%B9%07%64%00%99%2a%69%00%99%24%69%81%70%89%2a%00%00%89%24%00%00%99%2a%55%81%70%89%2a%00%00%99%2a%5f%00%99%26%5f%81%70%89%2a%00%00%89%26%00%00%99%2a%55%81%70%89%2a%00%00%99%2a%5f%00%99%24%5f%81%70%89%2a%00%00%89%24%00%00%99%2a%55%81%70%89%2a%00%00%99%2a%5f%00%99%26%5f%81%70%89%2a%00%00%89%26%00%00%99%2a%55%81%70%89%2a%00%00%99%26%69%81%70%89%26%00%00%99%26%55%81%70%89%26%00%00%99%26%5f%81%70%89%26%00%00%99%26%55%81%70%89%26%00%00%FF%2F%00" + + + it("midi-piano", function() { + var midi = abcjs.synth.getMidiFile(abcMidi, { midiOutputType: "link", pan: [ -0.5, 0.5 ]}); + var contents = setMidiLink(midi); + + var msg = "\nrcv: " + JSON.stringify(contents) + "\n" + + "exp: " + JSON.stringify(expectedMidi) + "\n"; + chai.assert.strictEqual(contents, expectedMidi, msg); + }) + + it("midi-staccato", function() { + var midi = abcjs.synth.getMidiFile(abcStaccato, { midiOutputType: "link"}); + var contents = setMidiLink(midi); +// console.log(midi) + var msg = "\nrcv: " + JSON.stringify(contents) + "\n" + + "exp: " + JSON.stringify(expectedStaccato) + "\n"; + chai.assert.strictEqual(contents, expectedStaccato, msg); + }) + + it("midi-drums", function() { + var midi = abcjs.synth.getMidiFile(abcDrums, { midiOutputType: "link"}); + var contents = setMidiLink(midi); +// console.log(midi) + var msg = "\nrcv: " + JSON.stringify(contents) + "\n" + + "exp: " + JSON.stringify(expectedDrums) + "\n"; + chai.assert.strictEqual(contents, expectedDrums, msg); + }) +}) + +function setMidiLink(midi) { + var el = document.getElementById("midi"); + el.innerHTML = midi; + el = el.querySelector("a"); + var contents = el.href; + return contents; +} diff --git a/tests/synth/options.test.js b/tests/synth/options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..545a06db1f64398b76386f80d1b247db39d22121 --- /dev/null +++ b/tests/synth/options.test.js @@ -0,0 +1,100 @@ +describe("Synth Options", function() { + var abcBasic = + 'M:4/4\n' + + 'L:1/4\n' + + 'K:Dm\n' + + '"Dm"DFAd|"A7"Aceg||\n'; + + var optionsDrum = { + drum: "dddd 76 77 77 77 60 30 30 30", + drumBars: 2, + drumIntro: 1, + } + var optionsCountin = { + drum: "dddd 76 77 77 77 60 30 30 30", + drumIntro: 1, + drumOff: true, + } + var optionsNoChord = { + chordsOff: true, + } + var optionsNoVoice = { + voicesOff: true, + } + var optionsTranspose = { + midiTranspose: 2, + visualTranspose: -4, + } + var optionsTempo = { + qpm: 50, + defaultQpm: 60, + } + var optionsProgram = { + program: 11, + channel: 2, + + bassprog: 20, + bassvol: 21, + chordprog: 30, + chordvol: 31, + gchord: "fHIHfhih", + + } + + var expectedDrum = {"tempo":180,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":62,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":65,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":105,"start":2,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":72,"volume":95,"start":2.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":2.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":79,"volume":95,"start":2.75,"duration":0.25,"instrument":0,"gap":0}],[{"cmd":"program","channel":1,"instrument":0},{"cmd":"note","pitch":38,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":2,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":28,"volume":64,"start":2.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}],[{"cmd":"program","channel":2,"instrument":128},{"cmd":"note","pitch":"76","volume":"60","start":0,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":0.5,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":1,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":1.5,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"76","volume":"60","start":1,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":1.5,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":2,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":2.5,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"76","volume":"60","start":2,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":2.5,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":3,"duration":0.5,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":3.5,"duration":0.5,"gap":0,"instrument":128}]],"totalDuration":3} + + var expectedCountin = {"tempo":180,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":62,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":65,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":105,"start":2,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":72,"volume":95,"start":2.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":2.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":79,"volume":95,"start":2.75,"duration":0.25,"instrument":0,"gap":0}],[{"cmd":"program","channel":1,"instrument":0},{"cmd":"note","pitch":38,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":2,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":2.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":28,"volume":64,"start":2.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":2.75,"duration":0.125,"gap":0,"instrument":0}],[{"cmd":"program","channel":2,"instrument":128},{"cmd":"note","pitch":"76","volume":"60","start":0,"duration":0.25,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":0.25,"duration":0.25,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":0.5,"duration":0.25,"gap":0,"instrument":128},{"cmd":"note","pitch":"77","volume":"30","start":0.75,"duration":0.25,"gap":0,"instrument":128}]],"totalDuration":3} + + var expectedNoChord = {"tempo":180,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":62,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":65,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":72,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":79,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0}]],"totalDuration":2} + + var expectedNoVoice = {"tempo":180,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":62,"volume":0,"start":0,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":65,"volume":0,"start":0.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":0,"start":0.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":0,"start":0.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":0,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":72,"volume":0,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":0,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":79,"volume":0,"start":1.75,"duration":0.25,"instrument":0,"gap":0}],[{"cmd":"program","channel":1,"instrument":0},{"cmd":"note","pitch":38,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":28,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}]],"totalDuration":2} + + var expectedProgram = {"tempo":180,"instrument":11,"tracks":[[{"cmd":"program","channel":2,"instrument":11},{"cmd":"note","pitch":62,"volume":105,"start":0,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":65,"volume":95,"start":0.25,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":69,"volume":95,"start":0.5,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":0.75,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":69,"volume":105,"start":1,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":72,"volume":95,"start":1.25,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":1.5,"duration":0.25,"instrument":11,"gap":0},{"cmd":"note","pitch":79,"volume":95,"start":1.75,"duration":0.25,"instrument":11,"gap":0}],[{"cmd":"program","channel":1,"instrument":30},{"cmd":"note","pitch":38,"volume":21,"start":0,"duration":0.125,"gap":0,"instrument":20},{"cmd":"note","pitch":53,"volume":31,"start":0.125,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":57,"volume":31,"start":0.25,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":53,"volume":31,"start":0.375,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":33,"volume":21,"start":0.5,"duration":0.125,"gap":0,"instrument":20},{"cmd":"note","pitch":65,"volume":31,"start":0.625,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":69,"volume":31,"start":0.75,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":65,"volume":31,"start":0.875,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":33,"volume":21,"start":1,"duration":0.125,"gap":0,"instrument":20},{"cmd":"note","pitch":49,"volume":31,"start":1.125,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":52,"volume":31,"start":1.25,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":49,"volume":31,"start":1.375,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":28,"volume":21,"start":1.5,"duration":0.125,"gap":0,"instrument":20},{"cmd":"note","pitch":61,"volume":31,"start":1.625,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":64,"volume":31,"start":1.75,"duration":0.125,"gap":0,"instrument":30},{"cmd":"note","pitch":61,"volume":31,"start":1.875,"duration":0.125,"gap":0,"instrument":30}]],"totalDuration":2} + + var expectedTempo = {"tempo":50,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":62,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":65,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":69,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":72,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":79,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0}],[{"cmd":"program","channel":1,"instrument":0},{"cmd":"note","pitch":38,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":50,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":53,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":33,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":28,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":45,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":49,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}]],"totalDuration":2} + + var expectedTranspose = {"tempo":180,"instrument":0,"tracks":[[{"cmd":"program","channel":0,"instrument":0},{"cmd":"note","pitch":64,"volume":105,"start":0,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":67,"volume":95,"start":0.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":71,"volume":95,"start":0.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":76,"volume":95,"start":0.75,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":71,"volume":105,"start":1,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":74,"volume":95,"start":1.25,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":78,"volume":95,"start":1.5,"duration":0.25,"instrument":0,"gap":0},{"cmd":"note","pitch":81,"volume":95,"start":1.75,"duration":0.25,"instrument":0,"gap":0}],[{"cmd":"program","channel":1,"instrument":0},{"cmd":"note","pitch":40,"volume":64,"start":0,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":59,"volume":48,"start":0.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":35,"volume":64,"start":0.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":52,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":55,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":59,"volume":48,"start":0.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":35,"volume":64,"start":1,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":47,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":51,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":54,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.25,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":30,"volume":64,"start":1.5,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":47,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":51,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":54,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0},{"cmd":"note","pitch":57,"volume":48,"start":1.75,"duration":0.125,"gap":0,"instrument":0}]],"totalDuration":2} + + ////////////////////////////////////////////////////////// + + + it("all-MIDI-options", function() { + doFlattenTest(abcBasic, expectedDrum, optionsDrum) + doFlattenTest(abcBasic, expectedCountin, optionsCountin) + doFlattenTest(abcBasic, expectedNoChord, optionsNoChord) + doFlattenTest(abcBasic, expectedNoVoice, optionsNoVoice) + doFlattenTest(abcBasic, expectedProgram, optionsProgram) + doFlattenTest(abcBasic, expectedTempo, optionsTempo) + doFlattenTest(abcBasic, expectedTranspose, optionsTranspose) + }) + +}) + +function doFlattenTest(abc, expected, options) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var flatten = visualObj[0].setUpAudio(options); + for (var ii = 0; ii < flatten.tracks.length; ii++) { + for (var jj = 0; jj < flatten.tracks[ii].length; jj++) { + delete flatten.tracks[ii][jj].startChar + delete flatten.tracks[ii][jj].endChar + } + } + console.log(JSON.stringify(flatten)) + chai.assert.equal(flatten.tempo, expected.tempo, "Tempo") + chai.assert.equal(flatten.tracks.length, expected.tracks.length, "Number of Tracks") + chai.assert.equal(flatten.totalDuration, expected.totalDuration, "Total Duration") + for (var i = 0; i < expected.tracks.length; i++) { + chai.assert.equal(flatten.tracks[i].length, expected.tracks[i].length, "length of track "+i) + for (var j = 0; j < expected.tracks[i].length; j++) { + var msg = "trk: " + i + " ev: " + j + "\nrcv: " + JSON.stringify(flatten.tracks[i][j]) + "\n" + + "exp: " + JSON.stringify(expected.tracks[i][j]) + "\n"; + // TODO-PER: There are too many changes from adding start and end char, so just ignore them at least for now - they aren't what needs to be tested here anyway. + var t = flatten.tracks[i][j] + if (t.startChar) + delete t.startChar; + if (t.endChar) + delete t.endChar; + chai.assert.deepStrictEqual(t,expected.tracks[i][j], msg) + } + } +} diff --git a/tests/synth/synth.test.js b/tests/synth/synth.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6816a310c3a8afc068a0d024ae60eff220540e76 --- /dev/null +++ b/tests/synth/synth.test.js @@ -0,0 +1,178 @@ +describe("Synth", function() { + var abcThatsAPlenty = + 'T:That\'s A Plenty\n' + + 'M:4/4\n' + + 'L:1/8\n' + + 'Q:1/4=220\n' + + 'K:Dm\n' + + 'P:A\n' + + '"Dm"d2^c2d2c2|d2^cd- dc =c=B|"Gm"_B2A2B2A2|B2AB- BA GF|\n' + + '"A7"E2BA-A4|E2BA-A4|1"Dm"D^C DE FE D^D|"A7"E^D EA-A4:|2A^G AB A=G FE|"Dm"D8y||\n'; + + ////////////////////////////////////////////////////////// + + + it("drum-intro", function() { + this.timeout(22000); + const tune = abcjs.renderAbc("paper", abcThatsAPlenty, { + add_classes: true, +// visualTranspose: currentTransposition, +// responsive: "resize", + paddingtop: 0, + paddingbottom: 0, + paddingright: 0, + paddingleft: 0, + format: { + vocalfont: "Helvetica 14", + } + }); + const meter = tune[0].getMeter(); + const synthController = new abcjs.synth.SynthController(); + synthController.load("#midi", self.cursorControl, { + displayLoop: true, + displayRestart: true, + displayPlay: true, + displayProgress: true, + displayWarp: true + }); + synthController.setTune(tune[0], false, { + // qpm: tempo, + // midiTranspose: currentTransposition, + drum: getDrumString(meter), + drumIntro: 2, + voicesOff: false, + chordsOff: false, + }); + return synthController.play().then(function (response){ + return sleep(20000).then(function () { + synthController.pause(); + }); + }); + }) + + it("two-at-once", function() { + this.timeout(5000); + const startTime = Date.now(); + const tune = abcjs.renderAbc("paper", "CDEFGA|", {}) + const tune2 = abcjs.renderAbc("paper2", "EFGABc|", {}) + const midiBuffer = new abcjs.synth.CreateSynth(); + const midiBuffer2 = new abcjs.synth.CreateSynth(); + + const promise1 = midiBuffer.init({ + visualObj: tune[0], + millisecondsPerMeasure: tune[0].millisecondsPerMeasure() + }) + + const promise2 = midiBuffer2.init({ + visualObj: tune2[0], + millisecondsPerMeasure: tune2[0].millisecondsPerMeasure() + }) + console.log("after init started", Date.now()-startTime) + Promise.allSettled([promise1, promise2]).then(function () { + console.log("init settled", Date.now()-startTime) + const promise3 = midiBuffer.prime(); + const promise4 = midiBuffer2.prime(); + console.log("after prime started", Date.now()-startTime) + Promise.allSettled([promise3, promise4]).then(function () { + console.log("prime settled", Date.now()-startTime) + midiBuffer.start(); + midiBuffer2.start(); + console.log("after start", Date.now()-startTime) + }) + }) + + }) + + // it("swing", function() { + // this.timeout(22000); + // const tune = abcjs.renderAbc("paper", abcThatsAPlenty, { + // add_classes: true, + // }); + // const midiBuffer = new abcjs.synth.CreateSynth(); + // return midiBuffer.init({ + // visualObj: tune[0], + // millisecondsPerMeasure: tune[0].millisecondsPerMeasure(), + // swing: 0.5 + // }).then(function (response) { + // return midiBuffer.prime().then(function () { + // midiBuffer.start(); + // return sleep(20000).then(function () { + // console.log("finished") + // }); + // }) + // }) + + // }) +}) + +function setUpSynth() { + midiBuffer = new ABCJS.synth.CreateSynth(); + + // midiBuffer.init preloads and caches all the notes needed. There may be significant network traffic here. + return midiBuffer.init({ + visualObj: visualObj, + audioContext: audioContext, + millisecondsPerMeasure: visualObj.millisecondsPerMeasure() + }).then(function (response) { + statusDiv.innerHTML += "
Audio object has been initialized
"; + // console.log(response); // this contains the list of notes that were loaded. + // midiBuffer.prime actually builds the output buffer. + return midiBuffer.prime(); + }).then(function () { + statusDiv.innerHTML += "
Audio object has been primed
"; + // At this point, everything slow has happened. midiBuffer.start will return very quickly and will start playing very quickly without lag. + midiBuffer.start(); + statusDiv.innerHTML += "
Audio started
"; + return Promise.resolve(); + }).catch(function (error) { + if (error.status === "NotSupported") { + stopAudioButton.setAttribute("style", "display:none;"); + var audioError = document.querySelector(".audio-error"); + audioError.setAttribute("style", ""); + } else + console.warn("synth error", error); + }); +} + +function getDrumString(meter) { + let num; + let den; + switch (meter.type) { + case "common_time": num = 4; den = 4; break; + case "cut_time": num = 2; den = 2; break; + default: + num = meter.value[0].num; + den = meter.value[0].den; + } + let drum = ""; + switch (num+"/"+den) { + case "2/2": + drum = "dd 76 77 60 30"; + break; + case "2/4": + drum = "dd 76 77 60 30"; + break; + case "3/4": + drum = "ddd 76 77 77 60 30 30"; + break; + case "4/4": + drum = "dddd 76 77 77 77 60 30 30 30"; + break; + case "5/4": + drum = "ddddd 76 77 77 76 77 60 30 30 60 30"; + break; + case "6/8": + drum = "dd 76 77 60 30"; + break; + case "9/8": + drum = "ddd 76 77 77 60 30 30"; + break; + } + return drum; +} + +function sleep(ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms); + }); +} diff --git a/tests/synth/timing.test.js b/tests/synth/timing.test.js new file mode 100644 index 0000000000000000000000000000000000000000..213988f99e6f47a73dad167e6e6e7e096e27b5c4 --- /dev/null +++ b/tests/synth/timing.test.js @@ -0,0 +1,416 @@ +describe("Timing", function() { + var abcRepeatedSections = 'X:1\n' + + 'L:1/4\n' + + 'M:3/4\n' + + 'Q:1/4=60\n' + + 'K:Bb\n' + + 'CDE|:FG[Ab]|1 Bcd:|2 efg|]\n'; + + var expectedRepeatedSections = [ + { ms: 0, pitches: [60] }, + { ms: 1000, pitches: [62] }, + { ms: 2000, pitches: [63] }, + { bar: true }, + { ms: [3000,9000], pitches: [65] }, + { ms: [4000,10000], pitches: [67] }, + { ms: [5000,11000], pitches: [69, 82] }, + { bar: true }, + { ms: 6000, pitches: [70] }, + { ms: 7000, pitches: [72] }, + { ms: 8000, pitches: [74] }, + { bar: true }, + { ms: 12000, pitches: [75] }, + { ms: 13000, pitches: [77] }, + { ms: 14000, pitches: [79] }, + { bar: true }, + ]; + + var abcElevenEight = 'X:1\n' + + '%%score { 1 | 2 }\n' + + 'L:1/8\n' + + 'Q:1/4=140\n' + + 'M:11/8\n' + + 'K:C\n' + + 'V:1 treble nm=\"Piano\"\n' + + 'V:2 bass\n' + + 'V:1\n' + + 'A,B,CD EFD E2 E2 |\n' + + 'V:2\n' + + 'A,,2 .[E,C,]2 E,,2 .[E,C,] A,,2 .[E,C,]2 |\n'; + + var expectedElevenEight = [ + { ms: 0, pitches: [57] }, + { ms: 214.28571428571428, pitches: [59] }, + { ms: 428.57142857142856, pitches: [60] }, + { ms: 642.8571428571429, pitches: [62] }, + { ms: 857.1428571428571, pitches: [64] }, + { ms: 1071.4285714285713, pitches: [65] }, + { ms: 1285.7142857142858, pitches: [62] }, + { ms: 1500, pitches: [64] }, + { ms: 1928.5714285714284, pitches: [64] }, + { bar: true }, + ]; + + var abcTempoChange = 'X:1\n' + + 'L:1/4\n' + + 'M:4/4\n' + + 'K:F\n' + + '[Q:1/4=129.0476605]CDEF |[Q:1/4=127]GABc | [Q:1/4=131] CDEF |[Q:1/4=130] GABc |[Q:1/4=127]CDEF |\n' ; + + var abcWarp = 'X:1\n' + + 'L:1/4\n' + + 'Q:1/4=60\n' + + 'M:4/4\n' + + 'K:F\n' + + 'CD E>F | (3GAB Ac :|\n' ; + + var warpTests = [ + { bpm: 30, measuresOfDelay: 0 }, + { bpm: 30, measuresOfDelay: 1 }, + { bpm: 60, measuresOfDelay: 0 }, + { bpm: 60, measuresOfDelay: 1 }, + { bpm: 90, measuresOfDelay: 0 }, + { bpm: 90, measuresOfDelay: 2 }, + ]; + + var warpMs = [ + { millisecondsPerMeasure: 8000, ms: [0,2000,4000,7000,8000,9333,10667,12000,14000,16000,18000,20000,23000,24000,25333,26667,28000,30000,32000]}, + { millisecondsPerMeasure: 8000, ms: [8000,10000,12000,15000,16000,17333,18667,20000,22000,24000,26000,28000,31000,32000,33333,34667,36000,38000,40000]}, + { millisecondsPerMeasure: 4000, ms: [0,1000,2000,3500,4000,4667,5333,6000,7000,8000,9000,10000,11500,12000,12667,13333,14000,15000,16000]}, + { millisecondsPerMeasure: 4000, ms: [4000,5000,6000,7500,8000,8667,9333,10000,11000,12000,13000,14000,15500,16000,16667,17333,18000,19000,20000]}, + { millisecondsPerMeasure: 2666.666666666667, ms: [0,667,1333,2333,2667,3111,3556,4000,4667,5333,6000,6667,7667,8000,8444,8889,9333,10000,10667]}, + { millisecondsPerMeasure: 2666.666666666667, ms: [5333,6000,6667,7667,8000,8444,8889,9333,10000,10667,11333,12000,13000,13333,13778,14222,14667,15333,16000]}, + ]; + + var abcWarpNoQ = 'X:1\n' + + 'L:1/4\n' + + 'M:4/4\n' + + 'K:F\n' + + 'CD E>F | (3GAB Ac :|\n' ; + + var warpTestsNoQ = [ + { bpm: 30, measuresOfDelay: 0 }, + { bpm: 30, measuresOfDelay: 1 }, + { bpm: 60, measuresOfDelay: 0 }, + { bpm: 60, measuresOfDelay: 1 }, + { bpm: 90, measuresOfDelay: 0 }, + { bpm: 90, measuresOfDelay: 2 }, + ]; + + var warpMsNoQ = [ + { millisecondsPerMeasure: 8000, ms: [0,2000,4000,7000,8000,9333,10667,12000,14000,16000,18000,20000,23000,24000,25333,26667,28000,30000,32000]}, + { millisecondsPerMeasure: 8000, ms: [8000,10000,12000,15000,16000,17333,18667,20000,22000,24000,26000,28000,31000,32000,33333,34667,36000,38000,40000]}, + { millisecondsPerMeasure: 4000, ms: [0,1000,2000,3500,4000,4667,5333,6000,7000,8000,9000,10000,11500,12000,12667,13333,14000,15000,16000]}, + { millisecondsPerMeasure: 4000, ms: [4000,5000,6000,7500,8000,8667,9333,10000,11000,12000,13000,14000,15500,16000,16667,17333,18000,19000,20000]}, + { millisecondsPerMeasure: 2666.666666666667, ms: [0,667,1333,2333,2667,3111,3556,4000,4667,5333,6000,6667,7667,8000,8444,8889,9333,10000,10667]}, + { millisecondsPerMeasure: 2666.666666666667, ms: [5333,6000,6667,7667,8000,8444,8889,9333,10000,10667,11333,12000,13000,13333,13778,14222,14667,15333,16000]}, + ]; + + var abcSubTitleCrash = 'X:1\n' + + 'T:subtitle-crash\n' + + 'L:1/4\n' + + 'K:C\n' + + 'cdef|\n' + + 'T:subtitle\n' + + 'fabg|\n'; + + var abcRepeatAtStartCrash = 'X:1\n' + + 'T:repeat-at-start-of-line-crash\n' + + 'K:C\n' + + 'E8|\n' + + '|1 D8 :|2 C8\n'; + + var abcSkipTiesCrash = 'X:1 \n' + + 'T:skip-ties-crash\n' + + 'M:4/4\n' + + 'K:C\n' + + 'E8- | E8 |]\n'; + + var abcTieRepeatCrash = 'X:1\n' + + 'T:tie-repeat-crash\n' + + 'L:1/8\n' + + 'Q:1/4=100\n' + + 'M:4/4\n' + + 'K:G\n' + + 'G3 F- F4 :|\n'; + + var abcBeatCallback = "X:1\n" + + "M:2/4\n" + + "L:1/16\n" + + "K:C clef=bass\n" + + "!f!C,2D,2 E,4|G,6 A,2|G,4 E,4|]\n" + +var expectedBeatCallback = [ + {beat: 0, left: 'NONE' }, + {beat: 1, left: 'NONE' }, + {beat: 2, left: 'NONE' }, + {beat: 3, left: 'NONE' }, + {beat: 4, left: 70 }, + {beat: 5, left: 165 }, + {beat: 6, left: 240 }, + {beat: 4, left: 70 }, + {beat: 5, left: 165 }, + {beat: 6, left: 240 }, + {beat: 7, left: 290 }, + {beat: 8, left: 375 }, + {beat: 9, left: 440 }, + {beat: 10, left: 'NONE'}, +] + +var abcTieOverLineBreak = 'X:1\n' + +'%%stretchlast 1\n' + +'M:4/4\n' + +'K:C\n' + +'C4 D4-|\n' + +'D4 F2-F2 |\n'; + +var expectedTieOverLineBreak = [ + {beat: 0, top: 23, left: 70 }, + {beat: 0.5, top: 23, left: 105 }, + {beat: 1, top: 23, left: 150 }, + {beat: 1.5, top: 23, left: 195 }, + {beat: 2, top: 23, left: 240 }, + {beat: 2.5, top: 23, left: 275 }, + {beat: 3, top: 23, left: 325 }, + {beat: 3.5, top: 23, left: 370 }, + {beat: 4, top: 115, left: 50 }, + {beat: 4.5, top: 115, left: 85 }, + {beat: 5, top: 115, left: 135 }, + {beat: 5.5, top: 115, left: 185 }, + {beat: 6, top: 115, left: 230 }, + {beat: 6.5, top: 115, left: 270 }, + {beat: 7, top: 115, left: 315 }, + {beat: 7.5, top: 115, left: 365 }, + {beat: 8, top: 'NONE', left: 'NONE' }, +] + +////////////////////////////////////////////////////////// + + it("of repeated sections", function() { + doTimingTest(abcRepeatedSections, expectedRepeatedSections); + }); + + it("repeated sections callback", function() { + doClickTest2(abcRepeatedSections, expectedRepeatedSections); + }); + + it("of 11/8", function() { + doTimingTest(abcElevenEight, expectedElevenEight); + }); + + it("tempo change2 animation", function() { + doAnimationTest(abcTempoChange); + }); + + it("subtitle crash", function() { + doCreationTest(abcSubTitleCrash); + }); + + it("repeat at start crash", function() { + doCreationTest(abcRepeatAtStartCrash); + }); + + it("skip ties crash", function() { + doCreationTest(abcSkipTiesCrash); + }); + + it("tie repeat crash", function() { + doCreationTest(abcTieRepeatCrash); + }); + + it("warp", function() { + for (var i = 0; i < warpTests.length; i++) { + doWarpTest(abcWarp, warpTests[i], warpMs[i]); + } + }); + + it("warp no q", function() { + for (var i = 0; i < warpTestsNoQ.length; i++) { + doWarpTest(abcWarpNoQ, warpTestsNoQ[i], warpMsNoQ[i]); + } + }); + + it("beat-callback", function() { + return doBeatCallbackTest(abcBeatCallback, expectedBeatCallback) + }); + + it("tieOverLineBreak", function() { + return doBeatCallbackTestTies(abcTieOverLineBreak, expectedTieOverLineBreak) + }); +}); + +////////////////////////////////////////////////////////// + +function doWarpTest(abc, warps, warpMs) { + var visualObj = abcjs.renderAbc("paper", abc); + //visualObj[0].setUpAudio(); + visualObj[0].setTiming(warps.bpm, warps.measuresOfDelay); + var ms = []; + for (var i = 0; i < visualObj[0].noteTimings.length; i++) + ms.push(visualObj[0].noteTimings[i].milliseconds); + // console.log(visualObj[0].noteTimings[0].millisecondsPerMeasure) + // console.log(JSON.stringify(ms)) + chai.assert.equal(visualObj[0].noteTimings[0].millisecondsPerMeasure, warpMs.millisecondsPerMeasure, 'millisecondsPerMeasure for test { bpm:' + warps.bpm +', measuresOfDelay:' + warps.measuresOfDelay + ' }'); + var msg = 'millisecs for test { bpm:' + warps.bpm +', measuresOfDelay:' + warps.measuresOfDelay + ' }\n'; + msg += "rcv: " + JSON.stringify(ms) + "\nexp: " + JSON.stringify(warpMs.ms); + chai.assert.deepStrictEqual(ms, warpMs.ms, msg); +} + +////////////////////////////////////////////////////////// + +function doAnimationTest(abc) { + var visualObj = abcjs.renderAbc("paper", abc); + visualObj[0].setUpAudio(); + visualObj[0].setTiming(); + var ms = visualObj[0].millisecondsPerMeasure(); + //console.log(JSON.stringify(visualObj[0].noteTimings)) + for (var i = 0; i < visualObj[0].noteTimings.length; i++) { + var ev = visualObj[0].noteTimings[i]; + + if (ev.midiPitches) { + var start = Math.round(ev.midiPitches[0].start * ms); + chai.assert.equal(ev.milliseconds, start, 'timing for element ' + i); + } + } +} +var trans = function(nt, i, ms) { console.log(nt[i].milliseconds, nt[i].midiPitches[0].start, ms, nt[i].midiPitches[0].start* ms) } + +////////////////////////////////////////////////////////// + +function doClickTest2(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + visualObj[0].setUpAudio(); + var selectables = visualObj[0].engraver.selectables; + //console.log(selectables) + var index = 0; + for (var i = 0; i < expected.length; i++) { + if (!expected[i].bar) { + listener(selectables[index].absEl.abcelem, expected[i]); + index++; + } + } +} + +////////////////////////////////////////////////////////// + +function doCreationTest(abc) { + var visualObj = abcjs.renderAbc("paper", abc); + visualObj[0].setUpAudio(); + visualObj[0].setTiming(); +} + +////////////////////////////////////////////////////////// + +function doTimingTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + visualObj[0].setUpAudio(); + var voice = visualObj[0].lines[0].staff[0].voices[0]; + //console.log(voice) + chai.assert.equal(voice.length,expected.length, 'num items'); + for (var i = 0; i < voice.length; i++) { + if (voice[i].el_type === 'bar' && expected[i].bar) + continue; + + chai.assert.deepStrictEqual(voice[i].currentTrackMilliseconds, expected[i].ms, "millisecs for " + i); + chai.assert.equal(voice[i].midiPitches.length,expected[i].pitches.length, 'num pitches for ' + i); + for (var j = 0; j < voice[i].midiPitches.length; j++) { + chai.assert.equal(voice[i].midiPitches[j].pitch,expected[i].pitches[j], 'pitch for ' + i + ' ' + j); + } + } +} + +function listener(abcElem, expected) { + var pitches = []; + for (var i = 0; i < abcElem.midiPitches.length; i++) + chai.assert.equal(abcElem.midiPitches[i].pitch,expected.pitches[i]); + chai.assert.deepStrictEqual(abcElem.currentTrackMilliseconds,expected.ms); +} + +////////////////////////////////////////////////////////// + +function doBeatCallbackTestTies(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, { staffwidth: 400, stretchlast: true}) + var timing = new abcjs.TimingCallbacks(visualObj[0], { + beatCallback: beatCallback, + beatSubdivisions: 2, + qpm: 480, + }) + console.log(visualObj[0].noteTimings) + console.log(timing) + //timing.noteTimings[2].left = 40 + for (var k = 0; k < timing.noteTimings.length; k++) { + var t = timing.noteTimings[k] + console.log(k, t.milliseconds, t.line, t.left ) + } + var actual = [] + function beatCallback(beat,total,totalTime,position) { + var left = position.left === undefined ? 'NONE' : Math.round(position.left/5)*5 + var top = position.top === undefined ? 'NONE' : Math.round(position.top) + actual.push({beat: beat, top: top, left: left}) + } + + var svg = document.querySelector('#paper svg') + timing.start() + return sleep(1950).then(function () { + var msg = [] + for (var i = 0; i < Math.min(actual.length, expected.length); i++) { + var err = JSON.stringify(actual[i]) !== JSON.stringify(expected[i]) ? 'XXXX' : '' + msg.push(JSON.stringify(actual[i]) + ' = ' + JSON.stringify(expected[i]) + ' ' + err) + if (actual[i].left !== 'NONE' && actual[i].top !== 'NONE') + createLine(actual[i].left, actual[i].top, actual[i].left, actual[i].top+20, "stroke-width:2;stroke:blue", svg) + console.log(actual[i]) + } + msg = "\n" + msg.join("\n") + "\n" + chai.assert.deepStrictEqual(actual,expected, msg); + return Promise.resolve(); + }) +} + +const svgNS = "http://www.w3.org/2000/svg"; + +function createLine(x, y, x2, y2, style, parent) { + const line = document.createElementNS(svgNS, "line"); + line.setAttribute("x1", ''+x); + line.setAttribute("y1", ''+y); + line.setAttribute("x2", ''+x2); + line.setAttribute("y2", ''+y2); + if (style) + line.setAttribute("style", style); + parent.appendChild(line); +} + +function doBeatCallbackTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, { staffwidth: 500, stretchlast: true}) + var timing = new abcjs.TimingCallbacks(visualObj[0], { + beatCallback: beatCallback, + beatSubdivisions: 1, + extraMeasuresAtBeginning: 2, + qpm: 400, + }) + var actual = [] + function beatCallback(beat,total,totalTime,position) { + var left = position.left === undefined ? 'NONE' : Math.round(position.left/5)*5 + actual.push({beat: beat, left: left}) + } + + timing.start() + return sleep(900).then(function () { + timing.setProgress(0.6, "seconds") + return sleep(900).then(function () { + var msg = [] + for (var i = 0; i < Math.min(actual.length, expected.length); i++) { + var err = JSON.stringify(actual[i]) !== JSON.stringify(expected[i]) ? 'XXXX' : '' + msg.push(JSON.stringify(actual[i]) + ' = ' + JSON.stringify(expected[i]) + ' ' + err) + } + msg = "\n" + msg.join("\n") + "\n" + chai.assert.deepStrictEqual(actual,expected, msg); + return Promise.resolve(); + }) + }) +} + +function sleep(ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms); + }); +} diff --git a/tests/tablature.html b/tests/tablature.html new file mode 100644 index 0000000000000000000000000000000000000000..3fd47b590116b79c2e920778fee938c7e476771c --- /dev/null +++ b/tests/tablature.html @@ -0,0 +1,31 @@ + + + + + Tablature Tests + + + + +
+
+
+ + + + + + + + + + + diff --git a/tests/visual/decorations.test.js b/tests/visual/decorations.test.js new file mode 100644 index 0000000000000000000000000000000000000000..b3da5d9a3782c34134375c8edda435f2d1933c4f --- /dev/null +++ b/tests/visual/decorations.test.js @@ -0,0 +1,62 @@ +describe("Decorations", function() { + + var abcFermata = "X:1\n" + + "L:1/8\n" + + "M:4/4\n" + + "%%score (S A) B\n" + + "K:C\n" + + "[V:S]!invertedfermata!EF !invertedfermata!G2 | !invertedfermata!ef !invertedfermata!g2 |!fermata!EF !fermata!G2 | !fermata!ef !fermata!g2 ||\n" + + "[V:A]!invertedfermata!A,B, !invertedfermata!C2 | !invertedfermata!cd !invertedfermata!e2 |!fermata!A,B, !fermata!C2 | !fermata!cd !fermata!e2 ||\n" + + "[V:B]!invertedfermata!A,B, !invertedfermata!C2 | !invertedfermata!cd !invertedfermata!e2 |!fermata!A,B, !fermata!C2 | !fermata!cd !fermata!e2 ||\n" + + var expectedFermata = [ + { "pitch": -1, "note": "E" }, + { "pitch": 1, "note": "G" }, + { "pitch": 6, "note": "e" }, + { "pitch": 8, "note": "g" }, + { "pitch": 14, "note": "E" }, + { "pitch": 14, "note": "G" }, + { "pitch": 20, "note": "e" }, + { "pitch": 20, "note": "g" }, + { "pitch": -11, "note": "A," }, + { "pitch": -10, "note": "C" }, + { "pitch": -5, "note": "c" }, + { "pitch": -1, "note": "e" }, + { "pitch": 14, "note": "A," }, + { "pitch": 14, "note": "C" }, + { "pitch": 14, "note": "c" }, + { "pitch": 14, "note": "e" }, + { "pitch": -5, "note": "A," }, + { "pitch": -3, "note": "C" }, + { "pitch": -5, "note": "c" }, + { "pitch": -1, "note": "e" }, + { "pitch": 14, "note": "A," }, + { "pitch": 14, "note": "C" }, + { "pitch": 14, "note": "c" }, + { "pitch": 14, "note": "e" } + ] + + it("fermata", function() { + var visualObj = abcjs.renderAbc("paper", abcFermata, {add_classes: true}); + var fermatas = [] + for (var i = 0; i < visualObj[0].lines.length; i++) { + var line = visualObj[0].lines[i] + for (var j = 0; j < line.staffGroup.voices.length; j++) { + var voice = line.staffGroup.voices[j]; + for (var k = 0; k < voice.children.length; k++) { + var absElem = voice.children[k] + for (var ii = 0; ii < absElem.children.length; ii++) { + var relElem = absElem.children[ii] + if (relElem.c === 'scripts.dfermata' || relElem.c === 'scripts.ufermata') { + fermatas.push({pitch: Math.round(relElem.pitch), note: relElem.parent.children[0].name}) + } + } + } + } + } + console.log(fermatas) + chai.assert.deepStrictEqual(fermatas, expectedFermata); + }) + +}) + diff --git a/tests/visual/directives.test.js b/tests/visual/directives.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ec2f93684741450ee8d338310d0802d0677298c3 --- /dev/null +++ b/tests/visual/directives.test.js @@ -0,0 +1,79 @@ +describe("Directives", function() { + var stretchLastAbc1 = "M:4/4\ncdef cdef|"; + + var stretchLastFalse1 = [70.846, 100.846, 130.846, 160.846, 190.846, 220.846, 250.846, 280.846]; + var stretchLastTrue1 = [70.846, 156.24025, 241.6345, 327.02875, 412.423, 497.81725, 583.2115, 668.60575]; + + it("stretchlast", function() { + allTests(stretchLastAbc1, "stretchlast", stretchLastFalse1, + [ false, true, 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], + [ stretchLastFalse1, stretchLastTrue1, stretchLastFalse1, stretchLastFalse1, stretchLastFalse1, + stretchLastFalse1, stretchLastFalse1, stretchLastFalse1, stretchLastTrue1, stretchLastTrue1, stretchLastTrue1, + stretchLastTrue1, stretchLastTrue1 + ] + ); + }) + + var voiceColorAbc = 'K:C\nV:1\nC\nV:2\n%%voicecolor orange\nE\nV:3\n%%voicecolor "#f0a0f0"\nG\nV:4\nB\nV:1\n%%voicecolor blue\nC\nV:2\nE\nV:3\nG\nV:4\nB\n' + + var voiceColorExpected = [ 'currentColor', 'orange', '#f0a0f0', 'currentColor', 'blue', 'orange', '#f0a0f0', 'currentColor', ] + + var voiceColorAbc2 = '%%staves (1 2 3 4)\nK:C\nV:1\nC,\nV:2\n%%voicecolor "orange"\nE\nV:3\n%%voicecolor "#f0a0f0"\ne\nV:4\nb\'\nV:1\n%%voicecolor blue\nC,\nV:2\nE\nV:3\ne\nV:4\nb\'\n' + + it("voicecolor", function() { + doColorTest(voiceColorAbc, voiceColorExpected) + doColorTest(voiceColorAbc2, voiceColorExpected) + }) + + var abcRemark = "DEF [r:and this is a remark] FED:|\n" + + var abcRemarkExpected = ['note', 'note', 'note', 'note', 'note', 'note', 'bar'] + + it("remark", function() { + doNoteTest(abcRemark, abcRemarkExpected) + }) + +}) + +function allTests(abc, directive, blankExpected, values, expectedArray) { + doDirectiveTest(abc, {}, blankExpected, "undefined"); + for (var i = 0; i < values.length; i++) { + var formatting = {}; + formatting[directive] = values[i]; + doDirectiveTest(abc, { format: formatting}, expectedArray[i], "[param]" + directive + '=' + values[i]); + var directiveAbc = "%%" + directive + " " + values[i] + "\n" + abc; + doDirectiveTest(directiveAbc, { }, expectedArray[i], "[%%]" + directive + '=' + values[i]); + } +} + +function doDirectiveTest(abc, params, expected, comment) { + var visualObj = abcjs.renderAbc("paper", abc, params); + var selectables = visualObj[0].engraver.selectables; + var xes = []; + for (var i = 0; i < selectables.length; i++) + xes.push(selectables[i].absEl.x); + var msg = comment + "\nrcv: " + JSON.stringify(xes) + "\n" + + "exp: " + JSON.stringify(expected) + "\n"; + chai.assert.deepStrictEqual(xes, expected, msg); +} + +function doColorTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + console.log(visualObj[0].lines[0].staff) + var els = document.querySelectorAll('[data-name="note"]') + var colors = [] + for (var i = 0; i < els.length; i++) + colors.push(els[i].attributes.fill.value) + chai.assert.deepStrictEqual(colors, expected) +} + +function doNoteTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + chai.assert.equal(visualObj[0].warnings, undefined) + var elements = [ ] + for (var i = 0; i < visualObj[0].lines[0].staff[0].voices[0].length; i++) { + var item = visualObj[0].lines[0].staff[0].voices[0][i] + elements.push(item.el_type) + } + chai.assert.deepStrictEqual(elements, expected) +} \ No newline at end of file diff --git a/tests/visual/layout.test.js b/tests/visual/layout.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4934177055685764764d57e6a7df360c71d9274a --- /dev/null +++ b/tests/visual/layout.test.js @@ -0,0 +1,334 @@ +describe("Layout", function() { + var abcBarLinesTreble = "X:1\n%%barlabelfont Times-Bold 18 box\n%%setbarnb 42\n%%barnumbers 1\nM: 4/4\nL: 1/16\nK:D\nz8 |\n z8 |\n" + + var expectedBarLinesTreble = [{x: 20, y: 88}] + + var abcBarLinesBass = "X:1\n%%barlabelfont Times-Bold 18 box\n%%setbarnb 42\n%%barnumbers 1\nM: 4/4\nL: 1/16\nK:D clef=bass\nz8 |\n z8 |\n" + + var expectedBarLinesBass = [{x: 20, y: 84}] + + var abcMinSpacing = "X:1\nL:1/8\nM:4/4\ncdef cdef|\ncdef cdef|cdef cdef|\ncdef cdef|cdef cdef|cdef cdef|\n"; + + var expectedMinSpacing0 = [ + [71,96,122,147,172,198,223,249,274], + [49,62,75,88,101,114,127,141,156,167,180,193,207,220,233,246,259,275], + [49,60,71,81,92,103,114,125,141,152,162,173,184,195,206,216,227,243,254,265,276,286,297,308,319,330,345] + ]; + + var expectedMinSpacing10 = [ + [81,105,129,153,177,202,226,250,276], + [59,80,101,121,142,163,184,205,231,252,272,293,314,335,356,376,397,423], + [59,80,101,121,142,163,184,205,231,252,272,293,314,335,356,376,397,423,444,465,486,506,527,548,569,590,615] + ]; + + var abcCollidingNotes = "X:1\n" + + "L:1/8\n" + + "M:4/4\n" + + "%%score (S A)\n" + + "V:S clef=treble middle=B stem=up\n" + + "V:A clef=treble middle=B stem=down\n" + + "K:C\n" + + "[V:S]G4 zA G2 | GG GG GG GG | G/G/G/G/ [GB]/G/[cG]/G/ G/G/G/G/ G/G/G/G/ | C6 ||\n" + + "[V:A]F2 F2 F2 F2 | FF EF FE FC | F/C/D/A,/ F/F/F/F/ [DF]/F/[FD]/F/ F/F/F/F/ | _B,6 ||"; + + var expectedCollidingNotes = [[{"w":24.051,"dx":5},{"w":11.795,"dx":0},{"w":10.37,"dx":0},{"w":7.534,"dx":0},{"w":15.902000000000001,"dx":9.21},{"w":9.81,"dx":0},{"w":1,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":1,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0}, {"w":1,"dx":0},{"w":16.82,"dx":13.37}, {"w":4,"dx":0}], + [ + {"w":20.18,"dx":10.37},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":19.62,"dx":9.81},{"w":1,"dx":0}, + {"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":9.81,"dx":0},{"w":19.62,"dx":9.81}, {"w":19.62,"dx":9.81},{"w":9.81,"dx":0},{"w":19.62,"dx":9.81},{"w":9.81,"dx":0}, {"w":1,"dx":0}, + {"w":19.62,"dx":9.81},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0}, + {"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81}, + {"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81}, + {"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":19.62,"dx":9.81},{"w":1,"dx":0}, + {"w":27.189999999999998,"dx":23.74},{"w":4,"dx":0} + ]]; + + var abcNotCollision1 = "X:1\n" + + "M:3/8\n" + + "L:1/8\n" + + "K:C\n" + + "C3 & ABc | [CF]3 & ABc |C3 & [FA]Bc |" + + var expectedNotCollision1 = [[{"w":24.051,"dx":5},{"w":10.926,"dx":0.5955000000000004},{"w":16.26,"dx":12.81},{"w":1,"dx":0},{"w":16.26,"dx":12.81},{"w":1,"dx":0},{"w":16.26,"dx":12.81},{"w":1,"dx":0}],[{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":1,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":1,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":1,"dx":0}]] + + var twoStavesCollision = "X: 1\n" + + "%%staves [(1 2) (3 4)]\n" + + "V: 1 clef=treble\n" + + "V: 2 clef=treble\n" + + "V: 3 clef=bass\n" + + "V: 4 clef=bass\n" + + "K: C\n" + + "[V: 1]A2 |d2 |\n" + + "[V: 2]F2 |d2 |\n" + + "[V: 3]A,2 |E,2 |\n" + + "[V: 4]D,2 |D,2 |\n"; + + var expectedTwoStavesCollision = [ + [ + {"w": 24.051, "dx": 5}, {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0}, {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0} + ], [ + {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0}, {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0} + ], [ + {"w": 25.153, "dx": 5}, {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0}, {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0} + ], [ + {"w": 9.81, "dx": 0}, {"w": 1, "dx": 0}, {"w": 19.62, "dx": 9.81}, {"w": 1, "dx": 0} + ] + ]; + + var abcNotCollision2 = "X:1\n" + + "L:1/4\n" + + "K:GMin\n" + + "V:1 up\n" + + "V:2 merge down\n" + + "V:3 bass,, up\n" + + "V:4 bass,, merge down\n" + + "K:GMin\n" + + "[V:1] B2 A2 |\n" + + "[V:2] G2 ^F2 |\n" + + "[V:3] D3 C|\n" + + "[V:4] D, C, D,2|" + + var expectedNotCollision2 = [ + [ + {"w":24.051,"dx":5},{"w":15.5,"dx":0},{"w":10.37,"dx":0},{"w":10.37,"dx":0},{"w":1,"dx":0} + ],[ + {"w":10.37,"dx":0},{"w":10.37,"dx":-10.25},{"w":1,"dx":0} + ],[ + {"w":25.153,"dx":5},{"w":15.5,"dx":0},{"w":16.82,"dx":13.37},{"w":9.81,"dx":0},{"w":1,"dx":0} + ],[ + {"w":9.81,"dx":0},{"w":9.81,"dx":0},{"w":10.37,"dx":0},{"w":1,"dx":0} + ] + ]; + + var abcChordLayout = '"F"c3c|"C7"c2df|1f4- & "F"xx"Bb"x"Bbm"x|"F"f3z:|2f4- & "Bb"!style=harmonic!d2 "F"!style=harmonic!c "C7"!style=harmonic!B|"F"f4 & !style=harmonic!A4||\n' + + var expectedChordLayout = [{"x":54,"y":32},{"x":106,"y":32},{"x":344,"y":32},{"x":533,"y":32},{"x":190,"y":32},{"x":208,"y":32},{"x":254,"y":32},{"x":405,"y":32},{"x":455,"y":32},{"x":476,"y":32}]; + + var abcStaccatoPlacement = "E.B .B" + + var expectedStaccatoPlacement = [{ x: 82, y: 64 }, { x: 112, y: 40 }] + + var abcRhythmPlacement = "R: reel\n" + + "C" + + var expectedRhythmPlacement = [{ x: 20, y: 53 }] + + var lineTooWide = + "T:The title should be centered\n" + + "R:Left\n" + + "C:Right\n" + + "L:1/4\n" + + "K:B\n" + + "c2c2|cccc|c2c2|cccc|\n" + + 'T:Subtitle\n' + + '%%center Here is some centered text\n' + + "G/G/G/G/G/G/G/G/|G/G/G/G/G/G/G/G/|G/G/G/G/G/G/G/G/|G/G/G/G/G/G/G/G/|G/G/G/G/G/G/G/G/|\n" + + + var expectedLineTooWide = [ + [108,157,205,216,251,285,319,354,365,413,462,473,507,541,575,610], + [313], + [313], + [108,119,130,141,152,162,173,184,200,211,222,232,243,254,265,276,287,302,313,324,335,346,357,367,378,389,405,416,427,438,448,459,470,481,492,507,518,529,540,551,562,573,583,594,610] + ] + + var equalSpacing = + "T: Notes should take up the space proportional to their duration\n" + + "L:1/4\n" + + "Q:1/4=83\n" + + "K:C\n" + + "C (3DEF G/A/ | f4 | B//c//d//e// f2 (3g/f/e/ |z d z/c/ B//z// A/|\n" + + "z d z/c/ B//z// A/|C (3DEF G/A/ | f4 | B//c//d//e// f2 (3g/f/e/ |\n" + + " B//c//d//e// f2 (3g/f/e/ |z d z/c/ B//z// A/|C (3DEF G/A/ | f4 |\n" + + "C4 | f4 | g3/2 a/ f3/4 e// d3/8 c/// B// A//|d3 c|\n" + + "^C (3^D^E^F ^G/^A/ | f4 | g3/2 a/ f3/4 e// d3/8 c/// B// A//|d3 c|\n" + + "\n" + + var accentSpacing = "X:1\n" + + "L:1/4\n" + + "%%staffwidth 100\n" + + "K:C\n" + + "A/ !>!A/ |]\n" + + "w: L R|\n" + + "A/!>!A/ |]\n" + // the beam is pushing the dynamics down + "w: L R|\n" + + var expectedAccentSpacing = [ + {x: 49, y: 82}, + {x: 91, y: 82}, + {x: 49, y: 174}, + {x: 91, y: 174}, + ] + + it("line-too-wide", function() { + var visualObj = doLayoutTest(lineTooWide, {staffwidth: 500, expandToWidest: true }, expectedLineTooWide, 'staffwidth=500'); + var expected = ['', 313, '', '', 15, 611, ''] + var results = [] + //console.log(visualObj[0].topText) + for (var i = 0; i < visualObj[0].topText.rows.length; i++) { + var left = visualObj[0].topText.rows[i].left + results.push(left ? Math.round(left) : '') + } + chai.assert.deepStrictEqual(results, expected, "top text"); + }) + + it("min-spacing", function() { + doLayoutTest(abcMinSpacing, {staffwidth: 260 }, expectedMinSpacing0, 'minPadding=0'); + doLayoutTest(abcMinSpacing, {staffwidth: 260, minPadding: 10 }, expectedMinSpacing10, 'minPadding=10'); + }) + + it("colliding-notes1", function() { + doCollidingNotesTest(abcCollidingNotes, expectedCollidingNotes) + }) + + it("not-colliding-notes", function() { + doCollidingNotesTest(abcNotCollision1, expectedNotCollision1) + }) + + it("two-staves-collision", function() { + doCollidingNotesTest(twoStavesCollision, expectedTwoStavesCollision) + }) + + it("not-collision", function() { + doCollidingNotesTest(abcNotCollision2, expectedNotCollision2) + }) + + it("chord-layout", function() { + doChordLayoutTest(abcChordLayout, expectedChordLayout) + }) + + it("staccato-placement", function() { + doItemPlacementTest(abcStaccatoPlacement, expectedStaccatoPlacement, '[data-name="scripts.staccato"]'); + }) + + it("rhythm-placement", function() { + doItemPlacementTest(abcRhythmPlacement, expectedRhythmPlacement, '[data-name="clefs.G"]'); + }) + + it("accent-spacing", function() { + doItemPlacementTest(accentSpacing, expectedAccentSpacing, '[data-name="lyric"]'); + }) + + it("measure-numbers", function() { + doItemPlacementTest(abcBarLinesTreble, expectedBarLinesTreble, '[data-name="bar-number"]'); + doItemPlacementTest(abcBarLinesBass, expectedBarLinesBass, '[data-name="bar-number"]'); + }) + + it("equal-spacing", function() { + abcjs.renderAbc("paper", equalSpacing, {add_classes: true, timeBasedLayout:{minPadding: 5, minWidth: 1200}}); + var svg = document.querySelector('#paper svg') + var OFFSET = 50 + var SPACING = 291.48725/4 + for (var i = 0; i < 17; i++) { + const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); + line.setAttribute("x1", ''+(OFFSET+SPACING*i)); + line.setAttribute("y1", ''+90); + line.setAttribute("x2", ''+(OFFSET+SPACING*i)); + line.setAttribute("y2", ''+510); + line.setAttribute("stroke", "#0000ff50"); + svg.appendChild(line); + } + }) + +}) + +function doItemPlacementTest(abc, expected, selector) { + abcjs.renderAbc("paper", abc, {add_classes: true}); + var els = document.querySelectorAll("#paper "+selector) + var pos = [] + for (var i = 0; i < els.length; i++) { + var bb = els[i].getBBox() + pos.push({x: Math.round(bb.x), y: Math.round(bb.y)}) + } + //console.log(pos) + chai.assert.deepEqual(pos, expected) +} + +function doChordLayoutTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, { showDebug: "box", add_classes: true, staffwidth: 260, format: { gchordfont: "20"}}); + var els = document.querySelectorAll("#paper .abcjs-chord") + var pos = [] + for (var i = 0; i < els.length; i++) { + var bb = els[i].getBBox() + pos.push({x: Math.round(bb.x), y: Math.round(bb.y)}) + } + console.log(pos) + chai.assert.deepEqual(pos, expected) +} + +function doCollidingNotesTest(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc, {}); + var results = []; + var errors = []; + for (var ln = 0; ln < visualObj[0].lines.length; ln++) { + var line = visualObj[0].lines[ln] + if (line.staffGroup && line.staffGroup.voices) { + if (line.staffGroup.voices.length > 1) { + for (var v = 0; v < line.staffGroup.voices.length; v++) { + if (!results[v]) results[v] = []; + var voice = line.staffGroup.voices[v]; + for (var e = 0; e < voice.children.length; e++) { + var el = voice.children[e] + var r = { w: el.w, dx: el.children[0].dx} + results[v].push(r) + if (!expected[v] || !expected[v][e]) + console.log(v, e, r) + if (expected[v][e].w !== r.w || expected[v][e].dx !== r.dx) + errors.push({v: v, e: e, exp: expected[v][e], rcv: r}) + //console.log(v, e, "EXP", expected[v][e], "RCV", r) + } + } + } + } + } + console.log(JSON.stringify(results)) + if (errors.length > 0) + chai.assert.equal(true, false, JSON.stringify(errors, null, " ")); + +} + +function doLayoutTest(abc, params, expected, comment) { + var visualObj = abcjs.renderAbc("paper", abc, params); + if (params.staffwidth) + verticalLine("#paper svg", params.staffwidth+15) // 15 is for the padding + var result = []; + for (var j = 0; j < visualObj[0].lines.length; j++) { + var arr = []; + if (visualObj[0].lines[j].staff) { + for (var i = 0; i < visualObj[0].lines[j].staff[0].voices[0].length; i++) { + var el = visualObj[0].lines[j].staff[0].voices[0][i]; + arr.push(Math.round(el.abselem.x)); + } + } else if (visualObj[0].lines[j].nonMusic && visualObj[0].lines[j].nonMusic.rows) { + for (var k = 0; k < visualObj[0].lines[j].nonMusic.rows.length; k++) { + var row = visualObj[0].lines[j].nonMusic.rows[k] + if (row.left) + arr.push(Math.round(row.left)) + } + } + result.push(arr); + } + for (j = 0; j < result.length; j++) { + var recv = result[j]; + var exp = expected[j]; + var msg = comment + "\nrcv: " + JSON.stringify(recv) + "\n" + + "exp: " + JSON.stringify(exp) + "\n"; + chai.assert.deepStrictEqual(recv, exp, msg); + } + + //console.log(result) + return visualObj +} + +function verticalLine(selector, x) { + var svgNS = "http://www.w3.org/2000/svg"; + var cursor = document.createElementNS(svgNS, "line"); + cursor.setAttribute("class", "abcjs-cursor"); + cursor.setAttributeNS(null, 'x1', x); + cursor.setAttributeNS(null, 'y1', 0); + cursor.setAttributeNS(null, 'x2', x); + cursor.setAttributeNS(null, 'y2', 800); + cursor.setAttributeNS(null, 'stroke', "blue"); + var svg = document.querySelector(selector); + svg.appendChild(cursor); + +} diff --git a/tests/visual/misc.test.js b/tests/visual/misc.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1384c86c1cac9de25de528b08b8995350c1abe59 --- /dev/null +++ b/tests/visual/misc.test.js @@ -0,0 +1,332 @@ +describe("Miscellaneous", function () { + var abcJazzChords = "X:1\n" + + "%%jazzchords\n" + + "K:C\n" + + "\"C7\"C \"C/B\"B \"x\"A \"x/C\"G \"/E\"E|"; + + var expectedJazzChords = [ + {input: 'C7', output: 'C\x037\x03'}, + {input: 'C/B', output: 'C\x03\x03/B'}, + {input: 'x', output: '\x03x\x03'}, + {input: 'x/C', output: '\x03x\x03/C'}, + {input: '/E', output: '\x03\x03/E'}, + ] + + var abcGlissando = "X:1\n" + + "L:1/4\n" + + "%%stretchlast\n" + + "!glissando(!C!glissando)!e\n" + + "!glissando(!C!glissando)!E !glissando(!C!glissando)!F !glissando(!C!glissando)!G !glissando(!C!glissando)!A !glissando(!C!glissando)!B !glissando(!C!glissando)!c \n" + + "!glissando(!C!glissando)!d !glissando(!C!glissando)!e !glissando(!C!glissando)!f\n" + + "!glissando(!g!glissando)!f !glissando(!g!glissando)!e !glissando(!g!glissando)!d !glissando(!g!glissando)!c !glissando(!g!glissando)!f !glissando(!g!glissando)!A !glissando(!g!glissando)!G !glissando(!g!glissando)!F !glissando(!g!glissando)!E !glissando(!g!glissando)!D \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + var expectedGlissando = [ + {x: 63, y: 36, w: 329, h: 40}, + {x: 63, y: 156, w: 35, h: 12}, + {x: 181, y: 153, w: 35, h: 14}, + {x: 298, y: 150, w: 35, h: 16}, + {x: 416, y: 147, w: 35, h: 18}, + {x: 533, y: 144, w: 35, h: 20}, + {x: 651, y: 139, w: 41, h: 25}, + {x: 63, y: 227, w: 95, h: 32}, + {x: 298, y: 223, w: 95, h: 35}, + {x: 533, y: 218, w: 101, h: 40}, + {x: 63, y: 307, w: 17, h: 7}, + {x: 133, y: 308, w: 17, h: 8}, + {x: 204, y: 309, w: 17, h: 9}, + {x: 275, y: 311, w: 17, h: 10}, + {x: 345, y: 307, w: 17, h: 7}, + {x: 416, y: 313, w: 17, h: 11}, + {x: 486, y: 315, w: 17, h: 12}, + {x: 557, y: 316, w: 23, h: 18}, + {x: 628, y: 318, w: 23, h: 20}, + {x: 698, y: 319, w: 29, h: 28}, + ] + + var abcDirectives = "X:1\n" + + "L:1/4\n" + + "CCCC!D.C.alcoda!|DDDD!D.C.alfine!|EEEE!D.S.alcoda!|FFFF!D.S.alfine!|!D.C.!G!5!GGG|\n" + + var expectedDirectives = [ + { c: 'D.C. al coda', anchor: 'end'}, + { c: 'D.C. al fine', anchor: 'end'}, + { c: 'D.S. al coda', anchor: 'end'}, + { c: 'D.S. al fine', anchor: 'end'}, + { c: 'D.C.', anchor: 'middle'}, + { c: '5', anchor: 'middle'}, + ] + + var abcSetFont = 'X:1\n' + + '%%setfont-1 cursive 16 bold\n' + + '%%setfont-7 Arial 19 bold\n' + + 'T:Title $1bold$0 $$100 reg, the\n' + + 'T:subtitle $1bold$0 reg\n' + + 'C:Thomas $7Fats$0 Waller\n' + + 'O:Copyright $11924$0 All Rights Reserved\n' + + 'A:by $1Me\n' + + 'P:AA$1BB$0CC\n' + + 'H:one $1two $0 three\n' + + 'H:aye $1bee $0 sea\n' + + 'B:$1book\n' + + 'D:records\n' + + 'N:four $1five $0 six\n' + + 'N:seven\n' + + 'L:1/4\n' + + 'M:4/4\n' + + 'W: lo $1loo $0lou $$1dollar\n' + + 'K:C\n' + + '"C$1m$7\\ntwo"G "Dm7"E2 F | "^above $1the$0 staff"D4 |]\n' + + 'w: la $1le $0lu\n' + + 'P:chorus $1with feeling\n' + + 'A4|' + + '\n' + + 'X:1\n' + + 'T:Title bold $$100 reg, the\n' + + 'T:subtitle bold reg\n' + + 'C:Thomas Fats Waller\n' + + 'O:Copyright 1924 All Rights Reserved\n' + + 'A:by Me\n' + + 'P:AABBCC\n' + + 'H:one two three\n' + + 'H:aye bee sea\n' + + 'N:four five six\n' + + 'N:seven\n' + + 'L:1/4\n' + + 'M:4/4\n' + + 'W: lo loo lou $$1dollar\n' + + 'K:C\n' + + '"Cm"G E2 F | "^above the staff"D4 |]\n' + + 'w: la le lu\n' + + 'P:chorus with feeling\n' + + 'A4|' + + var expectedSetFont = [ + {"klass":"abcjs-title","phrases":[ + {"content":"The Title ","attrs":{"font-family":"Times New Roman","font-size":27,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"bold","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" $100 reg","attrs":{"font-family":"Times New Roman","font-size":27,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]}, + {"klass":"abcjs-text abcjs-subtitle","phrases":[ + {"content":"subtitle ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"bold","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" reg","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]}, + {"klass":"abcjs-composer","phrases":[ + {"content":"Thomas ","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}, + {"content":"Fats","attrs":{"font-family":"Arial","font-size":19,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" Waller","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}, + {"content":" (","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}},{"content":"Copyright ","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}, + {"content":"1924","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" All Rights Reserved","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}, + {"content":")","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}]}, + {"klass":"abcjs-author","phrases":[ + {"content":"by ","attrs":{"font-family":"Times New Roman","font-size":19,"font-weight":"normal","font-style":"italic","font-decoration":"none"}}, + {"content":"Me","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}]}, + {"klass":"abcjs-part-order","phrases":[ + {"content":"AA","attrs":{"font-family":"Times New Roman","font-size":20,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"BB","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":"CC","attrs":{"font-family":"Times New Roman","font-size":20,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}]}, + {"key":"subtitle","text":[{"text":"subtitle "},{"font":{"face":"cursive","weight":"bold","style":"normal","decoration":"none","size":16},"text":"bold"},{"text":" reg"}]}, + {"key":"chord","text":"C$1m$7"}, + {"key":"lyric","text":"la"}, + {"key":"chord","text":"Dm7"}, + {"key":"lyric","text":"$1le"}, + {"key":"lyric","text":"$0lu"}, + {"key":"chord","text":"above $1the$0 staff"}, + {"phrases":[ + {"content":"lo ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"loo ","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":"lou $1dollar","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]}, + {"klass":"abcjs-extra-text abcjs-book","phrases":[ + {"content":"Book: ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"book","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}} + ]}, + {"klass":"abcjs-extra-text abcjs-discography","font":"historyfont","text":"Discography: records"}, + {"font":"historyfont","text":"Notes:"}, + {"phrases":[ + {"content":"four ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"five ","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" six","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]}, + {"font":"historyfont","text":"seven"}, + {"font":"historyfont","text":"History:"}, + {"phrases":[ + {"content":"one ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"two ","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" three","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]}, + {"phrases":[ + {"content":"aye ","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}}, + {"content":"bee ","attrs":{"font-family":"cursive","font-size":16,"font-weight":"bold","font-style":"normal","font-decoration":"none"}}, + {"content":" sea","attrs":{"font-family":"Times New Roman","font-size":21,"font-weight":"normal","font-style":"normal","font-decoration":"none"}} + ]} + ] + + var abcLineWidth = 'X:1\n' + + "L:1/8\n" + + "AB||ef g D|Df f/D/| A,4 c'4|]\n" + + var abcAccentPosition = 'X:1\n' + + "K:C\n" + + "!>!A2!>!c2|T!>!A2T!>!c2|]\n" + + it("line-width", function () { + abcjs.renderAbc("paper", abcLineWidth, { add_classes: true}); + var height = extractHeight() + chai.assert.equal(height, "0.700", "No change to lineThickness") + abcjs.renderAbc("paper", abcLineWidth, { add_classes: true, lineThickness: 0.1}); + height = extractHeight() + chai.assert.equal(height, "0.900", "lineThickness = 0.1") + abcjs.renderAbc("paper", abcLineWidth, { add_classes: true, lineThickness: 0.5}); + height = extractHeight() + chai.assert.equal(height, "1.700", "lineThickness = 0.5") + }) + + function extractHeight() { + var topLine = document.querySelector("#paper .abcjs-top-line") + var path = topLine.getAttribute('d') + var coordinates = path.split(" L ") + var first = coordinates[1].split(' ') + var second = coordinates[2].split(' ') + return (parseFloat(second[1]) - parseFloat(first[1])).toFixed(3) + } + + it("accent position", function () { + abcjs.renderAbc("paper", abcAccentPosition, { accentAbove: true }); + abcjs.renderAbc("paper2", abcAccentPosition, { }); + }) + + it("jazz chords", function () { + extractChords(abcJazzChords, expectedJazzChords); + }) + + it("glissando", function () { + draw(abcGlissando, expectedGlissando); + }) + + it("directives", function () { + var visualObj = abcjs.renderAbc("paper", abcDirectives); + var voice = visualObj[0].lines[0].staff[0].voices[0] + var results = [] + for (var i = 0; i < voice.length; i++) { + var elem = voice[i].abselem.children; + for (var j = 0; j < elem.length; j++) { + var rel = elem[j] + if (rel.type === 'decoration') + results.push({ c: elem[j].c, anchor: elem[j].anchor }) + } + } + chai.assert.deepEqual(results, expectedDirectives) + }) + + it("set-font", function() { + var visualObj = abcjs.renderAbc(["paper", 'paper2'], abcSetFont, {add_classes:true, jazzchords: true}); + var results = extractText(visualObj) + for (var i = 0; i < results.length; i++) { + chai.assert.deepEqual(results[i], expectedSetFont[i], "index: " + i + "\n" + JSON.stringify(results[i])+"\n" + JSON.stringify(expectedSetFont[i])) + } + }) +}) + +function extractText(visualObj) { + var textResults = [] + // Object.keys(visualObj[0].metaText).forEach(key => { + // if (key !== 'unalignedWords' && key !== 'history') + // textResults.push({key: key, text: visualObj[0].metaText[key] }) + // }) + + var topText = visualObj[0].topText.rows.filter(item => { + return item.text !== undefined || item.phrases !== undefined + }).map(item => { + var ret = {} + if (item.klass) ret.klass = item.klass; + if (item.font) ret.font = item.font + if (item.text) ret.text = item.text + if (item.phrases) ret.phrases = item.phrases + return ret + }) + textResults = textResults.concat(topText) + + for (var i = 0; i < visualObj[0].lines.length; i++) { + var line = visualObj[0].lines[i] + if (line.subtitle) { + textResults.push({key: 'subtitle', text:line.subtitle.text}) + } else if (line.staff) { + var voice = line.staff[0].voices[0] + for (var i = 0; i < voice.length; i++) { + var elem = voice[i]; + if (elem.chord) { + for (var j = 0; j < elem.chord.length; j++) { + var chord = elem.chord[j] + var item = { key: "chord", text: chord.name} + if (chord.font) + item.font = chord.font + textResults.push(item) + } + } + if (elem.lyric) { + for (var j = 0; j < elem.lyric.length; j++) { + var lyric = elem.lyric[j] + var item = {key: "lyric", text: lyric.syllable} + if (lyric.font) + item.font = lyric.font + textResults.push(item) + } + } + } + } + } + + var bottomText = visualObj[0].bottomText.rows.filter(item => { + return item.text !== undefined || item.phrases !== undefined + }).map(item => { + var ret = {} + if (item.klass) ret.klass = item.klass; + if (item.font) ret.font = item.font + if (item.text) ret.text = item.text + if (item.phrases) ret.phrases = item.phrases + return ret + }) + textResults = textResults.concat(bottomText) + console.log(JSON.stringify(textResults)) + return textResults +} + +function extractChords(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + var line = visualObj[0].lines[0]; + var staff = line.staff[0]; + var voice = staff.voices[0]; + var chords = []; + for (var i = 0; i < voice.length; i++) { + var el = voice[i]; + if (el.chord) { + var output = el.abselem.extra[0] + chords.push({input: el.chord[0].name, output: output.c}) + } + } + //console.log(chords) + chai.assert.equal(chords.length, expected.length, "wrong number of chords"); + for (i = 0; i < chords.length; i++) { + chai.assert.equal(chords[i].input, expected[i].input, i + ": Inputs different"); + chai.assert.equal(chords[i].output, expected[i].output, i + ": Outputs different"); + } +} + +function draw(abc, expected) { + var visualObj = abcjs.renderAbc("paper", abc); + var gliss = document.querySelectorAll('[data-name="glissando"]') + for (var i = 0; i < gliss.length; i++) { + var bb = gliss[i].getBBox() + bb = {x: Math.round(bb.x), y: Math.round(bb.y), w: Math.round(bb.width), h: Math.round(bb.height)} + //console.log(bb) + chai.assert.deepEqual(bb, expected[i]) + } +} + diff --git a/tests/visual/mouse-click.test.js b/tests/visual/mouse-click.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f27cf164d1f8a440a86184f85725d966a9500a29 --- /dev/null +++ b/tests/visual/mouse-click.test.js @@ -0,0 +1,363 @@ +describe("Mouse Click", function() { + var abcMultiple = 'X:1\n' + + 'M:4/4\n' + + 'L:1/16\n' + + '%%titlefont Times New Roman 22.0\n' + + '%%partsfont box\n' + + '%%barnumbers 1\n' + + 'T: Selection Test\n' + + 'T: Everything should be selectable\n' + + 'C: public domain\n' + + 'R: Hit it\n' + + 'A: Yours Truly\n' + + 'S: My own testing\n' + + 'W: Now is the time for all good men\n' + + 'W:\n' + + 'W: To come to the aid of their party.\n' + + 'H: This shows every type of thing that can possibly be drawn.\n' + + 'H:\n' + + 'H: And two lines of history!\n' + + 'Q: "Easy Swing" 1/4=140\n' + + 'P: AABB\n' + + '%%staves {(PianoRightHand extra) (PianoLeftHand)}\n' + + 'V:PianoRightHand clef=treble+8 name=RH\n' + + 'V:PianoLeftHand clef=bass name=LH\n' + + 'K:Bb\n' + + 'P:A\n' + + '%%text there is some random text\n' + + '%%sep 0.4cm 0.4cm 6cm\n' + + '[V: PianoRightHand] !mp![b8B8d8] f3g !//!f4|!<(![d12b12] !<)![b4g4]|z4 b^f_df (3B2d2c2 B4|1[Q:"left" 1/4=170"right"]!f![c4f4] z4 [b8d8]| (3[G8e8] Tu[c8f8] G8|]\n' + + 'w:Strang- ers\n' + + '[V: extra] B,16 | "Bb"{C}B,4 ({^CD}B,4 =B,8) |\n' + + 'T:Inserted subtitle\n' + + '[V: PianoLeftHand] B,6 .D2 !arpeggio![F,8F8A,8]|(B,2 B,,2 C,12)|"^annotation"F,16|[F,16D,16]|Z2|]\n'; + + var expectedMultiple = [ + {"isParent":true,"type":"title","index":0,"originalText":"T: Selection Test","name":"title"}, + {"isParent":true,"type":"subtitle","index":1,"originalText":"T: Everything should be selectable","name":"subtitle"}, + {"isParent":true,"type":"rhythm","index":2,"originalText":"R: Hit it","name":"rhythm"}, + {"isParent":true,"type":"composer","index":3,"originalText":"C: public domain","name":"composer"}, + {"isParent":true,"type":"author","index":4,"originalText":"A: Yours Truly","name":"author"}, + {"isParent":true,"type":"partOrder","index":5,"originalText":"P: AABB","name":"part-order"}, + {"isParent":false,"type":"partOrder","index":5,"originalText":"P: AABB","name":"part-order"}, + {"isParent":false,"type":"partOrder","index":5,"originalText":"P: AABB","name":"box"}, + {"isParent":true,"type":"freeText","index":6,"originalText":"%%text there is some random text","name":"free-text"}, + {"isParent":true,"type":"brace","index":7,"originalText":"TODO","name":"brace"}, + {"isParent":true,"type":"voiceName","index":8,"originalText":"TODO","name":"voice-name"}, + {"isParent":true,"type":"clef","index":9,"originalText":"TODO","name":"staff-extra clef"}, + {"isParent":false,"type":"clef","index":9,"originalText":"TODO","name":"clefs.G"}, + {"isParent":false,"type":"clef","index":9,"originalText":"TODO","name":"8"}, + {"isParent":true,"type":"keySignature","index":10,"originalText":"TODO","name":"staff-extra key-signature"}, + {"isParent":false,"type":"keySignature","index":10,"originalText":"TODO","name":"accidentals.flat"}, + {"isParent":false,"type":"keySignature","index":10,"originalText":"TODO","name":"accidentals.flat"}, + {"isParent":true,"type":"timeSignature","index":11,"originalText":"TODO","name":"staff-extra time-signature"}, + {"isParent":false,"type":"timeSignature","index":11,"originalText":"TODO","name":"4"}, + {"isParent":false,"type":"timeSignature","index":11,"originalText":"TODO","name":"4"}, + {"isParent":true,"type":"tempo","index":12,"originalText":"Q: \"Easy Swing\" 1/4=140","name":"tempo"}, + {"isParent":false,"type":"tempo","index":12,"originalText":"Q: \"Easy Swing\" 1/4=140","name":"pre"}, + {"isParent":false,"type":"tempo","index":12,"originalText":"Q: \"Easy Swing\" 1/4=140","name":"noteheads.quarter"}, + {"isParent":false,"type":"tempo","index":12,"originalText":"Q: \"Easy Swing\" 1/4=140","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"tempo","index":12,"originalText":"Q: \"Easy Swing\" 1/4=140","name":"beats"}, + {"isParent":true,"type":"part","index":13,"originalText":"P:A","name":"part"}, + {"isParent":false,"type":"part","index":13,"originalText":"P:A","name":"A"}, + {"isParent":false,"type":"part","index":13,"originalText":"P:A","name":"box"}, + {"isParent":true,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"note"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"B","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"d","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"b","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"lyric","classes":"abcjs-lyric abcjs-l3 abcjs-m0 abcjs-mm0 abcjs-v0 abcjs-n0"}, + {"isParent":false,"type":"note","index":14,"originalText":" !mp![b8B8d8] ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"note","index":15,"originalText":"f3","name":"note"}, + {"isParent":false,"type":"note","index":15,"originalText":"f3","name":"dots.dot"}, + {"isParent":false,"type":"note","index":15,"originalText":"f3","name":"f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":15,"originalText":"f3","name":"lyric","classes":"abcjs-lyric abcjs-l3 abcjs-m0 abcjs-mm0 abcjs-v0 abcjs-n1"}, + {"isParent":false,"type":"note","index":15,"originalText":"f3","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":16,"originalText":"g ","name":"note"}, + {"isParent":false,"type":"note","index":16,"originalText":"g ","name":"g","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":16,"originalText":"g ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":17,"originalText":"!//!f4","name":"note"}, + {"isParent":false,"type":"note","index":17,"originalText":"!//!f4","name":"f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":17,"originalText":"!//!f4","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":17,"originalText":"!//!f4","name":"flags.ugrace"}, + {"isParent":false,"type":"note","index":17,"originalText":"!//!f4","name":"flags.ugrace"}, + {"isParent":true,"type":"bar","index":18,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":18,"originalText":"|","name":"bar-number","classes":"abcjs-bar-number abcjs-l3 abcjs-m0 abcjs-mm0 abcjs-v0"}, + {"isParent":false,"type":"bar","index":18,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"note"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"dots.dot"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"d","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"dots.dot"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"b","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":19,"originalText":"!<(![d12b12] ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"note","index":20,"originalText":"!<)![b4g4]","name":"note"}, + {"isParent":false,"type":"note","index":20,"originalText":"!<)![b4g4]","name":"g","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":20,"originalText":"!<)![b4g4]","name":"b","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":20,"originalText":"!<)![b4g4]","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":20,"originalText":"!<)![b4g4]","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"bar","index":21,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":21,"originalText":"|","name":"bar-number","classes":"abcjs-bar-number abcjs-l3 abcjs-m1 abcjs-mm1 abcjs-v0"}, + {"isParent":false,"type":"bar","index":21,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":22,"originalText":"z4 ","name":"rest"}, + {"isParent":false,"type":"note","index":22,"originalText":"z4 ","name":"rests.quarter"}, + {"isParent":true,"type":"note","index":23,"originalText":"b","name":"note"}, + {"isParent":false,"type":"note","index":23,"originalText":"b","name":"b","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":23,"originalText":"b","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":23,"originalText":"b","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":24,"originalText":"^f","name":"note"}, + {"isParent":false,"type":"note","index":24,"originalText":"^f","name":"accidentals.sharp"}, + {"isParent":false,"type":"note","index":24,"originalText":"^f","name":"^f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":24,"originalText":"^f","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":25,"originalText":"_d","name":"note"}, + {"isParent":false,"type":"note","index":25,"originalText":"_d","name":"accidentals.flat"}, + {"isParent":false,"type":"note","index":25,"originalText":"_d","name":"_d","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":25,"originalText":"_d","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":26,"originalText":"f ","name":"note"}, + {"isParent":false,"type":"note","index":26,"originalText":"f ","name":"f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":26,"originalText":"f ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":27,"originalText":"(3B2","name":"note"}, + {"isParent":false,"type":"note","index":27,"originalText":"(3B2","name":"B","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":27,"originalText":"(3B2","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":28,"originalText":"d2","name":"note"}, + {"isParent":false,"type":"note","index":28,"originalText":"d2","name":"d","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":28,"originalText":"d2","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":29,"originalText":"c2 ","name":"note"}, + {"isParent":false,"type":"note","index":29,"originalText":"c2 ","name":"c","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":29,"originalText":"c2 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":30,"originalText":"B4","name":"note"}, + {"isParent":false,"type":"note","index":30,"originalText":"B4","name":"B","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":30,"originalText":"B4","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"bar","index":31,"originalText":"|1","name":"bar"}, + {"isParent":false,"type":"bar","index":31,"originalText":"|1","name":"bar-number","classes":"abcjs-bar-number abcjs-l3 abcjs-m2 abcjs-mm2 abcjs-v0"}, + {"isParent":false,"type":"bar","index":31,"originalText":"|1","name":"bar"}, + {"isParent":true,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"tempo"}, + {"isParent":false,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"pre"}, + {"isParent":false,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"noteheads.quarter"}, + {"isParent":false,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"beats"}, + {"isParent":false,"type":"tempo","index":32,"originalText":"[Q:\"left\" 1/4=170\"right\"]","name":"post"}, + {"isParent":true,"type":"note","index":33,"originalText":"!f![c4f4] ","name":"note"}, + {"isParent":false,"type":"note","index":33,"originalText":"!f![c4f4] ","name":"c","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":33,"originalText":"!f![c4f4] ","name":"f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":33,"originalText":"!f![c4f4] ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":34,"originalText":"z4 ","name":"rest"}, + {"isParent":false,"type":"note","index":34,"originalText":"z4 ","name":"rests.quarter"}, + {"isParent":true,"type":"note","index":35,"originalText":"[b8d8]","name":"note"}, + {"isParent":false,"type":"note","index":35,"originalText":"[b8d8]","name":"d","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":35,"originalText":"[b8d8]","name":"b","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":35,"originalText":"[b8d8]","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":35,"originalText":"[b8d8]","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"bar","index":36,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":36,"originalText":"|","name":"bar-number","classes":"abcjs-bar-number abcjs-l3 abcjs-m3 abcjs-mm3 abcjs-v0"}, + {"isParent":false,"type":"bar","index":36,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":37,"originalText":" (3[G8e8] ","name":"note"}, + {"isParent":false,"type":"note","index":37,"originalText":" (3[G8e8] ","name":"G","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":37,"originalText":" (3[G8e8] ","name":"e","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":37,"originalText":" (3[G8e8] ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"note"}, + {"isParent":false,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"c","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"f","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"scripts.trill"}, + {"isParent":false,"type":"note","index":38,"originalText":"Tu[c8f8] ","name":"scripts.upbow"}, + {"isParent":true,"type":"note","index":39,"originalText":"G8","name":"note"}, + {"isParent":false,"type":"note","index":39,"originalText":"G8","name":"G","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":39,"originalText":"G8","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"bar","index":40,"originalText":"|]","name":"bar"}, + {"isParent":false,"type":"bar","index":40,"originalText":"|]","name":"bar"}, + {"isParent":false,"type":"bar","index":40,"originalText":"|]","name":"bar"}, + {"isParent":true,"type":"dynamicDecoration","index":41,"originalText":"TODO","name":"dynamics"}, + {"isParent":false,"type":"dynamicDecoration","index":41,"originalText":"TODO","name":"dynamics"}, + {"isParent":false,"type":"dynamicDecoration","index":41,"originalText":"TODO","name":"dynamics"}, + {"isParent":true,"type":"dynamicDecoration","index":42,"originalText":"TODO","name":"dynamics"}, + {"isParent":true,"type":"triplet","index":43,"originalText":"TODO","name":"triplet"}, + {"isParent":false,"type":"triplet","index":43,"originalText":"TODO","name":"3"}, + {"isParent":true,"type":"ending","index":44,"originalText":"TODO","name":"ending"}, + {"isParent":false,"type":"ending","index":44,"originalText":"TODO","name":"line"}, + {"isParent":false,"type":"ending","index":44,"originalText":"TODO","name":"1"}, + {"isParent":true,"type":"dynamicDecoration","index":45,"originalText":"TODO","name":"dynamics"}, + {"isParent":true,"type":"triplet","index":46,"originalText":"TODO","name":"triplet"}, + {"isParent":false,"type":"triplet","index":46,"originalText":"TODO","name":"triplet-bracket"}, + {"isParent":false,"type":"triplet","index":46,"originalText":"TODO","name":"3"}, + {"isParent":true,"type":"note","index":47,"originalText":" B,16 ","name":"note"}, + {"isParent":false,"type":"note","index":47,"originalText":" B,16 ","name":"B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":47,"originalText":" B,16 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"note"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"flags.u8th"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"C","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":48,"originalText":" \"Bb\"{C}B,4 ","name":"chord","classes":"abcjs-chord abcjs-l3 abcjs-m1 abcjs-mm1 abcjs-v1"}, + {"isParent":true,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"note"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"accidentals.sharp"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"^C","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"D","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":49,"originalText":"{^CD}B,4 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":50,"originalText":"=B,8) ","name":"note"}, + {"isParent":false,"type":"note","index":50,"originalText":"=B,8) ","name":"accidentals.nat"}, + {"isParent":false,"type":"note","index":50,"originalText":"=B,8) ","name":"=B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":50,"originalText":"=B,8) ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":50,"originalText":"=B,8) ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"slur","index":51,"originalText":"| \"Bb\"{C}B,4 (","name":"slur"}, + {"isParent":true,"type":"slur","index":52,"originalText":"TODO","name":"tie"}, + {"isParent":true,"type":"slur","index":53,"originalText":"({^CD}B,4 =","name":"slur"}, + {"isParent":true,"type":"voiceName","index":54,"originalText":"TODO","name":"voice-name"}, + {"isParent":true,"type":"clef","index":55,"originalText":"TODO","name":"staff-extra clef"}, + {"isParent":false,"type":"clef","index":55,"originalText":"TODO","name":"clefs.F"}, + {"isParent":true,"type": "keySignature", "index":56,"originalText":"TODO","name":"staff-extra key-signature"}, + {"isParent":false,"type": "keySignature", "index":56,"originalText":"TODO","name":"accidentals.flat"}, + {"isParent":false,"type": "keySignature", "index":56,"originalText":"TODO","name":"accidentals.flat"}, + {"isParent":true,"type":"timeSignature","index":57,"originalText":"TODO","name":"staff-extra time-signature"}, + {"isParent":false,"type":"timeSignature","index":57,"originalText":"TODO","name":"4"}, + {"isParent":false,"type":"timeSignature","index":57,"originalText":"TODO","name":"4"}, + {"isParent":true,"type":"note","index":58,"originalText":" B,6 ","name":"note"}, + {"isParent":false,"type":"note","index":58,"originalText":" B,6 ","name":"dots.dot"}, + {"isParent":false,"type":"note","index":58,"originalText":" B,6 ","name":"B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":58,"originalText":" B,6 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":59,"originalText":".D2 ","name":"note"}, + {"isParent":false,"type":"note","index":59,"originalText":".D2 ","name":"flags.d8th"}, + {"isParent":false,"type":"note","index":59,"originalText":".D2 ","name":"D","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":59,"originalText":".D2 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":59,"originalText":".D2 ","name":"scripts.staccato"}, + {"isParent":false,"type":"note","index":59,"originalText":".D2 ","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"note"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"F,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"A,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"F","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"stem","classes":"abcjs-stem"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"scripts.arpeggio"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"scripts.arpeggio"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"scripts.arpeggio"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"scripts.arpeggio"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"scripts.arpeggio"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":false,"type":"note","index":60,"originalText":"!arpeggio![F,8F8A,8]","name":"ledger","classes":"abcjs-ledger"}, + {"isParent":true,"type":"bar","index":61,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":61,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":62,"originalText":"(B,2 ","name":"note"}, + {"isParent":false,"type":"note","index":62,"originalText":"(B,2 ","name":"flags.d8th"}, + {"isParent":false,"type":"note","index":62,"originalText":"(B,2 ","name":"B,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":62,"originalText":"(B,2 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":63,"originalText":"B,,2 ","name":"note"}, + {"isParent":false,"type":"note","index":63,"originalText":"B,,2 ","name":"flags.u8th"}, + {"isParent":false,"type":"note","index":63,"originalText":"B,,2 ","name":"B,,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":63,"originalText":"B,,2 ","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"note","index":64,"originalText":"C,12)","name":"note"}, + {"isParent":false,"type":"note","index":64,"originalText":"C,12)","name":"dots.dot"}, + {"isParent":false,"type":"note","index":64,"originalText":"C,12)","name":"C,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":64,"originalText":"C,12)","name":"stem","classes":"abcjs-stem"}, + {"isParent":true,"type":"bar","index":65,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":65,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":66,"originalText":"\"^annotation\"F,16","name":"note"}, + {"isParent":false,"type":"note","index":66,"originalText":"\"^annotation\"F,16","name":"F,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":66,"originalText":"\"^annotation\"F,16","name":"annotation","classes":"abcjs-annotation abcjs-l3 abcjs-m2 abcjs-mm2 abcjs-v2"}, + {"isParent":true,"type":"bar","index":67,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":67,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":68,"originalText":"[F,16D,16]","name":"note"}, + {"isParent":false,"type":"note","index":68,"originalText":"[F,16D,16]","name":"D,","classes":"abcjs-notehead"}, + {"isParent":false,"type":"note","index":68,"originalText":"[F,16D,16]","name":"F,","classes":"abcjs-notehead"}, + {"isParent":true,"type":"bar","index":69,"originalText":"|","name":"bar"}, + {"isParent":false,"type":"bar","index":69,"originalText":"|","name":"bar"}, + {"isParent":true,"type":"note","index":70,"originalText":"Z2","name":"rest"}, + {"isParent":false,"type":"note","index":70,"originalText":"Z2","name":"rests.multimeasure"}, + {"isParent":false,"type":"note","index":70,"originalText":"Z2","name":"rest","classes":"abcjs-rest abcjs-l3 abcjs-m4 abcjs-mm4 abcjs-v2 abcjs-n0 abcjs-l3 abcjs-m4 abcjs-mm4 abcjs-v2 abcjs-n0"}, + {"isParent":true,"type":"bar","index":71,"originalText":"|]","name":"bar"}, + {"isParent":false,"type":"bar","index":71,"originalText":"|]","name":"bar"}, + {"isParent":false,"type":"bar","index":71,"originalText":"|]","name":"bar"}, + {"isParent":true,"type":"slur","index":72,"originalText":"|(B,2 B,,2 C,12)|","name":"slur"}, + {"isParent":true,"type":"subtitle","index":73,"originalText":"T:Inserted subtitle","name":"subtitle"}, + {"isParent":true,"type":"unalignedWords","index":74,"originalText":"TODO","name":"unalignedWords"}, + {"isParent":false,"type":"unalignedWords","index":74,"originalText":"TODO","name":"unalignedWords"}, + {"isParent":false,"type":"unalignedWords","index":74,"originalText":"TODO","name":"unalignedWords"}, + {"isParent":true,"type":"extraText","index":75,"originalText":"TODO","name":"description"}, + {"isParent":true,"type":"extraText","index":76,"originalText":"TODO","name":"description"}, + ]; + +////////////////////////////////////////////////////////// + it("click all types of elements", function() { + doClickTest(abcMultiple, expectedMultiple); + }) +}) + +var results = []; +var testString; + +function doClickTest(abcString, expected) { + draw(abcString); + results = []; + testString = abcString; + selectAll(); + //console.log(JSON.stringify(results)) + for (var i = 0; i < results.length; i++) { + var msg = "index: " + i + "\nrcv: " + JSON.stringify(results[i]) + "\n" + + "exp: " + JSON.stringify(expected[i]) + "\n"; + chai.assert.deepStrictEqual(results[i], expected[i], msg); + } +} + +function draw(abcString) { + var options = { + add_classes: true, + clickListener: clickListener, + selectTypes: true + }; + abcjs.renderAbc("paper", abcString, options); + +} + +function processOneSvgEl(el) { + if (el.getBBox) { + var box = el.getBBox(); + var evDown = document.createEvent("SVGEvents"); + evDown.initEvent("mousedown", true, true); + evDown.offsetX = box.x; + evDown.offsetY = box.y; + evDown.button = 0; + var evUp = document.createEvent("SVGEvents"); + evUp.initEvent("mouseup", true, true); + evUp.offsetX = box.x; + evUp.offsetY = box.y; + evUp.button = 0; + el.dispatchEvent(evDown); + el.dispatchEvent(evUp); + if (el.tagName === 'g') { + for (var i = 0; i < el.children.length; i++) { + processOneSvgEl(el.children[i]); + } + } + } +} + +function selectAll() { + var svg = document.querySelector("#paper svg") + for (var i = 0; i < svg.children.length; i++) { + processOneSvgEl(svg.children[i]); + } +} + +function clickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) { + var target = mouseEvent.target; + var isParent = target.dataset.index !== undefined; + var result = { + isParent: isParent, + type: abcelem.el_type, + index: drag.index + }; + if (abcelem.startChar >= 0) + result.originalText = testString.substring(abcelem.startChar, abcelem.endChar); + else + result.originalText = "TODO"; + result.name = target.dataset.name ? target.dataset.name : analysis.selectableElement.dataset.name; + if (!isParent && target.classList.length > 0) + result.classes = ''+target.classList; + results.push(result) +} diff --git a/tests/visual/options.test.js b/tests/visual/options.test.js new file mode 100644 index 0000000000000000000000000000000000000000..04e2c3ac15e9d4d15b77f86306bf3daa3b3944bb --- /dev/null +++ b/tests/visual/options.test.js @@ -0,0 +1,152 @@ +describe("Visual Options", function() { + var abcFontBox = 'X:1\n' + + '%%gchordfont Arial 25 box\n' + + '%%annotationfont Times-Roman 15 box\n' + + '%%composerfont Arial 8 box\n' + + '%%footerfont Tahoma 8 box\n' + + '%%headerfont Geneva 15 box\n' + + '%%historyfont Palatino 9 box\n' + + '%%infofont Monaco 11 box\n' + + '%%measurefont Helvetica 7 box\n' + + '%%partsfont sans-serif 29 box\n' + + '%%repeatfont Helvetica 13 box\n' + + '%%subtitlefont Arial 17 box\n' + + '%%tempofont serif 19 box\n' + + '%%textfont Verdana 21 box\n' + + '%%titlefont cursive 23 box\n' + + '%%tripletfont cursive 39 box\n' + + '%%voicefont Verdana 17 box\n' + + '%%vocalfont sans-serif 11 box\n' + + '%%wordsfont Georgia 13 box\n' + + '%%header The header\n' + + '%%footer The footer\n' + + '%%measurenb 1\n' + + 'T:fonts\n' + + 'T:changing all fonts\n' + + 'C:composer\n' + + 'A:author\n' + + 'R:hopping\n' + + 'H:History\n' + + 'N:The notes\n' + + 'O:Origin\n' + + 'S:source\n' + + 'P:Part Order\n' + + 'L:1/4\n' + + 'Q:"This is" 1/4=190 "Fast"\n' + + 'W:The extra verses\n' + + 'W:The extra verses - line 2\n' + + 'K:C\n' + + 'P:Part\n' + + 'V:1 name=voice\n' + + '|1"C"CE"^No Chord"Gc:|(3aaaa|\n' + + 'w:words to this tune, words to this tune,\n' + + '%%text extra text\n' + + 'T:Another Subtitle\n' + + '|GGGG|AAAA|]\n' + + '%%text more extra text\n'; + + var expected10 = [ + [{"tag":"text","text":"fonts","x":348,"y":22,"w":67,"h":49},{"tag":"path","text":"","x":349,"y":23,"w":74,"h":56}], + [{"tag":"text","text":"changing all fonts","x":295,"y":99,"w":179,"h":26},{"tag":"path","text":"","x":293,"y":94,"w":185,"h":32}], + [{"tag":"text","text":"hopping","x":17,"y":142,"w":63,"h":19},{"tag":"path","text":"","x":15,"y":141,"w":67,"h":23}], + [{"tag":"text","text":"composer (Origin)","x":666,"y":143,"w":88,"h":12},{"tag":"path","text":"","x":665,"y":141,"w":91,"h":16}], + [{"tag":"text","text":"author","x":723,"y":161,"w":31,"h":12},{"tag":"path","text":"","x":721,"y":159,"w":35,"h":15}], + [{"tag":"text","text":"Part Order","x":19,"y":184,"w":182,"h":45},{"tag":"path","text":"","x":15,"y":177,"w":191,"h":54}], + [{"tag":"text","text":"This is","x":143,"y":255,"w":67,"h":29},{"tag":"path","text":"","x":220,"y":267,"w":10,"h":8},{"tag":"path","text":"","x":226,"y":256,"w":1,"h":14},{"tag":"text","text":"= 190","x":232,"y":255,"w":58,"h":29},{"tag":"text","text":"Fast","x":301,"y":255,"w":42,"h":29}], + [{"tag":"text","text":"Part","x":146,"y":285,"w":72,"h":45},{"tag":"path","text":"","x":143,"y":279,"w":80,"h":54}], + [{"tag":"text","text":"C","x":186,"y":357,"w":24,"h":37},{"tag":"path","text":"","x":183,"y":351,"w":31,"h":45}], + [{"tag":"text","text":"No Chord","x":334,"y":355,"w":80,"h":22},{"tag":"path","text":"","x":332,"y":351,"w":85,"h":27}], + [{"tag":"path","text":"","x":515,"y":409,"w":123,"h":5},{"tag":"text","text":"3","x":564,"y":355,"w":23,"h":82}], + [{"tag":"text","text":"extra text","x":18,"y":517,"w":138,"h":34},{"tag":"path","text":"","x":15,"y":514,"w":144,"h":41}], + [{"tag":"text","text":"Another Subtitle","x":303,"y":567,"w":164,"h":26},{"tag":"path","text":"","x":301,"y":563,"w":169,"h":32}], + [{"tag":"text","text":"more extra text","x":18,"y":686,"w":220,"h":34},{"tag":"path","text":"","x":15,"y":683,"w":227,"h":41}], + [{"tag":"text","text":"The extra verses","x":15,"y":753,"w":122,"h":20},{"tag":"text","text":"The extra verses - line 2","x":15,"y":775,"w":178,"h":20}], + [{"tag":"text","text":"Source: source","x":16,"y":840,"w":76,"h":13},{"tag":"path","text":"","x":15,"y":836,"w":80,"h":16}], + [{"tag":"text","text":"Notes:The notes","x":16,"y":862,"w":51,"h":27},{"tag":"path","text":"","x":15,"y":856,"w":55,"h":31}], + [{"tag":"text","text":"History:History","x":16,"y":901,"w":43,"h":28},{"tag":"path","text":"","x":15,"y":895,"w":47,"h":31}], + [{"tag":"text","text":"2","x":468,"y":401,"w":5,"h":10},{"tag":"path","text":"","x":467,"y":399,"w":8,"h":13}], + [{"tag":"path","text":"","x":515,"y":389,"w":123,"h":5},{"tag":"text","text":"3","x":565,"y":335,"w":23,"h":82}], + [{"tag":"text","text":"3","x":12,"y":591,"w":5,"h":10},{"tag":"path","text":"","x":12,"y":589,"w":8,"h":13}], + [{"tag":"text","text":"4","x":398,"y":591,"w":5,"h":10},{"tag":"path","text":"","x":397,"y":589,"w":8,"h":13}], + ]; + + var expected50 = [ + [{"tag":"text","text":"fonts","x":348,"y":34,"w":67,"h":49},{"tag":"path","text":"","x":336,"y":23,"w":99,"h":81}], + [{"tag":"text","text":"changing all fonts","x":295,"y":162,"w":179,"h":26},{"tag":"path","text":"","x":284,"y":148,"w":203,"h":50}], + [{"tag":"text","text":"hopping","x":23,"y":242,"w":63,"h":19},{"tag":"path","text":"","x":15,"y":235,"w":79,"h":35}], + [{"tag":"text","text":"composer (Origin)","x":661,"y":241,"w":88,"h":12},{"tag":"path","text":"","x":656,"y":235,"w":100,"h":24}], + [{"tag":"text","text":"author","x":718,"y":278,"w":31,"h":12},{"tag":"path","text":"","x":713,"y":272,"w":43,"h":24}], + [{"tag":"text","text":"Part Order","x":35,"y":331,"w":182,"h":45},{"tag":"path","text":"","x":15,"y":309,"w":222,"h":85}], + [{"tag":"text","text":"This is","x":216,"y":455,"w":67,"h":29},{"tag":"path","text":"","x":293,"y":467,"w":10,"h":8},{"tag":"path","text":"","x":300,"y":456,"w":1,"h":14},{"tag":"text","text":"= 190","x":306,"y":455,"w":58,"h":29},{"tag":"text","text":"Fast","x":375,"y":455,"w":42,"h":29}], + [{"tag":"text","text":"Part","x":236,"y":501,"w":72,"h":45},{"tag":"path","text":"","x":216,"y":479,"w":112,"h":85}], + [{"tag":"text","text":"C","x":285,"y":633,"w":24,"h":37},{"tag":"path","text":"","x":268,"y":613,"w":58,"h":71}], + [{"tag":"text","text":"No Chord","x":414,"y":625,"w":80,"h":22},{"tag":"path","text":"","x":404,"y":613,"w":101,"h":43}], + [{"tag":"path","text":"","x":560,"y":725,"w":101,"h":5},{"tag":"text","text":"3","x":599,"y":671,"w":23,"h":82}], + [{"tag":"text","text":"extra text","x":29,"y":844,"w":138,"h":34},{"tag":"path","text":"","x":15,"y":830,"w":167,"h":63}], + [{"tag":"text","text":"Another Subtitle","x":303,"y":937,"w":164,"h":26},{"tag":"path","text":"","x":292,"y":923,"w":188,"h":50}], + [{"tag":"text","text":"more extra text","x":29,"y":1106,"w":220,"h":34},{"tag":"path","text":"","x":15,"y":1092,"w":249,"h":63}], + [{"tag":"text","text":"The extra verses","x":15,"y":1207,"w":122,"h":20},{"tag":"text","text":"The extra verses - line 2","x":15,"y":1229,"w":178,"h":20}], + [{"tag":"text","text":"Source: source","x":21,"y":1298,"w":76,"h":13},{"tag":"path","text":"","x":15,"y":1290,"w":89,"h":26}], + [{"tag":"text","text":"Notes:The notes","x":21,"y":1341,"w":51,"h":27},{"tag":"path","text":"","x":15,"y":1331,"w":64,"h":40}], + [{"tag":"text","text":"History:History","x":21,"y":1422,"w":43,"h":28},{"tag":"path","text":"","x":15,"y":1412,"w":56,"h":41}], + [{"tag":"text","text":"voice","x":27,"y":706,"w":60,"h":28},{"tag":"path","text":"","x":15,"y":701,"w":84,"h":52}], + [{"tag":"text","text":"2","x":514,"y":690,"w":5,"h":10},{"tag":"path","text":"","x":509,"y":684,"w":15,"h":20}], + [{"tag":"text","text":"3","x":5,"y":965,"w":5,"h":10},{"tag":"path","text":"","x":1,"y":960,"w":15,"h":20}], + [{"tag":"text","text":"4","x":398,"y":965,"w":5,"h":10},{"tag":"path","text":"","x":394,"y":960,"w":15,"h":20}], + ]; + + var expected100 = [ + [{"tag":"text","text":"fonts","x":348,"y":50,"w":67,"h":49},{"tag":"path","text":"","x":321,"y":23,"w":130,"h":112}], + [{"tag":"text","text":"changing all fonts","x":295,"y":241,"w":179,"h":26},{"tag":"path","text":"","x":272,"y":216,"w":226,"h":73}], + [{"tag":"text","text":"hopping","x":30,"y":369,"w":63,"h":19},{"tag":"path","text":"","x":15,"y":354,"w":94,"h":50}], + [{"tag":"text","text":"composer (Origin)","x":656,"y":366,"w":88,"h":12},{"tag":"path","text":"","x":645,"y":354,"w":111,"h":35}], + [{"tag":"text","text":"author","x":713,"y":428,"w":31,"h":12},{"tag":"path","text":"","x":702,"y":416,"w":54,"h":35}], + [{"tag":"text","text":"Part Order","x":54,"y":520,"w":182,"h":45},{"tag":"path","text":"","x":15,"y":478,"w":261,"h":124}], + [{"tag":"text","text":"This is","x":308,"y":710,"w":67,"h":29},{"tag":"path","text":"","x":385,"y":722,"w":10,"h":8},{"tag":"path","text":"","x":392,"y":711,"w":1,"h":14},{"tag":"text","text":"= 190","x":398,"y":710,"w":58,"h":29},{"tag":"text","text":"Fast","x":467,"y":710,"w":42,"h":29}], + [{"tag":"text","text":"Part","x":347,"y":776,"w":72,"h":45},{"tag":"path","text":"","x":308,"y":734,"w":151,"h":124}], + [{"tag":"text","text":"C","x":410,"y":982,"w":24,"h":37},{"tag":"path","text":"","x":377,"y":946,"w":91,"h":104}], + [{"tag":"text","text":"No Chord","x":557,"y":968,"w":80,"h":22},{"tag":"path","text":"","x":537,"y":946,"w":121,"h":63}], + [{"tag":"path","text":"","x":643,"y":1142,"w":62,"h":5},{"tag":"text","text":"3","x":662,"y":1088,"w":23,"h":82}], + [{"tag":"text","text":"extra text","x":43,"y":1275,"w":138,"h":34},{"tag":"path","text":"","x":15,"y":1247,"w":195,"h":91}], + [{"tag":"text","text":"Another Subtitle","x":303,"y":1421,"w":164,"h":26},{"tag":"path","text":"","x":280,"y":1396,"w":211,"h":73}], + [{"tag":"text","text":"more extra text","x":43,"y":1657,"w":220,"h":34},{"tag":"path","text":"","x":15,"y":1629,"w":277,"h":91}], + [{"tag":"text","text":"The extra verses","x":15,"y":1800,"w":122,"h":20},{"tag":"text","text":"The extra verses - line 2","x":15,"y":1822,"w":178,"h":20}], + [{"tag":"text","text":"Source: source","x":27,"y":1897,"w":76,"h":13},{"tag":"path","text":"","x":15,"y":1883,"w":101,"h":38}], + [{"tag":"text","text":"Notes:The notes","x":27,"y":1966,"w":51,"h":27},{"tag":"path","text":"","x":15,"y":1950,"w":76,"h":52}], + [{"tag":"text","text":"History:History","x":27,"y":2100,"w":43,"h":28},{"tag":"path","text":"","x":15,"y":2084,"w":68,"h":53}], + [{"tag":"text","text":"voice","x":38,"y":1087,"w":60,"h":28},{"tag":"path","text":"","x":15,"y":1082,"w":107,"h":75}], + [{"tag":"text","text":"2","x":597,"y":1068,"w":5,"h":10},{"tag":"path","text":"","x":588,"y":1058,"w":24,"h":29}], + [{"tag":"text","text":"3","x":5,"y":1464,"w":5,"h":10},{"tag":"path","text":"","x":-4,"y":1454,"w":24,"h":29}], + [{"tag":"text","text":"4","x":398,"y":1464,"w":5,"h":10},{"tag":"path","text":"","x":389,"y":1454,"w":24,"h":29}], + ]; + + + it("font-box", function() { + doFontTest(abcFontBox, { }, expected10, "10"); + doFontTest(abcFontBox, { format: { fontboxpadding: 0.5 }}, expected50, "50"); + doFontTest(abcFontBox, { format: { fontboxpadding: 1 }}, expected100, "100"); + }) +}) + +var allTextSelectors = ".abcjs-title,.abcjs-subtitle,.abcjs-rhythm,.abcjs-composer,.abcjs-author,.abcjs-part-order,.abcjs-tempo,.abcjs-part,.abcjs-chord,.abcjs-annotation,.abcjs-voice-name,.abcjs-triplet,.abcjs-defined-text,.abcjs-bar-number,.abcjs-unaligned-words,.abcjs-extra-text"; + +function doFontTest(abc, params, expected, comment) { + params.add_classes = true; + var visualObj = abcjs.renderAbc("paper", abc, params); + var textElements = document.getElementById("paper").querySelectorAll(allTextSelectors); + for (var i = 0; i < textElements.length; i++) { + var el = textElements[i]; + var children = []; + for (var j = 0; j < el.children.length; j++) { + var child = el.children[j]; + var sz = child.getBBox(); + children.push({tag: child.tagName, text: child.textContent, + x: Math.round(sz.x), y: Math.round(sz.y), w: Math.round(sz.width), h: Math.round(sz.height)}); + } + // if (comment === "100") + console.log(JSON.stringify(children)+',') + var msg = "Index: " + i + ' ' + comment + "\nrcv: " + JSON.stringify(children) + "\n" + + "exp: " + JSON.stringify(expected[i]) + "\n"; + chai.assert.deepStrictEqual(children, expected[i], msg); + } +} diff --git a/tests/visual/parsing.test.js b/tests/visual/parsing.test.js new file mode 100644 index 0000000000000000000000000000000000000000..4c59f23656882bb92cf7ce071053b0e3c69d24d7 --- /dev/null +++ b/tests/visual/parsing.test.js @@ -0,0 +1,220 @@ +describe("Parsing", function () { + var abc1 = "X:3\n{Azzz}e2\n" + + var expected1 = [[{ + gracenotes: [{pitch: 5, name: 'A', duration: 0.125, verticalPos: 5}], + pitches: [{pitch: 9, name: 'e', verticalPos: 9, highestVert: 9}], + duration: 0.25, + el_type: "note", + }]] + + + var abc2 = "X: 789\nSx\n" + + var expected2 = [[{ + rest: { type: 'invisible' }, + duration: 0.125, + el_type: "note", + }]] + + var abc3 = "X: 360\n[V:1]f|\\\n[V:1]f|\n" + + var expected3 = [[ + { pitches:[{ pitch: 10, name: 'f', verticalPos: 10, highestVert: 10}], duration: 0.125, el_type: 'note'}, + { type: 'bar_thin', el_type: 'bar'}, + { pitches:[{ pitch: 10, name: 'f', verticalPos: 10, highestVert: 10}], duration: 0.125, el_type: 'note'}, + { type: 'bar_thin', el_type: 'bar'}, + ]] + + var abc4 = "X: 360\n[V:T]c|\\\n[V:B]A|\\\n[V:T]d|\n" + + var expected4 = [ + [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 7, name: 'c', verticalPos: 7, highestVert: 7}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 5, name: 'A', verticalPos: 5, highestVert: 11}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 8, name: 'd', verticalPos: 8, highestVert: 8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + var abc5 = "X: 360\n[V:T]c|[V:B]A|[V:T]d|\n" + + var expected5 = [ + [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 7, name: 'c', verticalPos: 7, highestVert: 7}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 5, name: 'A', verticalPos: 5, highestVert: 11}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 8, name: 'd', verticalPos: 8, highestVert: 8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + var abc6 = "X: 360\n%%score (T B)\n[V:T]c|\\\n[V:B]A|\\\n[V:T]d|\n" + + var expected6 = [ + [ + { direction: 'up', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":7,"name":"c","verticalPos":7,"highestVert":13}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'down', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":5,"name":"A","verticalPos":5,"highestVert":5}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{"pitch":8,"name":"d","verticalPos":8,"highestVert":8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 8, name: 'd', verticalPos: 8, highestVert: 8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + + var abc7 = "X: 360\n%%score (T B)\n[V:T]c|[V:B]A|[V:T]d|\n" + + var expected7 = [ + [ + { direction: 'up', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":7,"name":"c","verticalPos":7,"highestVert":13}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'down', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":5,"name":"A","verticalPos":5,"highestVert":5}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{"pitch":8,"name":"d","verticalPos":8,"highestVert":8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { duration: 0.125, el_type: 'note', pitches: [{pitch: 8, name: 'd', verticalPos: 8, highestVert: 8}]}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + var abc8 = "X: 360\n%%score (T B)\n[V:T]a|\n[V:T]b|\n[V:T]c|\n[V:B]A|\n[V:B]B|\n[V:B]C|\n" + + var expected8 = [ + [ + { direction: 'up', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":12,"name":"a","verticalPos":12,"highestVert":18}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'down', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":5,"name":"A","verticalPos":5,"highestVert":5}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'up', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":13,"name":"b","verticalPos":13,"highestVert":19}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'down', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":6,"name":"B","verticalPos":6,"highestVert":6}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'up', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":7,"name":"c","verticalPos":7,"highestVert":13}]}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { direction: 'down', el_type: 'stem'}, + { duration: 0.125, el_type: 'note', pitches: [{"pitch":0,"name":"C","verticalPos":0,"highestVert":0}]}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + var abc9 = "X: 360\n%%score (T B)\n[V:T]a|\\\n[V:T]b|\\\n[V:T]c|\\\n[V:B]A|\\\n[V:B]B|\\\n[V:B]C|\n" + + var expected9 = [ + [ + { el_type: 'stem', direction: 'up'}, + { el_type: 'note', pitches: [{ pitch: 12, name: 'a', verticalPos: 12, highestVert: 18}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + { el_type: 'note', pitches: [{ pitch: 13, name: 'b', verticalPos: 13, highestVert: 19}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + { el_type: 'note', pitches: [{ pitch: 7, name: 'c', verticalPos: 7, highestVert: 13}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + ], [ + { el_type: 'stem', direction: 'down'}, + { el_type: 'note', pitches: [{ pitch: 5, name: 'A', verticalPos: 5, highestVert: 5}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + { el_type: 'note', pitches: [{ pitch: 6, name: 'B', verticalPos: 6, highestVert: 6}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + { el_type: 'note', pitches: [{ pitch: 0, name: 'C', verticalPos: 0, highestVert: 0}], duration: 0.125}, + { el_type: 'bar', type: 'bar_thin'}, + ] + ] + + it("crashes", function () { + testParser(abc1, expected1, "abc1"); + }) + + it("crash2", function () { + testParser(abc2, expected2, "abc2"); + }) + + it("crash3", function () { + testParser(abc3, expected3, "abc3"); + }) + + it("crash4", function () { + testParser(abc4, expected4, "abc4"); + }) + + it("crash5", function () { + testParser(abc5, expected5, "abc5"); + }) + + it("crash6", function () { + testParser(abc6, expected6, "abc6"); + }) + + it("crash7", function () { + testParser(abc7, expected7, "abc7"); + }) + + it("crash8", function () { + testParser(abc8, expected8, "abc8"); + }) + + it("crash9", function () { + testParser(abc9, expected9, "abc9"); + }) + + function testParser(abc, expectedLines, comment) { + var visualObj = abcjs.renderAbc("paper", abc); + var testIndex = 0 + for (var lineNum = 0; lineNum < visualObj[0].lines.length; lineNum++) { + var line = visualObj[0].lines[lineNum] + for (var staffNum = 0; staffNum < line.staff.length; staffNum++) { + for (var voiceNum = 0; voiceNum < line.staff[staffNum].voices.length; voiceNum++) { + var line1 = line.staff[staffNum].voices[voiceNum] + var expected = expectedLines[testIndex++] + console.log(lineNum, staffNum, line1) + chai.assert.equal(line1.length, expected.length, "number of elements doesn't match") + for (var i = 0; i < expected.length; i++) { + var expectedEl = expected[i] + var keys = Object.keys(expectedEl).sort() + var foundEl = line1[i] + var foundKeys = Object.keys(foundEl).sort() + foundKeys = foundKeys.filter(k => { + return k !== 'averagepitch' && k !== 'endChar' && k !== 'maxpitch' && k !== 'minpitch' && k !== 'startChar' && k !== 'abselem' + }) + console.log(foundKeys) + chai.assert.deepStrictEqual(foundKeys, keys, 'keys mismatch ' +comment); + for (var j = 0; j < keys.length; j++) { + var expectedAttr = expectedEl[keys[j]] + var foundAttr = line1[i][keys[j]] + console.log(JSON.stringify(foundAttr)) + chai.assert.deepStrictEqual(foundAttr, expectedAttr, keys[j]+' '+i+' '+' '+' '+comment); + } + } + } + } + } + } +}) diff --git a/tests/visual/selection.test.js b/tests/visual/selection.test.js new file mode 100644 index 0000000000000000000000000000000000000000..1b4626fd8a9142185bd07ab8f9f2ce60577ca3d3 --- /dev/null +++ b/tests/visual/selection.test.js @@ -0,0 +1,7778 @@ +describe("Selection", function() { + var abcMultiple = 'X:1\n' + +'M:4/4\n' + +'L:1/16\n' + +'%%titlefont Times New Roman 22.0\n' + +'%%vocalfont Helvetica 10.0\n' + +'%%voicefont Helvetica-Bold 10.0\n' + +'%%measurefont Times-Italic 11\n' + +'%%partsfont box\n' + +'%%stretchlast .7\n' + +'%%musicspace 0\n' + +'%%barnumbers 1\n' + +'T: Selection Test\n' + +'T: Everything should be selectable\n' + +'C: public domain\n' + +'R: Hit it\n' + +'A: Yours Truly\n' + +'S: My own testing\n' + +'W: Now is the time for all good men\n' + +'W:\n' + +'W: To come to the aid of their party.\n' + +'H: This shows every type of thing that can possibly be drawn.\n' + +'H:\n' + +'H: And two lines of history!\n' + +'Q: "Easy Swing" 1/4=140\n' + +'P: AABB\n' + +'%%staves {(PianoRightHand extra) (PianoLeftHand)}\n' + +'V:PianoRightHand clef=treble name=RH\n' + +'V:PianoLeftHand clef=bass name=LH\n' + +'K:Bb\n' + +'P:A\n' + +'%%text there is some random text\n' + +'[V: PianoRightHand] !mp![b8B8d8] f3g f4|!<(![d12b12] !<)![b4g4]|z4 !<(! (bfdf) (3B2d2c2 !<)!B4|[Q:"left" 1/4=170"right"]!f![c4f4] z4 [b8d8]| !p![G8e8] Tu[c8f8]|!<(![d12f12] !<)!g4|\n' + +'!f!a4 [g4b4] z4 =e4|[A8c8f8] d8|1 [c8F8] [B4G4] z4|[d12B12] A4|!>(![D8A8] Bcde fAB!>)!c|!mp!d16|]\n' + +'w:Strang- ers in the night\n' + +'[V: extra] B,4- B,4- B,4 B,4 | "Bb"{C}B,4 {CD}B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 |\n' + +'B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |"^annotation"B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |\n' + +'[V: PianoLeftHand] B,6 D2 [F,8F8A,8]|B,2B,,2 C,4 D,4 E,F,G,2|F,2A,2 D4 D4 G,2E,2|[C4F,4A,4] z4 [F8B,8]|G,8 A,8|A,12 B,G,D,E,|\n' + +'F,G,A,F, (G,A,B,G,) C4 C4|[C,8A,8] [F8F,8B,8]|A,3C B,3D G,F,E,D, F,2A,2|D,2C,2 B,,2A,,2 G,,4 F,,A,,C,F,|F,,6 D,,2 [D,4G,,4] z4|B,,16|]\n'; + + var expectedMultiple = [ + { + "draggable": false, + "svgEl": { + "x": "385", + "y": "51.56", + "selectable": "true", + "tabindex": "0", + "data-index": "0" + }, + "abcEl": { + "el_type": "title", + "name":"title", + "startChar":202, + "endChar":219, + "text": "Selection Test" + }, + "size": { + "x": 304, + "y": 26, + "width": 163, + "height": 32 + } + }, + { + "draggable": false, + "svgEl": { + "x": "385", + "y": "82.34", + "selectable": "true", + "tabindex": "0", + "data-index": "1" + }, + "abcEl": { + "el_type": "subtitle", + "name":"subtitle", + "startChar":220, + "endChar":254, + "text": "Everything should be selectable" + }, + "size": { + "x": 251, + "y": 63, + "width": 268, + "height": 24 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "113.9", + "selectable": "true", + "tabindex": "0", + "data-index": "2" + }, + "abcEl": { + "el_type": "rhythm", + "name":"rhythm", + "startChar":272, + "endChar":281, + "text": "Hit it" + }, + "size": { + "x": 14, + "y": 97, + "width": 40, + "height": 22 + } + }, + { + "draggable": false, + "svgEl": { + "x": "755", + "y": "113.9", + "selectable": "true", + "tabindex": "0", + "data-index": "3" + }, + "abcEl": { + "el_type": "composer", + "name":"composer", + "startChar":255, + "endChar":271, + "text": "public domain" + }, + "size": { + "x": 644, + "y": 97, + "width": 111, + "height": 22 + } + }, + { + "draggable": false, + "svgEl": { + "x": "755", + "y": "137.9", + "selectable": "true", + "tabindex": "0", + "data-index": "4" + }, + "abcEl": { + "el_type": "author", + "name":"author", + "startChar":282, + "endChar":296, + "text": "Yours Truly" + }, + "size": { + "x": 668, + "y": 121, + "width": 87, + "height": 22 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "5" + }, + "abcEl": { + "el_type": "partOrder", + "name":"part-order", + "startChar":510, + "endChar":517, + "text": "AABB" + }, + "size": { + "x": 15, + "y": 143, + "width": 61, + "height": 28 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "208.4", + "selectable": "true", + "tabindex": "0", + "data-index": "6" + }, + "abcEl": { + "el_type": "freeText", + "name":"free-text", + "startChar":648, + "endChar":680, + "text": "there is some random text" + }, + "size": { + "x": 15, + "y": 189, + "width": 217, + "height": 24 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "7" + }, + "abcEl": { + "el_type": "brace", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 43, + "y": 369, + "width": 8, + "height": 155 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "376.74", + "selectable": "true", + "tabindex": "0", + "data-index": "8" + }, + "abcEl": { + "el_type": "voiceName", + "startChar": -1, + "endChar": -1, + "text": "RH" + }, + "size": { + "x": 15, + "y": 365, + "width": 19, + "height": 15 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "9" + }, + "abcEl": { + "type": "treble", + "verticalPos": 0, + "clefPos": 4, + "el_type": "clef" + }, + "size": { + "x": 58, + "y": 354, + "width": 19, + "height": 57 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "10" + }, + "abcEl": { + "accidentals": [ + { + "acc": "flat", + "note": "B", + "verticalPos": 6 + }, + { + "acc": "flat", + "note": "e", + "verticalPos": 9 + } + ], + "root": "B", + "acc": "b", + "mode": "", + "el_type":"keySignature", + }, + "size": { + "x": 88, + "y": 358, + "width": 16, + "height": 30 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "11" + }, + "abcEl": { + "type": "specified", + "value": [ + { + "num": "4", + "den": "4" + } + ], + "el_type": "timeSignature" + }, + "size": { + "x": 114, + "y": 369, + "width": 12, + "height": 30 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "12" + }, + "abcEl": { + "startChar":486, + "endChar":509, + "preString": "Easy Swing", + "duration": [ + 0.25 + ], + "bpm": 140, + "type": "tempo", + "el_type": "tempo" + }, + "size": { + "x": 136, + "y": 215, + "width": 167, + "height": 23 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "13" + }, + "abcEl": { + "title": "A", + "el_type": "part", + "startChar": 644, + "endChar": 647 + }, + "size": { + "x": 136, + "y": 238, + "width": 19, + "height": 28 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "14" + }, + "abcEl": { + "decoration": [ + "mp" + ], + "duration": 0.5, + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 6 + }, + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": 13, + "name": "b", + "verticalPos": 13, + "highestVert": 13 + } + ], + "el_type": "note", + "startChar": 700, + "endChar": 714, + "averagepitch": 9, + "minpitch": 6, + "maxpitch": 13 + }, + "size": { + "x": 134, + "y": 330, + "width": 14, + "height": 58 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "15" + }, + "abcEl": { + "pitches": [ + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 16 + } + ], + "duration": 0.1875, + "el_type": "note", + "startChar": 714, + "endChar": 716, + "startBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 176, + "y": 337, + "width": 16, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "16" + }, + "abcEl": { + "pitches": [ + { + "pitch": 11, + "name": "g", + "verticalPos": 11, + "highestVert": 17 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 716, + "endChar": 718, + "endBeam": true, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 193, + "y": 329, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "17" + }, + "abcEl": { + "pitches": [ + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 16 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 718, + "endChar": 720, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 204, + "y": 337, + "width": 10, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "18" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 2, + "el_type": "bar", + "startChar": 720, + "endChar": 721 + }, + "size": { + "x": 216, + "y": 346, + "width": 8, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "19" + }, + "abcEl": { + "decoration": [ + "crescendo(" + ], + "duration": 0.75, + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": 13, + "name": "b", + "verticalPos": 13, + "highestVert": 13 + } + ], + "el_type": "note", + "startChar": 721, + "endChar": 734, + "averagepitch": 10.5, + "minpitch": 8, + "maxpitch": 13 + }, + "size": { + "x": 242, + "y": 325, + "width": 19, + "height": 51 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "20" + }, + "abcEl": { + "decoration": [ + "crescendo)" + ], + "duration": 0.25, + "pitches": [ + { + "pitch": 11, + "name": "g", + "verticalPos": 11, + "highestVert": 11 + }, + { + "pitch": 13, + "name": "b", + "verticalPos": 13, + "highestVert": 13 + } + ], + "el_type": "note", + "startChar": 734, + "endChar": 744, + "averagepitch": 12, + "minpitch": 11, + "maxpitch": 13 + }, + "size": { + "x": 315, + "y": 325, + "width": 14, + "height": 39 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "21" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 3, + "el_type": "bar", + "startChar": 744, + "endChar": 745 + }, + "size": { + "x": 350, + "y": 346, + "width": 8, + "height": 49 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "22" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 745, + "endChar": 748, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 365, + "y": 352, + "width": 8, + "height": 22 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "23" + }, + "abcEl": { + "decoration": [ + "crescendo(" + ], + "pitches": [ + { + "pitch": 13, + "name": "b", + "startSlur": [ + { + "label": 101 + } + ], + "verticalPos": 13, + "highestVert": 19 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 748, + "endChar": 755, + "startBeam": true, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": { + "x": 387, + "y": 318, + "width": 14, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "24" + }, + "abcEl": { + "pitches": [ + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 16 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 755, + "endChar": 756, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 399, + "y": 320, + "width": 10, + "height": 48 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "25" + }, + "abcEl": { + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 14 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 756, + "endChar": 757, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 410, + "y": 323, + "width": 10, + "height": 53 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "26" + }, + "abcEl": { + "pitches": [ + { + "pitch": 10, + "name": "f", + "endSlur": [ + 101 + ], + "verticalPos": 10, + "highestVert": 16 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 757, + "endChar": 760, + "endBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 421, + "y": 325, + "width": 10, + "height": 43 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "27" + }, + "abcEl": { + "startTriplet": 3, + "tripletMultiplier": 0.6666666666666666, + "tripletR":3, + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 12 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 760, + "endChar": 764, + "startBeam": true, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 432, + "y": 345, + "width": 10, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "28" + }, + "abcEl": { + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 14 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 764, + "endChar": 766, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 443, + "y": 343, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "29" + }, + "abcEl": { + "pitches": [ + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 13 + } + ], + "duration": 0.125, + "endTriplet": true, + "el_type": "note", + "startChar": 766, + "endChar": 769, + "endBeam": true, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 453, + "y": 341, + "width": 10, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "30" + }, + "abcEl": { + "decoration": [ + "crescendo)" + ], + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 12 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 769, + "endChar": 775, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 464, + "y": 352, + "width": 10, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "31" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 4, + "el_type": "bar", + "startChar": 775, + "endChar": 776 + }, + "size": { + "x": 488, + "y": 346, + "width": 8, + "height": 49 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "32" + }, + "abcEl": { + "startChar": 776, + "endChar": 801, + "preString": "left", + "duration": [ + 0.25 + ], + "bpm": 170, + "postString": "right", + "el_type": "tempo", + "type": "tempo" + }, + "size": { + "x": 503, + "y": 212, + "width": 145, + "height": 22 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "33" + }, + "abcEl": { + "decoration": [ + "f" + ], + "duration": 0.25, + "pitches": [ + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 7 + }, + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 10 + } + ], + "el_type": "note", + "startChar": 801, + "endChar": 811, + "averagepitch": 8.5, + "minpitch": 7, + "maxpitch": 10 + }, + "size": { + "x": 503, + "y": 337, + "width": 10, + "height": 43 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "34" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 811, + "endChar": 814, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 519, + "y": 352, + "width": 8, + "height": 22 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "35" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": 13, + "name": "b", + "verticalPos": 13, + "highestVert": 13 + } + ], + "el_type": "note", + "startChar": 814, + "endChar": 820, + "averagepitch": 10.5, + "minpitch": 8, + "maxpitch": 13 + }, + "size": { + "x": 534, + "y": 325, + "width": 14, + "height": 51 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "36" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 5, + "el_type": "bar", + "startChar": 820, + "endChar": 821 + }, + "size": { + "x": 565, + "y": 346, + "width": 8, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "37" + }, + "abcEl": { + "decoration": [ + "p" + ], + "duration": 0.5, + "pitches": [ + { + "pitch": 4, + "name": "G", + "verticalPos": 4, + "highestVert": 4 + }, + { + "pitch": 9, + "name": "e", + "verticalPos": 9, + "highestVert": 9 + } + ], + "el_type": "note", + "startChar": 821, + "endChar": 832, + "averagepitch": 6.5, + "minpitch": 4, + "maxpitch": 9 + }, + "size": { + "x": 580, + "y": 341, + "width": 10, + "height": 51 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "38" + }, + "abcEl": { + "decoration": [ + "trill", + "upbow" + ], + "duration": 0.5, + "pitches": [ + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 7 + }, + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 10 + } + ], + "el_type": "note", + "startChar": 832, + "endChar": 840, + "averagepitch": 8.5, + "minpitch": 7, + "maxpitch": 10 + }, + "size": { + "x": 608, + "y": 299, + "width": 18, + "height": 81 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "39" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 6, + "el_type": "bar", + "startChar": 840, + "endChar": 841 + }, + "size": { + "x": 642, + "y": 346, + "width": 8, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "40" + }, + "abcEl": { + "decoration": [ + "crescendo(" + ], + "duration": 0.75, + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 10 + } + ], + "el_type": "note", + "startChar": 841, + "endChar": 854, + "averagepitch": 9, + "minpitch": 8, + "maxpitch": 10 + }, + "size": { + "x": 657, + "y": 337, + "width": 17, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "41" + }, + "abcEl": { + "decoration": [ + "crescendo)" + ], + "pitches": [ + { + "pitch": 11, + "name": "g", + "verticalPos": 11, + "highestVert": 17 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 854, + "endChar": 860, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 707, + "y": 333, + "width": 10, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "42" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 860, + "endChar": 861 + }, + "size": { + "x": 755, + "y": 364, + "width": 1, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "43" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1, + "decoration": "mp" + }, + "size": { + "x": 134, + "y": 444, + "width": 27, + "height": 13 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "44" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 244, + "y": 442, + "width": 72, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "45" + }, + "abcEl": { + "el_type": "slur", + "startChar": 747, + "endChar": 761 + }, + "size": { + "x": 399, + "y": 330, + "width": 26, + "height": 5 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "46" + }, + "abcEl": { + "el_type": "triplet", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 444, + "y": 323, + "width": 8, + "height": 17 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "47" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 389, + "y": 442, + "width": 76, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "48" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1, + "decoration": "f" + }, + "size": { + "x": 500, + "y": 439, + "width": 16, + "height": 19 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "49" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1, + "decoration": "p" + }, + "size": { + "x": 576, + "y": 444, + "width": 15, + "height": 13 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "50" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 657, + "y": 442, + "width": 50, + "height": 8 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "51" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "startTie": {}, + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 997, + "endChar": 1003, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 134, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "52" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "startTie": {}, + "endTie": true, + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1003, + "endChar": 1008, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 150, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "53" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "endTie": true, + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1008, + "endChar": 1012, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 174, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "54" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1012, + "endChar": 1016, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 202, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "55" + }, + "abcEl": { + "chord": [ + { + "name": "B♭", + "position": "default" + } + ], + "gracenotes": [ + { + "pitch": 0, + "name": "C", + "duration": 0.125, + "verticalPos": 0 + } + ], + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1017, + "endChar": 1029, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 233, + "y": 279, + "width": 29, + "height": 155 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "56" + }, + "abcEl": { + "gracenotes": [ + { + "pitch": 0, + "name": "C", + "duration": 0.125, + "verticalPos": 0 + }, + { + "pitch": 1, + "name": "D", + "duration": 0.125, + "verticalPos": 1 + } + ], + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1029, + "endChar": 1037, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 263, + "y": 380, + "width": 33, + "height": 54 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "57" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1037, + "endChar": 1041, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 298, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "58" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1041, + "endChar": 1045, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 315, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "59" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1046, + "endChar": 1051, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 363, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "60" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1051, + "endChar": 1055, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 387, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "61" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1055, + "endChar": 1059, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 430, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "62" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1059, + "endChar": 1063, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 462, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "63" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1064, + "endChar": 1069, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 501, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "64" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1069, + "endChar": 1073, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 517, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "65" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1073, + "endChar": 1077, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 534, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "66" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1077, + "endChar": 1081, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 550, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "67" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1082, + "endChar": 1087, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 578, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "68" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1087, + "endChar": 1091, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 594, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "69" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1091, + "endChar": 1095, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 611, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "70" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1095, + "endChar": 1099, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 627, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "71" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1100, + "endChar": 1105, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 655, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "72" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1105, + "endChar": 1109, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 671, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "73" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1109, + "endChar": 1113, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 688, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "74" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1113, + "endChar": 1117, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 705, + "y": 402, + "width": 14, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "75" + }, + "abcEl": { + "el_type": "slur", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 142, + "y": 411, + "width": 15, + "height": 5 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "76" + }, + "abcEl": { + "el_type": "slur", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 158, + "y": 411, + "width": 21, + "height": 6 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "77" + }, + "abcEl": { + "el_type": "slur", + "startChar": 1016, + "endChar": 1030 + }, + "size": { + "x": 237, + "y": 409, + "width": 11, + "height": 6 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "78" + }, + "abcEl": { + "el_type": "slur", + "startChar": 1028, + "endChar": 1038 + }, + "size": { + "x": 267, + "y": 409, + "width": 21, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "509.25", + "selectable": "true", + "tabindex": "0", + "data-index": "79" + }, + "abcEl": { + "el_type": "voiceName", + "startChar": -1, + "endChar": -1, + "text": "LH" + }, + "size": { + "x": 15, + "y": 497, + "width": 17, + "height": 15 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "80" + }, + "abcEl": { + "type": "bass", + "verticalPos": -12, + "clefPos": 8, + "el_type": "clef" + }, + "size": { + "x": 58, + "y": 488, + "width": 20, + "height": 23 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "81" + }, + "abcEl": { + "accidentals": [ + { + "acc": "flat", + "note": "B", + "verticalPos": 4 + }, + { + "acc": "flat", + "note": "e", + "verticalPos": 7 + } + ], + "root": "B", + "acc": "b", + "mode": "", + "el_type": "keySignature" + }, + "size": { + "x": 88, + "y": 486, + "width": 16, + "height": 30 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "82" + }, + "abcEl": { + "type": "specified", + "value": [ + { + "num": "4", + "den": "4" + } + ], + "el_type": "timeSignature" + }, + "size": { + "x": 114, + "y": 489, + "width": 12, + "height": 30 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "83" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + } + ], + "duration": 0.375, + "el_type": "note", + "startChar": 1254, + "endChar": 1259, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 136, + "y": 480, + "width": 16, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "84" + }, + "abcEl": { + "pitches": [ + { + "pitch": 1, + "name": "D", + "verticalPos": 13, + "highestVert": 13 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1259, + "endChar": 1262, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": { + "x": 162, + "y": 473, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "85" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + }, + { + "pitch": 3, + "name": "F", + "verticalPos": 15, + "highestVert": 15 + } + ], + "el_type": "note", + "startChar": 1262, + "endChar": 1272, + "averagepitch": 11, + "minpitch": 8, + "maxpitch": 15 + }, + "size": { + "x": 174, + "y": 465, + "width": 14, + "height": 58 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "86" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1272, + "endChar": 1273 + }, + "size": { + "x": 220, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "87" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1273, + "endChar": 1276, + "startBeam": true, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 244, + "y": 480, + "width": 10, + "height": 56 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "88" + }, + "abcEl": { + "pitches": [ + { + "pitch": -8, + "name": "B,,", + "verticalPos": 4, + "highestVert": 4 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1276, + "endChar": 1281, + "endBeam": true, + "averagepitch": 4, + "minpitch": 4, + "maxpitch": 4 + }, + "size": { + "x": 256, + "y": 507, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "89" + }, + "abcEl": { + "pitches": [ + { + "pitch": -7, + "name": "C,", + "verticalPos": 5, + "highestVert": 11 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1281, + "endChar": 1285, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": { + "x": 284, + "y": 481, + "width": 10, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "90" + }, + "abcEl": { + "pitches": [ + { + "pitch": -6, + "name": "D,", + "verticalPos": 6, + "highestVert": 6 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1285, + "endChar": 1289, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 300, + "y": 500, + "width": 10, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "91" + }, + "abcEl": { + "pitches": [ + { + "pitch": -5, + "name": "E,", + "verticalPos": 7, + "highestVert": 7 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1289, + "endChar": 1291, + "startBeam": true, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 317, + "y": 496, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "92" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1291, + "endChar": 1293, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 328, + "y": 492, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "93" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1293, + "endChar": 1296, + "endBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 338, + "y": 488, + "width": 10, + "height": 37 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "94" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1296, + "endChar": 1297 + }, + "size": { + "x": 354, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "95" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1297, + "endChar": 1300, + "startBeam": true, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 365, + "y": 492, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "96" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1300, + "endChar": 1304, + "endBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 377, + "y": 484, + "width": 10, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "97" + }, + "abcEl": { + "pitches": [ + { + "pitch": 1, + "name": "D", + "verticalPos": 13, + "highestVert": 13 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1304, + "endChar": 1307, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": { + "x": 387, + "y": 473, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "98" + }, + "abcEl": { + "pitches": [ + { + "pitch": 1, + "name": "D", + "verticalPos": 13, + "highestVert": 13 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1307, + "endChar": 1310, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": { + "x": 430, + "y": 473, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "99" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1310, + "endChar": 1313, + "startBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 464, + "y": 488, + "width": 10, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "100" + }, + "abcEl": { + "pitches": [ + { + "pitch": -5, + "name": "E,", + "verticalPos": 7, + "highestVert": 7 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1313, + "endChar": 1316, + "endBeam": true, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 476, + "y": 496, + "width": 10, + "height": 33 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "101" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1316, + "endChar": 1317 + }, + "size": { + "x": 492, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "102" + }, + "abcEl": { + "duration": 0.25, + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + }, + { + "pitch": 0, + "name": "C", + "verticalPos": 12, + "highestVert": 12 + } + ], + "el_type": "note", + "startChar": 1317, + "endChar": 1328, + "averagepitch": 10, + "minpitch": 8, + "maxpitch": 12 + }, + "size": { + "x": 501, + "y": 476, + "width": 14, + "height": 47 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "103" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 1328, + "endChar": 1331, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 519, + "y": 492, + "width": 8, + "height": 21 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "104" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + }, + { + "pitch": 3, + "name": "F", + "verticalPos": 15, + "highestVert": 15 + } + ], + "el_type": "note", + "startChar": 1331, + "endChar": 1338, + "averagepitch": 13, + "minpitch": 11, + "maxpitch": 15 + }, + "size": { + "x": 534, + "y": 465, + "width": 14, + "height": 47 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "105" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1338, + "endChar": 1339 + }, + "size": { + "x": 569, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "106" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.5, + "el_type": "note", + "startChar": 1339, + "endChar": 1343, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 580, + "y": 488, + "width": 10, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "107" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.5, + "el_type": "note", + "startChar": 1343, + "endChar": 1346, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 613, + "y": 484, + "width": 10, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "108" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1346, + "endChar": 1347 + }, + "size": { + "x": 646, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "109" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.75, + "el_type": "note", + "startChar": 1347, + "endChar": 1352, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 657, + "y": 483, + "width": 17, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "110" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1352, + "endChar": 1354, + "startBeam": true, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 707, + "y": 480, + "width": 10, + "height": 45 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "111" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1354, + "endChar": 1356, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 717, + "y": 488, + "width": 10, + "height": 40 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "112" + }, + "abcEl": { + "pitches": [ + { + "pitch": -6, + "name": "D,", + "verticalPos": 6, + "highestVert": 6 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1356, + "endChar": 1358, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 728, + "y": 500, + "width": 10, + "height": 30 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "113" + }, + "abcEl": { + "pitches": [ + { + "pitch": -5, + "name": "E,", + "verticalPos": 7, + "highestVert": 7 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1358, + "endChar": 1360, + "endBeam": true, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 739, + "y": 496, + "width": 10, + "height": 37 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "114" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1360, + "endChar": 1361 + }, + "size": { + "x": 755, + "y": 395, + "width": 1, + "height": 124 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "115" + }, + "abcEl": { + "el_type": "brace", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 15, + "y": 636, + "width": 8, + "height": 151 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "116" + }, + "abcEl": { + "type": "treble", + "verticalPos": 0, + "clefPos": 4, + "el_type": "clef" + }, + "size": { + "x": 25, + "y": 618, + "width": 24, + "height": 61 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "117" + }, + "abcEl": { + "accidentals": [ + { + "acc": "flat", + "note": "B", + "verticalPos": 6 + }, + { + "acc": "flat", + "note": "e", + "verticalPos": 9 + } + ], + "root": "B", + "acc": "b", + "mode": "", + "el_type": "keySignature" + }, + "size": { + "x": 59, + "y": 626, + "width": 16, + "height": 30 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "118" + }, + "abcEl": { + "decoration": [ + "f" + ], + "pitches": [ + { + "pitch": 12, + "name": "a", + "verticalPos": 12, + "highestVert": 18 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 862, + "endChar": 868, + "lyric": [ + { + "syllable": "Strang", + "divider": "-" + } + ], + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12 + }, + "size": { + "x": 86, + "y": 601, + "width": 43, + "height": 125 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "119" + }, + "abcEl": { + "duration": 0.25, + "pitches": [ + { + "pitch": 11, + "name": "g", + "verticalPos": 11, + "highestVert": 11 + }, + { + "pitch": 13, + "name": "b", + "verticalPos": 13, + "highestVert": 13 + } + ], + "el_type": "note", + "startChar": 868, + "endChar": 875, + "lyric": [ + { + "syllable": "ers", + "divider": " " + } + ], + "averagepitch": 12, + "minpitch": 11, + "maxpitch": 13 + }, + "size": { + "x": 141, + "y": 597, + "width": 21, + "height": 128 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "120" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 875, + "endChar": 878, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 193, + "y": 624, + "width": 8, + "height": 21 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "121" + }, + "abcEl": { + "pitches": [ + { + "accidental": "natural", + "pitch": 9, + "name": "=e", + "verticalPos": 9, + "highestVert": 15 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 878, + "endChar": 881, + "lyric": [ + { + "syllable": "in", + "divider": " " + } + ], + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 205, + "y": 613, + "width": 17, + "height": 113 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "122" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 8, + "el_type": "bar", + "startChar": 881, + "endChar": 882 + }, + "size": { + "x": 225, + "y": 618, + "width": 8, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "123" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": 5, + "name": "A", + "verticalPos": 5, + "highestVert": 5 + }, + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 7 + }, + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 10 + } + ], + "el_type": "note", + "startChar": 882, + "endChar": 891, + "lyric": [ + { + "syllable": "the", + "divider": " " + } + ], + "averagepitch": 7.333333333333333, + "minpitch": 5, + "maxpitch": 10 + }, + "size": { + "x": 239, + "y": 609, + "width": 19, + "height": 117 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "124" + }, + "abcEl": { + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 14 + } + ], + "duration": 0.5, + "el_type": "note", + "startChar": 891, + "endChar": 893, + "lyric": [ + { + "syllable": "night", + "divider": " " + } + ], + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 260, + "y": 617, + "width": 28, + "height": 109 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "125" + }, + "abcEl": { + "type": "bar_thin", + "startEnding": "1", + "barNumber": 9, + "el_type": "bar", + "startChar": 893, + "endChar": 895 + }, + "size": { + "x": 297, + "y": 618, + "width": 8, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "126" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": 3, + "name": "F", + "verticalPos": 3, + "highestVert": 3 + }, + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 7 + } + ], + "el_type": "note", + "startChar": 895, + "endChar": 903, + "averagepitch": 5, + "minpitch": 3, + "maxpitch": 7 + }, + "size": { + "x": 330, + "y": 620, + "width": 10, + "height": 47 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "127" + }, + "abcEl": { + "duration": 0.25, + "pitches": [ + { + "pitch": 4, + "name": "G", + "verticalPos": 4, + "highestVert": 4 + }, + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 6 + } + ], + "el_type": "note", + "startChar": 903, + "endChar": 910, + "averagepitch": 5, + "minpitch": 4, + "maxpitch": 6 + }, + "size": { + "x": 386, + "y": 624, + "width": 10, + "height": 39 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "128" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 910, + "endChar": 912, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 429, + "y": 624, + "width": 8, + "height": 21 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "129" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 10, + "el_type": "bar", + "startChar": 912, + "endChar": 913 + }, + "size": { + "x": 448, + "y": 618, + "width": 15, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "130" + }, + "abcEl": { + "duration": 0.75, + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 6 + }, + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + } + ], + "el_type": "note", + "startChar": 913, + "endChar": 922, + "averagepitch": 7, + "minpitch": 6, + "maxpitch": 8 + }, + "size": { + "x": 467, + "y": 617, + "width": 17, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "131" + }, + "abcEl": { + "pitches": [ + { + "pitch": 5, + "name": "A", + "verticalPos": 5, + "highestVert": 11 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 922, + "endChar": 924, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": { + "x": 521, + "y": 628, + "width": 10, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "132" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 11, + "el_type": "bar", + "startChar": 924, + "endChar": 925 + }, + "size": { + "x": 562, + "y": 618, + "width": 14, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "133" + }, + "abcEl": { + "decoration": [ + "diminuendo(" + ], + "duration": 0.5, + "pitches": [ + { + "pitch": 1, + "name": "D", + "verticalPos": 1, + "highestVert": 1 + }, + { + "pitch": 5, + "name": "A", + "verticalPos": 5, + "highestVert": 5 + } + ], + "el_type": "note", + "startChar": 925, + "endChar": 936, + "averagepitch": 3, + "minpitch": 1, + "maxpitch": 5 + }, + "size": { + "x": 580, + "y": 628, + "width": 10, + "height": 47 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "134" + }, + "abcEl": { + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 12 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 936, + "endChar": 937, + "startBeam": true, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 615, + "y": 612, + "width": 10, + "height": 43 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "135" + }, + "abcEl": { + "pitches": [ + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 13 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 937, + "endChar": 938, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 626, + "y": 610, + "width": 10, + "height": 42 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "136" + }, + "abcEl": { + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 14 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 938, + "endChar": 939, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 637, + "y": 607, + "width": 10, + "height": 40 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "137" + }, + "abcEl": { + "pitches": [ + { + "pitch": 9, + "name": "e", + "verticalPos": 9, + "highestVert": 15 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 939, + "endChar": 941, + "endBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 647, + "y": 605, + "width": 10, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "138" + }, + "abcEl": { + "pitches": [ + { + "pitch": 10, + "name": "f", + "verticalPos": 10, + "highestVert": 16 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 941, + "endChar": 942, + "startBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 658, + "y": 601, + "width": 10, + "height": 39 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "139" + }, + "abcEl": { + "pitches": [ + { + "pitch": 5, + "name": "A", + "verticalPos": 5, + "highestVert": 11 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 942, + "endChar": 943, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": { + "x": 669, + "y": 604, + "width": 10, + "height": 56 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "140" + }, + "abcEl": { + "pitches": [ + { + "pitch": 6, + "name": "B", + "verticalPos": 6, + "highestVert": 12 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 943, + "endChar": 944, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 680, + "y": 606, + "width": 10, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "141" + }, + "abcEl": { + "decoration": [ + "diminuendo)" + ], + "pitches": [ + { + "pitch": 7, + "name": "c", + "verticalPos": 7, + "highestVert": 13 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 944, + "endChar": 949, + "endBeam": true, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 691, + "y": 609, + "width": 10, + "height": 43 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "142" + }, + "abcEl": { + "type": "bar_thin", + "barNumber": 12, + "el_type": "bar", + "startChar": 949, + "endChar": 950 + }, + "size": { + "x": 699, + "y": 618, + "width": 15, + "height": 49 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "143" + }, + "abcEl": { + "decoration": [ + "mp" + ], + "pitches": [ + { + "pitch": 8, + "name": "d", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 1, + "el_type": "note", + "startChar": 950, + "endChar": 957, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 717, + "y": 640, + "width": 15, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "144" + }, + "abcEl": { + "type": "bar_thin_thick", + "endEnding": true, + "el_type": "bar", + "startChar": 957, + "endChar": 959 + }, + "size": { + "x": 766, + "y": 636, + "width": 8, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "145" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1, + "decoration": "f" + }, + "size": { + "x": 104, + "y": 542, + "width": 16, + "height": 19 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "146" + }, + "abcEl": { + "el_type": "ending", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 301, + "y": 575, + "width": 468, + "height": 23 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "147" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1 + }, + "size": { + "x": 580, + "y": 544, + "width": 110, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "148" + }, + "abcEl": { + "el_type": "dynamicDecoration", + "startChar": -1, + "endChar": -1, + "decoration": "mp" + }, + "size": { + "x": 716, + "y": 547, + "width": 27, + "height": 13 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "149" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1119, + "endChar": 1123, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 105, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "150" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1123, + "endChar": 1127, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 148, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "151" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1127, + "endChar": 1131, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 191, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "152" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1131, + "endChar": 1135, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 210, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "153" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1136, + "endChar": 1141, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 246, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "154" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1141, + "endChar": 1145, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 257, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "155" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1145, + "endChar": 1149, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 272, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "156" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1149, + "endChar": 1153, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 283, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "157" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1154, + "endChar": 1158, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 328, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "158" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1158, + "endChar": 1162, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 356, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "159" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1162, + "endChar": 1166, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 384, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "160" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1166, + "endChar": 1170, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 427, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "161" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1171, + "endChar": 1175, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 465, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "162" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1175, + "endChar": 1179, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 487, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "163" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1179, + "endChar": 1183, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 508, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "164" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1183, + "endChar": 1187, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 519, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "165" + }, + "abcEl": { + "chord": [ + { + "name": "annotation", + "position": "above" + } + ], + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1188, + "endChar": 1205, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 578, + "y": 577, + "width": 77, + "height": 128 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "166" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1205, + "endChar": 1209, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 589, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "167" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1209, + "endChar": 1213, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 613, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "168" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1213, + "endChar": 1217, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 656, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "169" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1218, + "endChar": 1222, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 715, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "170" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1222, + "endChar": 1226, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 726, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "171" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1226, + "endChar": 1230, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 737, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "172" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": -1, + "highestVert": -1 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1230, + "endChar": 1234, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 748, + "y": 674, + "width": 14, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "173" + }, + "abcEl": { + "type": "bass", + "verticalPos": -12, + "clefPos": 8, + "el_type": "clef" + }, + "size": { + "x": 30, + "y": 755, + "width": 20, + "height": 23 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "174" + }, + "abcEl": { + "accidentals": [ + { + "acc": "flat", + "note": "B", + "verticalPos": 4 + }, + { + "acc": "flat", + "note": "e", + "verticalPos": 7 + } + ], + "root": "B", + "acc": "b", + "mode": "", + "el_type": "keySignature" + }, + "size": { + "x": 59, + "y": 753, + "width": 16, + "height": 30 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "175" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1362, + "endChar": 1364, + "startBeam": true, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 107, + "y": 759, + "width": 10, + "height": 29 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "176" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1364, + "endChar": 1366, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 118, + "y": 755, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "177" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1366, + "endChar": 1368, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 129, + "y": 751, + "width": 10, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "178" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1368, + "endChar": 1371, + "endBeam": true, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 139, + "y": 759, + "width": 10, + "height": 29 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "179" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "startSlur": [ + { + "label": 101 + } + ], + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1371, + "endChar": 1374, + "startBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 150, + "y": 755, + "width": 10, + "height": 29 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "180" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1374, + "endChar": 1376, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 161, + "y": 751, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "181" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1376, + "endChar": 1378, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 172, + "y": 748, + "width": 10, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "182" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "endSlur": [ + 101 + ], + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1378, + "endChar": 1382, + "endBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 183, + "y": 755, + "width": 10, + "height": 29 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "183" + }, + "abcEl": { + "pitches": [ + { + "pitch": 0, + "name": "C", + "verticalPos": 12, + "highestVert": 12 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1382, + "endChar": 1385, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12 + }, + "size": { + "x": 191, + "y": 744, + "width": 14, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "184" + }, + "abcEl": { + "pitches": [ + { + "pitch": 0, + "name": "C", + "verticalPos": 12, + "highestVert": 12 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1385, + "endChar": 1387, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12 + }, + "size": { + "x": 210, + "y": 744, + "width": 14, + "height": 31 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "185" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1387, + "endChar": 1388 + }, + "size": { + "x": 228, + "y": 667, + "width": 1, + "height": 120 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "186" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": -7, + "name": "C,", + "verticalPos": 5, + "highestVert": 5 + }, + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "el_type": "note", + "startChar": 1388, + "endChar": 1397, + "averagepitch": 7.5, + "minpitch": 5, + "maxpitch": 10 + }, + "size": { + "x": 248, + "y": 751, + "width": 10, + "height": 51 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "187" + }, + "abcEl": { + "duration": 0.5, + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + }, + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + }, + { + "pitch": 3, + "name": "F", + "verticalPos": 15, + "highestVert": 15 + } + ], + "el_type": "note", + "startChar": 1397, + "endChar": 1407, + "averagepitch": 11.333333333333334, + "minpitch": 8, + "maxpitch": 15 + }, + "size": { + "x": 272, + "y": 732, + "width": 14, + "height": 58 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "188" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1407, + "endChar": 1408 + }, + "size": { + "x": 300, + "y": 667, + "width": 1, + "height": 120 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "189" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.1875, + "el_type": "note", + "startChar": 1408, + "endChar": 1411, + "startBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 330, + "y": 750, + "width": 16, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "190" + }, + "abcEl": { + "pitches": [ + { + "pitch": 0, + "name": "C", + "verticalPos": 12, + "highestVert": 12 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1411, + "endChar": 1413, + "endBeam": true, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12 + }, + "size": { + "x": 345, + "y": 744, + "width": 14, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "191" + }, + "abcEl": { + "pitches": [ + { + "pitch": -1, + "name": "B,", + "verticalPos": 11, + "highestVert": 11 + } + ], + "duration": 0.1875, + "el_type": "note", + "startChar": 1413, + "endChar": 1416, + "startBeam": true, + "averagepitch": 11, + "minpitch": 11, + "maxpitch": 11 + }, + "size": { + "x": 358, + "y": 748, + "width": 16, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "192" + }, + "abcEl": { + "pitches": [ + { + "pitch": 1, + "name": "D", + "verticalPos": 13, + "highestVert": 13 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1416, + "endChar": 1418, + "endBeam": true, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": { + "x": 373, + "y": 740, + "width": 14, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "193" + }, + "abcEl": { + "pitches": [ + { + "pitch": -3, + "name": "G,", + "verticalPos": 9, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1418, + "endChar": 1420, + "startBeam": true, + "averagepitch": 9, + "minpitch": 9, + "maxpitch": 9 + }, + "size": { + "x": 386, + "y": 755, + "width": 10, + "height": 37 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "194" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1420, + "endChar": 1422, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 397, + "y": 759, + "width": 10, + "height": 36 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "195" + }, + "abcEl": { + "pitches": [ + { + "pitch": -5, + "name": "E,", + "verticalPos": 7, + "highestVert": 7 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1422, + "endChar": 1424, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 408, + "y": 763, + "width": 10, + "height": 34 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "196" + }, + "abcEl": { + "pitches": [ + { + "pitch": -6, + "name": "D,", + "verticalPos": 6, + "highestVert": 6 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1424, + "endChar": 1427, + "endBeam": true, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 418, + "y": 767, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "197" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 8 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1427, + "endChar": 1430, + "startBeam": true, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 429, + "y": 759, + "width": 10, + "height": 33 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "198" + }, + "abcEl": { + "pitches": [ + { + "pitch": -2, + "name": "A,", + "verticalPos": 10, + "highestVert": 10 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1430, + "endChar": 1433, + "endBeam": true, + "averagepitch": 10, + "minpitch": 10, + "maxpitch": 10 + }, + "size": { + "x": 440, + "y": 751, + "width": 10, + "height": 37 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "199" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1433, + "endChar": 1434 + }, + "size": { + "x": 456, + "y": 667, + "width": 1, + "height": 120 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "200" + }, + "abcEl": { + "pitches": [ + { + "pitch": -6, + "name": "D,", + "verticalPos": 6, + "highestVert": 12 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1434, + "endChar": 1437, + "startBeam": true, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": { + "x": 467, + "y": 740, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "201" + }, + "abcEl": { + "pitches": [ + { + "pitch": -7, + "name": "C,", + "verticalPos": 5, + "highestVert": 11 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1437, + "endChar": 1441, + "endBeam": true, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": { + "x": 478, + "y": 744, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "202" + }, + "abcEl": { + "pitches": [ + { + "pitch": -8, + "name": "B,,", + "verticalPos": 4, + "highestVert": 10 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1441, + "endChar": 1445, + "startBeam": true, + "averagepitch": 4, + "minpitch": 4, + "maxpitch": 4 + }, + "size": { + "x": 489, + "y": 748, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "203" + }, + "abcEl": { + "pitches": [ + { + "pitch": -9, + "name": "A,,", + "verticalPos": 3, + "highestVert": 9 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1445, + "endChar": 1450, + "endBeam": true, + "averagepitch": 3, + "minpitch": 3, + "maxpitch": 3 + }, + "size": { + "x": 499, + "y": 752, + "width": 10, + "height": 35 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "204" + }, + "abcEl": { + "pitches": [ + { + "pitch": -10, + "name": "G,,", + "verticalPos": 2, + "highestVert": 8 + } + ], + "duration": 0.25, + "el_type": "note", + "startChar": 1450, + "endChar": 1455, + "averagepitch": 2, + "minpitch": 2, + "maxpitch": 2 + }, + "size": { + "x": 510, + "y": 759, + "width": 10, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "205" + }, + "abcEl": { + "pitches": [ + { + "pitch": -11, + "name": "F,,", + "verticalPos": 1, + "highestVert": 7 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1455, + "endChar": 1458, + "startBeam": true, + "averagepitch": 1, + "minpitch": 1, + "maxpitch": 1 + }, + "size": { + "x": 521, + "y": 736, + "width": 10, + "height": 58 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "206" + }, + "abcEl": { + "pitches": [ + { + "pitch": -9, + "name": "A,,", + "verticalPos": 3, + "highestVert": 9 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1458, + "endChar": 1461, + "averagepitch": 3, + "minpitch": 3, + "maxpitch": 3 + }, + "size": { + "x": 532, + "y": 733, + "width": 10, + "height": 53 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "207" + }, + "abcEl": { + "pitches": [ + { + "pitch": -7, + "name": "C,", + "verticalPos": 5, + "highestVert": 11 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1461, + "endChar": 1463, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": { + "x": 543, + "y": 731, + "width": 10, + "height": 48 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "208" + }, + "abcEl": { + "pitches": [ + { + "pitch": -4, + "name": "F,", + "verticalPos": 8, + "highestVert": 14 + } + ], + "duration": 0.0625, + "el_type": "note", + "startChar": 1463, + "endChar": 1465, + "endBeam": true, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8 + }, + "size": { + "x": 553, + "y": 728, + "width": 10, + "height": 39 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "209" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1465, + "endChar": 1466 + }, + "size": { + "x": 569, + "y": 667, + "width": 1, + "height": 120 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "210" + }, + "abcEl": { + "pitches": [ + { + "pitch": -11, + "name": "F,,", + "verticalPos": 1, + "highestVert": 7 + } + ], + "duration": 0.375, + "el_type": "note", + "startChar": 1466, + "endChar": 1471, + "averagepitch": 1, + "minpitch": 1, + "maxpitch": 1 + }, + "size": { + "x": 580, + "y": 763, + "width": 16, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "211" + }, + "abcEl": { + "pitches": [ + { + "pitch": -13, + "name": "D,,", + "verticalPos": -1, + "highestVert": 5 + } + ], + "duration": 0.125, + "el_type": "note", + "startChar": 1471, + "endChar": 1476, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": { + "x": 596, + "y": 771, + "width": 17, + "height": 31 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "212" + }, + "abcEl": { + "duration": 0.25, + "pitches": [ + { + "pitch": -10, + "name": "G,,", + "verticalPos": 2, + "highestVert": 2 + }, + { + "pitch": -6, + "name": "D,", + "verticalPos": 6, + "highestVert": 6 + } + ], + "el_type": "note", + "startChar": 1476, + "endChar": 1486, + "averagepitch": 4, + "minpitch": 2, + "maxpitch": 6 + }, + "size": { + "x": 615, + "y": 744, + "width": 10, + "height": 47 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "213" + }, + "abcEl": { + "rest": { + "type": "rest" + }, + "duration": 0.25, + "el_type": "note", + "startChar": 1486, + "endChar": 1488, + "averagepitch": 7, + "minpitch": 7, + "maxpitch": 7 + }, + "size": { + "x": 657, + "y": 759, + "width": 8, + "height": 21 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "214" + }, + "abcEl": { + "type": "bar_thin", + "el_type": "bar", + "startChar": 1488, + "endChar": 1489 + }, + "size": { + "x": 706, + "y": 667, + "width": 1, + "height": 120 + } + }, + { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "215" + }, + "abcEl": { + "pitches": [ + { + "pitch": -8, + "name": "B,,", + "verticalPos": 4, + "highestVert": 4 + } + ], + "duration": 1, + "el_type": "note", + "startChar": 1489, + "endChar": 1494, + "averagepitch": 4, + "minpitch": 4, + "maxpitch": 4 + }, + "size": { + "x": 717, + "y": 775, + "width": 15, + "height": 8 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "216" + }, + "abcEl": { + "type": "bar_thin_thick", + "el_type": "bar", + "startChar": 1494, + "endChar": 1496 + }, + "size": { + "x": 766, + "y": 667, + "width": 8, + "height": 120 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "217" + }, + "abcEl": { + "el_type": "slur", + "startChar": 1370, + "endChar": 1383 + }, + "size": { + "x": 156, + "y": 743, + "width": 30, + "height": 7 + } + }, + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "218" + }, + "abcEl": { + "el_type": "unalignedWords", + "name": "unalignedWords", + "startChar": -1, + "endChar": -1, + "text": "" + }, + "size": { + "x": 15, + "y": 832, + "width": 278, + "height": 74 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "974.87", + "selectable": "true", + "tabindex": "0", + "data-index": "219" + }, + "abcEl": { + "el_type": "extraText", + "name": "description", + "startChar": -2, + "endChar": -2, + "text": "Source: My own testing" + }, + "size": { + "x": 15, + "y": 956, + "width": 201, + "height": 24 + } + }, + { + "draggable": false, + "svgEl": { + "x": "15", + "y": "1000.87", + "selectable": "true", + "tabindex": "0", + "data-index": "220" + }, + "abcEl": { + "el_type": "extraText", + "name": "description", + "startChar": -2, + "endChar": -2, + "text": "History:\nThis shows every type of thing that can possibly be drawn.\n\nAnd two lines of history!" + }, + "size": { + "x": 15, + "y": 987, + "width": 494, + "height": 100 + } + }, + ] + + var expectedNone = [{ + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "0"}, + "abcEl": { + "decoration": ["mp"], + "duration": 0.5, + "pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 6}, {"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}, {"pitch": 13, "name": "b", "verticalPos": 13, "highestVert": 13}], + "el_type": "note", + "startChar": 700, + "endChar": 714, + "averagepitch": 9, + "minpitch": 6, + "maxpitch": 13 + }, + "size": {"x": 134, "y": 325, "width": 14, "height": 58} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "1"}, + "abcEl": {"pitches": [{"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 16}], "duration": 0.1875, "el_type": "note", "startChar": 714, "endChar": 716, "startBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 176, "y": 333, "width": 16, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "2"}, + "abcEl": {"pitches": [{"pitch": 11, "name": "g", "verticalPos": 11, "highestVert": 17}], "duration": 0.0625, "el_type": "note", "startChar": 716, "endChar": 718, "endBeam": true, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 193, "y": 329, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "3"}, + "abcEl": {"pitches": [{"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 16}], "duration": 0.25, "el_type": "note", "startChar": 718, "endChar": 720, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 204, "y": 337, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "4"}, + "abcEl": { + "decoration": ["crescendo("], + "duration": 0.75, + "pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}, {"pitch": 13, "name": "b", "verticalPos": 13, "highestVert": 13}], + "el_type": "note", + "startChar": 721, + "endChar": 734, + "averagepitch": 10.5, + "minpitch": 8, + "maxpitch": 13 + }, + "size": {"x": 242, "y": 325, "width": 19, "height": 51} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "5"}, + "abcEl": { + "decoration": ["crescendo)"], + "duration": 0.25, + "pitches": [{"pitch": 11, "name": "g", "verticalPos": 11, "highestVert": 11}, {"pitch": 13, "name": "b", "verticalPos": 13, "highestVert": 13}], + "el_type": "note", + "startChar": 734, + "endChar": 744, + "averagepitch": 12, + "minpitch": 11, + "maxpitch": 13 + }, + "size": {"x": 315, "y": 325, "width": 14, "height": 39} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "6"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 745, "endChar": 748, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 365, "y": 352, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "7"}, + "abcEl": { + "decoration": ["crescendo("], + "pitches": [{"pitch": 13, "name": "b", "startSlur": [{"label": 101}], "verticalPos": 13, "highestVert": 19}], + "duration": 0.0625, + "el_type": "note", + "startChar": 748, + "endChar": 755, + "startBeam": true, + "averagepitch": 13, + "minpitch": 13, + "maxpitch": 13 + }, + "size": {"x": 387, "y": 318, "width": 14, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "8"}, + "abcEl": {"pitches": [{"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 16}], "duration": 0.0625, "el_type": "note", "startChar": 755, "endChar": 756, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 399, "y": 320, "width": 10, "height": 48} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "9"}, + "abcEl": {"pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 14}], "duration": 0.0625, "el_type": "note", "startChar": 756, "endChar": 757, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 410, "y": 323, "width": 10, "height": 53} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "10"}, + "abcEl": {"pitches": [{"pitch": 10, "name": "f", "endSlur": [101], "verticalPos": 10, "highestVert": 16}], "duration": 0.0625, "el_type": "note", "startChar": 757, "endChar": 760, "endBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 421, "y": 325, "width": 10, "height": 43} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "11"}, + "abcEl": { + "startTriplet": 3, + "tripletMultiplier": 0.6666666666666666, + "tripletR": 3, + "pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 12}], + "duration": 0.125, + "el_type": "note", + "startChar": 760, + "endChar": 764, + "startBeam": true, + "averagepitch": 6, + "minpitch": 6, + "maxpitch": 6 + }, + "size": {"x": 432, "y": 345, "width": 10, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "12"}, + "abcEl": {"pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 14}], "duration": 0.125, "el_type": "note", "startChar": 764, "endChar": 766, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 443, "y": 343, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "13"}, + "abcEl": {"pitches": [{"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 13}], "duration": 0.125, "endTriplet": true, "el_type": "note", "startChar": 766, "endChar": 769, "endBeam": true, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 453, "y": 341, "width": 10, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "14"}, + "abcEl": {"decoration": ["crescendo)"], "pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 12}], "duration": 0.25, "el_type": "note", "startChar": 769, "endChar": 775, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 464, "y": 352, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "15"}, + "abcEl": { + "decoration": ["f"], + "duration": 0.25, + "pitches": [{"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 7}, {"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 10}], + "el_type": "note", + "startChar": 801, + "endChar": 811, + "averagepitch": 8.5, + "minpitch": 7, + "maxpitch": 10 + }, + "size": {"x": 503, "y": 337, "width": 10, "height": 43} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "16"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 811, "endChar": 814, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 519, "y": 352, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "17"}, + "abcEl": {"duration": 0.5, "pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}, {"pitch": 13, "name": "b", "verticalPos": 13, "highestVert": 13}], "el_type": "note", "startChar": 814, "endChar": 820, "averagepitch": 10.5, "minpitch": 8, "maxpitch": 13}, + "size": {"x": 534, "y": 325, "width": 14, "height": 51} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "18"}, + "abcEl": {"decoration": ["p"], "duration": 0.5, "pitches": [{"pitch": 4, "name": "G", "verticalPos": 4, "highestVert": 4}, {"pitch": 9, "name": "e", "verticalPos": 9, "highestVert": 9}], "el_type": "note", "startChar": 821, "endChar": 832, "averagepitch": 6.5, "minpitch": 4, "maxpitch": 9}, + "size": {"x": 580, "y": 341, "width": 10, "height": 51} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "19"}, + "abcEl": { + "decoration": ["trill", "upbow"], + "duration": 0.5, + "pitches": [{"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 7}, {"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 10}], + "el_type": "note", + "startChar": 832, + "endChar": 840, + "averagepitch": 8.5, + "minpitch": 7, + "maxpitch": 10 + }, + "size": {"x": 608, "y": 299, "width": 18, "height": 81} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "20"}, + "abcEl": { + "decoration": ["crescendo("], + "duration": 0.75, + "pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}, {"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 10}], + "el_type": "note", + "startChar": 841, + "endChar": 854, + "averagepitch": 9, + "minpitch": 8, + "maxpitch": 10 + }, + "size": {"x": 657, "y": 337, "width": 17, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "21"}, + "abcEl": {"decoration": ["crescendo)"], "pitches": [{"pitch": 11, "name": "g", "verticalPos": 11, "highestVert": 17}], "duration": 0.25, "el_type": "note", "startChar": 854, "endChar": 860, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 707, "y": 333, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "22"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "startTie": {}, "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 997, "endChar": 1003, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 134, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "23"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "startTie": {}, "endTie": true, "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1003, "endChar": 1008, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 150, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "24"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "endTie": true, "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1008, "endChar": 1012, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 174, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "25"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1012, "endChar": 1016, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 202, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "26"}, + "abcEl": { + "chord": [{"name": "B♭", "position": "default"}], + "gracenotes": [{"pitch": 0, "name": "C", "duration": 0.125, "verticalPos": 0}], + "pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], + "duration": 0.25, + "el_type": "note", + "startChar": 1017, + "endChar": 1029, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": {"x": 233, "y": 279, "width": 29, "height": 155} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "27"}, + "abcEl": { + "gracenotes": [{"pitch": 0, "name": "C", "duration": 0.125, "verticalPos": 0}, {"pitch": 1, "name": "D", "duration": 0.125, "verticalPos": 1}], + "pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], + "duration": 0.25, + "el_type": "note", + "startChar": 1029, + "endChar": 1037, + "averagepitch": -1, + "minpitch": -1, + "maxpitch": -1 + }, + "size": {"x": 263, "y": 380, "width": 33, "height": 54} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "28"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1037, "endChar": 1041, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 298, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "29"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1041, "endChar": 1045, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 315, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "30"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1046, "endChar": 1051, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 363, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "31"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1051, "endChar": 1055, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 387, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "32"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1055, "endChar": 1059, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 430, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "33"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1059, "endChar": 1063, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 462, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "34"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1064, "endChar": 1069, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 501, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "35"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1069, "endChar": 1073, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 517, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "36"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1073, "endChar": 1077, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 534, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "37"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1077, "endChar": 1081, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 550, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "38"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1082, "endChar": 1087, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 578, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "39"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1087, "endChar": 1091, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 594, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "40"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1091, "endChar": 1095, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 611, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "41"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1095, "endChar": 1099, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 627, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "42"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1100, "endChar": 1105, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 655, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "43"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1105, "endChar": 1109, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 671, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "44"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1109, "endChar": 1113, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 688, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "45"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1113, "endChar": 1117, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 705, "y": 402, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "46"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}], "duration": 0.375, "el_type": "note", "startChar": 1254, "endChar": 1259, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 136, "y": 480, "width": 16, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "47"}, + "abcEl": {"pitches": [{"pitch": 1, "name": "D", "verticalPos": 13, "highestVert": 13}], "duration": 0.125, "el_type": "note", "startChar": 1259, "endChar": 1262, "averagepitch": 13, "minpitch": 13, "maxpitch": 13}, + "size": {"x": 162, "y": 473, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "48"}, + "abcEl": { + "duration": 0.5, + "pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}, {"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}, {"pitch": 3, "name": "F", "verticalPos": 15, "highestVert": 15}], + "el_type": "note", + "startChar": 1262, + "endChar": 1272, + "averagepitch": 11, + "minpitch": 8, + "maxpitch": 15 + }, + "size": {"x": 174, "y": 465, "width": 14, "height": 58} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "49"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}], "duration": 0.125, "el_type": "note", "startChar": 1273, "endChar": 1276, "startBeam": true, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 244, "y": 480, "width": 10, "height": 56} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "50"}, + "abcEl": {"pitches": [{"pitch": -8, "name": "B,,", "verticalPos": 4, "highestVert": 4}], "duration": 0.125, "el_type": "note", "startChar": 1276, "endChar": 1281, "endBeam": true, "averagepitch": 4, "minpitch": 4, "maxpitch": 4}, + "size": {"x": 256, "y": 507, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "51"}, + "abcEl": {"pitches": [{"pitch": -7, "name": "C,", "verticalPos": 5, "highestVert": 11}], "duration": 0.25, "el_type": "note", "startChar": 1281, "endChar": 1285, "averagepitch": 5, "minpitch": 5, "maxpitch": 5}, + "size": {"x": 284, "y": 481, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "52"}, + "abcEl": {"pitches": [{"pitch": -6, "name": "D,", "verticalPos": 6, "highestVert": 6}], "duration": 0.25, "el_type": "note", "startChar": 1285, "endChar": 1289, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 300, "y": 500, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "53"}, + "abcEl": {"pitches": [{"pitch": -5, "name": "E,", "verticalPos": 7, "highestVert": 7}], "duration": 0.0625, "el_type": "note", "startChar": 1289, "endChar": 1291, "startBeam": true, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 317, "y": 496, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "54"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.0625, "el_type": "note", "startChar": 1291, "endChar": 1293, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 328, "y": 492, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "55"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.125, "el_type": "note", "startChar": 1293, "endChar": 1296, "endBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 338, "y": 488, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "56"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.125, "el_type": "note", "startChar": 1297, "endChar": 1300, "startBeam": true, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 365, "y": 492, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "57"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.125, "el_type": "note", "startChar": 1300, "endChar": 1304, "endBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 377, "y": 484, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "58"}, + "abcEl": {"pitches": [{"pitch": 1, "name": "D", "verticalPos": 13, "highestVert": 13}], "duration": 0.25, "el_type": "note", "startChar": 1304, "endChar": 1307, "averagepitch": 13, "minpitch": 13, "maxpitch": 13}, + "size": {"x": 387, "y": 473, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "59"}, + "abcEl": {"pitches": [{"pitch": 1, "name": "D", "verticalPos": 13, "highestVert": 13}], "duration": 0.25, "el_type": "note", "startChar": 1307, "endChar": 1310, "averagepitch": 13, "minpitch": 13, "maxpitch": 13}, + "size": {"x": 430, "y": 473, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "60"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.125, "el_type": "note", "startChar": 1310, "endChar": 1313, "startBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 464, "y": 488, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "61"}, + "abcEl": {"pitches": [{"pitch": -5, "name": "E,", "verticalPos": 7, "highestVert": 7}], "duration": 0.125, "el_type": "note", "startChar": 1313, "endChar": 1316, "endBeam": true, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 476, "y": 496, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "62"}, + "abcEl": { + "duration": 0.25, + "pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}, {"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}, {"pitch": 0, "name": "C", "verticalPos": 12, "highestVert": 12}], + "el_type": "note", + "startChar": 1317, + "endChar": 1328, + "averagepitch": 10, + "minpitch": 8, + "maxpitch": 12 + }, + "size": {"x": 501, "y": 476, "width": 14, "height": 47} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "63"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 1328, "endChar": 1331, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 519, "y": 492, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "64"}, + "abcEl": {"duration": 0.5, "pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}, {"pitch": 3, "name": "F", "verticalPos": 15, "highestVert": 15}], "el_type": "note", "startChar": 1331, "endChar": 1338, "averagepitch": 13, "minpitch": 11, "maxpitch": 15}, + "size": {"x": 534, "y": 465, "width": 14, "height": 47} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "65"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.5, "el_type": "note", "startChar": 1339, "endChar": 1343, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 580, "y": 488, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "66"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.5, "el_type": "note", "startChar": 1343, "endChar": 1346, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 613, "y": 484, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "67"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.75, "el_type": "note", "startChar": 1347, "endChar": 1352, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 657, "y": 483, "width": 17, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "68"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}], "duration": 0.0625, "el_type": "note", "startChar": 1352, "endChar": 1354, "startBeam": true, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 707, "y": 480, "width": 10, "height": 45} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "69"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1354, "endChar": 1356, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 717, "y": 488, "width": 10, "height": 40} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "70"}, + "abcEl": {"pitches": [{"pitch": -6, "name": "D,", "verticalPos": 6, "highestVert": 6}], "duration": 0.0625, "el_type": "note", "startChar": 1356, "endChar": 1358, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 728, "y": 500, "width": 10, "height": 30} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "71"}, + "abcEl": {"pitches": [{"pitch": -5, "name": "E,", "verticalPos": 7, "highestVert": 7}], "duration": 0.0625, "el_type": "note", "startChar": 1358, "endChar": 1360, "endBeam": true, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 739, "y": 496, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "72"}, + "abcEl": {"decoration": ["f"], "pitches": [{"pitch": 12, "name": "a", "verticalPos": 12, "highestVert": 18}], "duration": 0.25, "el_type": "note", "startChar": 862, "endChar": 868, "lyric": [{"syllable": "Strang", "divider": "-"}], "averagepitch": 12, "minpitch": 12, "maxpitch": 12}, + "size": {"x": 86, "y": 601, "width": 43, "height": 125} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "73"}, + "abcEl": { + "duration": 0.25, + "pitches": [{"pitch": 11, "name": "g", "verticalPos": 11, "highestVert": 11}, {"pitch": 13, "name": "b", "verticalPos": 13, "highestVert": 13}], + "el_type": "note", + "startChar": 868, + "endChar": 875, + "lyric": [{"syllable": "ers", "divider": " "}], + "averagepitch": 12, + "minpitch": 11, + "maxpitch": 13 + }, + "size": {"x": 141, "y": 597, "width": 21, "height": 128} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "74"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 875, "endChar": 878, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 193, "y": 624, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "75"}, + "abcEl": {"pitches": [{"accidental": "natural", "pitch": 9, "name": "=e", "verticalPos": 9, "highestVert": 15}], "duration": 0.25, "el_type": "note", "startChar": 878, "endChar": 881, "lyric": [{"syllable": "in", "divider": " "}], "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 205, "y": 613, "width": 17, "height": 113} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "76"}, + "abcEl": { + "duration": 0.5, + "pitches": [{"pitch": 5, "name": "A", "verticalPos": 5, "highestVert": 5}, {"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 7}, {"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 10}], + "el_type": "note", + "startChar": 882, + "endChar": 891, + "lyric": [{"syllable": "the", "divider": " "}], + "averagepitch": 7.333333333333333, + "minpitch": 5, + "maxpitch": 10 + }, + "size": {"x": 239, "y": 609, "width": 19, "height": 117} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "77"}, + "abcEl": {"pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 14}], "duration": 0.5, "el_type": "note", "startChar": 891, "endChar": 893, "lyric": [{"syllable": "night", "divider": " "}], "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 260, "y": 617, "width": 28, "height": 109} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "78"}, + "abcEl": {"duration": 0.5, "pitches": [{"pitch": 3, "name": "F", "verticalPos": 3, "highestVert": 3}, {"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 7}], "el_type": "note", "startChar": 895, "endChar": 903, "averagepitch": 5, "minpitch": 3, "maxpitch": 7}, + "size": {"x": 330, "y": 620, "width": 10, "height": 47} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "79"}, + "abcEl": {"duration": 0.25, "pitches": [{"pitch": 4, "name": "G", "verticalPos": 4, "highestVert": 4}, {"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 6}], "el_type": "note", "startChar": 903, "endChar": 910, "averagepitch": 5, "minpitch": 4, "maxpitch": 6}, + "size": {"x": 386, "y": 624, "width": 10, "height": 39} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "80"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 910, "endChar": 912, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 429, "y": 624, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "81"}, + "abcEl": {"duration": 0.75, "pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 6}, {"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}], "el_type": "note", "startChar": 913, "endChar": 922, "averagepitch": 7, "minpitch": 6, "maxpitch": 8}, + "size": {"x": 467, "y": 617, "width": 17, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "82"}, + "abcEl": {"pitches": [{"pitch": 5, "name": "A", "verticalPos": 5, "highestVert": 11}], "duration": 0.25, "el_type": "note", "startChar": 922, "endChar": 924, "averagepitch": 5, "minpitch": 5, "maxpitch": 5}, + "size": {"x": 521, "y": 628, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "83"}, + "abcEl": { + "decoration": ["diminuendo("], + "duration": 0.5, + "pitches": [{"pitch": 1, "name": "D", "verticalPos": 1, "highestVert": 1}, {"pitch": 5, "name": "A", "verticalPos": 5, "highestVert": 5}], + "el_type": "note", + "startChar": 925, + "endChar": 936, + "averagepitch": 3, + "minpitch": 1, + "maxpitch": 5 + }, + "size": {"x": 580, "y": 628, "width": 10, "height": 47} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "84"}, + "abcEl": {"pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 12}], "duration": 0.0625, "el_type": "note", "startChar": 936, "endChar": 937, "startBeam": true, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 615, "y": 612, "width": 10, "height": 43} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "85"}, + "abcEl": {"pitches": [{"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 13}], "duration": 0.0625, "el_type": "note", "startChar": 937, "endChar": 938, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 626, "y": 610, "width": 10, "height": 42} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "86"}, + "abcEl": {"pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 14}], "duration": 0.0625, "el_type": "note", "startChar": 938, "endChar": 939, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 637, "y": 607, "width": 10, "height": 40} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "87"}, + "abcEl": {"pitches": [{"pitch": 9, "name": "e", "verticalPos": 9, "highestVert": 15}], "duration": 0.0625, "el_type": "note", "startChar": 939, "endChar": 941, "endBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 647, "y": 605, "width": 10, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "88"}, + "abcEl": {"pitches": [{"pitch": 10, "name": "f", "verticalPos": 10, "highestVert": 16}], "duration": 0.0625, "el_type": "note", "startChar": 941, "endChar": 942, "startBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 658, "y": 601, "width": 10, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "89"}, + "abcEl": {"pitches": [{"pitch": 5, "name": "A", "verticalPos": 5, "highestVert": 11}], "duration": 0.0625, "el_type": "note", "startChar": 942, "endChar": 943, "averagepitch": 5, "minpitch": 5, "maxpitch": 5}, + "size": {"x": 669, "y": 604, "width": 10, "height": 56} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "90"}, + "abcEl": {"pitches": [{"pitch": 6, "name": "B", "verticalPos": 6, "highestVert": 12}], "duration": 0.0625, "el_type": "note", "startChar": 943, "endChar": 944, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 680, "y": 606, "width": 10, "height": 49} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "91"}, + "abcEl": {"decoration": ["diminuendo)"], "pitches": [{"pitch": 7, "name": "c", "verticalPos": 7, "highestVert": 13}], "duration": 0.0625, "el_type": "note", "startChar": 944, "endChar": 949, "endBeam": true, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 691, "y": 609, "width": 10, "height": 43} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "92"}, + "abcEl": {"decoration": ["mp"], "pitches": [{"pitch": 8, "name": "d", "verticalPos": 8, "highestVert": 8}], "duration": 1, "el_type": "note", "startChar": 950, "endChar": 957, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 717, "y": 640, "width": 15, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "93"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1119, "endChar": 1123, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 105, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "94"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1123, "endChar": 1127, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 148, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "95"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1127, "endChar": 1131, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 191, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "96"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1131, "endChar": 1135, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 210, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "97"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1136, "endChar": 1141, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 246, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "98"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1141, "endChar": 1145, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 257, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "99"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1145, "endChar": 1149, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 272, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "100"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1149, "endChar": 1153, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 283, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "101"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1154, "endChar": 1158, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 328, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "102"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1158, "endChar": 1162, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 356, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "103"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1162, "endChar": 1166, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 384, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "104"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1166, "endChar": 1170, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 427, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "105"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1171, "endChar": 1175, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 465, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "106"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1175, "endChar": 1179, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 487, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "107"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1179, "endChar": 1183, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 508, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "108"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1183, "endChar": 1187, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 519, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "109"}, + "abcEl": {"chord": [{"name": "annotation", "position": "above"}], "pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1188, "endChar": 1205, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 578, "y": 577, "width": 77, "height": 128} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "110"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1205, "endChar": 1209, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 589, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "111"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1209, "endChar": 1213, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 613, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "112"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1213, "endChar": 1217, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 656, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "113"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1218, "endChar": 1222, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 715, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "114"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1222, "endChar": 1226, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 726, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "115"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1226, "endChar": 1230, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 737, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "116"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": -1, "highestVert": -1}], "duration": 0.25, "el_type": "note", "startChar": 1230, "endChar": 1234, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 748, "y": 674, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "117"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.0625, "el_type": "note", "startChar": 1362, "endChar": 1364, "startBeam": true, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 107, "y": 759, "width": 10, "height": 29} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "118"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1364, "endChar": 1366, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 118, "y": 755, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "119"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.0625, "el_type": "note", "startChar": 1366, "endChar": 1368, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 129, "y": 751, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "120"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.0625, "el_type": "note", "startChar": 1368, "endChar": 1371, "endBeam": true, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 139, "y": 759, "width": 10, "height": 29} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "121"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "startSlur": [{"label": 101}], "verticalPos": 9, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1371, "endChar": 1374, "startBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 150, "y": 755, "width": 10, "height": 29} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "122"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.0625, "el_type": "note", "startChar": 1374, "endChar": 1376, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 161, "y": 751, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "123"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}], "duration": 0.0625, "el_type": "note", "startChar": 1376, "endChar": 1378, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 172, "y": 748, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "124"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "endSlur": [101], "verticalPos": 9, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1378, "endChar": 1382, "endBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 183, "y": 755, "width": 10, "height": 29} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "125"}, + "abcEl": {"pitches": [{"pitch": 0, "name": "C", "verticalPos": 12, "highestVert": 12}], "duration": 0.25, "el_type": "note", "startChar": 1382, "endChar": 1385, "averagepitch": 12, "minpitch": 12, "maxpitch": 12}, + "size": {"x": 191, "y": 744, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "126"}, + "abcEl": {"pitches": [{"pitch": 0, "name": "C", "verticalPos": 12, "highestVert": 12}], "duration": 0.25, "el_type": "note", "startChar": 1385, "endChar": 1387, "averagepitch": 12, "minpitch": 12, "maxpitch": 12}, + "size": {"x": 210, "y": 744, "width": 14, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "127"}, + "abcEl": {"duration": 0.5, "pitches": [{"pitch": -7, "name": "C,", "verticalPos": 5, "highestVert": 5}, {"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "el_type": "note", "startChar": 1388, "endChar": 1397, "averagepitch": 7.5, "minpitch": 5, "maxpitch": 10}, + "size": {"x": 248, "y": 751, "width": 10, "height": 51} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "128"}, + "abcEl": { + "duration": 0.5, + "pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}, {"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}, {"pitch": 3, "name": "F", "verticalPos": 15, "highestVert": 15}], + "el_type": "note", + "startChar": 1397, + "endChar": 1407, + "averagepitch": 11.333333333333334, + "minpitch": 8, + "maxpitch": 15 + }, + "size": {"x": 272, "y": 732, "width": 14, "height": 58} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "129"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.1875, "el_type": "note", "startChar": 1408, "endChar": 1411, "startBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 330, "y": 750, "width": 16, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "130"}, + "abcEl": {"pitches": [{"pitch": 0, "name": "C", "verticalPos": 12, "highestVert": 12}], "duration": 0.0625, "el_type": "note", "startChar": 1411, "endChar": 1413, "endBeam": true, "averagepitch": 12, "minpitch": 12, "maxpitch": 12}, + "size": {"x": 345, "y": 744, "width": 14, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "131"}, + "abcEl": {"pitches": [{"pitch": -1, "name": "B,", "verticalPos": 11, "highestVert": 11}], "duration": 0.1875, "el_type": "note", "startChar": 1413, "endChar": 1416, "startBeam": true, "averagepitch": 11, "minpitch": 11, "maxpitch": 11}, + "size": {"x": 358, "y": 748, "width": 16, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "132"}, + "abcEl": {"pitches": [{"pitch": 1, "name": "D", "verticalPos": 13, "highestVert": 13}], "duration": 0.0625, "el_type": "note", "startChar": 1416, "endChar": 1418, "endBeam": true, "averagepitch": 13, "minpitch": 13, "maxpitch": 13}, + "size": {"x": 373, "y": 740, "width": 14, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "133"}, + "abcEl": {"pitches": [{"pitch": -3, "name": "G,", "verticalPos": 9, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1418, "endChar": 1420, "startBeam": true, "averagepitch": 9, "minpitch": 9, "maxpitch": 9}, + "size": {"x": 386, "y": 755, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "134"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.0625, "el_type": "note", "startChar": 1420, "endChar": 1422, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 397, "y": 759, "width": 10, "height": 36} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "135"}, + "abcEl": {"pitches": [{"pitch": -5, "name": "E,", "verticalPos": 7, "highestVert": 7}], "duration": 0.0625, "el_type": "note", "startChar": 1422, "endChar": 1424, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 408, "y": 763, "width": 10, "height": 34} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "136"}, + "abcEl": {"pitches": [{"pitch": -6, "name": "D,", "verticalPos": 6, "highestVert": 6}], "duration": 0.0625, "el_type": "note", "startChar": 1424, "endChar": 1427, "endBeam": true, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 418, "y": 767, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "137"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 8}], "duration": 0.125, "el_type": "note", "startChar": 1427, "endChar": 1430, "startBeam": true, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 429, "y": 759, "width": 10, "height": 33} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "138"}, + "abcEl": {"pitches": [{"pitch": -2, "name": "A,", "verticalPos": 10, "highestVert": 10}], "duration": 0.125, "el_type": "note", "startChar": 1430, "endChar": 1433, "endBeam": true, "averagepitch": 10, "minpitch": 10, "maxpitch": 10}, + "size": {"x": 440, "y": 751, "width": 10, "height": 37} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "139"}, + "abcEl": {"pitches": [{"pitch": -6, "name": "D,", "verticalPos": 6, "highestVert": 12}], "duration": 0.125, "el_type": "note", "startChar": 1434, "endChar": 1437, "startBeam": true, "averagepitch": 6, "minpitch": 6, "maxpitch": 6}, + "size": {"x": 467, "y": 740, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "140"}, + "abcEl": {"pitches": [{"pitch": -7, "name": "C,", "verticalPos": 5, "highestVert": 11}], "duration": 0.125, "el_type": "note", "startChar": 1437, "endChar": 1441, "endBeam": true, "averagepitch": 5, "minpitch": 5, "maxpitch": 5}, + "size": {"x": 478, "y": 744, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "141"}, + "abcEl": {"pitches": [{"pitch": -8, "name": "B,,", "verticalPos": 4, "highestVert": 10}], "duration": 0.125, "el_type": "note", "startChar": 1441, "endChar": 1445, "startBeam": true, "averagepitch": 4, "minpitch": 4, "maxpitch": 4}, + "size": {"x": 489, "y": 748, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "142"}, + "abcEl": {"pitches": [{"pitch": -9, "name": "A,,", "verticalPos": 3, "highestVert": 9}], "duration": 0.125, "el_type": "note", "startChar": 1445, "endChar": 1450, "endBeam": true, "averagepitch": 3, "minpitch": 3, "maxpitch": 3}, + "size": {"x": 499, "y": 752, "width": 10, "height": 35} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "143"}, + "abcEl": {"pitches": [{"pitch": -10, "name": "G,,", "verticalPos": 2, "highestVert": 8}], "duration": 0.25, "el_type": "note", "startChar": 1450, "endChar": 1455, "averagepitch": 2, "minpitch": 2, "maxpitch": 2}, + "size": {"x": 510, "y": 759, "width": 10, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "144"}, + "abcEl": {"pitches": [{"pitch": -11, "name": "F,,", "verticalPos": 1, "highestVert": 7}], "duration": 0.0625, "el_type": "note", "startChar": 1455, "endChar": 1458, "startBeam": true, "averagepitch": 1, "minpitch": 1, "maxpitch": 1}, + "size": {"x": 521, "y": 736, "width": 10, "height": 58} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "145"}, + "abcEl": {"pitches": [{"pitch": -9, "name": "A,,", "verticalPos": 3, "highestVert": 9}], "duration": 0.0625, "el_type": "note", "startChar": 1458, "endChar": 1461, "averagepitch": 3, "minpitch": 3, "maxpitch": 3}, + "size": {"x": 532, "y": 733, "width": 10, "height": 53} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "146"}, + "abcEl": {"pitches": [{"pitch": -7, "name": "C,", "verticalPos": 5, "highestVert": 11}], "duration": 0.0625, "el_type": "note", "startChar": 1461, "endChar": 1463, "averagepitch": 5, "minpitch": 5, "maxpitch": 5}, + "size": {"x": 543, "y": 731, "width": 10, "height": 48} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "147"}, + "abcEl": {"pitches": [{"pitch": -4, "name": "F,", "verticalPos": 8, "highestVert": 14}], "duration": 0.0625, "el_type": "note", "startChar": 1463, "endChar": 1465, "endBeam": true, "averagepitch": 8, "minpitch": 8, "maxpitch": 8}, + "size": {"x": 553, "y": 728, "width": 10, "height": 39} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "148"}, + "abcEl": {"pitches": [{"pitch": -11, "name": "F,,", "verticalPos": 1, "highestVert": 7}], "duration": 0.375, "el_type": "note", "startChar": 1466, "endChar": 1471, "averagepitch": 1, "minpitch": 1, "maxpitch": 1}, + "size": {"x": 580, "y": 763, "width": 16, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "149"}, + "abcEl": {"pitches": [{"pitch": -13, "name": "D,,", "verticalPos": -1, "highestVert": 5}], "duration": 0.125, "el_type": "note", "startChar": 1471, "endChar": 1476, "averagepitch": -1, "minpitch": -1, "maxpitch": -1}, + "size": {"x": 596, "y": 771, "width": 17, "height": 31} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "150"}, + "abcEl": {"duration": 0.25, "pitches": [{"pitch": -10, "name": "G,,", "verticalPos": 2, "highestVert": 2}, {"pitch": -6, "name": "D,", "verticalPos": 6, "highestVert": 6}], "el_type": "note", "startChar": 1476, "endChar": 1486, "averagepitch": 4, "minpitch": 2, "maxpitch": 6}, + "size": {"x": 615, "y": 744, "width": 10, "height": 47} + }, { + "draggable": false, + "svgEl": {"selectable": "false", "data-index": "151"}, + "abcEl": {"rest": {"type": "rest"}, "duration": 0.25, "el_type": "note", "startChar": 1486, "endChar": 1488, "averagepitch": 7, "minpitch": 7, "maxpitch": 7}, + "size": {"x": 657, "y": 759, "width": 8, "height": 21} + }, { + "draggable": true, + "svgEl": {"selectable": "false", "data-index": "152"}, + "abcEl": {"pitches": [{"pitch": -8, "name": "B,,", "verticalPos": 4, "highestVert": 4}], "duration": 1, "el_type": "note", "startChar": 1489, "endChar": 1494, "averagepitch": 4, "minpitch": 4, "maxpitch": 4}, + "size": {"x": 717, "y": 775, "width": 15, "height": 8} + }] +////////////////////////////////////////////////////////// + var abcTempo = 'X:1\n' + +'M:4/4\n' + +'L:1/4\n' + +'Q: "Easy Swing" 1/4=140\n' + +'K:C\n' + +'G4| [Q:"left" 1/4=170"right"] A4 |\n'; + + var expectedTempo = [ + { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "0" + }, + "abcEl": {"type": "treble", "verticalPos": 0, "clefPos": 4, "el_type": "clef"}, + "size": {"x": 20, "y": 50, "width": 19, "height": 57} + }, { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "1" + }, + "abcEl": {"type": "specified", "value": [{"num": "4", "den": "4"}], "el_type": "timeSignature"}, + "size": {"x": 49, "y": 65, "width": 12, "height": 30} + }, { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "2" + }, + "abcEl": {"startChar": 16, "endChar": 39, "preString": "Easy Swing", "duration": [0.25], "bpm": 140, "type": "tempo", "el_type": "tempo"}, + "size": {"x": 71, "y": 27, "width": 167, "height": 22} + }, { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "3" + }, + "abcEl": { + "pitches": [{"pitch": 4, "name": "G", "verticalPos": 4, "highestVert": 4}], + "duration": 1, + "el_type": "note", + "startChar": 44, + "endChar": 46, + "averagepitch": 4, + "minpitch": 4, + "maxpitch": 4 + }, + "size": {"x": 71, "y": 83, "width": 15, "height": 8} + }, { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "4" + }, + "abcEl": {"type": "bar_thin", "el_type": "bar", "startChar": 46, "endChar": 47}, + "size": {"x": 156, "y": 64, "width": 1, "height": 31} + }, { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "5" + }, + "abcEl": { + "preString": "left", + "duration": [0.25], + "bpm": 170, + "postString": "right", + "el_type": "tempo", + "startChar": 48, + "endChar": 73, + "type": "tempo" + }, + "size": {"x": 167, "y": 27, "width": 145, "height": 22} + }, { + "draggable": true, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "6" + }, + "abcEl": { + "pitches": [{"pitch": 5, "name": "A", "verticalPos": 5, "highestVert": 5}], + "duration": 1, + "el_type": "note", + "startChar": 73, + "endChar": 77, + "averagepitch": 5, + "minpitch": 5, + "maxpitch": 5 + }, + "size": {"x": 167, "y": 79, "width": 15, "height": 8} + }, { + "draggable": false, + "svgEl": { + "selectable": "true", + "tabindex": "0", + "data-index": "7" + }, + "abcEl": {"type": "bar_thin", "el_type": "bar", "startChar": 77, "endChar": 78}, + "size": {"x": 252, "y": 64, "width": 1, "height": 31} + }] +////////////////////////////////////////////////////////// + var abcClefs = 'X:1\n' + + 'M:4/4\n' + + 'L:1/4\n' + + 'K:C clef=treble\n' + + 'C4 |\n' + + 'K:C clef=treble+8\n' + + 'C4 |\n' + + 'K:C clef=treble-8\n' + + 'C4 |\n' + + 'K:C clef=tenor\n' + + 'C4 |\n' + + 'K:C clef=bass-8\n' + + 'C4 |\n' + + 'K:C clef=bass+8\n' + + 'C4 |\n' + + 'K:C clef=bass\n' + + 'C4 |\n' + + '\n'; + + var expectedClefs = [ + { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "0"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 0, "highestVert": 0}], + "duration": 1, + "el_type": "note", + "startChar": 32, + "endChar": 35, + "averagepitch": 0, + "minpitch": 0, + "maxpitch": 0, + "currentTrackMilliseconds": 0, + "currentTrackWholeNotes": 0, + "midiPitches": [{"cmd": "note", "pitch": 60, "volume": 105, "start": 0, "duration": 1, "instrument": 0,"startChar":32,"endChar":35, "gap": 0}] + }, + "size": {"x": 69, "y": 83, "width": 19, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "1"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 0, "highestVert": 0}], + "duration": 1, + "el_type": "note", + "startChar": 55, + "endChar": 58, + "averagepitch": 0, + "minpitch": 0, + "maxpitch": 0, + "currentTrackMilliseconds": 1333.3333333333335, + "currentTrackWholeNotes": 1, + "midiPitches": [{"cmd": "note", "pitch": 72, "volume": 105, "start": 1, "duration": 1, "instrument": 0,"startChar":55,"endChar":58, "gap": 0}] + }, + "size": {"x": 47, "y": 176, "width": 19, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "2"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 0, "highestVert": 0}], + "duration": 1, + "el_type": "note", + "startChar": 78, + "endChar": 81, + "averagepitch": 0, + "minpitch": 0, + "maxpitch": 0, + "currentTrackMilliseconds": 2666.666666666667, + "currentTrackWholeNotes": 2, + "midiPitches": [{"cmd": "note", "pitch": 48, "volume": 105, "start": 2, "duration": 1, "instrument": 0,"startChar":78,"endChar":81, "gap": 0}] + }, + "size": {"x": 47, "y": 268, "width": 19, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "3"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 8, "highestVert": 8}], + "duration": 1, + "el_type": "note", + "startChar": 98, + "endChar": 101, + "averagepitch": 8, + "minpitch": 8, + "maxpitch": 8, + "currentTrackMilliseconds": 4000, + "currentTrackWholeNotes": 3, + "midiPitches": [{"cmd": "note", "pitch": 48, "volume": 105, "start": 3, "duration": 1, "instrument": 0,"startChar":98,"endChar":101, "gap": 0}] + }, + "size": {"x": 50, "y": 329, "width": 15, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "4"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 12, "highestVert": 12}], + "duration": 1, + "el_type": "note", + "startChar": 119, + "endChar": 122, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12, + "currentTrackMilliseconds": 5333.333333333334, + "currentTrackWholeNotes": 4, + "midiPitches": [{"cmd": "note", "pitch": 48, "volume": 105, "start": 4, "duration": 1, "instrument": 0,"startChar":119,"endChar":122, "gap": 0}] + }, + "size": {"x": 48, "y": 406, "width": 19, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "5"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 12, "highestVert": 12}], + "duration": 1, + "el_type": "note", + "startChar": 140, + "endChar": 143, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12, + "currentTrackMilliseconds": 6666.666666666666, + "currentTrackWholeNotes": 5, + "midiPitches": [{"cmd": "note", "pitch": 72, "volume": 105, "start": 5, "duration": 1, "instrument": 0,"startChar":140,"endChar":143, "gap": 0}] + }, + "size": {"x": 48, "y": 498, "width": 19, "height": 8} + }, { + "draggable": true, + "svgEl": {"selectable": "true", "tabindex": "0", "data-index": "6"}, + "abcEl": { + "pitches": [{"pitch": 0, "name":"C","verticalPos": 12, "highestVert": 12}], + "duration": 1, + "el_type": "note", + "startChar": 159, + "endChar": 162, + "averagepitch": 12, + "minpitch": 12, + "maxpitch": 12, + "currentTrackMilliseconds": 8000, + "currentTrackWholeNotes": 6, + "midiPitches": [{"cmd": "note", "pitch": 72, "volume": 105, "start": 6, "duration": 1, "instrument": 0,"startChar":159,"endChar":162, "gap": 0}] + }, + "size": {"x": 48, "y": 591, "width": 19, "height": 8} + }]; + +////////////////////////////////////////////////////////// + it("selection-multiple", function() { + doSelectionTest(abcMultiple, expectedMultiple, {selectTypes: true}); + }) + + it("selection-tempo", function() { + doSelectionTest(abcTempo, expectedTempo, {selectTypes: true}); + }) + it("selection-none", function() { + doSelectionTest(abcMultiple, expectedNone, {}); + }) + it("selection-clefs", function() { + doSelectionTest(abcClefs, expectedClefs, {selectTypes: [ 'note' ]}, true); + }) +}) + +////////////////////////////////////////////////////////// + +function doSelectionTest(abc, expected, options, audio) { + var visualObj = abcjs.renderAbc("paper", abc, options); + if (audio) + visualObj[0].setUpAudio(); + var selection = visualObj[0].engraver.selectables; + var results = [] + for (var i = 0; i < selection.length; i++) { + var sel = selection[i]; + var abcEl = copyAbcEl(sel.absEl.abcelem); + var svgEl = copySvgEl(sel.svgEl); + var size = sel.svgEl.getBBox(); + results.push({ + draggable: sel.isDraggable, svgEl: svgEl, abcEl: abcEl, + size: { + x: Math.round(size.x), + y: Math.round(size.y), + width: Math.round(size.width), + height: Math.round(size.height) + } + }); + } + //console.log(JSON.stringify(results)) + for (i = 0; i < results.length; i++) { + var msg = "index: " + i + "\nrcv: " + JSON.stringify(results[i]) + "\n" + + "exp: " + JSON.stringify(expected[i]) + "\n"; + chai.assert.deepStrictEqual(results[i], expected[i], msg); + } +} + +function copyAbcEl(abcEl) { + var el = {}; + var keys = Object.keys(abcEl); + for (var i = 0; i < keys.length; i++) { + if (keys[i] !== "abselem") + el[keys[i]] = abcEl[keys[i]]; + } + return el; +} + +var interestingAttributes = ['x', 'y', 'selectable', 'tabindex', 'data-index']; + +function copySvgEl(svgEl) { + var el = {}; + var attr = svgEl.attributes; + for (var i = 0; i < attr.length; i++) { + if (interestingAttributes.indexOf(attr[i].nodeName) >= 0) + el[attr[i].nodeName] = attr[i].nodeValue; + } + return el; +} diff --git a/tests/visual/slurs.test.js b/tests/visual/slurs.test.js new file mode 100644 index 0000000000000000000000000000000000000000..17df8aa37305a55014fcd5b7624aeb97f0e2de79 --- /dev/null +++ b/tests/visual/slurs.test.js @@ -0,0 +1,61 @@ +describe("Slurs", function() { + + var abcSlurBeams = "X:1\n" + + "L:1/8\n" + + "M:4/4\n" + + "%%score (S A)\n" + + "K:C\n" + + "[V:S]G2 cc3 ||\n" + + "[V:A]E2E (EFG) ||"; + + var abcSlurBeams2 = "X:1\n" + + "L:1/8\n" + + "M:4/4\n" + + "%%score (S A) (T B)\n" + + "K:C\n" + + "[V:S](EFGCF2) (EF) | (E2D2) C4 | (f2e)c ||\n" + + "[V:A](C4CB,) C2 | (C2B,2) C4 | (c2G)E ||\n" + + "[V:T](G,4 A,F,) (G,F,) | G,2>F,2 E,4 | f(e c2) ||\n" + + "[V:B](C,D, E,2 D,2) (C,A,,) | G,,4 C,4 | c(G E2) ||\n" + + + it("slur around beams", function() { + abcjs.renderAbc("paper", abcSlurBeams, {add_classes: true}); + var slur = document.querySelector('.abcjs-slur') + var beam = document.querySelector('.abcjs-beam-elem') + var slurPos = slur.getBBox() + var beamPos = beam.getBBox() + chai.assert.isAbove(slurPos.y, beamPos.y, "slur should be underneath beam"); + }) + + it("slurs-around-beams2", function() { + var visualObj = abcjs.renderAbc("paper", abcSlurBeams2, {add_classes: true}); + var slurY = [] + for (var i = 0; i < visualObj[0].lines[0].staffGroup.voices.length; i++) { + var voice = visualObj[0].lines[0].staffGroup.voices[i] + for (var s = 0; s < voice.otherchildren.length; s++) { + var slur = voice.otherchildren[s] + if (slur.type === 'TieElem') { + slurY.push({s: Math.round(slur.startY), e: Math.round(slur.endY)}) + } + } + } + var expected = [ + {s: 13, e: 3}, + {s: 10, e: 11}, + {s: 5, e: 4}, + {s: 13, e: 12}, + {s: 0, e: -8}, + {s: 0, e: -1}, + {s: 7, e: 4}, + {s: 0, e: 5}, + {s: 6, e: 6}, + {s: 12, e: 10}, + {s: -14, e: -6}, + {s: -15, e: -16}, + {s: 4, e: 2}, + ] + chai.assert.deepStrictEqual(slurY, expected, "Vertical placement of slurs"); + }) + +}) diff --git a/tests/visual/svg-per-line.test.js b/tests/visual/svg-per-line.test.js new file mode 100644 index 0000000000000000000000000000000000000000..d5e040563f94cad139c20ae3d2419a665643e8fe --- /dev/null +++ b/tests/visual/svg-per-line.test.js @@ -0,0 +1,92 @@ +describe("Svg Per Line", function() { + var abcMultiple = 'X:1\n' + +'M:4/4\n' + +'L:1/16\n' + +'%%titlefont Times New Roman 22.0\n' + +'%%vocalfont Helvetica 10.0\n' + +'%%voicefont Helvetica-Bold 10.0\n' + +'%%measurefont Times-Italic 11\n' + +'%%partsfont box\n' + +'%%stretchlast .7\n' + +'%%musicspace 0\n' + +'%%barnumbers 1\n' + +'T: Selection Test\n' + +'T: Everything should be selectable\n' + +'C: public domain\n' + +'R: Hit it\n' + +'A: Yours Truly\n' + +'S: My own testing\n' + +'W: Now is the time for all good men\n' + +'W:\n' + +'W: To come to the aid of their party.\n' + +'H: This shows every type of thing that can possibly be drawn.\n' + +'H:\n' + +'H: And two lines of history!\n' + +'Q: "Easy Swing" 1/4=140\n' + +'P: AABB\n' + +'%%staves {(PianoRightHand extra) (PianoLeftHand)}\n' + +'V:PianoRightHand clef=treble name=RH\n' + +'V:PianoLeftHand clef=bass name=LH\n' + +'K:Bb\n' + +'P:A\n' + +'%%text there is some random text\n' + +'[V: PianoRightHand] !mp![b8B8d8] f3g f4|!<(![d12b12] !<)![b4g4]|z4 !<(! (bfdf) (3B2d2c2 !<)!B4|[Q:"left" 1/4=170"right"]!f![c4f4] z4 [b8d8]| !p![G8e8] Tu[c8f8]|!<(![d12f12] !<)!(g4|\n' + +'!f!a4) [g4b4] z4 =e4|[A8c8f8] d8|1 [c8F8] [B4G4] z4|[d12B12] A4|!>(![D8A8] Bcde fAB!>)!c|!mp!d16|]\n' + +'w:Strang- ers in the night\n' + +'[V: extra] B,4- B,4- B,4 B,4 | "Bb"{C}B,4 {CD}B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 |\n' + +'B,4 B,4 B,4 B,4 | B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |"^annotation"B,4 B,4 B,4 B,4 |B,4 B,4 B,4 B,4 |\n' + +'[V: PianoLeftHand] B,6 D2 [F,8F8A,8]|B,2B,,2 C,4 D,4 E,F,G,2|F,2A,2 D4 D4 G,2E,2|[C4F,4A,4] z4 [F8B,8]|G,8 A,8|A,12 B,G,D,E,|\n' + +'F,G,A,F, (G,A,B,G,) C4 C4|[C,8A,8] [F8F,8B,8]|A,3C B,3D G,F,E,D, F,2A,2|D,2C,2 B,,2A,,2 G,,4 F,,A,,C,F,|F,,6 D,,2 [D,4G,,4] z4|B,,16|]\n'; + +////////////////////////////////////////////////////////// + it("svg-per-line", function() { + doSvgTest(abcMultiple, "paper", { oneSvgPerLine: true, selectTypes: true, clickListener: clickListener }, 5); + }) + + it("svg-per-line-responsive", function() { + doSvgTest(abcMultiple, "paper-resp", { oneSvgPerLine: true, responsive: "resize" }, 5); + }) + + it("normal", function() { + doSvgTest(abcMultiple, "paper-norm", { selectTypes: true }, 1); + }) + + it("normal-responsive", function() { + doSvgTest(abcMultiple, "paper-resp-norm", { responsive: "resize" }, 1); + }) + + it("svg-per-line-scaled", function() { + var abcScaled = 'X:1\n' + + '%%staffwidth 400\n' + + 'M:4/4\n' + + 'L:1/16\n' + + 'T: Scaled\n' + + 'K: G\n' + + 'CDEF|GABC||\n' + + 'cdef|gabc||\n' + doSvgTest(abcScaled, "paper-scaled", { oneSvgPerLine: true, scale: 0.5 }, 1); + }) + +}) + +////////////////////////////////////////////////////////// + +function replacer(key, value) { + // Filtering out properties + if (key === 'abselem') { + return 'abselem'; + } + return value; +} +function doSvgTest(abc, id, options, numLines) { + abcjs.renderAbc(id, abc, options); + var outputs = document.querySelectorAll("#" + id + " svg") + chai.assert.equal(outputs.length, numLines) +} + +function clickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) { + var info = document.getElementById("click-info") + info.innerHTML = JSON.stringify(abcelem, replacer) + "
" + classes + "
" + JSON.stringify(analysis) + "
" + JSON.stringify(drag) + console.log(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) +} + diff --git a/tests/visual/svg.test.js b/tests/visual/svg.test.js new file mode 100644 index 0000000000000000000000000000000000000000..18c006402e8b3e232e0ca85b732747536e23c10a --- /dev/null +++ b/tests/visual/svg.test.js @@ -0,0 +1,101 @@ +describe("SVG Creation", function() { + var abcSingleNote = "X:1\n" + + "L:1/4\n" + + "%%staffwidth 5\n" + + "%%musicspace 0\n" + + "K:C clef=none\n" + + "V:all stem=up\n" + + "B4\n" + var abcTimeSigList = "X:1\n" + + "L:1/4\n" + + "%%staffwidth 12\n" + + "%%musicspace 0\n" + + "K:C clef=none stafflines=0\n" + + "[M:2/4]y[M:3/4]y[M:4/4]\n" + var abc128Group = "X:1\n" + + "L:1/4\n" + + "M:12/8\n" + + "K:C clef=none\n" + + "A4\n" + + var abc128Results = [ + {"klass":"abcjs-staff-extra abcjs-time-signature abcjs-l0 abcjs-m0 abcjs-mm0 abcjs-v0","dataName":"staff-extra time-signature"}, + {"klass":null,"dataName":"12"}, + {"klass":null,"dataName":null}, + {"klass":null,"dataName":null}, + {"klass":null,"dataName":"8"} + ] + + it("single-note-compact", function() { + testSvg(abcSingleNote) + testCenter("note") + }) + it("time-sig-list-compact", function() { + testSvg(abcTimeSigList) + testCenterGroup() + }) + it("12-8-group", function() { + testSvg(abc128Group) + timeSigTest(abc128Results) + }) + + function testSvg(abc) { + abcjs.renderAbc("paper", abc, { add_classes: true, showDebug: [ 'box', 'grid' ], paddingleft: 0, paddingtop: 0, paddingright: 0, paddingbottom: 0 }); + } + + function testCenter(nameType) { + var svg = document.querySelector("#paper svg") + var width = svg.getAttribute("width") + var height = svg.getAttribute("height") + var svgXCenter = width/2 + var svgYCenter = height/2 + + var item = svg.querySelector('[data-name="' + nameType + '"]') + var box = item.getBBox() + var xCenter = box.x + box.width / 2; + var yCenter = box.y + box.height / 2; + var xDiff = Math.abs(xCenter - svgXCenter) + var yDiff = Math.abs(yCenter - svgYCenter) + chai.assert.isBelow(xDiff,1, "X coordinate not centered (exp: " + svgXCenter + " act: " + xCenter + ")") + chai.assert.isBelow(yDiff,1, "Y coordinate not centered (exp: " + svgYCenter + " act: " + yCenter + ")") + } + + function testCenterGroup() { + var svg = document.querySelector("#paper svg") + var width = svg.getAttribute("width") + var height = svg.getAttribute("height") + var svgXCenter = Math.round(width/2) + var svgYCenter = Math.round(height/2) + + var items = svg.querySelectorAll('g') + var box1 = items[0].getBBox() + var box2 = items[items.length-1].getBBox() + var xCenter = Math.round(box1.x + (box1.x + box2.x + box2.width) / 2); + var yCenter = Math.round(box1.y + box1.height / 2); + chai.assert.equal(xCenter,svgXCenter, "X coordinate not centered") + chai.assert.equal(yCenter,svgYCenter, "Y coordinate not centered") + } + + function getDataNameAndClass(el) { + var klass = el.getAttribute("class") + var dataName = el.getAttribute("data-name") + return { klass: klass, dataName: dataName } + } + + function timeSigTest(expected) { + var timeSig = document.querySelector("#paper .abcjs-time-signature") + var actual = []; + actual.push(getDataNameAndClass(timeSig)) + var children = timeSig.children + for (var i = 0; i < children.length; i++) { + var child = children[i] + actual.push(getDataNameAndClass(child)) + for (var j = 0; j < child.children.length; j++) { + var grandChild = child.children[j] + actual.push(getDataNameAndClass(grandChild)) + } + } + chai.assert.deepEqual(actual, expected, JSON.stringify(actual)) + //console.log(JSON.stringify(actual)) + } +}) diff --git a/tests/visual/tablature.test.js b/tests/visual/tablature.test.js new file mode 100644 index 0000000000000000000000000000000000000000..36e406a9d81de4a556ef4b4b50f874dda9230503 --- /dev/null +++ b/tests/visual/tablature.test.js @@ -0,0 +1,1188 @@ +function replacer(key, value) { + // Filtering out properties + if (key === 'abselem') { + return 'abselem'; + } + return value; +} + +describe("Tablature", function () { + var violinAllNotes = "L:1/4\nM:4/4\nG,^G,A,^A,|B,^B,C^C|D^DE^E|F^FG^G|\n" + + "A^AB^B|c^cd^d|e^ef^f|g^ga^a|\n" + + "b^bc'^c'|d'^d'e'^e'|f'^f'g'^g'|_A,_B,_C_D|\n" + + "_E_F_G_A|_B_c_d_e|_f_g_a_b|_c'_d'_e'_f'|"; + + var violinAllNotesOutput = [ + // line 0 + [ + { "el_type": "note", "startChar": 12, "endChar": 14, "notes": [{ "num": 0, "str": 3, "pitch": "G," }] }, + { "el_type": "note", "startChar": 14, "endChar": 17, "notes": [{ "num": 1, "str": 3, "pitch": "^G," }] }, + { "el_type": "note", "startChar": 17, "endChar": 19, "notes": [{ "num": 2, "str": 3, "pitch": "A," }] }, + { "el_type": "note", "startChar": 19, "endChar": 22, "notes": [{ "num": 3, "str": 3, "pitch": "^A," }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 23, "startChar": 22 }, + { "el_type": "note", "startChar": 23, "endChar": 25, "notes": [{ "num": 4, "str": 3, "pitch": "B," }] }, + { "el_type": "note", "startChar": 25, "endChar": 28, "notes": [{ "num": 5, "str": 3, "pitch": "^B," }] }, + { "el_type": "note", "startChar": 28, "endChar": 29, "notes": [{ "num": 5, "str": 3, "pitch": "C" }] }, + { "el_type": "note", "startChar": 29, "endChar": 31, "notes": [{ "num": 6, "str": 3, "pitch": "^C" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 32, "startChar": 31 }, + { "el_type": "note", "startChar": 32, "endChar": 33, "notes": [{ "num": 0, "str": 2, "pitch": "D" }] }, + { "el_type": "note", "startChar": 33, "endChar": 35, "notes": [{ "num": 1, "str": 2, "pitch": "^D" }] }, + { "el_type": "note", "startChar": 35, "endChar": 36, "notes": [{ "num": 2, "str": 2, "pitch": "E" }] }, + { "el_type": "note", "startChar": 36, "endChar": 38, "notes": [{ "num": 3, "str": 2, "pitch": "^E" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 39, "startChar": 38 }, + { "el_type": "note", "startChar": 39, "endChar": 40, "notes": [{ "num": 3, "str": 2, "pitch": "F" }] }, + { "el_type": "note", "startChar": 40, "endChar": 42, "notes": [{ "num": 4, "str": 2, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 42, "endChar": 43, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "note", "startChar": 43, "endChar": 45, "notes": [{ "num": 6, "str": 2, "pitch": "^G" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 46, "startChar": 45 } + ], + // line 1 + [ + { "el_type": "note", "startChar": 47, "endChar": 48, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 48, "endChar": 50, "notes": [{ "num": 1, "str": 1, "pitch": "^A" }] }, + { "el_type": "note", "startChar": 50, "endChar": 51, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 51, "endChar": 53, "notes": [{ "num": 3, "str": 1, "pitch": "^B" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 54, "startChar": 53 }, + { "el_type": "note", "startChar": 54, "endChar": 55, "notes": [{ "num": 3, "str": 1, "pitch": "c" }] }, + { "el_type": "note", "startChar": 55, "endChar": 57, "notes": [{ "num": 4, "str": 1, "pitch": "^c" }] }, + { "el_type": "note", "startChar": 57, "endChar": 58, "notes": [{ "num": 5, "str": 1, "pitch": "d" }] }, + { "el_type": "note", "startChar": 58, "endChar": 60, "notes": [{ "num": 6, "str": 1, "pitch": "^d" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 61, "startChar": 60 }, + { "el_type": "note", "startChar": 61, "endChar": 62, "notes": [{ "num": 0, "str": 0, "pitch": "e" }] }, + { "el_type": "note", "startChar": 62, "endChar": 64, "notes": [{ "num": 1, "str": 0, "pitch": "^e" }] }, + { "el_type": "note", "startChar": 64, "endChar": 65, "notes": [{ "num": 1, "str": 0, "pitch": "f" }] }, + { "el_type": "note", "startChar": 65, "endChar": 67, "notes": [{ "num": 2, "str": 0, "pitch": "^f" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 68, "startChar": 67 }, + { "el_type": "note", "startChar": 68, "endChar": 69, "notes": [{ "num": 3, "str": 0, "pitch": "g" }] }, + { "el_type": "note", "startChar": 69, "endChar": 71, "notes": [{ "num": 4, "str": 0, "pitch": "^g" }] }, + { "el_type": "note", "startChar": 71, "endChar": 72, "notes": [{ "num": 5, "str": 0, "pitch": "a" }] }, + { "el_type": "note", "startChar": 72, "endChar": 74, "notes": [{ "num": 6, "str": 0, "pitch": "^a" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 75, "startChar": 74 } + ], + // line 2 + [ + { "el_type": "note", "startChar": 76, "endChar": 77, "notes": [{ "num": 7, "str": 0, "pitch": "b" }] }, + { "el_type": "note", "startChar": 77, "endChar": 79, "notes": [{ "num": 8, "str": 0, "pitch": "^b" }] }, + { "el_type": "note", "startChar": 79, "endChar": 81, "notes": [{ "num": 8, "str": 0, "pitch": "c'" }] }, + { "el_type": "note", "startChar": 81, "endChar": 84, "notes": [{ "num": 9, "str": 0, "pitch": "^c'" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 85, "startChar": 84 }, + { "el_type": "note", "startChar": 85, "endChar": 87, "notes": [{ "num": 10, "str": 0, "pitch": "d'" }] }, + { "el_type": "note", "startChar": 87, "endChar": 90, "notes": [{ "num": 11, "str": 0, "pitch": "^d'" }] }, + { "el_type": "note", "startChar": 90, "endChar": 92, "notes": [{ "num": 12, "str": 0, "pitch": "e'" }] }, + { "el_type": "note", "startChar": 92, "endChar": 95, "notes": [{ "num": 13, "str": 0, "pitch": "^e'" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 96, "startChar": 95 }, + { "el_type": "note", "startChar": 96, "endChar": 98, "notes": [{ "num": 13, "str": 0, "pitch": "f'" }] }, + { "el_type": "note", "startChar": 98, "endChar": 101, "notes": [{ "num": 14, "str": 0, "pitch": "^f'" }] }, + { "el_type": "note", "startChar": 101, "endChar": 103, "notes": [{ "num": 15, "str": 0, "pitch": "g'" }] }, + { "el_type": "note", "startChar": 103, "endChar": 106, "notes": [{ "num": 16, "str": 0, "pitch": "^g'" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 107, "startChar": 106 }, + { "el_type": "note", "startChar": 107, "endChar": 110, "notes": [{ "num": 1, "str": 3, "pitch": "_A," }] }, + { "el_type": "note", "startChar": 110, "endChar": 113, "notes": [{ "num": 3, "str": 3, "pitch": "_B," }] }, + { "el_type": "note", "startChar": 113, "endChar": 115, "notes": [{ "num": 4, "str": 3, "pitch": "_C" }] }, + { "el_type": "note", "startChar": 115, "endChar": 117, "notes": [{ "num": 6, "str": 3, "pitch": "_D" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 118, "startChar": 117 }, + ], + // line 3 + [ + { "el_type": "note", "startChar": 119, "endChar": 121, "notes": [{ "num": 1, "str": 2, "pitch": "_E" }] }, + { "el_type": "note", "startChar": 121, "endChar": 123, "notes": [{ "num": 2, "str": 2, "pitch": "_F" }] }, + { "el_type": "note", "startChar": 123, "endChar": 125, "notes": [{ "num": 4, "str": 2, "pitch": "_G" }] }, + { "el_type": "note", "startChar": 125, "endChar": 127, "notes": [{ "num": 6, "str": 2, "pitch": "_A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 128, "startChar": 127 }, + { "el_type": "note", "startChar": 128, "endChar": 130, "notes": [{ "num": 1, "str": 1, "pitch": "_B" }] }, + { "el_type": "note", "startChar": 130, "endChar": 132, "notes": [{ "num": 2, "str": 1, "pitch": "_c" }] }, + { "el_type": "note", "startChar": 132, "endChar": 134, "notes": [{ "num": 4, "str": 1, "pitch": "_d" }] }, + { "el_type": "note", "startChar": 134, "endChar": 136, "notes": [{ "num": 6, "str": 1, "pitch": "_e" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 137, "startChar": 136 }, + { "el_type": "note", "startChar": 137, "endChar": 139, "notes": [{ "num": 0, "str": 0, "pitch": "_f" }] }, + { "el_type": "note", "startChar": 139, "endChar": 141, "notes": [{ "num": 2, "str": 0, "pitch": "_g" }] }, + { "el_type": "note", "startChar": 141, "endChar": 143, "notes": [{ "num": 4, "str": 0, "pitch": "_a" }] }, + { "el_type": "note", "startChar": 143, "endChar": 145, "notes": [{ "num": 6, "str": 0, "pitch": "_b" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 146, "startChar": 145 }, + { "el_type": "note", "startChar": 146, "endChar": 149, "notes": [{ "num": 7, "str": 0, "pitch": "_c'" }] }, + { "el_type": "note", "startChar": 149, "endChar": 152, "notes": [{ "num": 9, "str": 0, "pitch": "_d'" }] }, + { "el_type": "note", "startChar": 152, "endChar": 155, "notes": [{ "num": 11, "str": 0, "pitch": "_e'" }] }, + { "el_type": "note", "startChar": 155, "endChar": 158, "notes": [{ "num": 12, "str": 0, "pitch": "_f'" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 159, "startChar": 158 }, + ], + ]; + + var violinOutOfRange = "L:1/4\nM:4/4\nF,^F,G,e'''|\n"; + + var violinOutOfRangeOutput = [ + [ + { "el_type": "note", "startChar": 12, "endChar": 14, "notes": [{ "num": "?", "str": 3, "pitch": "F," }] }, + { "el_type": "note", "startChar": 14, "endChar": 17, "notes": [{ "num": "?", "str": 3, "pitch": "^F," }] }, + { "el_type": "note", "startChar": 17, "endChar": 19, "notes": [{ "num": 0, "str": 3, "pitch": "G," }] }, + { "el_type": "note", "startChar": 19, "endChar": 23, "notes": [{ "num": 36, "str": 0, "pitch": "e'''" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 24, "startChar": 23 } + ] + ]; + + var violinKeySigs = "L:1/4\nM:4/4\nK:Ab\nA,B,CD|EFGA| [K:B] A,B,CD|EFGA|\n"; + + var violinKeySigsOutput = [ + [ + { "el_type": "note", "startChar": 17, "endChar": 19, "notes": [{ "num": 1, "str": 3, "pitch": "_A," }] }, + { "el_type": "note", "startChar": 19, "endChar": 21, "notes": [{ "num": 3, "str": 3, "pitch": "_B," }] }, + { "el_type": "note", "startChar": 21, "endChar": 22, "notes": [{ "num": 5, "str": 3, "pitch": "C" }] }, + { "el_type": "note", "startChar": 22, "endChar": 23, "notes": [{ "num": 6, "str": 3, "pitch": "_D" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 24, "startChar": 23 }, + { "el_type": "note", "startChar": 24, "endChar": 25, "notes": [{ "num": 1, "str": 2, "pitch": "_E" }] }, + { "el_type": "note", "startChar": 25, "endChar": 26, "notes": [{ "num": 3, "str": 2, "pitch": "F" }] } + , + { "el_type": "note", "startChar": 26, "endChar": 27, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "note", "startChar": 27, "endChar": 28, "notes": [{ "num": 6, "str": 2, "pitch": "_A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 29, "startChar": 28 }, + { "el_type": "note", "startChar": 35, "endChar": 38, "notes": [{ "num": 3, "str": 3, "pitch": "^A," }] }, + { "el_type": "note", "startChar": 38, "endChar": 40, "notes": [{ "num": 4, "str": 3, "pitch": "B," }] }, + { "el_type": "note", "startChar": 40, "endChar": 41, "notes": [{ "num": 6, "str": 3, "pitch": "^C" }] }, + { "el_type": "note", "startChar": 41, "endChar": 42, "notes": [{ "num": 1, "str": 2, "pitch": "^D" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 43, "startChar": 42 }, + { "el_type": "note", "startChar": 43, "endChar": 44, "notes": [{ "num": 2, "str": 2, "pitch": "E" }] }, + { "el_type": "note", "startChar": 44, "endChar": 45, "notes": [{ "num": 4, "str": 2, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 45, "endChar": 46, "notes": [{ "num": 6, "str": 2, "pitch": "^G" }] }, + { "el_type": "note", "startChar": 46, "endChar": 47, "notes": [{ "num": 1, "str": 1, "pitch": "^A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 48, "startChar": 47 } + ] + ]; + + var violinCrossTune = "L:1/4\nM:4/4\nK:A\nA,B,CD|EFGA|Bcde|fgab|\n"; + + var violinCrossTuneOutput = [ + [ + { "el_type": "note", "startChar": 16, "endChar": 18, "notes": [{ "num": 0, "str": 3, "pitch": "A," }] }, + { "el_type": "note", "startChar": 18, "endChar": 20, "notes": [{ "num": 2, "str": 3, "pitch": "B," }] }, + { "el_type": "note", "startChar": 20, "endChar": 21, "notes": [{ "num": 4, "str": 3, "pitch": "^C" }] }, + { "el_type": "note", "startChar": 21, "endChar": 22, "notes": [{ "num": 5, "str": 3, "pitch": "D" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 23, "startChar": 22 }, + { "el_type": "note", "startChar": 23, "endChar": 24, "notes": [{ "num": 0, "str": 2, "pitch": "E" }] }, + { "el_type": "note", "startChar": 24, "endChar": 25, "notes": [{ "num": 2, "str": 2, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 25, "endChar": 26, "notes": [{ "num": 4, "str": 2, "pitch": "^G" }] }, + { "el_type": "note", "startChar": 26, "endChar": 27, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 28, "startChar": 27 }, + { "el_type": "note", "startChar": 28, "endChar": 29, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 29, "endChar": 30, "notes": [{ "num": 4, "str": 1, "pitch": "^c" }] }, + { "el_type": "note", "startChar": 30, "endChar": 31, "notes": [{ "num": 5, "str": 1, "pitch": "d" }] }, + { "el_type": "note", "startChar": 31, "endChar": 32, "notes": [{ "num": 0, "str": 0, "pitch": "e" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 33, "startChar": 32 }, + { "el_type": "note", "startChar": 33, "endChar": 34, "notes": [{ "num": 2, "str": 0, "pitch": "^f" }] }, + { "el_type": "note", "startChar": 34, "endChar": 35, "notes": [{ "num": 4, "str": 0, "pitch": "^g" }] }, + { "el_type": "note", "startChar": 35, "endChar": 36, "notes": [{ "num": 5, "str": 0, "pitch": "a" }] }, + { "el_type": "note", "startChar": 36, "endChar": 37, "notes": [{ "num": 7, "str": 0, "pitch": "b" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 38, "startChar": 37 } + ] + ]; + + var violinDoubleStops = "L:1/4\nK:A\n[EE] [AA] [ee] [D^F] [E^G] [F^G] [Ac] [gb] |\n"; + + var violinDoubleStopsOutput = [ + [ + { + "el_type": "note", "startChar": 10, "endChar": 15, "notes": [{ "num": 7, "str": 3, "pitch": "E" }, + { "num": 0, "str": 2, "pitch": "E" }] + }, + { + "el_type": "note", "startChar": 15, "endChar": 20, "notes": [{ "num": 5, "str": 2, "pitch": "A" }, + { "num": 0, "str": 1, "pitch": "A" }] + }, + { + "el_type": "note", "startChar": 20, "endChar": 25, "notes": [{ "num": 7, "str": 1, "pitch": "e" }, + { "num": 0, "str": 0, "pitch": "e" }] + }, + { + "el_type": "note", "startChar": 25, "endChar": 31, "notes": [{ "num": 5, "str": 3, "pitch": "D" }, + { "num": 2, "str": 2, "pitch": "^F" }] + }, + { + "el_type": "note", "startChar": 31, "endChar": 37, "notes": [{ "num": 7, "str": 3, "pitch": "E" }, + { "num": 4, "str": 2, "pitch": "^G" }] + }, + { + "el_type": "note", "startChar": 37, "endChar": 43, "notes": [{ "num": 9, "str": 3, "pitch": "F" }, + { "num": 4, "str": 2, "pitch": "^G" }] + }, + { + "el_type": "note", "startChar": 43, "endChar": 48, "notes": [{ "num": 5, "str": 2, "pitch": "A" }, + { "num": 4, "str": 1, "pitch": "^c" }] + }, + { + "el_type": "note", "startChar": 48, "endChar": 53, "notes": [{ "num": 11, "str": 1, "pitch": "g" }, + { "num": 7, "str": 0, "pitch": "b" }] + }, + { "el_type": "bar", "type": "bar_thin", "endChar": 54, "startChar": 53 } + ] + ]; + + var transposeDoubleStopsOutput = [ + [ + { + "el_type": "note", "startChar": 10, "endChar": 15, "notes": [{ "num": 9, "str": 3, "pitch": "^F" }, + { "num": 2, "str": 2, "pitch": "^F" }] + }, + { + "el_type": "note", "startChar": 15, "endChar": 20, "notes": [{ "num": 7, "str": 2, "pitch": "B" }, + { "num": 2, "str": 1, "pitch": "B" }] + }, + { + "el_type": "note", "startChar": 20, "endChar": 25, "notes": [{ "num": 9, "str": 1, "pitch": "^f" }, + { "num": 2, "str": 0, "pitch": "^f" }] + }, + { + "el_type": "note", "startChar": 25, "endChar": 31, "notes": [{ "num": 7, "str": 3, "pitch": "E" }, + { "num": 4, "str": 2, "pitch": "^G" }] + }, + { + "el_type": "note", "startChar": 31, "endChar": 37, "notes": [{ "num": 2, "str": 2, "pitch": "^F" }, + { "num": 1, "str": 1, "pitch": "^A" }] + }, + { + "el_type": "note", "startChar": 37, "endChar": 43, "notes": [{ "num": 4, "str": 2, "pitch": "G" }, + { "num": 1, "str": 1, "pitch": "^A" }] + }, + { + "el_type": "note", "startChar": 43, "endChar": 48, "notes": [{ "num": 7, "str": 2, "pitch": "B" }, + { "num": 6, "str": 1, "pitch": "^d" }] + }, + { + "el_type": "note", "startChar": 48, "endChar": 53, "notes": [{ "num": 13, "str": 1, "pitch": "a" }, + { "num": 9, "str": 0, "pitch": "^c'" }] + }, + { "el_type": "bar", "type": "bar_thin", "endChar": 54, "startChar": 53 } + ] + ]; + + var violinUnusualAccidentals = "L:1/4\nK:C\n^/G ^^A __B _/c|\n"; + + var violinUnusualAccidentalsOutput = [ + [ + { "el_type": "note", "startChar": 10, "endChar": 14, "notes": [{ "num": "5^", "str": 2, "pitch": "^/G" }] }, + { "el_type": "note", "startChar": 14, "endChar": 18, "notes": [{ "num": 2, "str": 1, "pitch": "^^A" }] }, + { "el_type": "note", "startChar": 18, "endChar": 22, "notes": [{ "num": 0, "str": 1, "pitch": "__B" }] }, + { "el_type": "note", "startChar": 22, "endChar": 25, "notes": [{ "num": "3v", "str": 1, "pitch": "_/c" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 26, "startChar": 25 } + ] + ]; + + var guitarCapo = "M: 2/4\n" + + "K:F\n" + + "|: [EE,]^FGA [B2B,]BA | [G2A,]^FE [B,B]^cBA | [G2A,]^FE [B,4^FB]:| \n"; + + var guitarCapoOutput = [ + [ + { "el_type": "bar", "type": "bar_left_repeat", "endChar": 13, "startChar": 11 }, + { "el_type": "note", "startChar": 13, "endChar": 19, "notes": [{ "num": 0, "str": 5, "pitch": "E," }, { "num": 0, "str": 3, "pitch": "E" }] }, + { "el_type": "note", "startChar": 19, "endChar": 21, "notes": [{ "num": 2, "str": 3, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 21, "endChar": 22, "notes": [{ "num": 3, "str": 3, "pitch": "G" }] }, + { "el_type": "note", "startChar": 22, "endChar": 24, "notes": [{ "num": 0, "str": 2, "pitch": "A" }] }, + { "el_type": "note", "startChar": 24, "endChar": 30, "notes": [{ "num": 6, "str": 5, "pitch": "_B," }, { "num": 1, "str": 2, "pitch": "_B" }] }, + { "el_type": "note", "startChar": 30, "endChar": 31, "notes": [{ "num": 1, "str": 2, "pitch": "_B" }] }, + { "el_type": "note", "startChar": 31, "endChar": 33, "notes": [{ "num": 0, "str": 2, "pitch": "A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 34, "startChar": 33 }, + { "el_type": "note", "startChar": 34, "endChar": 41, "notes": [{ "num": 5, "str": 5, "pitch": "A," }, { "num": 3, "str": 3, "pitch": "G" }] }, + { "el_type": "note", "startChar": 41, "endChar": 43, "notes": [{ "num": 2, "str": 3, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 43, "endChar": 45, "notes": [{ "num": 0, "str": 3, "pitch": "E" }] }, + { "el_type": "note", "startChar": 45, "endChar": 50, "notes": [{ "num": 6, "str": 5, "pitch": "_B," }, { "num": 1, "str": 2, "pitch": "_B" }] }, + { "el_type": "note", "startChar": 50, "endChar": 52, "notes": [{ "num": 2, "str": 1, "pitch": "^c" }] }, + { "el_type": "note", "startChar": 52, "endChar": 53, "notes": [{ "num": 1, "str": 2, "pitch": "_B" }] }, + { "el_type": "note", "startChar": 53, "endChar": 55, "notes": [{ "num": 0, "str": 2, "pitch": "A" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 56, "startChar": 55 }, + { "el_type": "note", "startChar": 56, "endChar": 63, "notes": [{ "num": 5, "str": 5, "pitch": "A," }, { "num": 3, "str": 3, "pitch": "G" }] }, + { "el_type": "note", "startChar": 63, "endChar": 65, "notes": [{ "num": 2, "str": 3, "pitch": "^F" }] }, + { "el_type": "note", "startChar": 65, "endChar": 67, "notes": [{ "num": 0, "str": 3, "pitch": "E" }] }, + { "el_type": "note", "startChar": 67, "endChar": 75, "notes": [{ "num": 6, "str": 5, "pitch": "_B," }, { "num": 2, "str": 3, "pitch": "^F" }, { "num": 1, "str": 2, "pitch": "_B" }] }, + { "el_type": "bar", "type": "bar_right_repeat", "endChar": 77, "startChar": 75 }] + ]; + + var twoVoices = "%%score (1 2)\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "V:1\n" + + "d|BGDg|\n" + + "V:2\n" + + "G|ABAB|"; + + var twoVoicesOutput = [ + [ + { "el_type": "note", "startChar": 38, "endChar": 39, "notes": [{ "num": 5, "str": 1, "pitch": "d" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 40, "startChar": 39 }, + { "el_type": "note", "startChar": 40, "endChar": 41, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 41, "endChar": 42, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "note", "startChar": 42, "endChar": 43, "notes": [{ "num": 0, "str": 2, "pitch": "D" }] }, + { "el_type": "note", "startChar": 43, "endChar": 44, "notes": [{ "num": 3, "str": 0, "pitch": "g" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 45, "startChar": 44 }, + ],[ + { "el_type": "note", "startChar": 50, "endChar": 51, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 52, "startChar": 51 }, + { "el_type": "note", "startChar": 52, "endChar": 53, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 53, "endChar": 54, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 54, "endChar": 55, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 55, "endChar": 56, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 57, "startChar": 56 }, + ], + ]; + + var twoStaves = "%%score 1|2\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "V:1\n" + + "d|BGDg|\n" + + "V:2\n" + + "G|ABAB|"; + + var twoStavesOutput = [ + [ + { "el_type": "note", "startChar": 36, "endChar": 37, "notes": [{ "num": 5, "str": 1, "pitch": "d" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 38, "startChar": 37 }, + { "el_type": "note", "startChar": 38, "endChar": 39, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 39, "endChar": 40, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "note", "startChar": 40, "endChar": 41, "notes": [{ "num": 0, "str": 2, "pitch": "D" }] }, + { "el_type": "note", "startChar": 41, "endChar": 42, "notes": [{ "num": 3, "str": 0, "pitch": "g" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 43, "startChar": 42 }, + ], + [ + { "el_type": "note", "startChar": 48, "endChar": 49, "notes": [{ "num": 3, "str": 3, "pitch": "G" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 50, "startChar": 49 }, + { "el_type": "note", "startChar": 50, "endChar": 51, "notes": [{ "num": 0, "str": 2, "pitch": "A" }] }, + { "el_type": "note", "startChar": 51, "endChar": 52, "notes": [{ "num": 0, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 52, "endChar": 53, "notes": [{ "num": 0, "str": 2, "pitch": "A" }] }, + { "el_type": "note", "startChar": 53, "endChar": 54, "notes": [{ "num": 0, "str": 1, "pitch": "B" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 55, "startChar": 54 }, + ], + ]; + + var skipStaff = "%%score 1|2|3\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "V:1\n" + + "d|BGDg|\n" + + "V:2\n" + + "G|ABAB|\n" + + "V:3\n" + + "G,|A,B,CD|"; + + var skipStaffOutput = [ + [ + { "el_type": "note", "startChar": 50, "endChar": 51, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 52, "startChar": 51 }, + { "el_type": "note", "startChar": 52, "endChar": 53, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 53, "endChar": 54, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "note", "startChar": 54, "endChar": 55, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 55, "endChar": 56, "notes": [{ "num": 2, "str": 1, "pitch": "B" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 57, "startChar": 56 }, + ], + ]; + var skipStaffParams = [ + { instrument: ""}, + { + instrument: 'violin', + } + ]; + + var guitarParams = [ + { + instrument: 'guitar', + label : 'Guitar (%T)', + tuning: ['D,', 'A,', 'D', 'G', 'A', 'd'], + capo: 2 + } + ]; + + var accidentalParams = [ + { + instrument: 'guitar', + label : 'Accidentals (%T)', + tuning: ['^D,', '_A,', '_D', '^G', '_B', '^d'], + } + ]; + + var badTuningParams = [ + { + instrument: 'guitar', + label : 'Guitar (%T)', + tuning: ['D', 'A', 'D', 'G', 'A', 'd'], + } + ]; + + var violinParams = [ + { + instrument: 'violin', + label: 'Violin', + tuning: ['G,', 'D', 'A', 'e'] + } + ]; + + var firstStaffOnlyParams = [ + { + instrument: 'violin', + label: 'Violin', + firstStaffOnly: true, + tuning: ['G,', 'D', 'A', 'e'] + } + ]; + + var violinGuitarParams = [ + // first + { + instrument: 'violin', + label: 'Violin', + tuning: ['G,', 'D', 'A', 'e'] + }, + // second + { + instrument: 'guitar', + label: 'Guitar (%T)', + tuning: ['D,', 'A,', 'D', 'G', 'A', 'd'], + capo: 2 + }, + // additional lines to be ignored + { + instrument: 'violin', + label: 'Violin2', + tuning: ['G,', 'D', 'A', 'e'] + }, + { + instrument: 'guitar', + label: 'Guitar (%T)2', + tuning: ['D,', 'A,', 'D', 'G', 'A', 'd'], + capo: 2 + }, + { + instrument: 'violin', + label: 'Violin3', + tuning: ['G,', 'D', 'A', 'e'] + }, + { + instrument: 'guitar', + label: 'Guitar (%T)3', + tuning: ['D,', 'A,', 'D', 'G', 'A', 'd'], + capo: 2 + }, + ]; + + var violinCrossTuneParams = [ + { + instrument: 'violin', + label: 'Violin (%T)', + tuning: ['A,', 'E', 'A', 'e'] + } + ]; + + var graceNotes = "M: 4/4\n" + + "L: 1/4\n" + + "K: C\n" + + "{B}A {dB}G {ABcd efga}b|"; + + var graceNotesOutput = [ + [ + { "el_type": "note", "startChar": 19, "endChar": 24, "notes": [{ "num": 2, "str": 1, "pitch": "B" }], "grace": true }, + { "el_type": "note", "startChar": 19, "endChar": 24, "notes": [{ "num": 0, "str": 1, "pitch": "A" }] }, + { "el_type": "note", "startChar": 24, "endChar": 30, "notes": [{ "num": 5, "str": 1, "pitch": "d" }], "grace": true }, + { "el_type": "note", "startChar": 24, "endChar": 30, "notes": [{ "num": 2, "str": 1, "pitch": "B" }], "grace": true }, + { "el_type": "note", "startChar": 24, "endChar": 30, "notes": [{ "num": 5, "str": 2, "pitch": "G" }] }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 0, "str": 1, "pitch": "A" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 2, "str": 1, "pitch": "B" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 3, "str": 1, "pitch": "c" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 5, "str": 1, "pitch": "d" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 0, "str": 0, "pitch": "e" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 1, "str": 0, "pitch": "f" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 3, "str": 0, "pitch": "g" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 5, "str": 0, "pitch": "a" }], "grace": true }, + { "el_type": "note", "startChar": 30, "endChar": 42, "notes": [{ "num": 7, "str": 0, "pitch": "b" }] }, + { "el_type": "bar", "type": "bar_thin", "endChar": 43, "startChar": 42 } + ] + ]; + + var warningRecovery = "X:1\n" + + "K:C\n" + + "Gr"; + + var warningRecoveryOutput = [ + [ + {"el_type":"note","startChar":8,"endChar":9,"notes":[{"num":5,"str":2,"pitch":"G"}]} + ] + ] + + var liningUp = "X: 1\n" + + "K:C\n" + + 'G>^FG>_A G2 "a very very long chord"D2| [CD] {fe}d|\n'; + + var bracketPlacement = "X:5\n" + + "L:1/8\n" + + "%%staves {RH LH}\n" + + "V: RH clef=treble\n" + + "V: LH clef=bass\n" + + "K:C\n" + + "[V: RH]\n" + + "|: c\n" + + "[V: LH]\n" + + "|: G,\n" + + var barNumbers = "X:1\n" + + "%% barnumbers 1\n" + + "K:C \n" + + "F8|G8| \n" + + "F8|!coda!G8|]" + + var barNumbersOutput = [ + [ + {"el_type":"note","startChar":25,"endChar":27,"notes":[{"num":3,"str":2,"pitch":"F"}]}, + {"el_type":"bar","type":"bar_thin","endChar":28,"startChar":27}, + {"el_type":"note","startChar":28,"endChar":30,"notes":[{"num":5,"str":2,"pitch":"G"}]}, + {"el_type":"bar","type":"bar_thin","endChar":31,"startChar":30}, + ], + [ + {"el_type":"note","startChar":33,"endChar":35,"notes":[{"num":3,"str":2,"pitch":"F"}]}, + {"el_type":"bar","type":"bar_thin","endChar":36,"startChar":35}, + {"el_type":"note","startChar":36,"endChar":44,"notes":[{"num":5,"str":2,"pitch":"G"}]}, + {"el_type":"bar","type":"bar_thin_thick","endChar":46,"startChar":44}, + ] + ] + + var noExtraVertical = "X:1\n" + + '"A7"A' + + var extraVertical = "X:1\n" + + "Q:150\n" + + "P:Verse\n" + + '"A7"A' + + var bracketWidth = "X:1\n" + + "%%staves {RH LH}\n" + + "V:RH clef=treble\n" + + "V:LH clef=bass\n" + + "K:C\n" + + "V:RH\n" + + "ABc\n" + + "V:LH\n" + + "A,B,C" + + var subTitle = "X: 1\n" + + "T: First\n" + + "T: Second\n" + + "M: C\n" + + "K: F\n" + + "A\n" + + var crashChord = "X: 1\n" + + "M: 4/4\n" + + "L: 1/4\n" + + "K: Bm\n" + + "[F,^G,]" + + var graceOnRest = "X:1\n" + + "K:C\n" + + "(f3 {a})y" + + var graceOnRestOutput = [ + [ + {"el_type":"note","startChar":8,"endChar":12,"notes":[{"num":1,"str":0,"pitch":"f"}]}, + { "el_type": "note", "startChar": 12, "endChar": 17, "notes": [{ "num": 5, "str": 0, "pitch": "a" }], "grace": true }, + ] + ] + + var tiedNote = "X:1\n" + + "K:C\n" + + "f-f" + + var tiedNoteOutput = [ + [ + {"el_type":"note","startChar":8,"endChar":10,"notes":[{"num":1,"str":0,"pitch":"f"}]} + ] + ] + + var percussionClef = "X:1\n" + + "K:C clef=perc stafflines=1 \n" + + "B" + + var overlay = "X:1\n" + + "K:C\n" + + "G8 & C4 D4|E4 F4|]" + + var overlayOutput = [ + [ + {"el_type":"note","startChar":8,"endChar":11,"notes":[{"num":5,"str":2,"pitch":"G"}]}, + {"el_type":"bar","type":"bar_thin","endChar":19,"startChar":18}, + {"el_type":"note","startChar":19,"endChar":22,"notes":[{"num":2,"str":2,"pitch":"E"}]}, + {"el_type":"note","startChar":22,"endChar":24,"notes":[{"num":3,"str":2,"pitch":"F"}]}, + {"el_type":"bar","type":"bar_thin_thick","endChar":26,"startChar":24}, + ], + [ + {"el_type":"note","startChar":12,"endChar":16,"notes":[{"num":5,"str":3,"pitch":"C"}]}, + {"el_type":"note","startChar":16,"endChar":18,"notes":[{"num":0,"str":2,"pitch":"D"}]}, + {"el_type":"bar","type":"bar_thin","endChar":19,"startChar":18}, + {"el_type":"bar","type":"bar_thin_thick","endChar":26,"startChar":24} + ], + ] + + var clefNone = "X: 1\n" + + "K: clef=none\n" + + "C |\n" + + var kitchenSink = "X:1\n" + + "M:4/4\n" + + "L:1/16\n" + + "%%titlefont Times 22.0\n" + + "%%partsfont box\n" + + "%%barnumbers 1\n" + + "T: all-element-types\n" + + "T: Everything should be selectable\n" + + "C: public domain\n" + + "R: Hit it\n" + + "A: Yours Truly\n" + + "S: My own testing\n" + + "W: Now is the time for all good men\n" + + "W:\n" + + "W: To come to the aid of their party.\n" + + "H: This shows every type of thing that can possibly be drawn.\n" + + "H:\n" + + "H: And two lines of history!\n" + + "Q: \"Easy Swing\" 1/4=140\n" + + "P: AABB\n" + + "%%staves {(PianoRightHand extra) (PianoLeftHand)}\n" + + "V:PianoRightHand clef=treble+8 name=RH\n" + + "V:PianoLeftHand clef=bass name=LH\n" + + "K:Bb\n" + + "P:A\n" + + "%%text there is some random text\n" + + "%%sep 0.4cm 0.4cm 6cm\n" + + "[V: PianoRightHand] !mp![b8B8d8] f3g !//!f4|!<(![d12b12] !<)![b4g4]|z4 b^f_df (3B2d2c2 B4|1[Q:\"left\" 1/4=170\"right\"]!f![c4f4] z4 [b8d8]| (3[G8e8] Tu[c8f8] G8|]\n" + + "w:Strang- ers\n" + + "[V: extra] B,16 | \"Bb\"{C}B,4 ({^CD}B,4 =B,8) |\n" + + "T:Inserted subtitle\n" + + "[V: PianoLeftHand] B,6 .D2 !arpeggio![F,8F8A,8]|(B,2 B,,2 C,12)|\"^annotation\"F,16|[F,16D,16]|Z2|]\n" + + var kitchenSinkOutput = [ + 429, + 786, + 580, + 657 + ] + + var octaveClef = "X: 1\n" + + "K: C treble-8\n" + + " G, G | g g' |\n" + + var octaveClefOutput = [ + [ + {"el_type":"note","startChar":20,"endChar":23,"notes":[{"num":"?","str":3,"pitch":"G,,"}]}, + {"el_type":"note","startChar":23,"endChar":25,"notes":[{"num":0,"str":3,"pitch":"G,"}]}, + {"el_type":"bar","type":"bar_thin","endChar":26,"startChar":25}, + {"el_type":"note","startChar":26,"endChar":29,"notes":[{"num":5,"str":2,"pitch":"G"}]}, + {"el_type":"note","startChar":29,"endChar":32,"notes":[{"num":3,"str":0,"pitch":"g"}]}, + {"el_type":"bar","type":"bar_thin","endChar":33,"startChar":32} + ] + ] + + var unusualFontSize = "X:1\n" + + "%%stretchlast\n" + + "%%gchordfont Arial 10 box\n" + + "L:1/4\n" + + "K:C\n" + + "|1\"Gbmaj7\"DEGB:|\n" + + "%%gchordfont Arial 20 box\n" + + "|1\"Gbmaj7\"DEGB:|\n" + + "%%gchordfont Arial 40 box\n" + + "|1\"Gbmaj7\"DEGB:|\n" + + "%%gchordfont Arial 80 box\n" + + "|1\"Gbmaj7\"DEGB:|\n" + + "%%gchordfont Arial 130 box\n" + + "|1\"Gbmaj7\"DEGB:|\n" + + var unusualFontSizeOutput = [ + 72, + 138, + 269, + 335, + 505, + 571, + 823, + 889, + 1242, + 1308, + ] + + var weirdNoteConstruction = "X:1\n" + + "K:C\n" + + "a, B'" + + var weirdNoteConstructionOutput = [ + [ + {"el_type":"note","startChar":8,"endChar":11,"notes":[{"num":0,"str":1,"pitch":"A"}]}, + {"el_type":"note","startChar":11,"endChar":13,"notes":[{"num":7,"str":0,"pitch":"b"}]} + ] + ] + + // TODO-PER: Eventually the tablature should support strings being tuned out of order (like a uke or banjo) + var badTuning = "X:1\n" + + "K:C\n" + + "D, A, D G B e" + + var staffPlacement = "X:1\n" + + "%%score (1 | 2)\n" + + "L:1/4\n" + + "M:4/4\n" + + "K:D\n" + + "V:1\n" + + "A G/F/ G A3/4 A/4 |\n" + + "[|]1 F z z A :|\n" + + "[|]2 F z A d [|] |\n" + + "V:2\n" + + "F E/D/ E F3/4 F/4 |\n" + + "[|]1 D z z A :|\n" + + "[|]2 D z F A [|] |\n" + + var staffPlacementOutput = [ + 44, + 145, + 258, + 363, + 475, + 580 + ] + + var accidentals2 = "X: 1\n" + + "M: 4/4\n" + + "L: 1/4\n" + + "K: G\n" + + "A2^A_A|F_F^F=F|F^E^^EE|G_GG=G|[K: Eb]A2=A2|\n" + + var accidentals2Output = [ + [ + {"el_type":"note","startChar":24,"endChar":26,"notes":[{"num":0,"str":1,"pitch":"A"}]}, + {"el_type":"note","startChar":26,"endChar":28,"notes":[{"num":1,"str":1,"pitch":"^A"}]}, + {"el_type":"note","startChar":28,"endChar":30,"notes":[{"num":6,"str":2,"pitch":"_A"}]}, + {"el_type":"bar","type":"bar_thin","endChar":31,"startChar":30}, + {"el_type":"note","startChar":31,"endChar":32,"notes":[{"num":4,"str":2,"pitch":"^F"}]}, + {"el_type":"note","startChar":32,"endChar":34,"notes":[{"num":2,"str":2,"pitch":"_F"}]}, + {"el_type":"note","startChar":34,"endChar":36,"notes":[{"num":4,"str":2,"pitch":"^F"}]}, + {"el_type":"note","startChar":36,"endChar":38,"notes":[{"num":3,"str":2,"pitch":"=F"}]}, + {"el_type":"bar","type":"bar_thin","endChar":39,"startChar":38}, + {"el_type":"note","startChar":39,"endChar":40,"notes":[{"num":4,"str":2,"pitch":"^F"}]}, + {"el_type":"note","startChar":40,"endChar":42,"notes":[{"num":3,"str":2,"pitch":"^E"}]}, + {"el_type":"note","startChar":42,"endChar":45,"notes":[{"num":4,"str":2,"pitch":"^^E"}]}, + {"el_type":"note","startChar":45,"endChar":46,"notes":[{"num":4,"str":2,"pitch":"E"}]}, + {"el_type":"bar","type":"bar_thin","endChar":47,"startChar":46}, + {"el_type":"note","startChar":47,"endChar":48,"notes":[{"num":5,"str":2,"pitch":"G"}]}, + {"el_type":"note","startChar":48,"endChar":50,"notes":[{"num":4,"str":2,"pitch":"_G"}]}, + {"el_type":"note","startChar":50,"endChar":51,"notes":[{"num":4,"str":2,"pitch":"G"}]}, + {"el_type":"note","startChar":51,"endChar":53,"notes":[{"num":5,"str":2,"pitch":"=G"}]}, + {"el_type":"bar","type":"bar_thin","endChar":54,"startChar":53}, + {"el_type":"note","startChar":61,"endChar":63,"notes":[{"num":6,"str":2,"pitch":"_A"}]}, + {"el_type":"note","startChar":63,"endChar":66,"notes":[{"num":0,"str":1,"pitch":"=A"}]}, + {"el_type":"bar","type":"bar_thin","endChar":67,"startChar":66}, + ] + ] + + var accidentalsInDef = "X: 1\n" + + "M: 4/4\n" + + "L: 1/4\n" + + "K: G\n" + + "G,CEGda\n" + + var accidentalsInDefOutput = [ + [ + {"el_type":"note","startChar":24,"endChar":26,"notes":[{"num":4,"str":5,"pitch":"G,"}]}, + {"el_type":"note","startChar":26,"endChar":27,"notes":[{"num":4,"str":4,"pitch":"C"}]}, + {"el_type":"note","startChar":27,"endChar":28,"notes":[{"num":3,"str":3,"pitch":"E"}]}, + {"el_type":"note","startChar":28,"endChar":29,"notes":[{"num":6,"str":3,"pitch":"G"}]}, + {"el_type":"note","startChar":29,"endChar":30,"notes":[{"num":4,"str":1,"pitch":"d"}]}, + {"el_type":"note","startChar":30,"endChar":31,"notes":[{"num":6,"str":0,"pitch":"a"}]}, + ] + ] + + var lyrics = "X: 1\n" + + "M: 3/4\n" + + "L: 1/4\n" + + "K: G\n" + + "GAB|\n" + + "w: Tra la la\n" + + "w: Tra la la\n" + + var lyricsOutput = [ + 41, + 154, + ] + + var firstStaffOnly = "X:1\n" + + "%%stretchlast\n" + + "L:1/4\n" + + "K:C\n" + + "|:\"Gbmaj7\"DEGB:|\n" + + "|:\"Gbmaj7\"DEGB:|\n" + + "|:\"Gbmaj7\"DEGB:|\n" + + "|:\"Gbmaj7\"DEGB:|\n" + + "|:\"Gbmaj7\"DEGB:|\n" + + var firstStaffOnlyOutput1 = [ + 62, + 128, + 238, + 304, + 413, + 479, + 589, + 655, + 764, + 830, + ] + + var firstStaffOnlyOutput2 = [ + 62, + 128, + 238, + 304, + 392, + 458, + 547, + 612, + 701, + 767, + ] + + it("accidentals-in-def", function () { + doStaffTest(accidentalsInDef, accidentalsInDefOutput, accidentalParams); + }); + + it("accidentals", function () { + doStaffTest(violinAllNotes, violinAllNotesOutput, violinParams); + }); + + it("accidentals2", function () { + doStaffTest(accidentals2, accidentals2Output, violinParams); + }); + + it("out of range", function () { + doStaffTest(violinOutOfRange, violinOutOfRangeOutput, violinParams); + }); + + it("key sigs", function () { + doStaffTest(violinKeySigs, violinKeySigsOutput, violinParams); + }); + + it("cross tune", function () { + doStaffTest(violinCrossTune, violinCrossTuneOutput, violinCrossTuneParams); + }); + + it("double stops", function () { + doStaffTest(violinDoubleStops, violinDoubleStopsOutput, violinCrossTuneParams); + }); + + it("unusual accidentals", function () { + doStaffTest(violinUnusualAccidentals, violinUnusualAccidentalsOutput, violinParams); + }); + + it("capo", function () { + doStaffTest(guitarCapo, guitarCapoOutput, guitarParams); + }); + + it("two voices", function () { + doStaffTest(twoVoices, twoVoicesOutput, violinParams); + }); + + it("two staves", function () { + doStaffTest(twoStaves, twoStavesOutput, violinGuitarParams); + }); + + it("skip staff", function () { + doStaffTest(skipStaff, skipStaffOutput, skipStaffParams); + }); + + it("transpose", function () { + doStaffTest(violinDoubleStops, transposeDoubleStopsOutput, violinCrossTuneParams, { visualTranspose: 2 }); + }); + + it("grace notes", function () { + doStaffTest(graceNotes, graceNotesOutput, violinParams); + }); + + it("warning recovery", function () { + doStaffTest(warningRecovery, warningRecoveryOutput, violinParams); + }); + + it("more tabs than voices", function () { + doStaffTest(warningRecovery, warningRecoveryOutput, violinGuitarParams); + }); + + it("grace-on-rest", function () { + doStaffTest(graceOnRest, graceOnRestOutput, violinParams); + }); + + it("tied note", function () { + doStaffTest(tiedNote, tiedNoteOutput, violinParams); + }); + + it("overlay", function () { + doStaffTest(overlay, overlayOutput, violinParams); + }); + + it("octave clef", function () { + doStaffTest(octaveClef, octaveClefOutput, violinParams); + }); + + it("font-size", function () { + doVerticalTest(unusualFontSize, unusualFontSizeOutput, violinParams); + }); + + it("weird note construction", function () { + doStaffTest(weirdNoteConstruction, weirdNoteConstructionOutput, violinParams); + }); + + it("staff-placement", function () { + doVerticalTest(staffPlacement, staffPlacementOutput, violinParams); + }); + + it("fonts", function () { + var params = { + format: { + tablabelfont: "Courier New", + tabnumberfont: "cursive", + tabgracefont: "serif" + } + } + doRender(graceNotes, violinParams, params); + var label = document.querySelector("#paper .abcjs-instrument-name") + var number = document.querySelector("#paper .abcjs-tab-number") + var grace = document.querySelector("#paper .abcjs-tab-grace") + var labelFont = label.getAttribute("font-family") + var numberFont = number.getAttribute("font-family") + var graceFont = grace.getAttribute("font-family") + chai.assert.equal(labelFont, params.format.tablabelfont, "Label font not set") + chai.assert.equal(numberFont, params.format.tabnumberfont, "Number font not set") + chai.assert.equal(graceFont, params.format.tabgracefont, "Grace font not set") + }); + + it("lining up", function () { + var visualObj = doRender(liningUp, violinParams); + var noteheads = document.querySelectorAll('path[data-name="G"],path[data-name="^F"],path[data-name="_A"],path[data-name="D"],path[data-name="C"],path[data-name="f"],path[data-name="e"],path[data-name="d"]') + var i; + for (i = 0; i < noteheads.length; i++) { + var dim = noteheads[i].getBBox() + var guide = document.createElementNS("http://www.w3.org/2000/svg", "line"); + guide.setAttribute("class", "abcjs-cursor"); + guide.setAttribute('x1', dim.x); + guide.setAttribute('y1', dim.y); + guide.setAttribute('x2', dim.x); + guide.setAttribute('y2', dim.y+100); + guide.setAttribute('stroke', "red"); + var svg = document.querySelector("#paper svg"); + svg.appendChild(guide); + guide = document.createElementNS("http://www.w3.org/2000/svg", "line"); + guide.setAttribute("class", "abcjs-cursor"); + guide.setAttribute('x1', dim.x+dim.width); + guide.setAttribute('y1', dim.y); + guide.setAttribute('x2', dim.x+dim.width); + guide.setAttribute('y2', dim.y+100); + guide.setAttribute('stroke', "red"); + svg.appendChild(guide); + } + var dots = visualObj[0].lines[0].staff[0].voices[0]; + var tab = visualObj[0].lines[0].staff[1].voices[0]; + for (i = 0; i < dots.length; i++) { + if (dots.el_type === "note") { + var dot = dots[i]; + var number = tab[i]; + chai.assert.equal(Math.round(number.abselem.x), Math.round(dot.abselem.heads[0].x + dot.abselem.heads[0].w / 2), "Number not centered") + } + } + }); + + it("bar numbers", function () { + function checkForBarNumbers(el, line, element) { + if (el.abselem.abcelem.type === 'bar_thin') { + for (var i = 0; i < el.abselem.children.length; i++) { + var relEl = el.abselem.children[i] + if (relEl.type === "barNumber") + chai.assert(false, "bar number found on line "+line+" element "+element) + } + } + } + doStaffTest(barNumbers, barNumbersOutput, violinParams, undefined, checkForBarNumbers); + }); + + it("extra vertical", function() { + var visualObj = doRender(noExtraVertical, violinParams) + var firstLineTop1 = visualObj[0].lines[0].staffGroup.staffs[0].absoluteY + var secondLineTop1 = visualObj[0].lines[0].staffGroup.staffs[1].absoluteY + var difference1 = Math.round((secondLineTop1 - firstLineTop1)*1000)/1000 + visualObj = doRender(extraVertical, violinParams) + var firstLineTop2 = visualObj[0].lines[0].staffGroup.staffs[0].absoluteY + var secondLineTop2 = visualObj[0].lines[0].staffGroup.staffs[1].absoluteY + var difference2 = Math.round((secondLineTop2 - firstLineTop2)*1000)/1000 + //console.log(firstLineTop1, secondLineTop1, difference1, firstLineTop2, secondLineTop2, difference2) + chai.assert.equal(difference2, difference1, "Spacing between staves is not correct") + }) + + it("bracket-width", function() { + var visualObj = doRender(bracketWidth, violinGuitarParams) + var name = document.querySelector(".abcjs-instrument-name") + var x = name.getAttribute("x") + chai.assert.equal(parseInt(x,10), 33, "Not enough left margin for instrument name") + }) + + it("bad-tuning", function () { + var visualObj = doRender(badTuning, badTuningParams) + chai.assert.equal(visualObj[0].warnings, "Invalid string Instrument tuning : D string lower than A string") + }); + + it("subtitle", function() { + // Just see it not crash + var visualObj = doRender(subTitle, violinParams) + }) + + it("crash chord", function() { + // Just see it not crash + var visualObj = doRender(crashChord, violinParams) + }) + + it("crash-clef", function() { + // Just see it not crash + var visualObj = doRender(clefNone, violinParams) + }) + + it("bracket-height", function() { + var visualObj = doRender(bracketPlacement, violinGuitarParams) + var lastStaff = document.querySelector(".abcjs-staff.abcjs-l0.abcjs-v3") + var dim = lastStaff.getBBox() + var bottom = dim.y + dim.height + var brace = document.querySelector(".abcjs-brace") + dim = brace.getBBox() + var braceBottom = dim.y+dim.height + chai.assert.equal(braceBottom, bottom, "Brace should go to bottom") + + doVerticalTest(bracketPlacement, bracketPlacementOutput, violinGuitarParams) + }) + + it("kitchen sink", function() { + doVerticalTest(kitchenSink, kitchenSinkOutput, violinGuitarParams) + }) + + it("percussion clef", function() { + var visualObj = doRender(percussionClef, violinParams) + chai.assert(visualObj[0].lines[0].staff.length === 1, "Should skip percussion clef") + }) + + it("firstStaffOnly", function() { + doVerticalTest(firstStaffOnly, firstStaffOnlyOutput1, violinParams) + doVerticalTest(firstStaffOnly, firstStaffOnlyOutput2, firstStaffOnlyParams) + }) + + it("tab-lyrics", function() { + doVerticalTest(lyrics, lyricsOutput, violinParams) + }) +}); + +function doRender(abc, tabParams, params) { + var warningLine = document.getElementById('warnings') + warningLine.innerHTML = "" + var options = { + add_classes: true, + tablature: tabParams + }; + if (params) { + var keys = Object.keys(params); + for (var k = 0; k < keys.length; k++) { + options[keys[k]] = params[keys[k]]; + } + } + var visualObj = abcjs.renderAbc("paper", abc, options ); + if (visualObj[0].warnings) { + var el = document.querySelector("#warnings") + if (el) + el.innerHTML = visualObj[0].warnings.join(",") + } + return visualObj; +} + +function getTabStaff(staffs, number) { + var tabNumber = 0; + for (var ii = 0; ii < staffs.length; ii++) { + if (staffs[ii].clef.type === 'TAB') { + if (tabNumber === number) { + return staffs[ii].voices; + } + tabNumber++; + } + } + return null; +} + +function doStaffTest(abc, expected, tabParams, params, callback) { + var visualObj = doRender(abc, tabParams, params); + var lineLength = visualObj[0].lines.length; + for (var i = 0; i < lineLength; i++) { + var line = visualObj[0].lines[i]; + var staffNumber = 0; + var tab = getTabStaff(line.staff, staffNumber); + if (tab == null) { + chai.assert(false,"unexpected null value getting tab staff"); + } + while (tab != null) { + for (var v = 0; v < tab.length; v++) { + var thisVoice = tab[v] + for (var j = 0; j < thisVoice.length; j++) { + var el = Object.assign({}, thisVoice[j]); + if (callback) + callback(el, i, j) + delete el.abselem + var msg = "\nrcv: " + JSON.stringify(el, replacer) + "\n" + + "exp: " + JSON.stringify(expected[i + staffNumber][j]) + "\n"; + chai.assert.deepStrictEqual(el, expected[i + staffNumber][j], msg); + } + chai.assert.equal(thisVoice.length, expected[v].length, "line " + i + " length mismatch"); + staffNumber++; + } + tab = getTabStaff(line.staff, staffNumber); + } + } + chai.assert.equal((lineLength-1) +staffNumber, expected.length, "different numbers of lines"); + return visualObj[0] +} + +function doVerticalTest(abc, expected, tabParams, params) { + var visualObj = doRender(abc, tabParams, params); + var yPos = document.querySelectorAll("#paper .abcjs-top-line") + for (var i = 0; i < yPos.length; i++) { + var topLine = yPos[i] + var dim = topLine.getBBox() + chai.assert.equal(Math.round(dim.y), expected[i], "Vertical spacing of staves wrong") + } +} + +// TODO-PER: Here are some more failures: +// X:19 +// K:C +// a, +// +// X:45 +// K:Am +// [d'e''] +// +// X:1 +// K:C +// [^F,_G,] +// +// X:1 +// T:small-notes +// M: 4/4 +// L: 1/8 +// K: Bb +// %%score (1 2) +// V:1 +// FF|:"Bb"DDE2=EF3|x8|BB_AG "F7"FG3|x8| +// "Bb"FB2F"Bb7"B2FF|"Eb"B2Bc- "Ebm"c4|"Bb"zB _AG FDE=E|1"F7"F2_DB,-"Bb"B,2"F+7"zF:|2"F7"F2_DB,-"Bb"B,4|| +// B2_AG FG3 | x8 & B2_AG FG3|BB_AG "F7"FG3|x8 & [I:voicescale 0.6] DDE2=EF3 | +// V:2 cue=on +// xx|:x8|DDE2=EF3|x8|"Bb"BF_AG "F7"FG3| +// +// X:1 +// T: spacer-in-triplet +// K: F +// %%score (L R) +// V: L +// d2 d2 (3 c2 y A2 G2 | F8 | +// V: R +// G2 G2 E2 y2/3 E2 | D8 | diff --git a/tests/visual/title.test.js b/tests/visual/title.test.js new file mode 100644 index 0000000000000000000000000000000000000000..772d4d0f2ac3decefa7c7111ae20763e9ae1b886 --- /dev/null +++ b/tests/visual/title.test.js @@ -0,0 +1,83 @@ +describe("Title", function () { + var abcTitleNormal = "X:1\n" + + "T:Not Transformed\n" + + "K:C\n" + + "C"; + + var expectedTitleNormal = "Not Transformed" + + var abcTitleThe = "X:1\n" + + "T:Transformed, The\n" + + "K:C\n" + + "C"; + + var expectedTitleThe = "The Transformed" + + var abcTitleThe2 = "X:1\n" + + "T:Transformed,the\n" + + "K:C\n" + + "C"; + + var expectedTitleThe2 = "The Transformed" + + var abcTitleA = "X:1\n" + + "T:Transformed, A\n" + + "K:C\n" + + "C"; + + var expectedTitleA = "A Transformed" + + var abcTitleAn = "X:1\n" + + "T:Transformed, An\n" + + "K:C\n" + + "C"; + + var expectedTitleAn = "An Transformed" + + var abcTitleA2 = "X:1\n" + + "T:Transformed, a\n" + + "K:C\n" + + "C"; + + var expectedTitleA2 = "A Transformed" + + var abcTitleNumberThe = "X:1\n" + + "T:24. Number Transform, The\n" + + "K:C\n" + + "C"; + + var expectedTitleNumberThe = "24. The Number Transform" + + var abcTitleNumberA = "X:1\n" + + "T:24. Number Transform, A\n" + + "K:C\n" + + "C"; + + var expectedTitleNumberA = "24. A Number Transform" + + var abcTitleMalformed = "X:1\n" + + "T:Mal , The Formed\n" + + "K:C\n" + + "C"; + + var expectedTitleMalformed = "Mal , The Formed" + + it("puts 'the' at the front of the title", function () { + testTitle(abcTitleNormal, expectedTitleNormal, "TitleNormal"); + testTitle(abcTitleThe, expectedTitleThe, "TitleThe"); + testTitle(abcTitleThe2, expectedTitleThe2, "TitleThe2"); + testTitle(abcTitleA, expectedTitleA, "TitleA"); + testTitle(abcTitleAn, expectedTitleAn, "TitleAn"); + testTitle(abcTitleA2, expectedTitleA2, "TitleA2"); + testTitle(abcTitleNumberThe, expectedTitleNumberThe, "TitleNumberThe"); + testTitle(abcTitleNumberA, expectedTitleNumberA, "TitleNumberA"); + testTitle(abcTitleMalformed, expectedTitleMalformed, "TitleMalformed"); + }) + + function testTitle(abc, expected, comment) { + var visualObj = abcjs.renderAbc("*", abc); + var title = visualObj[0].metaText.title + chai.assert.equal(title, expected, ": "+comment); + + } +}) diff --git a/tests/visual/transpose-output.test.js b/tests/visual/transpose-output.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5706ca5ea16089add63f3a7218f0a75e76956540 --- /dev/null +++ b/tests/visual/transpose-output.test.js @@ -0,0 +1,447 @@ +describe("Transpose Output", function () { + var abcCooley = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "|:D2|EB{c}BA B2 EB|~B2 AB dBAG|\n" + + var abcCooleyExpected_14 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Dm\n" + + "|:C,2|D,A,{B,}A,G, A,2 D,A,|~A,2 G,A, CA,G,F,|\n" + + var abcCooleyExpected_13 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Ebm\n" + + "|:D,2|E,B,{C}B,A, B,2 E,B,|~B,2 A,B, DB,A,G,|\n" + + var abcCooleyExpected_12 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "|:D,2|E,B,{C}B,A, B,2 E,B,|~B,2 A,B, DB,A,G,|\n" + + var abcCooleyExpected_11 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Fm\n" + + "|:E,2|F,C{D}CB, C2 F,C|~C2 B,C ECB,A,|\n" + + var abcCooleyExpected_10 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: F#m\n" + + "|:E,2|F,C{D}CB, C2 F,C|~C2 B,C ECB,A,|\n" + + var abcCooleyExpected_9 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Gm\n" + + "|:F,2|G,D{E}DC D2 G,D|~D2 CD FDCB,|\n" + + var abcCooleyExpected_8 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: G#m\n" + + "|:F,2|G,D{E}DC D2 G,D|~D2 CD FDCB,|\n" + + var abcCooleyExpected_7 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Am\n" + + "|:G,2|A,E{F}ED E2 A,E|~E2 DE GEDC|\n" + + var abcCooleyExpected_6 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Bbm\n" + + "|:A,2|B,F{G}FE F2 B,F|~F2 EF AFED|\n" + + var abcCooleyExpected_5 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Bm\n" + + "|:A,2|B,F{G}FE F2 B,F|~F2 EF AFED|\n" + + var abcCooleyExpected_4 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Cm\n" + + "|:B,2|CG{A}GF G2 CG|~G2 FG BGFE|\n" + + var abcCooleyExpected_3 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: C#m\n" + + "|:B,2|CG{A}GF G2 CG|~G2 FG BGFE|\n" + + var abcCooleyExpected_2 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Dm\n" + + "|:C2|DA{B}AG A2 DA|~A2 GA cAGF|\n" + + var abcCooleyExpected_1 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Ebm\n" + + "|:D2|EB{c}BA B2 EB|~B2 AB dBAG|\n" + + var abcCooleyExpected0 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "|:D2|EB{c}BA B2 EB|~B2 AB dBAG|\n" + + var abcCooleyExpected1 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Fm\n" + + "|:E2|Fc{d}cB c2 Fc|~c2 Bc ecBA|\n" + + var abcCooleyExpected2 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: F#m\n" + + "|:E2|Fc{d}cB c2 Fc|~c2 Bc ecBA|\n" + + var abcCooleyExpected3 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Gm\n" + + "|:F2|Gd{e}dc d2 Gd|~d2 cd fdcB|\n" + + var abcCooleyExpected4 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: G#m\n" + + "|:F2|Gd{e}dc d2 Gd|~d2 cd fdcB|\n" + + var abcCooleyExpected5 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Am\n" + + "|:G2|Ae{f}ed e2 Ae|~e2 de gedc|\n" + + var abcCooleyExpected6 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Bbm\n" + + "|:A2|Bf{g}fe f2 Bf|~f2 ef afed|\n" + + var abcCooleyExpected7 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Bm\n" + + "|:A2|Bf{g}fe f2 Bf|~f2 ef afed|\n" + + var abcCooleyExpected8 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Cm\n" + + "|:B2|cg{a}gf g2 cg|~g2 fg bgfe|\n" + + var abcCooleyExpected9 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: C#m\n" + + "|:B2|cg{a}gf g2 cg|~g2 fg bgfe|\n" + + var abcCooleyExpected10 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Dm\n" + + "|:c2|da{b}ag a2 da|~a2 ga c'agf|\n" + + var abcCooleyExpected11 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Ebm\n" + + "|:d2|eb{c'}ba b2 eb|~b2 ab d'bag|\n" + + var abcCooleyExpected12 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Em\n" + + "|:d2|eb{c'}ba b2 eb|~b2 ab d'bag|\n" + + var abcCooleyExpected13 = "T: Cooley's\n" + + "M: 4/4\n" + + "L: 1/8\n" + + "K: Fm\n" + + "|:e2|fc'{d'}c'b c'2 fc'|~c'2 bc' e'c'ba|\n" + + var abcAll = "T: Transpose Output\n" + + "K: Eb\n" + + "V: 1\n" + + '"Eb"C_D"Eb/C"!marcato!E^F ^^GAB"Ab7b5"z|E__E_E E =E^E^^E E|\n' + + "V: 2\n" + + 'C,_D,=E,^F, "N.C."G,A,B,"^Coda"c,| z8|\n' + + "V: 1\n" + + "c_d_e^f [gbd]__abc'|\n" + + "V: 2\n" + + "c'_d'e'^f' g'a'b'Tc''|\n" + + var abcAllExpected = "T: Transpose Output\n" + + "K: F\n" + + "V: 1\n" + + '"F"D_E"F/D"!marcato!F^G ^^ABc"Bb7b5"z|F_F=F F ^F^^F^^G G|\n' + + "V: 2\n" + + 'D,_E,^F,^G, "N.C."A,B,C"^Coda"D| z8|\n' + + "V: 1\n" + + "d_e=f^g [eac']__bc'd'|\n" + + "V: 2\n" + + "d'_e'f'^g' a'b'c''Td''|\n" + + var abcChords = "T: Transpose Output\n" + + "K: D\n" + + '[DEF] [_DE^F] [_E=F_G] |\n' + + var abcChordsExpected = "T: Transpose Output\n" + + "K: E\n" + + '[EFG] [_EF^G] [=F=G_A] |\n' + + var abcChordSymbols = 'X:1\n' + + "T: Transpose Output\n" + + 'K:C\n' + + '"N.C."c|"C"c"C#"c"Dmaj7"c"D#"c|"E"c"F"c"F#"c"G"c|"G#"c"A/F#"c"A#"c"B"c|"Db"c"Eb"c"Gb"c"Ab"c|"Bb"c "C/B"c "C/Bb"c "C/A#"c | "C/A"c "C/Ab"c "C/G#"c "C/G"c ||' + + var abcChordSymbolsExpected = 'X:1\n' + + "T: Transpose Output\n" + + 'K:D\n' + + // TODO-PER: the C#, D#, G#, and A# should probably be expressed as flats. + '"N.C."d|"D"d"D#"d"Emaj7"d"F"d|"F#"d"G"d"G#"d"A"d|"A#"d"B/G#"d"C"d"C#"d|"D#"d"F"d"G#"d"A#"d|"C"d "D/C#"d "D/C"d "D/C"d | "D/B"d "D/A#"d "D/A#"d "D/A"d ||' + + var abcMeasureAccidental = "T: Transpose Output\n" + + "L: 1/4\n" + + "K: C\n" + + 'ABcd _A_B_c_d z ABcd =A=B=c=d z ABcd |\n ^A^B^c^d z ABcd ^^A^^B^^c^^d z ABcd __A__B__c__d ABcd |\n' + + var abcMeasureAccidentalExpected = "T: Transpose Output\n" + + "L: 1/4\n" + + "K: D\n" + + 'Bcde _B=c_d_e z Bcde =B^c=d=e z Bcde |\n ^B^^c^d^e z Bcde ^^Bd^^d^^e z B^d^^de __B_c__d__e Bcde |\n' + + var abcInline = "T: Transpose Output\n" + + "K: C\n" + + "C_DE^F GABc| [K:D] d_e^fg abc'd'|\n" + + "d_e^fg abc'd'||\n" + + var abcInlineExpected = "T: Transpose Output\n" + + "K: D\n" + + "D_EF^G ABcd| [K:E] e=f^ga bc'd'e'|\n" + + "e=f^ga bc'd'e'||\n" + + var abcKeyChange = "T: Transpose Output\n" + + "K: Eb\n" + + "EFGA|\n" + + "K:B\n" + + "Bcde||\n" + + var abcKeyChangeExpected = "T: Transpose Output\n" + + "K: F\n" + + "FGAB|\n" + + "K:Db\n" + + "defg||\n" + + var abcMinor = "T: Transpose Output\n" + + "K: Em\n" + + "EFGA|Bcde|\n" + + var abcMinorExpected = "T: Transpose Output\n" + + "K: F#m\n" + + "FGAB|cdef|\n" + + var abcNone = "T: Transpose Output\n" + + "K: none\n" + + "CDEF ^C^D^E^F CDEF|GABc _G_A_B_c GABc|^^C^^D^^E^^F CDEF|__G__A__B__c GABc|\n" + + var abcNoneExpected = "T: Transpose Output\n" + + "K: none\n" + + "DEFG ^D^E^F^G DEFG|ABcd _A_B_c_d ABcd|^^D^^E^^F^^G DEFG|__A__B__c__d ABcd|\n" + + var abcPerc = "T: Transpose Output\n" + + "L:1/16\n" + + "K:clef=perc stafflines=1\n" + + "BBBB BzBB zBBB BBzB BBBz \n" + + var abcPercExpected = "T: Transpose Output\n" + + "L:1/16\n" + + "K:clef=perc stafflines=1\n" + + "BBBB BzBB zBBB BBzB BBBz \n" + + var abcDorian = "T: Transpose Output\n" + + "K: EDor\n" + + "EFGA|Bcde|\n" + + var abcDorianExpected = "T: Transpose Output\n" + + "K: F#Dor\n" + + "FGAB|cdef|\n" + + var abcGrace = "T: Transpose Output\n" + + "K: G\n" + + "{A}G{dcB}A|\n" + + var abcGraceExpected = "T: Transpose Output\n" + + "K: A\n" + + "{B}A{edc}B|\n" + + var abcBagpipes = "T:Scotland The Brave\n" + + "L:1/8\n" + + "M:4/4\n" + + "K:Hp\n" + + "e|{g}A2 {GdGe}A>B {gcd}c{e}A {gcd}ce| {ag}a2{g}a2 {GdG}ae {gcd}c{e}A|\n" + + var abcBagpipesExpected = "T:Scotland The Brave\n" + + "L:1/8\n" + + "M:4/4\n" + + "K:Hp\n" + + "e|{g}A2 {GdGe}A>B {gcd}c{e}A {gcd}ce| {ag}a2{g}a2 {GdG}ae {gcd}c{e}A|\n" + + var abcUnusual = "\n\nX:1\nT: Transpose Output\n" + + "K:F#min\n" + + '{/GA}B | [G3_d3] | =A !arpeggio!A !arpeggio![CEG^c] | D .- D | {A}B"<2"{c}+1+B ""_G-_G"D"|A>B|"<2"(de)| [BG]>[cA] |\n' + + var abcUnusualExpected = "\n\nX:1\nT: Transpose Output\n" + + "K:G#min\n" + + '{/AB}c | [A3_e3] | =B !arpeggio!B !arpeggio![DFA^d] | E .- E | {B}c"<2"{d}+1+c ""_A-_A"E"|B>c|"<2"(ef)| [Ac]>[Bd] |\n' + + var abcTemp = `T: Transpose Output +` + + var abcTempExpected = `T: Transpose Output +` + + it("output-cooley", function () { + outputTest(abcCooley, abcCooleyExpected0, 0, "★★ up 0 ★★") + outputTest(abcCooley, abcCooleyExpected1, 1, "★★ up 1 ★★") + outputTest(abcCooley, abcCooleyExpected2, 2, "★★ up 2 ★★") + outputTest(abcCooley, abcCooleyExpected3, 3, "★★ up 3 ★★") + outputTest(abcCooley, abcCooleyExpected4, 4, "★★ up 4 ★★") + outputTest(abcCooley, abcCooleyExpected5, 5, "★★ up 5 ★★") + outputTest(abcCooley, abcCooleyExpected6, 6, "★★ up 6 ★★") + outputTest(abcCooley, abcCooleyExpected7, 7, "★★ up 7 ★★") + outputTest(abcCooley, abcCooleyExpected8, 8, "★★ up 8 ★★") + outputTest(abcCooley, abcCooleyExpected9, 9, "★★ up 9 ★★") + outputTest(abcCooley, abcCooleyExpected10, 10, "★★ up 10 ★★") + outputTest(abcCooley, abcCooleyExpected11, 11, "★★ up 11 ★★") + outputTest(abcCooley, abcCooleyExpected12, 12, "★★ up 12 ★★") + outputTest(abcCooley, abcCooleyExpected13, 13, "★★ up 13 ★★") + outputTest(abcCooley, abcCooleyExpected_1, -1, "★★ down -1 ★★") + outputTest(abcCooley, abcCooleyExpected_2, -2, "★★ down -2 ★★") + outputTest(abcCooley, abcCooleyExpected_3, -3, "★★ down -3 ★★") + outputTest(abcCooley, abcCooleyExpected_4, -4, "★★ down -4 ★★") + outputTest(abcCooley, abcCooleyExpected_5, -5, "★★ down -5 ★★") + outputTest(abcCooley, abcCooleyExpected_6, -6, "★★ down -6 ★★") + outputTest(abcCooley, abcCooleyExpected_7, -7, "★★ down -7 ★★") + outputTest(abcCooley, abcCooleyExpected_8, -8, "★★ down -8 ★★") + outputTest(abcCooley, abcCooleyExpected_9, -9, "★★ down -9 ★★") + outputTest(abcCooley, abcCooleyExpected_10, -10, "★★ down -10 ★★") + outputTest(abcCooley, abcCooleyExpected_11, -11, "★★ down -11 ★★") + outputTest(abcCooley, abcCooleyExpected_12, -12, "★★ down -12 ★★") + outputTest(abcCooley, abcCooleyExpected_13, -13, "★★ down -13 ★★") + outputTest(abcCooley, abcCooleyExpected_14, -14, "★★ down -14 ★★") + }) + + it("output-transpose-chords", function () { + outputTest(abcChords, abcChordsExpected, 2) + }) + + it("output-transpose-chord-symbols", function () { + outputTest(abcChordSymbols, abcChordSymbolsExpected, 2) + }) + + it("output-measure-accidental", function () { + outputTest(abcMeasureAccidental, abcMeasureAccidentalExpected, 2) + }) + + it("output-all", function () { + outputTest(abcAll, abcAllExpected, 2) + }) + + it("output-inline-key", function () { + outputTest(abcInline, abcInlineExpected, 2) + }) + + it("output-dorian", function () { + outputTest(abcDorian, abcDorianExpected, 2) + }) + + it("output-minor", function () { + outputTest(abcMinor, abcMinorExpected, 2) + }) + + it("output-none", function () { + outputTest(abcNone, abcNoneExpected, 2) + }) + + it("output-perc", function () { + outputTest(abcPerc, abcPercExpected, 2) + }) + + it("output-grace", function () { + outputTest(abcGrace, abcGraceExpected, 2) + }) + + it("output-key-change", function () { + outputTest(abcKeyChange, abcKeyChangeExpected, 2) + }) + + it("output-bagpipes", function () { + outputTest(abcBagpipes, abcBagpipesExpected, 2) + }) + + it("output-unusual", function () { + outputTest(abcUnusual, abcUnusualExpected, 2) + }) + + // it("output-temp", function () { + // outputTest(abcTemp, abcTempExpected, 2) + // }) + + it ("output-unit", function () { + var fns = abcjs.strTranspose(null, "TEST", null) + var relativeMajor = fns.relativeMajor + var relativeMode = fns.relativeMode + + chai.assert.equal(relativeMajor("F"), "F", 'relativeMajor') + chai.assert.equal(relativeMajor("F#"), "F#", 'relativeMajor') + chai.assert.equal(relativeMajor("Gb"), "Gb", 'relativeMajor') + chai.assert.equal(relativeMajor("Gm"), "Bb", 'relativeMajor') + chai.assert.equal(relativeMajor("Amin"), "C", 'relativeMajor') + chai.assert.equal(relativeMajor("C#Min"), "E", 'relativeMajor') + chai.assert.equal(relativeMajor("D#Min"), "F#", 'relativeMajor') + chai.assert.equal(relativeMajor("BbMix"), "Eb", 'relativeMajor') + chai.assert.equal(relativeMajor("Bbmix"), "Eb", 'relativeMajor') + chai.assert.equal(relativeMajor("Bdor"), "A", 'relativeMajor') + chai.assert.equal(relativeMajor("DPhr"), "Bb", 'relativeMajor') + chai.assert.equal(relativeMajor("DMix_B_e"), "G", 'relativeMajor') + chai.assert.equal(relativeMajor("DDorian"), "C", 'relativeMajor') +// chai.assert.equal(relativeMajor("C clef=treble"), "C", 'relativeMajor') + + chai.assert.equal(relativeMode("F", ""), "F", 'relativeMode') + chai.assert.equal(relativeMode("F#", ""), "F#", 'relativeMode') + chai.assert.equal(relativeMode("Gb", ""), "Gb", 'relativeMode') + chai.assert.equal(relativeMode("Bb", "m"), "G", 'relativeMode') + chai.assert.equal(relativeMode("C", "min"), "A", 'relativeMode') + chai.assert.equal(relativeMode("E", "Min"), "C#", 'relativeMode') + chai.assert.equal(relativeMode("F#", "Min"), "D#", 'relativeMode') + chai.assert.equal(relativeMode("Eb", "Mix"), "Bb", 'relativeMode') + chai.assert.equal(relativeMode("Eb", "mix"), "Bb", 'relativeMode') + chai.assert.equal(relativeMode("A", "dor"), "B", 'relativeMode') + chai.assert.equal(relativeMode("Bb", "Phr"), "D", 'relativeMode') + chai.assert.equal(relativeMode("G", "Mix_B_e"), "D", 'relativeMode') + chai.assert.equal(relativeMode("C", "Dorian"), "D", 'relativeMode') + + }) +}) + +function outputTest(abc, expected, steps, comment) { + var visualObj = abcjs.renderAbc("paper", abc, { stretchlast: true }); + var output = abcjs.strTranspose(abc, visualObj, steps) + abcjs.renderAbc("paper2", output, { stretchlast: true }); + var message = (comment ? "\n" + comment : '') + "\n" + output.replace(/\n/g, ' ↲ ') + "\n" + expected.replace(/\n/g, ' ↲ ') + "\n" + chai.assert.equal(output, expected, message) +} diff --git a/tests/visual/transpose.test.js b/tests/visual/transpose.test.js new file mode 100644 index 0000000000000000000000000000000000000000..0256450bc1f5c632b774c5b68511ea3ccf1edfb3 --- /dev/null +++ b/tests/visual/transpose.test.js @@ -0,0 +1,208 @@ +describe("Transpose", function() { + + var abcInline = "K: C\n" + + "C_DE^F GABc| [K:D] d_e^fg abc'd'|\n" + + "d_e^fg abc'd'||\n" + + var expectedInline = [{ + "key": "Eb", + "notes": [{"note": "E", "pitch": 2}, {"note": "__F", "pitch": 3}, {"note": "G", "pitch": 4}, {"note": "=A", "pitch": 5}, + {"note": "B", "pitch": 6}, {"note": "c", "pitch": 7}, {"note": "d", "pitch": 8}, {"note": "e", "pitch": 9}, {}, + {"key": "F"}, {"note": "f", "pitch": 10}, {"note": "_g", "pitch": 11}, {"note": "=a", "pitch": 12}, {"note": "b", "pitch": 13}, + {"note": "c'", "pitch": 14}, {"note": "d'", "pitch": 15}, {"note": "e'", "pitch": 16}, {"note": "f'", "pitch": 17}, {}] + }, { + "key": "F", + "notes": [{"note": "f", "pitch": 10}, {"note": "_g", "pitch": 11}, {"note": "=a", "pitch": 12}, {"note": "b", "pitch": 13}, + {"note": "c'", "pitch": 14}, {"note": "d'", "pitch": 15}, {"note": "e'", "pitch": 16}, {"note": "f'", "pitch": 17}, {}] + }] + + var abcAccidentals = "X:1\n" + + "K:D\n" + + "F2__F_F =F^F^^F2|E2__E_E =E^E^^E2||[K:Ab]F2__F_F =F^F^^F2|E2__E_E =E^E^^E2||" + + var expectedAccidentals_2 = [{"key":"C","notes":[{"note":"E","pitch":2},{"note":"_undefined","pitch":1},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^undefined","pitch":2},{},{"note":"D","pitch":1},{"note":"__undefined","pitch":1},{"note":"_D","pitch":1},{"note":"=D","pitch":1},{"note":"^D","pitch":1},{"note":"^^undefined","pitch":1},{},{"key":"F#"},{"note":"D","pitch":1},{"note":"__undefined","pitch":1},{"note":"_D","pitch":1},{"note":"=D","pitch":1},{"note":"^D","pitch":1},{"note":"^^undefined","pitch":1},{},{"note":"C","pitch":0},{"note":"_undefined","pitch":0},{"note":"=C","pitch":0},{"note":"^C","pitch":0},{"note":"^^C","pitch":0},{"note":"^undefined","pitch":1},{}]}] + var expectedAccidentals_1 = [{"key":"Db","notes":[{"note":"F","pitch":3},{"note":"__F","pitch":2},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{},{"note":"E","pitch":2},{"note":"_E","pitch":1},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{},{"key":"G"},{"note":"E","pitch":2},{"note":"_undefined","pitch":1},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^undefined","pitch":2},{},{"note":"D","pitch":1},{"note":"__undefined","pitch":1},{"note":"_D","pitch":1},{"note":"=D","pitch":1},{"note":"^D","pitch":1},{"note":"^^undefined","pitch":1},{}]}] + var expectedAccidentals0 = [{"key":"D","notes":[{"note":"F","pitch":3},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{},{"note":"E","pitch":2},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{"note":"^^E","pitch":2},{},{"key":"Ab"},{"note":"F","pitch":3},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{},{"note":"E","pitch":2},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{"note":"^^E","pitch":2},{}]}] + var expectedAccidentals1 = [{"key":"Eb","notes":[{"note":"G","pitch":4},{"note":"_C,,,","pitch":3},{"note":"__G","pitch":4},{"note":"_G","pitch":4},{"note":"=G","pitch":4},{"note":"^C,,,","pitch":4},{},{"note":"F","pitch":3},{"note":"__C,,,","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^C,,,","pitch":3},{},{"key":"A"},{"note":"F","pitch":3},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{},{"note":"E","pitch":2},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{"note":"^^E","pitch":2},{}]}] + var expectedAccidentals2 = [{"key":"E","notes":[{"note":"G","pitch":4},{"note":"__C,,,","pitch":4},{"note":"_G","pitch":4},{"note":"=G","pitch":4},{"note":"^G","pitch":4},{"note":"^^C,,,","pitch":4},{},{"note":"F","pitch":3},{"note":"_C,,,","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{"note":"^C,,,","pitch":4},{},{"key":"Bb"},{"note":"G","pitch":4},{"note":"_C,,,","pitch":4},{"note":"=G","pitch":4},{"note":"^G","pitch":4},{"note":"^^G","pitch":4},{"note":"^C,,,","pitch":5},{},{"note":"F","pitch":3},{"note":"=C,,,","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{"note":"^F","pitch":4},{"note":"^^C,,,","pitch":4},{}]}] + + var abcMajor = "X:1\n" + + "K:C\n" + + "CDEF GABc|^C^D^E^F ^G^A^B^c|_C_D_E_F _G_A_B_c|F2__F_F =F^F^^F2||" + + var expectedMajor_2 = [{"key":"Bb","notes":[{"note":"B,","pitch":-1},{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{},{"note":"=B,","pitch":-1},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"=E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"=B","pitch":6},{},{"note":"__B,","pitch":-1},{"note":"_C","pitch":0},{"note":"_D","pitch":1},{"note":"__E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"__B","pitch":6},{},{"note":"E","pitch":2},{"note":"_undefined","pitch":1},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^undefined","pitch":2},{}]}] + var expectedMajor_1 = [{"key":"B","notes":[{"note":"B,","pitch":-1},{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{},{"note":"^B,","pitch":-1},{"note":"^^C","pitch":0},{"note":"^^D","pitch":1},{"note":"^E","pitch":2},{"note":"^^F","pitch":3},{"note":"^^G","pitch":4},{"note":"^^A","pitch":5},{"note":"^B","pitch":6},{},{"note":"_B,","pitch":-1},{"note":"=C","pitch":0},{"note":"=D","pitch":1},{"note":"_E","pitch":2},{"note":"=F","pitch":3},{"note":"=G","pitch":4},{"note":"=A","pitch":5},{"note":"_B","pitch":6},{},{"note":"E","pitch":2},{"note":"__undefined","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{"note":"^^undefined","pitch":2},{}]}] + var expectedMajor0 = [{"key":"C","notes":[{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{"note":"^c","pitch":7},{},{"note":"_C","pitch":0},{"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"_c","pitch":7},{},{"note":"F","pitch":3},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{}]}] + var expectedMajor1 = [{"key":"Db","notes":[{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{"note":"d","pitch":8},{},{"note":"=D","pitch":1},{"note":"=E","pitch":2},{"note":"^F","pitch":3},{"note":"=G","pitch":4},{"note":"=A","pitch":5},{"note":"=B","pitch":6},{"note":"^c","pitch":7},{"note":"=d","pitch":8},{},{"note":"__D","pitch":1},{"note":"__E","pitch":2},{"note":"_F","pitch":3},{"note":"__G","pitch":4},{"note":"__A","pitch":5},{"note":"__B","pitch":6},{"note":"_c","pitch":7},{"note":"__d","pitch":8},{},{"note":"G","pitch":4},{"note":"_C,,,","pitch":3},{"note":"__G","pitch":4},{"note":"_G","pitch":4},{"note":"=G","pitch":4},{"note":"^C,,,","pitch":4},{}]}] + var expectedMajor2 = [{"key":"D","notes":[{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{"note":"d","pitch":8},{},{"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{"note":"^^c","pitch":7},{"note":"^d","pitch":8},{},{"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"=F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"=c","pitch":7},{"note":"_d","pitch":8},{},{"note":"G","pitch":4},{"note":"__C,,,","pitch":4},{"note":"_G","pitch":4},{"note":"=G","pitch":4},{"note":"^G","pitch":4},{"note":"^^C,,,","pitch":4},{}]}] + + var abcMinor = "X:1\n" + + "K:Cm\n" + + "CDEF GABc|^C^D^E^F ^G^A^B^c|_C_D_E_F _G_A_B_c|F2__F_F =F^F^^F2||" + + var expectedMinor_2 = [{"key":"Bb","notes":[{"note":"B,","pitch":-1},{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{},{"note":"=B,","pitch":-1},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"=E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"=B","pitch":6},{},{"note":"__B,","pitch":-1},{"note":"_C","pitch":0},{"note":"_D","pitch":1},{"note":"__E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"__B","pitch":6},{},{"note":"E","pitch":2},{"note":"_undefined","pitch":1},{"note":"__E","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^undefined","pitch":2},{}]}] + var expectedMinor_1 = [{"key":"B","notes":[{"note":"B,","pitch":-1},{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{},{"note":"^B,","pitch":-1},{"note":"^^C","pitch":0},{"note":"^^D","pitch":1},{"note":"^E","pitch":2},{"note":"^^F","pitch":3},{"note":"^^G","pitch":4},{"note":"^^A","pitch":5},{"note":"^B","pitch":6},{},{"note":"_B,","pitch":-1},{"note":"=C","pitch":0},{"note":"=D","pitch":1},{"note":"_E","pitch":2},{"note":"=F","pitch":3},{"note":"=G","pitch":4},{"note":"=A","pitch":5},{"note":"_B","pitch":6},{},{"note":"E","pitch":2},{"note":"__undefined","pitch":2},{"note":"_E","pitch":2},{"note":"=E","pitch":2},{"note":"^E","pitch":2},{"note":"^^undefined","pitch":2},{}]}] + var expectedMinor0 = [{"key":"C","notes":[{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{"note":"^c","pitch":7},{},{"note":"_C","pitch":0},{"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"_c","pitch":7},{},{"note":"F","pitch":3},{"note":"__F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{}]}] + var expectedMinor1 = [{"key":"C#","notes":[{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{},{"note":"^^C","pitch":0},{"note":"^^D","pitch":1},{"note":"^^E","pitch":2},{"note":"^^F","pitch":3},{"note":"^^G","pitch":4},{"note":"^^A","pitch":5},{"note":"^^B","pitch":6},{"note":"^^c","pitch":7},{},{"note":"=C","pitch":0},{"note":"=D","pitch":1},{"note":"=E","pitch":2},{"note":"=F","pitch":3},{"note":"=G","pitch":4},{"note":"=A","pitch":5},{"note":"=B","pitch":6},{"note":"=c","pitch":7},{},{"note":"F","pitch":3},{"note":"_F","pitch":3},{"note":"=F","pitch":3},{"note":"^F","pitch":3},{"note":"^^F","pitch":3},{"note":"^F","pitch":4},{}]}] + var expectedMinor2 = [{"key":"D","notes":[{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{"note":"d","pitch":8},{},{"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{"note":"^^c","pitch":7},{"note":"^d","pitch":8},{},{"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"=F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"=c","pitch":7},{"note":"_d","pitch":8},{},{"note":"G","pitch":4},{"note":"__C,,,","pitch":4},{"note":"_G","pitch":4},{"note":"=G","pitch":4},{"note":"^G","pitch":4},{"note":"^^C,,,","pitch":4},{}]}] + + var abcAnnotations = 'X:1\n' + + 'T:transpose_annotations\n' + + 'L:1/4\n' + + 'M:4/4\n' + + 'K:C\n' + + '"C"c"D""G"d"^B"B"_A"A|"G"G"@1,1E"e"C/G"G|"Gb11b9/Cb"c4||' + + var expectedAnnotations_2 = [{"key":"Bb","notes":[{"note":"B","pitch":6,"chord":"B♭"},{"note":"c","pitch":7,"chord":"C\nF"},{"note":"A","pitch":5,"chord":"B"},{"note":"G","pitch":4,"chord":"A"},{},{"note":"E","pitch":2,"chord":"F"},{"note":"F","pitch":3,"chord":"G"},{"note":"d","pitch":8,"chord":"E"},{"note":"F","pitch":3,"chord":"B♭/F"},{},{"note":"B","pitch":6,"chord":"E11♭9/A"},{}]}] + var expectedAnnotations_1 = [{"key":"B","notes":[{"note":"B","pitch":6,"chord":"B"},{"note":"c","pitch":7,"chord":"C♯\nF♯"},{"note":"A","pitch":5,"chord":"B"},{"note":"G","pitch":4,"chord":"A"},{},{"note":"E","pitch":2,"chord":"F"},{"note":"F","pitch":3,"chord":"G"},{"note":"d","pitch":8,"chord":"E"},{"note":"F","pitch":3,"chord":"B/F♯"},{},{"note":"B","pitch":6,"chord":"F11♭9/A♯"},{}]}] + var expectedAnnotations0 = [{"key":"C","notes":[{"note":"c","pitch":7,"chord":"C"},{"note":"d","pitch":8,"chord":"D\nG"},{"note":"B","pitch":6,"chord":"B"},{"note":"A","pitch":5,"chord":"A"},{},{"note":"F","pitch":3,"chord":"F"},{"note":"G","pitch":4,"chord":"G"},{"note":"e","pitch":9,"chord":"E"},{"note":"G","pitch":4,"chord":"C/G"},{},{"note":"c","pitch":7,"chord":"G♭11♭9/C♭"},{}]}] + var expectedAnnotations1 = [{"key":"Db","notes":[{"note":"d","pitch":8,"chord":"D♭"},{"note":"e","pitch":9,"chord":"E♭\nA♭"},{"note":"c","pitch":7,"chord":"B"},{"note":"B","pitch":6,"chord":"A"},{},{"note":"G","pitch":4,"chord":"F"},{"note":"A","pitch":5,"chord":"G"},{"note":"f","pitch":10,"chord":"E"},{"note":"A","pitch":5,"chord":"D♭/A♭"},{},{"note":"d","pitch":8,"chord":"G11♭9/C"},{}]}] + var expectedAnnotations2 = [{"key":"D","notes":[{"note":"d","pitch":8,"chord":"D"},{"note":"e","pitch":9,"chord":"E\nA"},{"note":"c","pitch":7,"chord":"B"},{"note":"B","pitch":6,"chord":"A"},{},{"note":"G","pitch":4,"chord":"F"},{"note":"A","pitch":5,"chord":"G"},{"note":"f","pitch":10,"chord":"E"},{"note":"A","pitch":5,"chord":"D/A"},{},{"note":"d","pitch":8,"chord":"G♯11♭9/C♯"},{}]}] + + var abcChords = 'X:1\n' + + 'K:C\n' + + '"N.C."AB|"C"c"C#"^c"D"d"D#"^d|"E"e"F"F"F#"^F"G"G|"G#"^G"A/F#"A"A#"^A"B"B|"Db"_d"Eb"_e"Gb"_G"Ab"_A|"Bb"_B4||' + + var expectedChords_2 = [{"key":"Bb","notes":[{ note: 'G', pitch: 4, chord: 'N.C.' },{ note: 'A', pitch: 5 },{},{"note":"B","pitch":6,"chord":"B♭"},{"note":"=B","pitch":6,"chord":"B"},{"note":"c","pitch":7,"chord":"C"},{"note":"^c","pitch":7,"chord":"D♭"},{},{"note":"d","pitch":8,"chord":"D"},{"note":"E","pitch":2,"chord":"E♭"},{"note":"=E","pitch":2,"chord":"E"},{"note":"F","pitch":3,"chord":"F"},{},{"note":"^F","pitch":3,"chord":"G♭"},{"note":"G","pitch":4,"chord":"G"},{"note":"^G","pitch":4,"chord":"A♭"},{"note":"A","pitch":5,"chord":"A"},{},{"note":"_c","pitch":7,"chord":"B"},{"note":"_d","pitch":8,"chord":"D♭"},{"note":"_F","pitch":3,"chord":"E"},{"note":"_G","pitch":4,"chord":"G♭"},{},{"note":"_A","pitch":5,"chord":"A♭"},{}]}] + var expectedChords_1 = [{"key":"B","notes":[{ note: 'G', pitch: 4, chord: 'N.C.' },{ note: 'A', pitch: 5 },{},{"note":"B","pitch":6,"chord":"B"},{"note":"^B","pitch":6,"chord":"C"},{"note":"c","pitch":7,"chord":"C♯"},{"note":"^^c","pitch":7,"chord":"D"},{},{"note":"d","pitch":8,"chord":"D♯"},{"note":"E","pitch":2,"chord":"E"},{"note":"^E","pitch":2,"chord":"F"},{"note":"F","pitch":3,"chord":"F♯"},{},{"note":"^^F","pitch":3,"chord":"G"},{"note":"G","pitch":4,"chord":"G♯"},{"note":"^^G","pitch":4,"chord":"A"},{"note":"A","pitch":5,"chord":"A♯"},{},{"note":"=c","pitch":7,"chord":"C"},{"note":"=d","pitch":8,"chord":"D"},{"note":"=F","pitch":3,"chord":"F"},{"note":"=G","pitch":4,"chord":"G"},{},{"note":"=A","pitch":5,"chord":"A"},{}]}] + var expectedChords0 = [{"key":"C","notes":[{ note: 'G', pitch: 4, chord: 'N.C.' },{ note: 'A', pitch: 5 },{},{"note":"c","pitch":7,"chord":"C"},{"note":"^c","pitch":7,"chord":"C♯"},{"note":"d","pitch":8,"chord":"D"},{"note":"^d","pitch":8,"chord":"D♯"},{},{"note":"e","pitch":9,"chord":"E"},{"note":"F","pitch":3,"chord":"F"},{"note":"^F","pitch":3,"chord":"F♯"},{"note":"G","pitch":4,"chord":"G"},{},{"note":"^G","pitch":4,"chord":"G♯"},{"note":"A","pitch":5,"chord":"A"},{"note":"^A","pitch":5,"chord":"A♯"},{"note":"B","pitch":6,"chord":"B"},{},{"note":"_d","pitch":8,"chord":"D♭"},{"note":"_e","pitch":9,"chord":"E♭"},{"note":"_G","pitch":4,"chord":"G♭"},{"note":"_A","pitch":5,"chord":"A♭"},{},{"note":"_B","pitch":6,"chord":"B♭"},{}]}] + var expectedChords1 = [{"key":"Db","notes":[{"note":"d","pitch":8,"chord":"D♭"},{"note":"=d","pitch":8,"chord":"D"},{"note":"e","pitch":9,"chord":"E♭"},{"note":"=e","pitch":9,"chord":"E"},{},{"note":"f","pitch":10,"chord":"F"},{"note":"G","pitch":4,"chord":"G♭"},{"note":"=G","pitch":4,"chord":"G"},{"note":"A","pitch":5,"chord":"A♭"},{},{"note":"=A","pitch":5,"chord":"A"},{"note":"B","pitch":6,"chord":"B♭"},{"note":"=B","pitch":6,"chord":"B"},{"note":"c","pitch":7,"chord":"C"},{},{"note":"__e","pitch":9,"chord":"D"},{"note":"_f","pitch":10,"chord":"E"},{"note":"__A","pitch":5,"chord":"G"},{"note":"__B","pitch":6,"chord":"A"},{},{"note":"_c","pitch":7,"chord":"B"},{}]}] + var expectedChords2 = [{"key":"D","notes":[{ note: 'G', pitch: 4, chord: 'N.C.' },{ note: 'A', pitch: 5 },{},{"note":"d","pitch":8,"chord":"D"},{"note":"^d","pitch":8,"chord":"D♯"},{"note":"e","pitch":9,"chord":"E"},{"note":"^e","pitch":9,"chord":"F"},{},{"note":"f","pitch":10,"chord":"F♯"},{"note":"G","pitch":4,"chord":"G"},{"note":"^G","pitch":4,"chord":"G♯"},{"note":"A","pitch":5,"chord":"A"},{},{"note":"^A","pitch":5,"chord":"A♯"},{"note":"B","pitch":6,"chord":"B"},{"note":"^B","pitch":6,"chord":"C"},{"note":"c","pitch":7,"chord":"C♯"},{},{"note":"_e","pitch":9,"chord":"D♯"},{"note":"=f","pitch":10,"chord":"F"},{"note":"_A","pitch":5,"chord":"G♯"},{"note":"_B","pitch":6,"chord":"A♯"},{},{"note":"=c","pitch":7,"chord":"C"},{}]}] + + var abcNone = "X:1\n" + + "K:none\n" + + "C,,D,E,F, G,A,B,c,|CDEF GABc|^C^D^E^F ^G^A^B^c|_C_D_E_F _G_A_B_c|defg abc''d'||" + + var expectedNone4 = [{"key":"none","notes":[ + {"note":"E,,","pitch":-12},{"note":"^F,","pitch":-4},{"note":"^G,","pitch":-3},{"note":"A,","pitch":-2},{"note":"B,","pitch":-1},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"E","pitch":2},{}, + {"note":"E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"^c","pitch":7},{"note":"^d","pitch":8},{"note":"e","pitch":9},{}, + {"note":"F","pitch":3},{"note":"G","pitch":3},{"note":"A","pitch":5},{"note":"^A","pitch":5},{"note":"c","pitch":7},{"note":"d","pitch":8},{"note":"e","pitch":9},{"note":"^f","pitch":10},{}, + {"note":"_E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"_c","pitch":7},{"note":"_d","pitch":8},{"note":"_e","pitch":9},{}, + {"note":"f","pitch":10},{"note":"g","pitch":11},{"note":"a","pitch":12},{"note":"b","pitch":13},{"note":"c'","pitch":14},{"note":"d'","pitch":15},{"note":"e''","pitch":23},{"note":"f'","pitch":17},{} + ]}] + + // TODO-PER: this test isn't correct + var expectedNone1 = [{"key":"fix-test","notes":[ + {"note":"D,,","pitch":-13},{"note":"E,","pitch":-5},{"note":"F,","pitch":-4},{"note":"G,","pitch":-3},{"note":"A,","pitch":-2},{"note":"B,","pitch":-1},{"note":"c,","pitch":0},{"note":"d,","pitch":1},{}, + {"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{"note":"c","pitch":7},{"note":"d","pitch":8},{}, + {"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{"note":"^c","pitch":7},{"note":"^d","pitch":8},{}, + {"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{"note":"_c","pitch":7},{"note":"_d","pitch":8},{}, + {"note":"e","pitch":9},{"note":"f","pitch":10},{"note":"g","pitch":11},{"note":"a","pitch":12},{"note":"b","pitch":13},{"note":"c'","pitch":14},{"note":"d''","pitch":22},{"note":"e'","pitch":16},{}]}] + + // TODO-PER: this test isn't correct + var expectedNone_1 = [{"key":"fix-test","notes":[ + {"note":"B,,,","pitch":-15},{"note":"C,","pitch":-7},{"note":"D,","pitch":-6},{"note":"E,","pitch":-5},{"note":"F,","pitch":-4},{"note":"G,","pitch":-3},{"note":"A,","pitch":-2},{"note":"B,","pitch":-1},{}, + {"note":"B,","pitch":-1},{"note":"C","pitch":0},{"note":"D","pitch":1},{"note":"E","pitch":2},{"note":"F","pitch":3},{"note":"G","pitch":4},{"note":"A","pitch":5},{"note":"B","pitch":6},{}, + {"note":"^B,","pitch":-1},{"note":"^C","pitch":0},{"note":"^D","pitch":1},{"note":"^E","pitch":2},{"note":"^F","pitch":3},{"note":"^G","pitch":4},{"note":"^A","pitch":5},{"note":"^B","pitch":6},{}, + {"note":"_B,","pitch":-1},{"note":"_C","pitch":0},{"note":"_D","pitch":1},{"note":"_E","pitch":2},{"note":"_F","pitch":3},{"note":"_G","pitch":4},{"note":"_A","pitch":5},{"note":"_B","pitch":6},{}, + {"note":"c","pitch":7},{"note":"d","pitch":8},{"note":"e","pitch":9},{"note":"f","pitch":10},{"note":"g","pitch":11},{"note":"a","pitch":12},{"note":"B''","pitch":20},{"note":"c'","pitch":14},{} + ]}] + + + it("transpose-inline", function () { + transposeTest(abcInline, 3, expectedInline) + }) + + it("transpose-directive", function () { + transposeTestDirective(abcInline, 3, expectedInline) + }) + + it("transpose-accidentals", function () { + transposeTest(abcAccidentals, -2, expectedAccidentals_2) + transposeTest(abcAccidentals, -1, expectedAccidentals_1) + transposeTest(abcAccidentals, 0, expectedAccidentals0) + transposeTest(abcAccidentals, 1, expectedAccidentals1) + transposeTest(abcAccidentals, 2, expectedAccidentals2) + }) + + it("transpose-major", function () { + transposeTest(abcMajor, -2, expectedMajor_2) + transposeTest(abcMajor, -1, expectedMajor_1) + transposeTest(abcMajor, 0, expectedMajor0) + transposeTest(abcMajor, 1, expectedMajor1) + transposeTest(abcMajor, 2, expectedMajor2) + }) + + it("transpose-minor", function () { + transposeTest(abcMinor, -2, expectedMinor_2) + transposeTest(abcMinor, -1, expectedMinor_1) + transposeTest(abcMinor, 0, expectedMinor0) + transposeTest(abcMinor, 1, expectedMinor1) + transposeTest(abcMinor, 2, expectedMinor2) + }) + + it("transpose-annotations", function () { + transposeTest(abcAnnotations, -2, expectedAnnotations_2) + transposeTest(abcAnnotations, -1, expectedAnnotations_1) + transposeTest(abcAnnotations, 0, expectedAnnotations0) + transposeTest(abcAnnotations, 1, expectedAnnotations1) + transposeTest(abcAnnotations, 2, expectedAnnotations2) + }) + + it("transpose-chords", function () { + transposeTest(abcChords, -2, expectedChords_2) + transposeTest(abcChords, -1, expectedChords_1) + transposeTest(abcChords, 0, expectedChords0) + transposeTest(abcChords, 1, expectedChords1) + transposeTest(abcChords, 2, expectedChords2) + }) + + it("transpose-none", function () { + transposeTest(abcNone, 4, expectedNone4) + transposeTest(abcNone, 1, expectedNone1) + transposeTest(abcNone, -1, expectedNone_1) + }) +}) + +function extractTransposeInfo(visualObj) { + var lines = visualObj[0].lines.map(function (line) { + line = line.staff[0] + var out = { key: line.key.root + line.key.acc} + var voice = line.voices[0] + out.notes = voice.map(function (el) { + switch (el.el_type) { + case "note": + var n = {note: el.pitches[0].name, pitch: el.pitches[0].pitch} + if (el.chord) { + n.chord = el.chord.map(function (c) { return c.name}).join(',') + } + return n + case "keySignature": + return {key: el.root + el.acc} + default: + return {} + } + }) + return out + }) + console.log(JSON.stringify(lines)) + return lines +} + +function compareResults(lines, expected, halfSteps) { + for (var i = 0; i < expected.length; i++) { + var exp = expected[i] + var rcv = lines[i] + chai.assert.equal(rcv.key, exp.key, "Key mismatch on line " + i + ' half steps: ' + halfSteps) + for (var j = 0; j < exp.notes.length; j++) { + var eNote = exp.notes[j] + var rNote = rcv.notes[j] + chai.assert.deepStrictEqual(rNote, eNote, "at location " + i + ' ' + j + ' steps:'+halfSteps) + } + } +} + +function transposeTest(abc, halfSteps, expected) { + var visualObj = abcjs.renderAbc("paper", abc, { + visualTranspose: halfSteps + }); + var lines = extractTransposeInfo(visualObj) + compareResults(lines, expected, halfSteps) +} + +function transposeTestDirective(abc, halfSteps, expected) { + abc = "%%visualTranspose "+halfSteps + var visualObj = abcjs.renderAbc("paper", abc); + var lines = extractTransposeInfo(visualObj) + compareResults(lines, expected, halfSteps) +} diff --git a/tests/visual/wrap.test.js b/tests/visual/wrap.test.js new file mode 100644 index 0000000000000000000000000000000000000000..7022d82fc418dff2e827c068fdab787f578dec47 --- /dev/null +++ b/tests/visual/wrap.test.js @@ -0,0 +1,307 @@ +describe("Automatic line wrapping", function() { + var abcSingleLine = "X:1\n" + +"L:1/4\n" + +"M:4/4\n" + +"K:Bb\n" + +"B,4|C2D2|E3F|GABc|d/e/f/g/ !marcato!D/ E/ F/ G/|B,4|C2D2|E3F|GABc|d/e/f/g/ D/ E/ F/ G/|\n"; + + var expectedSingleLine400 = [{"widths":{"left":74.551,"measureWidths":[42.78,38.74000000000002,44.629999999999995,59.24000000000001,126.84799999999996,31.985000000000014,38.74000000000001,44.629999999999995,59.23999999999978,126.84799999999996],"total":613.6809999999998},"lineBreakPoint":180.805,"minLineSize":116.23178571428572,"staffWidth":400,"minWidth":191}]; + + var expectedSingleLine400LineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":12},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":13,"end":21},{"ogLine":0,"line":2,"staff":0,"voice":0,"start":22,"end":34},{"ogLine":0,"line":3,"staff":0,"voice":0,"start":35,"end":44}] + ; + + var expectedSingleLine500 = [{"widths":{"left":74.551,"measureWidths":[42.78,38.74000000000002,44.629999999999995,59.24000000000001,126.84799999999996,31.985000000000014,38.74000000000001,44.629999999999995,59.23999999999978,126.84799999999996],"total":613.6809999999998},"lineBreakPoint":236.36055555555555,"minLineSize":151.94607142857143,"staffWidth":500,"minWidth":250}]; + + var expectedSingleLine500LineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":12},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":13,"end":26},{"ogLine":0,"line":2,"staff":0,"voice":0,"start":27,"end":44}] + ; + + var expectedSingleLine600 = [{"widths":{"left":74.551,"measureWidths":[42.78,38.74000000000002,44.629999999999995,59.24000000000001,126.84799999999996,31.985000000000014,38.74000000000001,44.629999999999995,59.23999999999978,126.84799999999996],"total":613.6809999999998},"lineBreakPoint":291.9161111111111,"minLineSize":187.66035714285715,"staffWidth":600,"minWidth":309}]; + + var expectedSingleLine600LineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":12},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":13,"end":29},{"ogLine":0,"line":2,"staff":0,"voice":0,"start":30,"end":44}]; + + var abcShortMeasures = "%%stretchlast 1\nX:1\nQ:1/4=70\nM:2/4\nL:1/4\nK:C clef=bass\nC,D,|D,2|E,F,|G,2|\nG,2|F,E,|D,D,|C,2|]\n"; + + var expectedShortMeasures740 = [{"widths":{"left":50.153,"measureWidths":[48.41500000000001,27.370000000000005,37.620000000000005,27.370000000000005,20.435498264067185,41.26007939944783,41.26007939944782,31.435498264067178],"total":275.16615532703},"lineBreakPoint":383.24833333333333,"minLineSize":246.3739285714286,"staffWidth":740,"minWidth":406}]; + + var expectedShortMeasures740LineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":9},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":10,"end":20}]; + + var abcSplitByText = + "M: 4/4\n" + + "L: 1/8\n" + + "R: reel\n" + + "K: Emin\n" + + "EB{c}BA B2 EB|\n" + + "~B2 AB dBAG|\n" + + "FDAD BDAD|\n" + + "FDAD dAFD|\n" + + "%%text Here is some text\n" + + "eB B2 eBgB|\n" + + "eB B2 defg|\n" + + "afe^c dBAF|\n" + + "DEFD E2|]\n"; + + var expectedSplitByText = [{"widths":{"left":67.301,"measureWidths":[112.46500000000002,113.1061625968321,114.21687500000006,115.74976562500005],"total":455.53780322183223},"lineBreakPoint":240.38833333333332,"minLineSize":154.53535714285715,"staffWidth":500,"minWidth":255},{"widths":{"left":67.301,"measureWidths":[115.94789193789369,115.94789193789369,116.5033630371094,109.50336303710942],"total":457.90250995000616},"lineBreakPoint":240.38833333333332,"minLineSize":154.53535714285715,"staffWidth":500,"minWidth":255}]; + + var expectedSplitByTextLineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":15},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":16,"end":34},{"ogLine":1,"line":2},{"ogLine":2,"line":3,"staff":0,"voice":0,"start":0,"end":15},{"ogLine":2,"line":4,"staff":0,"voice":0,"start":16,"end":31}] + ; + + var abcPiano = "X:1\n" + + "T:piano_wrap\n" + + "M:4/4\n" + + "L:1/16\n" + + "%%staves {(RH) (LH)}\n" + + "%%barnumbers -1\n" + + "V:RH clef=treble\n" + + "V:LH clef=bass\n" + + "K:C\n" + + "[V: RH]c4 f4 e8| fede g8 e4|g16|\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, | G,16| \n" + + "[V: RH]c4 f4 e8| fede g8 e4|\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, |\n" + + "[V: RH]c4 f4 e8| fede g8 e4|\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, |\n" + + "[V: RH]c4 f4 e8| fede g8 e4|\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, |\n" + + "[V: RH]c4 f4 e8| fede g8 e4|\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, |\n" + + "[V: RH]c4 f4 e8| fede g8 e4|]\n" + + "[V: LH] E,12 F,4| z4 G,F,A,G, z4 G,F,A,G, |]"; + + var expectedPiano = [{"widths":{"left":60.153,"measureWidths":[59.785000000000004,154.608,31.985000000000014,74.05661416569907,164.2341535414248,74.05661416569907,164.2341535414248,74.05661416569907,164.2341535414248,74.05661416569907,164.2341535414248,68.03144618785356,162.72786154696342],"total":1430.3003785633125},"lineBreakPoint":244.35944444444442,"minLineSize":157.0882142857143,"staffWidth":500,"minWidth":259}]; + + var expectedPianoLineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":12},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":13,"end":23},{"ogLine":0,"line":2,"staff":0,"voice":0,"start":24,"end":34},{"ogLine":0,"line":3,"staff":0,"voice":0,"start":35,"end":45},{"ogLine":0,"line":4,"staff":0,"voice":0,"start":46,"end":56},{"ogLine":0,"line":5,"staff":0,"voice":0,"start":57,"end":68},{"ogLine":0,"line":0,"staff":1,"voice":0,"start":0,"end":15},{"ogLine":0,"line":1,"staff":1,"voice":0,"start":16,"end":29},{"ogLine":0,"line":2,"staff":1,"voice":0,"start":30,"end":43},{"ogLine":0,"line":3,"staff":1,"voice":0,"start":44,"end":57},{"ogLine":0,"line":4,"staff":1,"voice":0,"start":58,"end":71},{"ogLine":0,"line":5,"staff":1,"voice":0,"start":72,"end":86}] + + ; + + var expectedBarNumbers = { + bars: [2, 3, undefined, 5, 6, undefined, 8, undefined, 10, 11, undefined, 13, undefined], + lines: [undefined, 4, 7, 9, 12] + } + + var abcQuartet = "X:1\n" + + "T: wrap quartet\n" + + "%%score (1 2) 3 4\n" + + "K: C\n" + + "V:1 name=\"Violin I\" snm=\"Vl.1\" clef=treble\n" + + "V:2 name=\"Violin II\" snm=\"Vl.2\" clef=treble\n" + + "V:3 name=\"Viola\" snm=\"Va\" clef=alto\n" + + "V:4 name=\"Violoncello\" snm=\"Vc\" clef=bass\n" + + "%\n" + + "[V:1] CDEF GABc | cdef gabc' |\n" + + "[V:2] E2G2 B2d2 | cBAG FEDC |\n" + + "[V:3] C2E2 G2B2 | C2E2 G2B2 |\n" + + "[V:4] C,8 | E,4 G,4 |\n" + + "[V:1] CDEF GABc | cdef gabc' |\n" + + "[V:2] EFGA Bcde | cBAG FEDC |\n" + + "[V:3] C2E2 G2B2 | C2E2 G2B2 |\n" + + "[V:4] C,8 | E,4 G,4 |\n" + + "[V:1] C8 | E8 |\n" + + "[V:2] E8 | G8 |\n" + + "[V:3] G,8 | G,8 |\n" + + "[V:4] C,8 | E,8 |\n" + + "[V:1] C8 | E8 |\n" + + "[V:2] E8 | G8 |\n" + + "[V:3] G,8 | G,8 |\n" + + "[V:4] C,8 | E,8 |\n" + + "[V:1] C8 | E8 |\n" + + "[V:2] E8 | G8 |\n" + + "[V:3] G,8 | G,8 |\n" + + "[V:4] C,8 | E,8 |\n" + + "[V:1] CDEF GABc | cdef gabc' |\n" + + "[V:2] EFGA Bcde | cBAG FEDC |\n" + + "[V:3] C2E2 G2B2 | C2E2 G2B2 |\n" + + "[V:4] C,8 | E,4 G,4 |\n" + + "[V:1] CDEF GABc | cdef gabc' |\n" + + "[V:2] EFGA Bcde | cBAG FEDC |\n" + + "[V:3] C2E2 G2B2 | C2E2 G2B2 |\n" + + "[V:4] C,8 | E,4 G,4 |\n"; + + var expectedQuartet = [{"widths":{"left":92.35545307159424,"measureWidths":[91.48000000000002,102.48000000000002,91.48000000000002,102.48000000000002,70.4572734642029,81.4572734642029,70.4572734642029,81.4572734642029,70.4572734642029,81.4572734642029,91.48000000000002,102.48000000000002,91.48000000000002,102.48000000000002],"total":1231.5836407852175},"lineBreakPoint":226.4691927380032,"minLineSize":145.58733818871636,"staffWidth":500,"minWidth":240}] + ; + + var expectedQuartetLineBreaks = [{"ogLine":0,"line":0,"staff":0,"voice":0,"start":0,"end":18},{"ogLine":0,"line":1,"staff":0,"voice":0,"start":19,"end":37},{"ogLine":0,"line":2,"staff":0,"voice":0,"start":38,"end":45},{"ogLine":0,"line":3,"staff":0,"voice":0,"start":46,"end":52},{"ogLine":0,"line":4,"staff":0,"voice":0,"start":53,"end":71},{"ogLine":0,"line":5,"staff":0,"voice":0,"start":72,"end":91},{"ogLine":0,"line":0,"staff":0,"voice":1,"start":0,"end":14},{"ogLine":0,"line":1,"staff":0,"voice":1,"start":15,"end":33},{"ogLine":0,"line":2,"staff":0,"voice":1,"start":34,"end":41},{"ogLine":0,"line":3,"staff":0,"voice":1,"start":42,"end":48},{"ogLine":0,"line":4,"staff":0,"voice":1,"start":49,"end":67},{"ogLine":0,"line":5,"staff":0,"voice":1,"start":68,"end":87},{"ogLine":0,"line":0,"staff":1,"voice":0,"start":0,"end":9},{"ogLine":0,"line":1,"staff":1,"voice":0,"start":10,"end":19},{"ogLine":0,"line":2,"staff":1,"voice":0,"start":20,"end":25},{"ogLine":0,"line":3,"staff":1,"voice":0,"start":26,"end":31},{"ogLine":0,"line":4,"staff":1,"voice":0,"start":32,"end":41},{"ogLine":0,"line":5,"staff":1,"voice":0,"start":42,"end":52},{"ogLine":0,"line":0,"staff":2,"voice":0,"start":0,"end":4},{"ogLine":0,"line":1,"staff":2,"voice":0,"start":5,"end":9},{"ogLine":0,"line":2,"staff":2,"voice":0,"start":10,"end":15},{"ogLine":0,"line":3,"staff":2,"voice":0,"start":16,"end":21},{"ogLine":0,"line":4,"staff":2,"voice":0,"start":22,"end":26},{"ogLine":0,"line":5,"staff":2,"voice":0,"start":27,"end":32}] + + + + var abcVoicesShareStaff = "X:0\n" + + "%%score { ( 1 2 ) | ( 3 4 ) }\n" + + "L:1/4\n" + + "M:4/4\n" + + "K:Bb\n" + + "V:1 treble\n" + + "V:2 treble\n" + + "V:3 bass\n" + + "V:4 bass\n" + + "V:1\n" + + "G A B G | ^F A G B | A G F2 |]\n" + + "V:2\n" + + "D F F E | D ^F G =F | F B,/C/ D2 |]\n" + + "V:3\n" + + "B, C D C/B,/ | A, D B, D | D G,/A,/ B,2 |] \n" + + "V:4\n" + + "G, F, B,, C, | D, D, G, B,,/C,/ | D, E, B,,2 |]"; + + var expectedVoicesShareStaff = [ + [ + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[4]},{"el_type":"note","pitches":[5]},{"el_type":"note","pitches":[6]},{"el_type":"note","pitches":[4]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[1]},{"el_type":"note","pitches":[3]},{"el_type":"note","pitches":[3]},{"el_type":"note","pitches":[2]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[-1]},{"el_type":"note","pitches":[0]},{"el_type":"note","pitches":[1]},{"el_type":"note","pitches":[0]},{"el_type":"note","pitches":[-1]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[-3]},{"el_type":"note","pitches":[-4]},{"el_type":"note","pitches":[-8]},{"el_type":"note","pitches":[-7]},{"el_type":"bar"}] + ], + [ + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[3]},{"el_type":"note","pitches":[5]},{"el_type":"note","pitches":[4]},{"el_type":"note","pitches":[6]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[1]},{"el_type":"note","pitches":[3]},{"el_type":"note","pitches":[4]},{"el_type":"note","pitches":[3]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[-2]},{"el_type":"note","pitches":[1]},{"el_type":"note","pitches":[-1]},{"el_type":"note","pitches":[1]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[-6]},{"el_type":"note","pitches":[-6]},{"el_type":"note","pitches":[-3]},{"el_type":"note","pitches":[-8]},{"el_type":"note","pitches":[-7]},{"el_type":"bar"}] + ], + [ + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[5]},{"el_type":"note","pitches":[4]},{"el_type":"note","pitches":[3]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[3]},{"el_type":"note","pitches":[-1]},{"el_type":"note","pitches":[0]},{"el_type":"note","pitches":[1]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"up"},{"el_type":"note","pitches":[1]},{"el_type":"note","pitches":[-3]},{"el_type":"note","pitches":[-2]},{"el_type":"note","pitches":[-1]},{"el_type":"bar"}], + [{"el_type":"stem","direction":"down"},{"el_type":"note","pitches":[-6]},{"el_type":"note","pitches":[-5]},{"el_type":"note","pitches":[-8]},{"el_type":"bar"}] + ], + ]; + + ////////////////////////////////////////////////////////////// + + it("wrap-short-measures", function() { + doWrapTest(abcShortMeasures, expectedShortMeasures740, expectedShortMeasures740LineBreaks, 740); + }); + + it("wrap-single-line400", function() { + doWrapTest(abcSingleLine, expectedSingleLine400, expectedSingleLine400LineBreaks, 400); + }) + + it("wrap-single-line500", function() { + doWrapTest(abcSingleLine, expectedSingleLine500, expectedSingleLine500LineBreaks, 500); + }) + + it("wrap-single-line600", function() { + doWrapTest(abcSingleLine, expectedSingleLine600, expectedSingleLine600LineBreaks, 600); + }) + + it("split-by-text", function() { + doWrapTest(abcSplitByText, expectedSplitByText, expectedSplitByTextLineBreaks, 500); + }) + + it("piano-wrap", function() { + doWrapTest(abcPiano, expectedPiano, expectedPianoLineBreaks, 500); + }) + + it("quartet-wrap", function() { + doWrapTest(abcQuartet, expectedQuartet, expectedQuartetLineBreaks, 500); + }) + + it("share-staff", function() { + doWrapTestContents(abcVoicesShareStaff, expectedVoicesShareStaff, 300); + }) + + it("measure-numbers", function() { + var visualObj = abcjs.renderAbc("paper", abcPiano, { + staffwidth: 500, + wrap: { + minSpacing: 1.8, + maxSpacing: 2.8, + preferredMeasuresPerLine: 4 + } + }); + var bars = [] + var lines = [] + for (var i = 0; i < visualObj[0].lines.length; i++) { + var line = visualObj[0].lines[i] + var voice = line.staff[0].voices[0] + bars = bars.concat(voice.filter(function (v) { return v.el_type === "bar" }).map(function(v) { return v.barNumber })) + lines.push(line.staff[0].barNumber) + } + chai.assert.deepEqual(bars, expectedBarNumbers.bars, "Bar number incorrect on bar element\n" + JSON.stringify(bars) + "\n" + JSON.stringify(expectedBarNumbers.bars)) + chai.assert.deepEqual(lines, expectedBarNumbers.lines, "Bar number incorrect on line\n" + JSON.stringify(lines) + "\n" + JSON.stringify(expectedBarNumbers.lines)) + }) + +}) + +////////////////////////////////////////////////////////// + +function doWrapTestContents(abc, expected, width) { + var visualObj = abcjs.renderAbc("paper", abc, { + staffwidth: width, + wrap: { + minSpacing: 1.8, + maxSpacing: 2.8, + preferredMeasuresPerLine: 4 + } + }); + var lines = []; + for (var i = 0; i < visualObj[0].lines.length; i++) { + var staves = visualObj[0].lines[i].staff; + if (!staves) + continue; + var line = []; + for (var j = 0; j < staves.length; j++) { + for (var k = 0; k < staves[j].voices.length; k++) { + var els = []; + for (var kk = 0; kk < staves[j].voices[k].length; kk++) { + var el = {el_type: staves[j].voices[k][kk].el_type}; + if (el.el_type === "stem") + el.direction = staves[j].voices[k][kk].direction; + else if (el.el_type === "note") { + el.pitches = []; + for (var ii = 0; ii < staves[j].voices[k][kk].pitches.length; ii++) + el.pitches.push(staves[j].voices[k][kk].pitches[ii].pitch) + } + els.push(el); + } + line.push(els); + } + } + lines.push(line); + } + console.log(lines) + for (i = 0; i < lines.length; i++) { + for (j = 0; j < lines[i].length; j++) { + for (k = 0; k < lines[i][j].length; k++) { + var msg = "\nrcv: " + JSON.stringify(lines[i][j][k]) + "\n" + + "exp: " + JSON.stringify(expected[i][j][k]) + "\nLine: " + i + ' Voice: ' + j + ' Element: ' + k + '\n'; + chai.assert.deepStrictEqual(lines[i][j][k], expected[i][j][k], msg); + } + } + } +} + +function doWrapTest(abc, expected, expectedLineBreaks, width) { + var visualObj = abcjs.renderAbc("paper", abc, { + staffwidth: width, + wrap: { + minSpacing: 1.8, + maxSpacing: 2.8, + preferredMeasuresPerLine: 4 + } + }); + var lines = []; + for (var i = 0; i < visualObj[0].lines.length; i++) { + var staves = visualObj[0].lines[i].staff; + if (!staves) + continue; + var line = []; + for (var j = 0; j < staves.length; j++) { + var staff = []; + for (var k = 0; k < staves[j].voices.length; k++) { + console.log(staves[j].voices[k]) + staff.push(staves[j].voices[k].length); + } + line.push(staff); + } + lines.push(line); + } + + for (var e = 0; e < visualObj[0].explanation.length; e++) + delete visualObj[0].explanation[e].attempts; + + // console.log(JSON.stringify(visualObj[0].explanation)) + // console.log(JSON.stringify(visualObj[0].lineBreaks)) + var msg = "\nrcv: " + JSON.stringify(visualObj[0].explanation) + "\n" + + "exp: " + JSON.stringify(expected) + "\n"; + chai.assert.deepStrictEqual(visualObj[0].explanation, expected, msg); + msg = "\nrcv: " + JSON.stringify(visualObj[0].lineBreaks) + "\n" + + "exp: " + JSON.stringify(expectedLineBreaks) + "\n"; + chai.assert.deepStrictEqual(visualObj[0].lineBreaks, expectedLineBreaks, msg); +} diff --git a/tests/web-audio.html b/tests/web-audio.html new file mode 100644 index 0000000000000000000000000000000000000000..11e1fc4c26b5ceec50cc7b0f685726a1fd5663e3 --- /dev/null +++ b/tests/web-audio.html @@ -0,0 +1,45 @@ + + + + + Audio Tests + + + + + +

These tests use the AudioContext which must be started after a user interaction, so you +need to click the button below to do the testing.

+ +
+
+
+
+
+ + + + + + + + + + + diff --git a/tests/wrap.html b/tests/wrap.html new file mode 100644 index 0000000000000000000000000000000000000000..213273b1e1840415a542d26c92765242ac852f11 --- /dev/null +++ b/tests/wrap.html @@ -0,0 +1,30 @@ + + + + + Wrap Tests + + + + +
+
+ + + + + + + + + + + diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..c89249f9837494333bce1286ae8ff1f4ed1b181a --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,1332 @@ +declare module 'abcjs' { + // + // Enumerations + // + + export type Clef = 'treble' | 'tenor' | 'bass' | 'alto' | 'treble+8' | 'tenor+8' | 'bass+8' | 'alto+8' | 'treble-8' | 'tenor-8' | 'bass-8' | 'alto-8' | 'none' | 'perc'; + + export type Bar = 'bar_dbl_repeat' | 'bar_right_repeat' | 'bar_left_repeat' | 'bar_invisible' | 'bar_thick_thin' | 'bar_thin_thin' | 'bar_thin' | 'bar_thin_thick'; + + export type MeterType = 'common_time' | 'cut_time' | 'specified' | 'tempus_perfectum' | 'tempus_imperfectum' | 'tempus_perfectum_prolatio' | 'tempus_imperfectum_prolatio'; + + export type NoteLetter = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g'; + + export type AccidentalName = 'flat' | 'natural' | 'sharp' | 'dblsharp' | 'dblflat' | 'quarterflat' | 'quartersharp'; + + export type ChordRoot = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G'; + + export type KeyRoot = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'HP' | 'Hp' | 'none'; + + export type KeyAccidentalName = '' | '#' | 'b'; + + export type Mode = '' | 'm' | 'Dor' | 'Mix' | 'Loc' | 'Phr' | 'Lyd'; + + export type ChordType = '' | 'm' | '7' | 'm7' | 'maj7' | 'M7' | '6' | 'm6' | 'aug' | '+' | 'aug7' | 'dim' | 'dim7' | '9' | + 'm9' | 'maj9' | 'M9' | '11' | 'dim9' | 'sus' | 'sus9' | '7sus4' | '7sus9' | '5'; + + export type Placement = 'above' | 'below'; + + export type ChordPlacement = 'above' | 'below' | 'left' | 'right' | 'default'; + + export type BracePosition = "start" | "continue" | "end"; + + export type Alignment = 'left' | 'center' |'right'; + + export type Media = 'screen' | 'print'; + + export type ProgressUnit = "seconds" | "beats" | "percent"; + + export type NoteTimingEventType = "end" | "event"; + + export type MidiOutputType = 'encoded' | 'binary' | 'link'; + + export type Responsive = 'resize'; + + export type DragTypes = "author" | "bar" | "brace" | "clef" | "composer" | "dynamicDecoration" | "ending" | "extraText" | + "freeText" | "keySignature" | "note" | "part" | "partOrder" | "rhythm" | "slur" | "subtitle" | "tempo" | "timeSignature" | "title" | + "unalignedWords" | "voiceName"; + + export type FormatAttributes = "titlefont" | "gchordfont" | "composerfont" | "footerfont" | "headerfont" | "historyfont" | "infofont" | + "measurefont" | "partsfont" | "repeatfont" | "subtitlefont" | "tempofont" | "textfont" | "voicefont" | "tripletfont" | "vocalfont" | + "wordsfont" | "annotationfont" | "scale" | "partsbox" | "freegchord" | "fontboxpadding" | "stretchlast" | "tablabelfont" | "tabnumberfont" | "tabgracefont" | 'stafftopmargin'; + + export type MidiCommands = "nobarlines" | "barlines" | "beataccents" | "nobeataccents" | "droneon" | "droneoff" | "noportamento" | "channel" | "c" | + "drumon" | "drumoff" | "fermatafixed" | "fermataproportional" | "gchordon" | "gchordoff" | "bassvol" | "chordvol" | "bassprog" | "chordprog" | + "controlcombo" | "temperamentnormal" | "gchord" | "ptstress" | "beatmod" | "deltaloudness" | "drumbars" | "pitchbend" | + "gracedivider" | "makechordchannels" | "randomchordattack" | "chordattack" | "stressmodel" | "transpose" | + "rtranspose" | "volinc" | "program" | "ratio" | "snt" | "bendvelocity" | "control" | "temperamentlinear" | "beat" | "beatstring" | + "drone" | "drummap" | "portamento" | "expand" | "grace" | "trim" | "drum" | "chordname"; + + export type StemDirection = 'up' | 'down' | 'auto' | 'none'; + + export type NoteHeadType = 'normal' | 'harmonic' | 'rhythm' | 'x' | 'triangle'; + + export type Decorations = "trill" | "lowermordent" | "uppermordent" | "mordent" | "pralltriller" | "accent" | + "fermata" | "invertedfermata" | "tenuto" | "0" | "1" | "2" | "3" | "4" | "5" | "+" | "wedge" | + "open" | "thumb" | "snap" | "turn" | "roll" | "irishroll" | "breath" | "shortphrase" | "mediumphrase" | "longphrase" | + "segno" | "coda" | "D.S." | "D.C." | "fine" | "crescendo(" | "crescendo)" | "diminuendo(" | "diminuendo)" |"glissando(" | "glissando)" | + "p" | "pp" | "f" | "ff" | "mf" | "mp" | "ppp" | "pppp" | "fff" | "ffff" | "sfz" | "repeatbar" | "repeatbar2" | "slide" | + "upbow" | "downbow" | "staccato" | "trem1" | "trem2" | "trem3" | "trem4" | + "/" | "//" | "///" | "////" | "turnx" | "invertedturn" | "invertedturnx" | "arpeggio" | "trill(" | "trill)" | "xstem" | + "mark" | "marcato" | "umarcato" | + "D.C.alcoda" | "D.C.alfine" | "D.S.alcoda" | "D.S.alfine" | "editorial" | "courtesy" + + export type TablatureInstrument = 'guitar' | 'mandolin' | 'fiddle' | 'violin' | ''; + + // + // Basic types + // + export type Selector = string | HTMLElement + + type NumberFunction = () => number; + + export interface MeterFraction { + num: number; + den?: number; + } + + export interface ClefProperties { + stafflines?: number; + staffscale?: number; + transpose?: number; + type: Clef; + verticalPos: number; + clefPos?: number; + } + + export interface Meter { + type: MeterType; + value?: Array; + beat_division?: Array; + } + + export interface Accidental { + acc: AccidentalName; + note: NoteLetter; + verticalPos: number; + } + + export interface KeySignature { + accidentals?: Array; + root: KeyRoot; + acc: KeyAccidentalName; + mode: Mode; + } + + export interface Font { + face: string; + size: number; + weight: 'normal' | 'bold'; + style: 'normal' | 'italic'; + decoration: 'none' | 'underline'; + } + + export interface TempoProperties { + duration?: Array; + bpm?: number; + endChar: number; + preString?: string; + postString?: string; + startChar: number; + suppress?: boolean; + suppressBpm?: boolean; + } + + export interface TextFieldProperties { + endChar?: number; + font: Font; + text: string; + center?: boolean; + startChar?: number; + } + + export interface ChordProperties { + name: string; + chord: { + root: ChordRoot; + type: ChordType; + }, + position?: ChordPlacement + rel_position?: { + x: number; + y: number; + } + } + + export interface CharRange { + startChar: number; + endChar: number; + } + + export type MidiParam = Array; + + export type MidiGracePitches = Array<{instrument: number; pitch: number; volume: number; cents?: number; durationInMeasures: number}>; + + export interface MidiPitch { + instrument: number; + pitch: number; + duration: number; + volume: number; + cents?: number; + start: number; + gap: number; + } + + export type MidiPitches = Array; + + export interface RelativeElement { + x: number; + c: string; + dx: number; + w: number; + pitch: number; + pitch2?: number; + scaleX: number; + scaleY: number; + type: string; + name: string; + linewidth?: number; + klass?: string; + anchor?: "start" | "middle" | "end"; + top: number; + bottom: number; + dim?: number; + position?: number; + realWidth?: number; + partHeightAbove?: number; + chordHeightAbove?: number; + chordHeightBelow?: number; + lyricHeightAbove?: number; + lyricHeightBelow?: number; + } + + export interface AbsoluteElement { + abcelem : AbcElem; + bottom : number; + children : Array + duration : number; + durationClass : number; + elemset : Array + extra : Array + extraw : number; + fixed : {w: number, t: number, b: number} + heads : Array + invisible : boolean; + minspacing : number; + notePositions : Array<{x:number; y:number;}> + right : Array + specialY : Array<{ + chordHeightAbove : number; + chordHeightBelow : number; + dynamicHeightAbove : number; + dynamicHeightBelow : number; + endingHeightAbove : number; + lyricHeightAbove : number; + lyricHeightBelow : number; + partHeightAbove : number; + tempoHeightAbove : number; + volumeHeightAbove : number; + volumeHeightBelow : number; + }> + top : number; + tuneNumber : number; + type : "symbol" | "tempo" | "part" | "rest" | "note" | "bar" | "staff-extra clef" | "staff-extra key-signature" | "staff-extra time-signature"; + w : number; + x : number; + } + + export type AbstractEngraver = any; + + export type NoteProperties = any; // TODO + + export type AudioTrackCommand = 'program' | 'text' | 'note'; + // + // Input Types + // + + // renderAbc + export interface Wrap { + preferredMeasuresPerLine: number; + minSpacing: number; + maxSpacing: number; + lastLineLimit?: number; + minSpacingLimit?: number; + } + + export interface Tablature { + instrument?: TablatureInstrument, + capo?: number + label?: string, + tuning?: Array, + highestNote?: string, + } + + export interface AbcVisualParams { + accentAbove?: boolean; + add_classes?: boolean; + afterParsing?: AfterParsing; + ariaLabel?: string; + clickListener?: ClickListener; + dragColor?: string; + dragging?: boolean; + expandToWidest?: boolean; + foregroundColor?: string; + format?: { [attr in FormatAttributes]?: any }; + header_only?: boolean; + initialClef?: boolean; + jazzchords?: boolean; + germanAlphabet?: boolean; + lineBreaks?: Array; + lineThickness?: number; + minPadding?: number; + oneSvgPerLine?: boolean; + paddingbottom?: number; + paddingleft?: number; + paddingright?: number; + paddingtop?: number; + print?: boolean; + responsive?: Responsive; + scale?: number; + scrollHorizontal?: boolean; + selectionColor?: string; + selectTypes?: boolean | Array; + showDebug?: Array<"grid" | "box">; + staffwidth?: number; + stafftopmargin?: number; + startingTune?: number; + stop_on_warning?: boolean; + tablature?: Array; + textboxpadding?: number; + timeBasedLayout?: { minPadding?:number, minWidth?:number, align?: 'left'|'center'}; + viewportHorizontal?: boolean; + viewportVertical?: boolean; + visualTranspose?: number; + wrap?: Wrap; + } + + // TimingCallbacks + export interface AnimationOptions { + qpm?: number; + extraMeasuresAtBeginning?: number; + lineEndAnticipation?: number; + beatSubdivisions?: number; + beatCallback?: BeatCallback; + eventCallback?: EventCallback; + lineEndCallback?: LineEndCallback; + } + + // Editor + + export interface EditorSynth { + synthControl?: SynthObjectController; + el: Selector; + cursorControl?: CursorControl; + options?: SynthOptions; + } + + export interface EditorOptions { + canvas_id?: Selector; + paper_id?: Selector; + generate_warnings?: boolean; + warnings_id?: Selector; + onchange?: OnChange; + selectionChangeCallback?: SelectionChangeCallback; + abcjsParams?: AbcVisualParams; + indicate_changed?: boolean; + synth?: EditorSynth; + } + + // Audio + export interface NoteMapTrackItem { + pitch: number; + instrument: string; + start: number; + end: number; + startChar: number; + endChar: number; + volume: number; + style?: string; + cents?: number; + } + export type NoteMapTrack = Array + + export interface SynthOptions { + soundFontUrl?: string; + soundFontVolumeMultiplier?: number; + programOffsets?: {[instrument: string]: number} + fadeLength?: number; + sequenceCallback?: (sequence: Array, context: any) => Array; + callbackContext?: any; // Anything is ok. It is just passed back in the callback + onEnded?: (context: any) => void; + pan?: Array; + voicesOff?: boolean | Array; + drum?: string; + drumBars?: number; + drumIntro?: number; + drumOff?: boolean; + program?: number; + midiTranspose?: number; + visualTranspose?: number; + channel?: number; + qpm?: number; + defaultQpm?: number; + chordsOff?: boolean; + detuneOctave?: boolean; + + swing?: number; + bassprog?: number; + bassvol?: number; + chordprog?: number; + chordvol?: number; + gchord?: string; + } + + export interface SynthVisualOptions { + displayLoop?: boolean; + displayRestart?: boolean; + displayPlay?: boolean; + displayProgress?: boolean; + displayWarp?: boolean; + } + + export type DownloadLabelFn = (visualObj: TuneObject, index: number) => string; + + export interface MidiFileOptions extends SynthOptions { + midiOutputType?: MidiOutputType + downloadClass?: string + preTextDownload?: string + downloadLabel?: string | DownloadLabelFn + postTextDownload?: string + fileName?: string + } + + export interface MidiBufferOptions { + audioContext? : AudioContext; + visualObj?: TuneObject; + sequence?: AudioSequence; + millisecondsPerMeasure?: number; + debugCallback? : (message: string) => void; + options?: SynthOptions; + onEnded?: (context: any) => void; + } + + // Glyph + export interface GlyphDef { + d: Array<[string, ...number[]]>; + w: number; + h: number; + } + + // + // Return Types + // + + // renderAbc + export interface NoteTimingEvent { + milliseconds: number; + millisecondsPerMeasure: number; + type: NoteTimingEventType; + + elements?: Array>; + endChar?: number; + endCharArray?: Array; + endX?: number; + height?: number; + left?: number; + line?: number; + measureNumber?: number; + midiPitches?: MidiPitches; + midiGraceNotePitches?: MidiGracePitches; + startChar?: number; + startCharArray?: Array; + top?: number; + width?: number; + measureStart?: boolean; + } + + // make an alias for backwards compatibility + export interface TimingEvent extends NoteTimingEvent { + + } + + export interface PercMapElement { + sound: number; + noteHead: NoteHeadType; + } + + export interface Formatting { + alignbars?: number; + aligncomposer?: Alignment; + auquality?: string; + bagpipes?: boolean; + botmargin?: number; + botspace?: number; + bstemdown?: boolean; + composerspace?: number; + continueall?: boolean; + continuous?: string; + dynalign?: boolean; + exprabove?: boolean; + exprbelow?: boolean; + flatbeams?: boolean; + footer?: string; + freegchord?: boolean; + gchordbox?: boolean; + graceSlurs?: boolean; + gracespacebefore?: number; + gracespaceinside?: number; + gracespaceafter?: number; + header?: string; + indent?: number; + infoline?: boolean; + infospace?: number; + leftmargin?: number; + linesep?: number; + lineskipfac?: number; + map?: string; + maxshrink?: number; + maxstaffsep?: number; + maxsysstaffsep?: number; + measurebox?: boolean; + midi?: { + barlines?: MidiParam; + bassprog?: MidiParam; + bassvol?: MidiParam; + beat?: MidiParam; + beataccents?: MidiParam; + beatmod?: MidiParam; + beatstring?: MidiParam; + bendvelocity?: MidiParam; + c?: MidiParam; + channel?: MidiParam; + chordattack?: MidiParam; + chordname?: MidiParam; + chordprog?: MidiParam; + chordvol?: MidiParam; + control?: MidiParam; + controlcombo?: MidiParam; + deltaloudness?: MidiParam; + drone?: MidiParam; + droneoff?: MidiParam; + droneon?: MidiParam; + drum?: MidiParam; + drumbars?: MidiParam; + drummap: MidiParam; + drumoff?: MidiParam; + drumon?: MidiParam; + expand?: MidiParam; + fermatafixed?: MidiParam; + fermataproportional?: MidiParam; + gchord?: MidiParam; + gchordon?: MidiParam; + gchordoff?: MidiParam; + grace?: MidiParam; + gracedivider?: MidiParam; + makechordchannels?: MidiParam; + nobarlines?: MidiParam; + nobeataccents?: MidiParam; + noportamento?: MidiParam; + pitchbend?: MidiParam; + program?: MidiParam; + portamento?: MidiParam; + ptstress?: MidiParam; + randomchordattack?: MidiParam; + ratio?: MidiParam; + rtranspose?: MidiParam; + snt?: MidiParam; + stressmodel?: MidiParam; + temperamentlinear?: MidiParam; + temperamentnormal?: MidiParam; + transpose?: MidiParam; + trim?: MidiParam; + volinc: MidiParam; + } + musicspace?: number; + nobarcheck?: string; + notespacingfactor?: number; + parskipfac?: number; + partsbox?: boolean; + partsspace?: number; + percmap?: Array; + playtempo?: string; + rightmargin?: number; + scale?: number; + score?: string; + slurheight?: number; + splittune?: boolean; + squarebreve?: boolean; + staffsep?: number; + staffwidth?: number; + stemheight?: number; + straightflags?: boolean; + stretchlast?: number; + stretchstaff?: boolean; + subtitlespace?: number; + sysstaffsep?: number; + systemsep?: number; + stafftopmargin?: number; + textspace?: number; + titleformat?: string; + titleleft?: boolean; + titlespace?: number; + topmargin?: number; + topspace?: number; + vocalabove?: boolean; + vocalspace?: number; + wordsspace?: number; + + annotationfont: Font; + composerfont: Font; + footerfont: Font; + gchordfont: Font; + headerfont: Font; + historyfont: Font; + infofont: Font; + measurefont: Font; + pageheight: number; + pagewidth: number; + partsfont: Font; + repeatfont: Font; + subtitlefont: Font; + tabgracefont: Font; + tablabelfont: Font; + tabnumberfont: Font; + tempofont: Font; + textfont: Font; + titlefont: Font; + tripletfont: Font; + vocalfont: Font; + voicefont: Font; + wordsfont: Font; + } + + // Caution: The contents of this object may change at any time. If you reference this, be sure you retest for each abcjs release. + export interface EngraverController { + classes: any; + dragColor: string; + dragIndex: number; + dragMouseStart: { x: number, y: number; }; + dragTarget: null | any; + dragYStep: number; + dragging: boolean; + engraver: AbstractEngraver; + getFontAndAttr: any; + getTextSize: any; + listeners: [ClickListener]; + rangeHighlight: any; + renderer: any; + responsive?: boolean; + scale: number; + initialClef?: any; + selectTypes: boolean | Array; + selectables: Array; + selected: Array; + selectionColor: string; + space: number; + staffgroups: [any]; + staffwidthPrint: number; + staffwidthScreen: number; + width: number; + } + + export interface MetaText { + "abc-copyright"?: string; + "abc-creator"?: string; + "abc-version"?: string; + "abc-charset"?: string; + "abc-edited-by"?: string; + author?: string; + book?: string; + composer?: string; + decorationPlacement?: Placement; + discography?: string; + footer?: { + left: string; + center: string; + right: string; + }; + group?: string; + header?: { + left: string; + center: string; + right: string; + } + history?: string; + instruction?: string; + measurebox?: boolean; + notes?: string; + origin?: string; + partOrder?: string; + rhythm?: string; + source?: string; + tempo?: TempoProperties; + textBlock?: string; + title?: string; + transcription?: string; + unalignedWords?: Array; + url?: string; + } + + export interface MetaTextInfo { + "abc-copyright": CharRange; + "abc-creator": CharRange; + "abc-version": CharRange; + "abc-charset": CharRange; + "abc-edited-by": CharRange; + author: CharRange; + book: CharRange; + composer: CharRange; + discography: CharRange; + footer: CharRange; + group: CharRange; + header: CharRange; + history: CharRange; + instruction: CharRange; + notes: CharRange; + origin: CharRange; + partOrder: CharRange; + rhythm: CharRange; + source: CharRange; + tempo: CharRange; + textBlock: CharRange; + title: CharRange; + transcription: CharRange; + unalignedWords: CharRange; + url: CharRange; + } + + export interface VoiceItemClef { + el_type: "clef"; + stafflines?: number; + staffscale?: number; + transpose?: number; + type: Clef; + verticalPos: number; + clefPos?: number; + startChar: number; + endChar: number; + } + + export interface VoiceItemBar { + el_type: "bar"; + type: 'bar_dbl_repeat' | 'bar_right_repeat' | 'bar_left_repeat' |'bar_invisible' | 'bar_thick_thin' | 'bar_thin_thin' | 'bar_thin' | 'bar_thin_thick'; + barNumber?: number; + chord?: Array; + decoration: Array; + endEnding?: boolean; + startEnding?: string; + startChar: number; + endChar: number; + } + + export interface VoiceItemGap { + el_type: "gap"; + gap: number; + } + + export interface VoiceItemKey extends KeySignature { + el_type: "key"; + startChar: number; + endChar: number; + } + + export interface VoiceItemMeter extends Meter { + el_type: "meter"; + startChar: number; + endChar: number; + } + + export interface VoiceItemMidi { + el_type: "midi"; + cmd: MidiCommands; + params: Array; + startChar: number; + endChar: number; + } + + export interface VoiceItemOverlay { + el_type: "overlay"; + startChar: number; + endChar: number; + overlay: Array; + } + + export interface VoiceItemPart { + el_type: "part"; + startChar: number; + endChar: number; + title: string; + } + + export interface VoiceItemScale { + el_type: "scale"; + size: number; + } + + export interface VoiceItemStem { + el_type: "stem"; + direction: StemDirection; + } + + export interface VoiceItemStyle { + el_type: "style"; + head: NoteHeadType; + } + + export interface VoiceItemTempo extends TempoProperties { + el_type: "tempo"; + startChar: number; + endChar: number; + } + + export interface VoiceItemTranspose { + el_type: "transpose"; + steps: number; + } + + export interface VoiceItemNote extends NoteProperties { + el_type: "note"; + startChar: number; + endChar: number; + duration: number; + pitches?: Array; // TODO-PER + rest?: { type: 'rest' | 'spacer' | 'invisible' | 'invisible-multimeasure' | 'multimeasure', text? : number}; + } + export type VoiceItem = VoiceItemClef | VoiceItemBar | VoiceItemGap | VoiceItemKey | VoiceItemMeter | VoiceItemMidi | VoiceItemOverlay | VoiceItemPart | VoiceItemScale | VoiceItemStem | VoiceItemStyle | VoiceItemTempo | VoiceItemTranspose | VoiceItemNote; + + export interface TuneLine { + columns?: { formatting: any, lines: any }; + image?: string; + newpage?: number; + staffbreak?: number; + // Only one of separator, subtitle, text, or staff will be present + separator?: { + endChar: number; + lineLength?: number; + spaceAbove?: number; + spaceBelow?: number; + startChar: number; + }; + subtitle?: { + endChar: number; + startChar: number; + text: string; + }; + text?: { + endChar: number; + startChar: number; + text: TextFieldProperties; + }; + staff?: Array<{ + clef?: ClefProperties; + key?: KeySignature; + meter?: Meter; + voices?: Array>; + }>; + staffGroup?: { + barNumber?: number; + brace?: BracePosition; + bracket?: BracePosition; + connectBarLines?: BracePosition; + gchordfont?: Font; + tripletfont?: Font; + vocalfont?: Font; + spacingAbove?: number; + spacingBelow?: number; + stafflines?: number; + staffscale?: number; + title?: Array; + height?:number; + line?: number; + startx?:number; + w?:number; + gridStart?:number; + gridEnd?:number; + }; + vskip?: number; + } + + export interface Selectable { + absEl: AbsoluteElement; + isDraggable: boolean; + staffPos: { + height: number; + top: number; + zero: number; + } + svgEl: SVGElement; + } + + export interface SelectableReturn { + index: number; + classes: Array; + element: Selectable; + analysis: { + staffPos: number; + name: string; + voice: number; + line: number; + measure: number; + selectableElement: HTMLElement; + } + } + + export interface TuneObject { + formatting: Formatting; + engraver?: EngraverController; + lines: Array; + media: Media; + metaText: MetaText; + metaTextInfo: MetaTextInfo; + version: string; + warnings?: Array; + + getTotalTime: NumberFunction; + getTotalBeats: NumberFunction; + getBarLength: NumberFunction; + getBeatLength: NumberFunction; + getBeatsPerMeasure: NumberFunction; + getBpm: (tempo?:TempoProperties) => number; + getMeter: () => Meter; + getMeterFraction: () => MeterFraction; + getPickupLength: NumberFunction; + getKeySignature: () => KeySignature; + getElementFromChar: (charPos: number) => VoiceItem | null; + millisecondsPerMeasure: (bpm?: number) => number; + setTiming: (bpm?: number, measuresOfDelay? : number) => void; + setupEvents: (startingDelay: number, timeDivider:number, startingBpm: number, warp?: number) => Array; + setUpAudio: (options: SynthOptions) => AudioTracks; + makeVoicesArray: () => Array + deline: () => Array; + findSelectableElement: (target: HTMLElement) => SelectableReturn | null; + getSelectableArray: () => Array + lineBreaks?: Array; + visualTranspose?: number; + } + + export type TuneObjectArray = [TuneObject] + + export interface AbcElem { + el_type: string; //TODO enumerate these + abselem: AbsoluteElement; + beambr?: number; + chord?: Array<{name: string; position: ChordPlacement}> + decoration: Array //TODO enumerate these + duration: number + endBeam?: boolean + endSlur?: number + endTriplet?: true + gracenotes?: Array<{duration: number; name:string; pitch: number; verticalPosition: number;}> + lyric?: Array<{syllable: string; divider: ' ' | '-' | '_';}> + noStem?: boolean + midiPitches?: MidiPitches; + midiGraceNotePitches?: MidiGracePitches; + pitches?: Array<{ + pitch: number; + name: string; + startSlur?: Array<{label: number}>; + endSlur?: Array; + startTie?: {}; + endTie?: boolean; + verticalPos: number; + highestVert: number; + }> + positioning?: any + rest?: {"type": "rest"} + startBeam?: boolean + startTriplet?: number + tripletMultiplier?: number + tripletR?: number + stemConnectsToAbove?: true + style?: NoteHeadType + startChar: number + endChar: number + } + + export interface ClickListenerDrag { + step: number; + max: number; + index: number; + setSelection: (index: number) => void; + } + + export interface ClickListenerAnalysis { + staffPos: number; + name: string; + clickedName: string; + parentClasses: Array; + clickedClasses: Array; + voice: number; + line: number; + measure: number; + selectableElement: HTMLElement; + } + + // TimingCallbacks + export interface LineEndInfo { + milliseconds: number; + top: number; + bottom: number; + } + + export interface LineEndDetails { + line: number; + endTimings: Array; + } + + export interface TimingCallbacksPosition { + top: number; + left: number; + height: number + } + + export interface TimingCallbacksDebug { + timestamp: number; + startTime: number; + ev: NoteTimingEvent; + endMs: number; + offMs: number; + offPx: number; + gapMs: number; + gapPx: number; + } + + // Audio + export interface SequenceInstrument { + el_type: "instrument"; + program: number; + pickupLength: number; + } + + export interface SequenceChannel { + el_type: "channel"; + channel: number; + } + + export interface SequenceTranspose { + el_type: "transpose"; + transpose: number; + } + + export interface SequenceName { + el_type: "name"; + trackName: string; + } + + export interface SequenceDrum { + el_type: "drum"; + pattern: string; + on: boolean; + bars?: number; + intro?: number; + } + + export interface SequenceTempo { + el_type: "tempo"; + qpm: number; + } + + export interface SequenceKey { + el_type: "key"; + accidentals: Array; + } + + export interface SequenceBeat { + el_type: "beat"; + beats: [number,number,number]; + } + + export interface SequenceBeatAccents { + el_type: "beataccents"; + value: boolean; + } + + export interface SequenceBagpipes { + el_type: "bagpipes"; + } + + export interface SequenceNote { + el_type: "note"; + duration: number; + elem: AbsoluteElement; + pitches: {pitch: number; name: NoteLetter}; + timing: number; + } + + export type AudioSequenceElement = SequenceInstrument | SequenceChannel | SequenceTranspose | SequenceName | SequenceDrum | SequenceTempo | SequenceKey | SequenceBeat | SequenceBeatAccents | SequenceBagpipes | SequenceNote; + + export type AudioSequenceVoice = Array; + + export type AudioSequence = Array; + + export type MidiFile = any // This is a standard midi file format + + export interface MidiBufferPromise { + cached: Array; + error: Array; + loaded: Array; + } + + export interface AudioTrackProgramItem { + cmd: 'program'; + channel: number; + instrument: number; + } + + export interface AudioTrackNoteItem { + cmd: 'note'; + duration: number; + endChar: number; + endType?: "staccato"|"tenuto"; + gap: number; + instrument: number; + pitch: number; + start: number; + startChar: number; + volume: number; + } + export interface AudioTrackTextItem { + cmd: 'text'; + type: 'name'; + text: string; + } + export type AudioTrack = Array + + export interface AudioTracks { + tempo: number; + instrument: number; + tracks: Array; + totalDuration: number; + } + + // Analysis + export interface AnalyzedTune { + abc: string; + id: string; + pure: string; + startPos: number; + title: string; + } + + export interface MeasureDef { + abc: string; + startEnding?: string; + endEnding?: true; + } + + export interface MeasureList { + header: string; + measures: Array; + hasPickup: boolean + } + + + // + // Callbacks + // + + // renderAbc + export type ClickListener = (abcElem: AbcElem, tuneNumber: number, classes: string, analysis: ClickListenerAnalysis, drag: ClickListenerDrag) => void; + + export type AfterParsing = (tune: TuneObject, tuneNumber: number, abcString: string) => TuneObject; + + // TimingCallbacks + export type BeatCallback = (beatNumber: number, totalBeats: number, totalTime: number, position: TimingCallbacksPosition, debugInfo: TimingCallbacksDebug) => void; + + type EventCallbackReturn = "continue" | Promise<"continue"> | undefined + + export type EventCallback = (event: NoteTimingEvent | null) => EventCallbackReturn; + + export type LineEndCallback = (info : LineEndInfo, event: NoteTimingEvent, details: LineEndDetails) => void; + + // Editor + export type OnChange = (editor: Editor) => void; + + export type SelectionChangeCallback = (startChar: number, endChar: number) => void; + + // Audio + export interface CursorControl { + beatSubDivision?: number + + onReady?(): void + onStart?(): void + onFinished?(): void + onBeat?(beatNumber: number, totalBeats: number, totalTime: number): void + onEvent?(event: NoteTimingEvent): void + } + + // + // Visual + // + let signature: string; + + export function renderAbc(target: Selector, code: string, params?: AbcVisualParams): TuneObjectArray + + export function tuneMetrics(code: string, params?: AbcVisualParams): Array<{sections: Array<{left: number, measureWidths:Array, total: number}>}> + + export function parseOnly(abc: string, params?: AbcVisualParams) : TuneObjectArray + + // + // Animation + // + export class TimingCallbacks { + constructor(visualObj: TuneObject, options?: AnimationOptions) ; + replaceTarget(visualObj: TuneObject): void; + start(position?: number, units?: ProgressUnit) : void; + pause() : void; + reset() : void; + stop() : void; + setProgress(position: number, units?: ProgressUnit ) : void; + currentMillisecond(): number; + + noteTimings: Array; + } + + // + // Editor + // + export class Editor { + constructor(target: Selector, options: EditorOptions); + paramChanged(options: AbcVisualParams): void; + synthParamChanged(options: SynthOptions): void; + setNotDirty(): void; + isDirty(): boolean; + pause(shouldPause: boolean): void; + millisecondsPerMeasure(): number; + pauseMidi(shouldPause: boolean): void; + fireChanged():void; + } + + // + // Audio + // + export interface AudioControl { + disable: (isDisabled: boolean) => void; + setWarp: (tempo: number, warp: number) => void; + setTempo: (tempo: number) => void; + resetAll: () => void; + pushPlay: (push: boolean) => void; + pushLoop: (push: boolean) => void; + setProgress: (percent: number, totalTime: number) => void; + } + + export interface MidiBuffer { + init(params?: MidiBufferOptions): Promise + prime(): Promise<{ status: string, duration: number}> + start(): void + pause(): number + resume(): void + seek(position: number, units?: ProgressUnit): void + stop(): number + download(): string // returns audio buffer in wav format as a reference to a blob + getIsRunning(): boolean + getAudioBuffer(): AudioBuffer | undefined + } + + export interface SynthInitResponse { + status: "no-audio-context" | "created"; + loadingResponse?: { + cached: Array + error: Array + loaded: Array + } + } + + export interface SynthObjectController { + disable(isDisabled: boolean): void + setTune(visualObj: TuneObject, userAction: boolean, audioParams?: SynthOptions): Promise + load(selector: Selector, cursorControl?: CursorControl | null, visualOptions?: SynthVisualOptions): void + play(): void + pause(): void + toggleLoop(): void + restart(): void + setProgress(ev: number): void + setWarp(percent: number): Promise + download(fName: string): void + getAudioBuffer(): AudioBuffer | undefined + } + + export interface SynthSequenceClass { + addTrack(): AudioTrack + setInstrument(trackNumber: number, instrumentNumber: number): void + appendNote(trackNumber: number, pitch: number, durationInMeasures: number, volume: number, cents: number): void + } + + export interface MidiRenderer { + setTempo(bpm: number): void + setGlobalInfo(bpm: number, name: string, key:KeySignature, time:MeterFraction): void + startTrack(): void + endTrack(): void + setText(type: string, text: string):void + setInstrument(instrument: number):void + setChannel(channel:number, pan?: number):void + startNote(pitch:number, loudness:number, cents?:number):void + endNote(pitch:number):void + addRest(length:number):void + + getData():string + embed(parent:Element, noplayer:boolean):void + } + + export interface SynthControlOptions { + ac?: AudioContext; + afterResume?: () => void; + loopHandler?: (ev: any) => Promise; + restartHandler?: (ev: any) => Promise; + playHandler?: (ev: any) => Promise; + playPromiseHandler?: (ev: any) => Promise; + progressHandler?: (ev: any) => Promise; + warpHandler?: (ev: any) => Promise; + hasClock?: boolean; + repeatTitle?: string; + repeatAria?: string; + restartTitle?: string; + restartAria?: string; + playTitle?: string; + playAria?: string; + randomTitle?: string; + randomAria?: string; + warpTitle?: string; + warpAria?: string; + bpm?: string; + } + + export namespace synth { + let instrumentIndexToName: [string] + let pitchToNoteName: [string] + let SynthController: { new (): SynthObjectController } + let CreateSynth: { new (): MidiBuffer } + let SynthSequence: { new (): SynthSequenceClass } + + export function supportsAudio(): boolean + export function registerAudioContext(ac?: AudioContext): boolean + export function activeAudioContext(): AudioContext + export function CreateSynthControl(element: Selector, options?: SynthControlOptions): AudioControl + export function getMidiFile(source: string | TuneObject, options?: MidiFileOptions): MidiFile; + export function playEvent(pitches: MidiPitches, graceNotes: MidiGracePitches | undefined, milliSecondsPerMeasure: number, soundFontUrl? : string, debugCallback?: (message: string) => void): Promise; + export function sequence(visualObj: TuneObject, options: AbcVisualParams): AudioSequence + export function midiRenderer(): MidiRenderer + } + + // + // Analysis + // + export class TuneBook { + constructor(tunebookString: string) ; + getTuneById(id: string | number): AnalyzedTune; + getTuneByTitle(id: string): AnalyzedTune; + + header: string; + tunes: Array; + } + + export function numberOfTunes(abc: string) : number; + export function extractMeasures(abc: string) : Array; + + export function strTranspose(originalAbc: string, visualObj: TuneObjectArray, steps: number): string; + + // + // Glyph + // + export function setGlyph(glyphName: string, glyph: GlyphDef) : void; +} diff --git a/version.js b/version.js new file mode 100644 index 0000000000000000000000000000000000000000..5579fd41ad5abd836751a980c3bfb081a3fedffe --- /dev/null +++ b/version.js @@ -0,0 +1,3 @@ +var version = '6.4.3'; + +module.exports = version; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..5ecb22532fd9e84405b32ff13cf2039e29aec790 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,70 @@ +const pkg = require("./package.json"); +const TerserPlugin = require('terser-webpack-plugin'); +const WebpackBundleAnalyzer = require("webpack-bundle-analyzer") + .BundleAnalyzerPlugin; + +module.exports = (env = {} , argv) => { + const defaults = (argv, type) => { + const config = { + target: ['web', 'es5'], + output: { + library: { + amd: 'abcjs', + root: 'ABCJS', + commonjs: 'abcjs' + }, + libraryTarget: 'umd', + globalObject: 'this', + filename: argv.mode === 'development' ? `abcjs-${type}.js` : `abcjs-${type}-min.js`, + }, + devtool: argv.mode === 'development' ? 'source-map' : false, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: "babel-loader" + } + ], + }, + mode: 'production', + optimization:{ + minimizer: [ + new TerserPlugin({ + extractComments: { + filename: '[file].LICENSE', + condition: /^\*\**!/i, + banner: makeBanner(type) + }, + }), + ], + } + } + + if (env.analyze) { + config.plugins = [ + new WebpackBundleAnalyzer({ + analyzerMode: "static" + }) + ] + } + return config + } + + return [ + { + name: 'basic', + entry: `./index.js`, + ...defaults(argv, 'basic') + }, { + name: 'plugin', + entry: `./plugin.js`, + ...defaults(argv, 'plugin') + } + ] +}; + +function makeBanner(type) { + let banner = `abcjs_${type} v${pkg.version} Copyright © 2009-2024 Paul Rosen and Gregory Dyke (https://abcjs.net) */\n` + return banner + `/*! For license information please see abcjs_${type}.LICENSE`; +}