PageRenderTime 82ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/abc2pd.js

https://code.google.com/p/sing/
JavaScript | 5260 lines | 4892 code | 72 blank | 296 comment | 236 complexity | 1c4a52606e3da5f826240f50381b4a2e MD5 | raw file
  1. /*
  2. http://www.JSON.org/json2.js
  3. 2011-02-23
  4. Public Domain.
  5. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  6. See http://www.JSON.org/js.html
  7. This code should be minified before deployment.
  8. See http://javascript.crockford.com/jsmin.html
  9. USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  10. NOT CONTROL.
  11. This file creates a global JSON object containing two methods: stringify
  12. and parse.
  13. JSON.stringify(value, replacer, space)
  14. value any JavaScript value, usually an object or array.
  15. replacer an optional parameter that determines how object
  16. values are stringified for objects. It can be a
  17. function or an array of strings.
  18. space an optional parameter that specifies the indentation
  19. of nested structures. If it is omitted, the text will
  20. be packed without extra whitespace. If it is a number,
  21. it will specify the number of spaces to indent at each
  22. level. If it is a string (such as '\t' or ' '),
  23. it contains the characters used to indent at each level.
  24. This method produces a JSON text from a JavaScript value.
  25. When an object value is found, if the object contains a toJSON
  26. method, its toJSON method will be called and the result will be
  27. stringified. A toJSON method does not serialize: it returns the
  28. value represented by the name/value pair that should be serialized,
  29. or undefined if nothing should be serialized. The toJSON method
  30. will be passed the key associated with the value, and this will be
  31. bound to the value
  32. For example, this would serialize Dates as ISO strings.
  33. Date.prototype.toJSON = function (key) {
  34. function f(n) {
  35. // Format integers to have at least two digits.
  36. return n < 10 ? '0' + n : n;
  37. }
  38. return this.getUTCFullYear() + '-' +
  39. f(this.getUTCMonth() + 1) + '-' +
  40. f(this.getUTCDate()) + 'T' +
  41. f(this.getUTCHours()) + ':' +
  42. f(this.getUTCMinutes()) + ':' +
  43. f(this.getUTCSeconds()) + 'Z';
  44. };
  45. You can provide an optional replacer method. It will be passed the
  46. key and value of each member, with this bound to the containing
  47. object. The value that is returned from your method will be
  48. serialized. If your method returns undefined, then the member will
  49. be excluded from the serialization.
  50. If the replacer parameter is an array of strings, then it will be
  51. used to select the members to be serialized. It filters the results
  52. such that only members with keys listed in the replacer array are
  53. stringified.
  54. Values that do not have JSON representations, such as undefined or
  55. functions, will not be serialized. Such values in objects will be
  56. dropped; in arrays they will be replaced with null. You can use
  57. a replacer function to replace those with JSON values.
  58. JSON.stringify(undefined) returns undefined.
  59. The optional space parameter produces a stringification of the
  60. value that is filled with line breaks and indentation to make it
  61. easier to read.
  62. If the space parameter is a non-empty string, then that string will
  63. be used for indentation. If the space parameter is a number, then
  64. the indentation will be that many spaces.
  65. Example:
  66. text = JSON.stringify(['e', {pluribus: 'unum'}]);
  67. // text is '["e",{"pluribus":"unum"}]'
  68. text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  69. // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  70. text = JSON.stringify([new Date()], function (key, value) {
  71. return this[key] instanceof Date ?
  72. 'Date(' + this[key] + ')' : value;
  73. });
  74. // text is '["Date(---current time---)"]'
  75. JSON.parse(text, reviver)
  76. This method parses a JSON text to produce an object or array.
  77. It can throw a SyntaxError exception.
  78. The optional reviver parameter is a function that can filter and
  79. transform the results. It receives each of the keys and values,
  80. and its return value is used instead of the original value.
  81. If it returns what it received, then the structure is not modified.
  82. If it returns undefined then the member is deleted.
  83. Example:
  84. // Parse the text. Values that look like ISO date strings will
  85. // be converted to Date objects.
  86. myData = JSON.parse(text, function (key, value) {
  87. var a;
  88. if (typeof value === 'string') {
  89. a =
  90. /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  91. if (a) {
  92. return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  93. +a[5], +a[6]));
  94. }
  95. }
  96. return value;
  97. });
  98. myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  99. var d;
  100. if (typeof value === 'string' &&
  101. value.slice(0, 5) === 'Date(' &&
  102. value.slice(-1) === ')') {
  103. d = new Date(value.slice(5, -1));
  104. if (d) {
  105. return d;
  106. }
  107. }
  108. return value;
  109. });
  110. This is a reference implementation. You are free to copy, modify, or
  111. redistribute.
  112. */
  113. /*jslint evil: true, strict: false, regexp: false */
  114. /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
  115. call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
  116. getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
  117. lastIndex, length, parse, prototype, push, replace, slice, stringify,
  118. test, toJSON, toString, valueOf
  119. */
  120. // Create a JSON object only if one does not already exist. We create the
  121. // methods in a closure to avoid creating global variables.
  122. var JSON;
  123. if (!JSON) {
  124. JSON = {};
  125. }
  126. (function () {
  127. "use strict";
  128. function f(n) {
  129. // Format integers to have at least two digits.
  130. return n < 10 ? '0' + n : n;
  131. }
  132. if (typeof Date.prototype.toJSON !== 'function') {
  133. Date.prototype.toJSON = function (key) {
  134. return isFinite(this.valueOf()) ?
  135. this.getUTCFullYear() + '-' +
  136. f(this.getUTCMonth() + 1) + '-' +
  137. f(this.getUTCDate()) + 'T' +
  138. f(this.getUTCHours()) + ':' +
  139. f(this.getUTCMinutes()) + ':' +
  140. f(this.getUTCSeconds()) + 'Z' : null;
  141. };
  142. String.prototype.toJSON =
  143. Number.prototype.toJSON =
  144. Boolean.prototype.toJSON = function (key) {
  145. return this.valueOf();
  146. };
  147. }
  148. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  149. escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  150. gap,
  151. indent,
  152. meta = { // table of character substitutions
  153. '\b': '\\b',
  154. '\t': '\\t',
  155. '\n': '\\n',
  156. '\f': '\\f',
  157. '\r': '\\r',
  158. '"' : '\\"',
  159. '\\': '\\\\'
  160. },
  161. rep;
  162. function quote(string) {
  163. // If the string contains no control characters, no quote characters, and no
  164. // backslash characters, then we can safely slap some quotes around it.
  165. // Otherwise we must also replace the offending characters with safe escape
  166. // sequences.
  167. escapable.lastIndex = 0;
  168. return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
  169. var c = meta[a];
  170. return typeof c === 'string' ? c :
  171. '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  172. }) + '"' : '"' + string + '"';
  173. }
  174. function str(key, holder) {
  175. // Produce a string from holder[key].
  176. var i, // The loop counter.
  177. k, // The member key.
  178. v, // The member value.
  179. length,
  180. mind = gap,
  181. partial,
  182. value = holder[key];
  183. // If the value has a toJSON method, call it to obtain a replacement value.
  184. if (value && typeof value === 'object' &&
  185. typeof value.toJSON === 'function') {
  186. value = value.toJSON(key);
  187. }
  188. // If we were called with a replacer function, then call the replacer to
  189. // obtain a replacement value.
  190. if (typeof rep === 'function') {
  191. value = rep.call(holder, key, value);
  192. }
  193. // What happens next depends on the value's type.
  194. switch (typeof value) {
  195. case 'string':
  196. return quote(value);
  197. case 'number':
  198. // JSON numbers must be finite. Encode non-finite numbers as null.
  199. return isFinite(value) ? String(value) : 'null';
  200. case 'boolean':
  201. case 'null':
  202. // If the value is a boolean or null, convert it to a string. Note:
  203. // typeof null does not produce 'null'. The case is included here in
  204. // the remote chance that this gets fixed someday.
  205. return String(value);
  206. // If the type is 'object', we might be dealing with an object or an array or
  207. // null.
  208. case 'object':
  209. // Due to a specification blunder in ECMAScript, typeof null is 'object',
  210. // so watch out for that case.
  211. if (!value) {
  212. return 'null';
  213. }
  214. // Make an array to hold the partial results of stringifying this object value.
  215. gap += indent;
  216. partial = [];
  217. // Is the value an array?
  218. if (Object.prototype.toString.apply(value) === '[object Array]') {
  219. // The value is an array. Stringify every element. Use null as a placeholder
  220. // for non-JSON values.
  221. length = value.length;
  222. for (i = 0; i < length; i += 1) {
  223. partial[i] = str(i, value) || 'null';
  224. }
  225. // Join all of the elements together, separated with commas, and wrap them in
  226. // brackets.
  227. v = partial.length === 0 ? '[]' : gap ?
  228. '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
  229. '[' + partial.join(',') + ']';
  230. gap = mind;
  231. return v;
  232. }
  233. // If the replacer is an array, use it to select the members to be stringified.
  234. if (rep && typeof rep === 'object') {
  235. length = rep.length;
  236. for (i = 0; i < length; i += 1) {
  237. if (typeof rep[i] === 'string') {
  238. k = rep[i];
  239. v = str(k, value);
  240. if (v) {
  241. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  242. }
  243. }
  244. }
  245. } else {
  246. // Otherwise, iterate through all of the keys in the object.
  247. for (k in value) {
  248. if (Object.prototype.hasOwnProperty.call(value, k)) {
  249. v = str(k, value);
  250. if (v) {
  251. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  252. }
  253. }
  254. }
  255. }
  256. // Join all of the member texts together, separated with commas,
  257. // and wrap them in braces.
  258. v = partial.length === 0 ? '{}' : gap ?
  259. '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
  260. '{' + partial.join(',') + '}';
  261. gap = mind;
  262. return v;
  263. }
  264. }
  265. // If the JSON object does not yet have a stringify method, give it one.
  266. if (typeof JSON.stringify !== 'function') {
  267. JSON.stringify = function (value, replacer, space) {
  268. // The stringify method takes a value and an optional replacer, and an optional
  269. // space parameter, and returns a JSON text. The replacer can be a function
  270. // that can replace values, or an array of strings that will select the keys.
  271. // A default replacer method can be provided. Use of the space parameter can
  272. // produce text that is more easily readable.
  273. var i;
  274. gap = '';
  275. indent = '';
  276. // If the space parameter is a number, make an indent string containing that
  277. // many spaces.
  278. if (typeof space === 'number') {
  279. for (i = 0; i < space; i += 1) {
  280. indent += ' ';
  281. }
  282. // If the space parameter is a string, it will be used as the indent string.
  283. } else if (typeof space === 'string') {
  284. indent = space;
  285. }
  286. // If there is a replacer, it must be a function or an array.
  287. // Otherwise, throw an error.
  288. rep = replacer;
  289. if (replacer && typeof replacer !== 'function' &&
  290. (typeof replacer !== 'object' ||
  291. typeof replacer.length !== 'number')) {
  292. throw new Error('JSON.stringify');
  293. }
  294. // Make a fake root object containing our value under the key of ''.
  295. // Return the result of stringifying the value.
  296. return str('', {'': value});
  297. };
  298. }
  299. // If the JSON object does not yet have a parse method, give it one.
  300. if (typeof JSON.parse !== 'function') {
  301. JSON.parse = function (text, reviver) {
  302. // The parse method takes a text and an optional reviver function, and returns
  303. // a JavaScript value if the text is a valid JSON text.
  304. var j;
  305. function walk(holder, key) {
  306. // The walk method is used to recursively walk the resulting structure so
  307. // that modifications can be made.
  308. var k, v, value = holder[key];
  309. if (value && typeof value === 'object') {
  310. for (k in value) {
  311. if (Object.prototype.hasOwnProperty.call(value, k)) {
  312. v = walk(value, k);
  313. if (v !== undefined) {
  314. value[k] = v;
  315. } else {
  316. delete value[k];
  317. }
  318. }
  319. }
  320. }
  321. return reviver.call(holder, key, value);
  322. }
  323. // Parsing happens in four stages. In the first stage, we replace certain
  324. // Unicode characters with escape sequences. JavaScript handles many characters
  325. // incorrectly, either silently deleting them, or treating them as line endings.
  326. text = String(text);
  327. cx.lastIndex = 0;
  328. if (cx.test(text)) {
  329. text = text.replace(cx, function (a) {
  330. return '\\u' +
  331. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  332. });
  333. }
  334. // In the second stage, we run the text against regular expressions that look
  335. // for non-JSON patterns. We are especially concerned with '()' and 'new'
  336. // because they can cause invocation, and '=' because it can cause mutation.
  337. // But just to be safe, we want to reject all unexpected forms.
  338. // We split the second stage into 4 regexp operations in order to work around
  339. // crippling inefficiencies in IE's and Safari's regexp engines. First we
  340. // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  341. // replace all simple value tokens with ']' characters. Third, we delete all
  342. // open brackets that follow a colon or comma or that begin the text. Finally,
  343. // we look to see that the remaining characters are only whitespace or ']' or
  344. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  345. if (/^[\],:{}\s]*$/
  346. .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  347. .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  348. .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  349. // In the third stage we use the eval function to compile the text into a
  350. // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  351. // in JavaScript: it can begin a block or an object literal. We wrap the text
  352. // in parens to eliminate the ambiguity.
  353. j = eval('(' + text + ')');
  354. // In the optional fourth stage, we recursively walk the new structure, passing
  355. // each name/value pair to a reviver function for possible transformation.
  356. return typeof reviver === 'function' ?
  357. walk({'': j}, '') : j;
  358. }
  359. // If the text is not JSON parseable, then a SyntaxError is thrown.
  360. throw new SyntaxError('JSON.parse');
  361. };
  362. }
  363. }());
  364. /*extern document */
  365. /*global Ajax */
  366. // A few useful prototype elements so we don't have to load the whole thing.
  367. Object.clone = function(source) {
  368. var destination = {};
  369. for (var property in source)
  370. destination[property] = source[property];
  371. return destination;
  372. };
  373. Object.keys = function(object) {
  374. var keys = [];
  375. for (var property in object)
  376. if (object.hasOwnProperty(property))
  377. keys.push(property);
  378. return keys;
  379. };
  380. Array.prototype.clone = function(source) {
  381. var destination = [];
  382. for (var i = 0; i < source.length; i++)
  383. destination.push(source[i]);
  384. return destination;
  385. };
  386. String.prototype.gsub = function(pattern, replacement) {
  387. return this.split(pattern).join(replacement);
  388. };
  389. String.prototype.strip = function() {
  390. return this.replace(/^\s+/, '').replace(/\s+$/, '');
  391. };
  392. String.prototype.startsWith = function(pattern) {
  393. return this.indexOf(pattern) === 0;
  394. };
  395. String.prototype.endsWith = function(pattern) {
  396. var d = this.length - pattern.length;
  397. return d >= 0 && this.lastIndexOf(pattern) === d;
  398. };
  399. Array.prototype.each = function(iterator, context) {
  400. for (var i = 0, length = this.length; i < length; i++)
  401. iterator.apply(context, [this[i],i]);
  402. };
  403. Array.prototype.last = function() {
  404. if (this.length === 0)
  405. return null;
  406. return this[this.length-1];
  407. };
  408. Array.prototype.compact = function() {
  409. var output = [];
  410. for (var i = 0; i < this.length; i++) {
  411. if (this[i])
  412. output.push(this[i]);
  413. }
  414. return output;
  415. };
  416. Array.prototype.detect = function(iterator) {
  417. for (var i = 0; i < this.length; i++) {
  418. if (iterator(this[i]))
  419. return true;
  420. }
  421. return false;
  422. };
  423. // abc_parse_header.js: parses a the header fields from a string representing ABC Music Notation into a usable internal structure.
  424. // Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
  425. //
  426. // This program is free software: you can redistribute it and/or modify
  427. // it under the terms of the GNU General Public License as published by
  428. // the Free Software Foundation, either version 3 of the License, or
  429. // (at your option) any later version.
  430. //
  431. // This program is distributed in the hope that it will be useful,
  432. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  433. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  434. // GNU General Public License for more details.
  435. //
  436. // You should have received a copy of the GNU General Public License
  437. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  438. /*extern AbcParseHeader */
  439. function AbcParseHeader(tokenizer, warn, multilineVars, tune) {
  440. var key1sharp = {acc: 'sharp', note: 'f'};
  441. var key2sharp = {acc: 'sharp', note: 'c'};
  442. var key3sharp = {acc: 'sharp', note: 'g'};
  443. var key4sharp = {acc: 'sharp', note: 'd'};
  444. var key5sharp = {acc: 'sharp', note: 'A'};
  445. var key6sharp = {acc: 'sharp', note: 'e'};
  446. var key7sharp = {acc: 'sharp', note: 'B'};
  447. var key1flat = {acc: 'flat', note: 'B'};
  448. var key2flat = {acc: 'flat', note: 'e'};
  449. var key3flat = {acc: 'flat', note: 'A'};
  450. var key4flat = {acc: 'flat', note: 'd'};
  451. var key5flat = {acc: 'flat', note: 'G'};
  452. var key6flat = {acc: 'flat', note: 'c'};
  453. var key7flat = {acc: 'flat', note: 'f'};
  454. var keys = {
  455. 'C#': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  456. 'A#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  457. 'G#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  458. 'D#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  459. 'E#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  460. 'F#Lyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  461. 'B#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
  462. 'F#': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  463. 'D#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  464. 'C#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  465. 'G#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  466. 'A#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  467. 'BLyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  468. 'E#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
  469. 'B': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  470. 'G#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  471. 'F#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  472. 'C#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  473. 'D#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  474. 'ELyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  475. 'A#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
  476. 'E': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  477. 'C#m': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  478. 'BMix': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  479. 'F#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  480. 'G#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  481. 'ALyd': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  482. 'D#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp ],
  483. 'A': [ key1sharp, key2sharp, key3sharp ],
  484. 'F#m': [ key1sharp, key2sharp, key3sharp ],
  485. 'EMix': [ key1sharp, key2sharp, key3sharp ],
  486. 'BDor': [ key1sharp, key2sharp, key3sharp ],
  487. 'C#Phr': [ key1sharp, key2sharp, key3sharp ],
  488. 'DLyd': [ key1sharp, key2sharp, key3sharp ],
  489. 'G#Loc': [ key1sharp, key2sharp, key3sharp ],
  490. 'D': [ key1sharp, key2sharp ],
  491. 'Bm': [ key1sharp, key2sharp ],
  492. 'AMix': [ key1sharp, key2sharp ],
  493. 'EDor': [ key1sharp, key2sharp ],
  494. 'F#Phr': [ key1sharp, key2sharp ],
  495. 'GLyd': [ key1sharp, key2sharp ],
  496. 'C#Loc': [ key1sharp, key2sharp ],
  497. 'G': [ key1sharp ],
  498. 'Em': [ key1sharp ],
  499. 'DMix': [ key1sharp ],
  500. 'ADor': [ key1sharp ],
  501. 'BPhr': [ key1sharp ],
  502. 'CLyd': [ key1sharp ],
  503. 'F#Loc': [ key1sharp ],
  504. 'C': [],
  505. 'Am': [],
  506. 'GMix': [],
  507. 'DDor': [],
  508. 'EPhr': [],
  509. 'FLyd': [],
  510. 'BLoc': [],
  511. 'F': [ key1flat ],
  512. 'Dm': [ key1flat ],
  513. 'CMix': [ key1flat ],
  514. 'GDor': [ key1flat ],
  515. 'APhr': [ key1flat ],
  516. 'BbLyd': [ key1flat ],
  517. 'ELoc': [ key1flat ],
  518. 'Bb': [ key1flat, key2flat ],
  519. 'Gm': [ key1flat, key2flat ],
  520. 'FMix': [ key1flat, key2flat ],
  521. 'CDor': [ key1flat, key2flat ],
  522. 'DPhr': [ key1flat, key2flat ],
  523. 'EbLyd': [ key1flat, key2flat ],
  524. 'ALoc': [ key1flat, key2flat ],
  525. 'Eb': [ key1flat, key2flat, key3flat ],
  526. 'Cm': [ key1flat, key2flat, key3flat ],
  527. 'BbMix': [ key1flat, key2flat, key3flat ],
  528. 'FDor': [ key1flat, key2flat, key3flat ],
  529. 'GPhr': [ key1flat, key2flat, key3flat ],
  530. 'AbLyd': [ key1flat, key2flat, key3flat ],
  531. 'DLoc': [ key1flat, key2flat, key3flat ],
  532. 'Ab': [ key1flat, key2flat, key3flat, key4flat ],
  533. 'Fm': [ key1flat, key2flat, key3flat, key4flat ],
  534. 'EbMix': [ key1flat, key2flat, key3flat, key4flat ],
  535. 'BbDor': [ key1flat, key2flat, key3flat, key4flat ],
  536. 'CPhr': [ key1flat, key2flat, key3flat, key4flat ],
  537. 'DbLyd': [ key1flat, key2flat, key3flat, key4flat ],
  538. 'GLoc': [ key1flat, key2flat, key3flat, key4flat ],
  539. 'Db': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  540. 'Bbm': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  541. 'AbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  542. 'EbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  543. 'FPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  544. 'GbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  545. 'CLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
  546. 'Gb': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  547. 'Ebm': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  548. 'DbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  549. 'AbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  550. 'BbPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  551. 'CbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  552. 'FLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
  553. 'Cb': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  554. 'Abm': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  555. 'GbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  556. 'DbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  557. 'EbPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  558. 'FbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  559. 'BbLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
  560. // The following are not in the 2.0 spec, but seem normal enough.
  561. // TODO-PER: These SOUND the same as what's written, but they aren't right
  562. 'A#': [ key1flat, key2flat ],
  563. 'B#': [],
  564. 'D#': [ key1flat, key2flat, key3flat ],
  565. 'E#': [ key1flat ],
  566. 'G#': [ key1flat, key2flat, key3flat, key4flat ],
  567. 'Gbm': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ]
  568. };
  569. var calcMiddle = function(clef, oct) {
  570. var mid = 0;
  571. switch(clef) {
  572. case 'treble':
  573. case 'perc':
  574. case 'none':
  575. case 'treble+8':
  576. case 'treble-8':
  577. break;
  578. case 'bass3':
  579. case 'bass':
  580. case 'bass+8':
  581. case 'bass-8':
  582. case 'bass+16':
  583. case 'bass-16':
  584. mid = -12;
  585. break;
  586. case 'tenor':
  587. mid = -8;
  588. break;
  589. case 'alto2':
  590. case 'alto1':
  591. case 'alto':
  592. case 'alto+8':
  593. case 'alto-8':
  594. mid = -6;
  595. break;
  596. }
  597. return mid+oct;
  598. };
  599. this.parseFontChangeLine = function(textstr) {
  600. var textParts = textstr.split('$');
  601. if (textParts.length > 1 && multilineVars.setfont) {
  602. var textarr = [ { text: textParts[0] }];
  603. for (var i = 1; i < textParts.length; i++) {
  604. if (textParts[i].charAt(0) === '0')
  605. textarr.push({ text: textParts[i].substring(1) });
  606. else if (textParts[i].charAt(0) === '1' && multilineVars.setfont[1])
  607. textarr.push({font: multilineVars.setfont[1], text: textParts[i].substring(1) });
  608. else if (textParts[i].charAt(0) === '2' && multilineVars.setfont[2])
  609. textarr.push({font: multilineVars.setfont[2], text: textParts[i].substring(1) });
  610. else if (textParts[i].charAt(0) === '3' && multilineVars.setfont[3])
  611. textarr.push({font: multilineVars.setfont[3], text: textParts[i].substring(1) });
  612. else if (textParts[i].charAt(0) === '4' && multilineVars.setfont[4])
  613. textarr.push({font: multilineVars.setfont[4], text: textParts[i].substring(1) });
  614. else
  615. textarr[textarr.length-1].text += '$' + textParts[i];
  616. }
  617. if (textarr.length > 1)
  618. return textarr;
  619. }
  620. return textstr;
  621. }
  622. this.deepCopyKey = function(key) {
  623. var ret = { accidentals: [], root: key.root, acc: key.acc, mode: key.mode };
  624. key.accidentals.each(function(k) {
  625. ret.accidentals.push(Object.clone(k));
  626. });
  627. return ret;
  628. };
  629. 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};
  630. this.addPosToKey = function(clef, key) {
  631. // Shift the key signature from the treble positions to whatever position is needed for the clef.
  632. // This may put the key signature unnaturally high or low, so if it does, then shift it.
  633. var mid = clef.verticalPos;
  634. key.accidentals.each(function(acc) {
  635. var pitch = pitches[acc.note];
  636. pitch = pitch - mid;
  637. acc.verticalPos = pitch;
  638. });
  639. if (mid < -10) {
  640. key.accidentals.each(function(acc) {
  641. acc.verticalPos -= 7;
  642. if (acc.verticalPos >= 11 || (acc.verticalPos === 10 && acc.acc === 'flat'))
  643. acc.verticalPos -= 7;
  644. });
  645. } else if (mid < -4) {
  646. key.accidentals.each(function(acc) {
  647. acc.verticalPos -= 7;
  648. });
  649. } else if (mid >= 7) {
  650. key.accidentals.each(function(acc) {
  651. acc.verticalPos += 7;
  652. });
  653. }
  654. };
  655. this.fixKey = function(clef, key) {
  656. var fixedKey = Object.clone(key);
  657. this.addPosToKey(clef, fixedKey);
  658. return fixedKey;
  659. };
  660. var parseMiddle = function(str) {
  661. var mid = pitches[str.charAt(0)];
  662. for (var i = 1; i < str.length; i++) {
  663. if (str.charAt(i) === ',') mid -= 7;
  664. else if (str.charAt(i) === ',') mid += 7;
  665. else break;
  666. }
  667. 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.
  668. };
  669. var normalizeAccidentals = function(accs) {
  670. for (var i = 0; i < accs.length; i++) {
  671. if (accs[i].note === 'b')
  672. accs[i].note = 'B';
  673. else if (accs[i].note === 'a')
  674. accs[i].note = 'A';
  675. else if (accs[i].note === 'F')
  676. accs[i].note = 'f';
  677. else if (accs[i].note === 'E')
  678. accs[i].note = 'e';
  679. else if (accs[i].note === 'D')
  680. accs[i].note = 'd';
  681. else if (accs[i].note === 'C')
  682. accs[i].note = 'c';
  683. else if (accs[i].note === 'G' && accs[i].acc === 'sharp')
  684. accs[i].note = 'g';
  685. else if (accs[i].note === 'g' && accs[i].acc === 'flat')
  686. accs[i].note = 'G';
  687. }
  688. };
  689. this.parseKey = function(str) // (and clef)
  690. {
  691. // returns:
  692. // { foundClef: true, foundKey: true }
  693. // Side effects:
  694. // calls warn() when there is a syntax error
  695. // sets these members of multilineVars:
  696. // clef
  697. // key
  698. // style
  699. //
  700. // The format is:
  701. // K: [?key?] [?modifiers?*]
  702. // modifiers are any of the following in any order:
  703. // [?clef?] [middle=?pitch?] [transpose=[-]?number?] [stafflines=?number?] [staffscale=?number?][style=?style?]
  704. // key is none|HP|Hp|?specified_key?
  705. // clef is [clef=] [?clef type?] [?line number?] [+8|-8]
  706. // specified_key is ?pitch?[#|b][mode(first three chars are significant)][accidentals*]
  707. if (str.length === 0) {
  708. // an empty K: field is the same as K:none
  709. str = 'none';
  710. }
  711. var tokens = tokenizer.tokenize(str, 0, str.length);
  712. var ret = {};
  713. // first the key
  714. switch (tokens[0].token) {
  715. case 'HP':
  716. this.addDirective("bagpipes");
  717. multilineVars.key = { root: "HP", accidentals: [], acc: "", mode: "" };
  718. ret.foundKey = true;
  719. tokens.shift();
  720. break;
  721. case 'Hp':
  722. this.addDirective("bagpipes");
  723. multilineVars.key = { root: "Hp", accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}], acc: "", mode: "" };
  724. ret.foundKey = true;
  725. tokens.shift();
  726. break;
  727. case 'none':
  728. // we got the none key - that's the same as C to us
  729. multilineVars.key = { root: "none", accidentals: [], acc: "", mode: "" };
  730. ret.foundKey = true;
  731. tokens.shift();
  732. break;
  733. default:
  734. var retPitch = tokenizer.getKeyPitch(tokens[0].token);
  735. if (retPitch.len > 0) {
  736. ret.foundKey = true;
  737. var acc = "";
  738. var mode = "";
  739. // The accidental and mode might be attached to the pitch, so we might want to just remove the first character.
  740. if (tokens[0].token.length > 1)
  741. tokens[0].token = tokens[0].token.substring(1);
  742. else
  743. tokens.shift();
  744. var key = retPitch.token;
  745. // We got a pitch to start with, so we might also have an accidental and a mode
  746. if (tokens.length > 0) {
  747. var retAcc = tokenizer.getSharpFlat(tokens[0].token);
  748. if (retAcc.len > 0) {
  749. if (tokens[0].token.length > 1)
  750. tokens[0].token = tokens[0].token.substring(1);
  751. else
  752. tokens.shift();
  753. key += retAcc.token;
  754. acc = retAcc.token;
  755. }
  756. if (tokens.length > 0) {
  757. var retMode = tokenizer.getMode(tokens[0].token);
  758. if (retMode.len > 0) {
  759. tokens.shift();
  760. key += retMode.token;
  761. mode = retMode.token;
  762. }
  763. }
  764. }
  765. // We need to do a deep copy because we are going to modify it
  766. multilineVars.key = this.deepCopyKey({accidentals: keys[key]});
  767. multilineVars.key.root = retPitch.token;
  768. multilineVars.key.acc = acc;
  769. multilineVars.key.mode = mode;
  770. }
  771. break;
  772. }
  773. // There are two special cases of deprecated syntax. Ignore them if they occur
  774. if (tokens.length === 0) return ret;
  775. if (tokens[0].token === 'exp') tokens.shift();
  776. if (tokens.length === 0) return ret;
  777. if (tokens[0].token === 'oct') tokens.shift();
  778. // now see if there are extra accidentals
  779. if (tokens.length === 0) return ret;
  780. var accs = tokenizer.getKeyAccidentals2(tokens);
  781. if (accs.warn)
  782. warn(accs.warn, str, 0);
  783. // If we have extra accidentals, first replace ones that are of the same pitch before adding them to the end.
  784. if (accs.accs) {
  785. if (!ret.foundKey) { // if there are only extra accidentals, make sure this is set.
  786. ret.foundKey = true;
  787. multilineVars.key = { root: "none", acc: "", mode: "", accidentals: [] };
  788. }
  789. normalizeAccidentals(accs.accs);
  790. for (var i = 0; i < accs.accs.length; i++) {
  791. var found = false;
  792. for (var j = 0; j < multilineVars.key.accidentals.length && !found; j++) {
  793. if (multilineVars.key.accidentals[j].note === accs.accs[i].note) {
  794. found = true;
  795. multilineVars.key.accidentals[j].acc = accs.accs[i].acc;
  796. }
  797. }
  798. if (!found)
  799. multilineVars.key.accidentals.push(accs.accs[i]);
  800. }
  801. }
  802. // Now see if any optional parameters are present. They have the form "key=value", except that "clef=" is optional
  803. var token;
  804. while (tokens.length > 0) {
  805. switch (tokens[0].token) {
  806. case "m":
  807. case "middle":
  808. tokens.shift();
  809. if (tokens.length === 0) { warn("Expected = after middle", str, 0); return ret; }
  810. token = tokens.shift();
  811. if (token.token !== "=") { warn("Expected = after middle", str, 0); break; }
  812. if (tokens.length === 0) { warn("Expected parameter after middle=", str, 0); return ret; }
  813. var pitch = tokenizer.getPitchFromTokens(tokens);
  814. if (pitch.warn)
  815. warn(pitch.warn, str, 0);
  816. if (pitch.position)
  817. multilineVars.clef.verticalPos = pitch.position - 6; // we get the position from the middle line, but want to offset it to the first ledger line.
  818. break;
  819. case "transpose":
  820. tokens.shift();
  821. if (tokens.length === 0) { warn("Expected = after transpose", str, 0); return ret; }
  822. token = tokens.shift();
  823. if (token.token !== "=") { warn("Expected = after transpose", str, 0); break; }
  824. if (tokens.length === 0) { warn("Expected parameter after transpose=", str, 0); return ret; }
  825. if (tokens[0].type !== 'number') { warn("Expected number after transpose", str, 0); break; }
  826. multilineVars.clef.transpose = parseInt(tokens[0].token);
  827. tokens.shift();
  828. break;
  829. case "stafflines":
  830. tokens.shift();
  831. if (tokens.length === 0) { warn("Expected = after stafflines", str, 0); return ret; }
  832. token = tokens.shift();
  833. if (token.token !== "=") { warn("Expected = after stafflines", str, 0); break; }
  834. if (tokens.length === 0) { warn("Expected parameter after stafflines=", str, 0); return ret; }
  835. if (tokens[0].type !== 'number') { warn("Expected number after stafflines", str, 0); break; }
  836. multilineVars.clef.stafflines = parseInt(tokens[0].token);
  837. tokens.shift();
  838. break;
  839. case "staffscale":
  840. tokens.shift();
  841. if (tokens.length === 0) { warn("Expected = after staffscale", str, 0); return ret; }
  842. token = tokens.shift();
  843. if (token.token !== "=") { warn("Expected = after staffscale", str, 0); break; }
  844. if (tokens.length === 0) { warn("Expected parameter after staffscale=", str, 0); return ret; }
  845. if (tokens[0].type !== 'number') { warn("Expected number after staffscale", str, 0); break; }
  846. multilineVars.clef.staffscale = parseInt(tokens[0].token);
  847. tokens.shift();
  848. break;
  849. case "style":
  850. tokens.shift();
  851. if (tokens.length === 0) { warn("Expected = after style", str, 0); return ret; }
  852. token = tokens.shift();
  853. if (token.token !== "=") { warn("Expected = after style", str, 0); break; }
  854. if (tokens.length === 0) { warn("Expected parameter after style=", str, 0); return ret; }
  855. switch (tokens[0].token) {
  856. case "normal":
  857. case "harmonic":
  858. case "rhythm":
  859. case "x":
  860. multilineVars.style = tokens[0].token;
  861. tokens.shift();
  862. break;
  863. default:
  864. warn("error parsing style element: " + tokens[0].token, str, 0);
  865. break;
  866. }
  867. break;
  868. case "clef":
  869. tokens.shift();
  870. if (tokens.length === 0) { warn("Expected = after clef", str, 0); return ret; }
  871. token = tokens.shift();
  872. if (token.token !== "=") { warn("Expected = after clef", str, 0); break; }
  873. if (tokens.length === 0) { warn("Expected parameter after clef=", str, 0); return ret; }
  874. //break; yes, we want to fall through. That allows "clef=" to be optional.
  875. case "treble":
  876. case "bass":
  877. case "alto":
  878. case "tenor":
  879. case "perc":
  880. // clef is [clef=] [?clef type?] [?line number?] [+8|-8]
  881. var clef = tokens.shift();
  882. switch (clef.token) {
  883. case 'treble':
  884. case 'tenor':
  885. case 'alto':
  886. case 'bass':
  887. case 'perc':
  888. case 'none':
  889. break;
  890. case 'C': clef.token = 'alto'; break;
  891. case 'F': clef.token = 'bass'; break;
  892. case 'G': clef.token = 'treble'; break;
  893. case 'c': clef.token = 'alto'; break;
  894. case 'f': clef.token = 'bass'; break;
  895. case 'g': clef.token = 'treble'; break;
  896. default:
  897. warn("Expected clef name. Found " + clef.token, str, 0);
  898. break;
  899. }
  900. if (tokens.length > 0 && tokens[0].type === 'number') {
  901. clef.token += tokens[0].token;
  902. tokens.shift();
  903. }
  904. if (tokens.length > 1 && (tokens[0].token === '-' || tokens[0].token === '+') && tokens[1].token === '8') {
  905. clef.token += tokens[0].token + tokens[1].token;
  906. tokens.shift();
  907. tokens.shift();
  908. }
  909. multilineVars.clef = {type: clef.token, verticalPos: calcMiddle(clef.token, 0)};
  910. ret.foundClef = true;
  911. break;
  912. default:
  913. warn("Unknown parameter: " + tokens[0].token, str, 0);
  914. tokens.shift();
  915. }
  916. }
  917. return ret;
  918. };
  919. this.parseKeyOld = function(str) // (and clef)
  920. {
  921. str = tokenizer.stripComment(str);
  922. var origStr = str;
  923. if (str.length === 0) {
  924. // an empty K: field is the same as K:none
  925. str = 'none';
  926. }
  927. // The format is:
  928. // [space][tonic[#|b][ ][3-letter-mode][ignored-chars][space]][ accidentals...][ clef=treble|bass|bass3|tenor|alto|alto2|alto1|none [+8|-8] [middle=note] [transpose=num] [stafflines=num] ]
  929. // K: ?key? [[clef=] [clef type] [line number] [+8|-8]] [middle=?pitch?] [transpose=] [stafflines=?number?] [staffscale=?number?][style=?style?]
  930. // V: ?voice name? [clef=] [clef type] [name=] [sname=] [merge] [stem=] [up | down | auto] [gstem=] [up | down | auto] [dyn=] [up | down | auto] [lyrics=] [up | down | auto] [gchord=] [up | down | auto] [scale=] [staffscale=] [stafflines=]
  931. // -- or -- the key can be "none"
  932. // First get the key letter: turn that into a index into the key array (0-11)
  933. // Then see if there is a sharp or flat. Increment or decrement.
  934. // Then see if there is a mode modifier. Add or subtract to the index.
  935. // Then do a mod 12 on the index and return the key.
  936. // TODO: This may leave unparsed characters at the end after something reasonable was found.
  937. // TODO: The above description does not seem to be valid as key names rather than indexes are used -- GD
  938. var setClefMiddle = function(str) {
  939. var i = tokenizer.skipWhiteSpace(str);
  940. str = str.substring(i);
  941. if (str.startsWith('m=') || str.startsWith('middle=')) {
  942. str = str.substring(str.indexOf('=')+1);
  943. var mid = parseMiddle(str);
  944. multilineVars.clef.verticalPos = mid.mid;
  945. str = mid.str;
  946. }
  947. i = tokenizer.skipWhiteSpace(str);
  948. str = str.substring(i);
  949. if (str.startsWith('transpose=')) {
  950. str = str.substring(str.indexOf('=')+1);
  951. var num = tokenizer.getInt(str);
  952. if (num.digits > 0) {
  953. str = str.substring(num.digits);
  954. multilineVars.clef.transpose = num.value;
  955. }
  956. }
  957. i = tokenizer.skipWhiteSpace(str);
  958. str = str.substring(i);
  959. if (str.startsWith('stafflines=')) {
  960. str = str.substring(str.indexOf('=')+1);
  961. var num2 = tokenizer.getInt(str);
  962. if (num2.digits > 0) {
  963. str = str.substring(num2.digits);
  964. multilineVars.clef.stafflines = num2.value;
  965. }
  966. }
  967. };
  968. // check first to see if there is only a clef. If so, just take that, but ignore an error after that.
  969. var retClef = tokenizer.getClef(str, true);
  970. if (retClef.token !== undefined && (retClef.explicit === true || retClef.token !== 'none')) { // none, C, F, and G are the only ambiguous marking. We need to assume that's a key
  971. multilineVars.clef = {type: retClef.token, verticalPos: calcMiddle(retClef.token, 0)};
  972. str = str.substring(retClef.len);
  973. setClefMiddle(str);
  974. return {foundClef: true};
  975. //TODO multilinevars.key is not set - is this normal? -- GD
  976. }
  977. var ret = { root: 'none', acc: '', mode: '' };
  978. var retPitch = tokenizer.getKeyPitch(str);
  979. if (retPitch.len > 0) {
  980. var key = retPitch.token;
  981. str = str.substring(retPitch.len);
  982. // We got a pitch to start with, so we might also have an accidental and a mode
  983. var retAcc = tokenizer.getSharpFlat(str);
  984. if (retAcc.len > 0) {
  985. key += retAcc.token;
  986. str = str.substring(retAcc.len);
  987. }
  988. var retMode = tokenizer.getMode(str);
  989. if (retMode.len > 0) {
  990. key += retMode.token;
  991. str = str.substring(retMode.len);
  992. }
  993. // We need to do a deep copy because we are going to modify it
  994. ret = this.deepCopyKey({accidentals: keys[key]});
  995. ret.root = retPitch.token;
  996. ret.acc = retAcc.token || "";
  997. ret.mode = retMode.token || "";
  998. } else if (str.startsWith('HP')) {
  999. this.addDirective("bagpipes");
  1000. ret.accidentals = [];
  1001. ret.root = "HP";
  1002. multilineVars.key = ret;
  1003. return {foundKey: true};
  1004. } else if (str.startsWith('Hp')) {
  1005. ret.accidentals = [ {acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'} ];
  1006. this.addDirective("bagpipes");
  1007. ret.root = "Hp";
  1008. multilineVars.key = ret;
  1009. return {foundKey: true};
  1010. } else {
  1011. var retNone = tokenizer.isMatch(str, 'none');
  1012. if (retNone > 0) {
  1013. // we got the none key - that's the same as C to us
  1014. ret.accidentals = [];
  1015. str = str.substring(retNone);
  1016. }
  1017. }
  1018. // There are two special cases of deprecated syntax. Ignore them if they occur
  1019. var j = tokenizer.skipWhiteSpace(str);
  1020. str = str.substring(j);
  1021. if (str.startsWith('exp') || str.startsWith('oct'))
  1022. str = str.substring(3);
  1023. // now see if there are extra accidentals
  1024. var done = false;
  1025. while (!done) {
  1026. var retExtra = tokenizer.getKeyAccidental(str);
  1027. if (retExtra.len === 0)
  1028. done = true;
  1029. else {
  1030. str = str.substring(retExtra.len);
  1031. if (retExtra.warn)
  1032. warn("error parsing extra accidentals:", origStr, 0);
  1033. else {
  1034. if (!ret.accidentals)
  1035. ret.accidentals = [];
  1036. ret.accidentals.push(retExtra.token);
  1037. }
  1038. }
  1039. }
  1040. // now see if there is a clef
  1041. retClef = tokenizer.getClef(str, false);
  1042. if (retClef.len > 0) {
  1043. if (retClef.warn)
  1044. warn("error parsing clef:" + retClef.warn, origStr, 0);
  1045. else {
  1046. //ret.clef = retClef.token;
  1047. multilineVars.clef = {type: retClef.token, verticalPos: calcMiddle(retClef.token, 0)};
  1048. str = str.substring(retClef.len);
  1049. setClefMiddle(str);
  1050. }
  1051. }
  1052. // now see if there is a note style
  1053. i = tokenizer.skipWhiteSpace(str);
  1054. str = str.substring(i);
  1055. if (str.startsWith('style=')) {
  1056. var style = tokenizer.getToken(str, 6, str.length);
  1057. switch (style) {
  1058. case "normal":
  1059. case "harmonic":
  1060. case "rhythm":
  1061. case "x":
  1062. multilineVars.style = style;
  1063. break;
  1064. default:
  1065. warn("error parsing style element of key: ", origStr, 0);
  1066. break;
  1067. }
  1068. str = str.substring(6+style.length);
  1069. }
  1070. // if (ret.accidentals === undefined && retClef.token === undefined) {
  1071. // warn("error parsing key: ", origStr, 0);
  1072. // return {};
  1073. // }
  1074. var result = {};
  1075. if (retClef.token !== undefined)
  1076. result.foundClef = true;
  1077. if (ret.accidentals !== undefined) {
  1078. // Adjust the octave of the accidentals, if necessary
  1079. ret.accidentals.each(function(acc) {
  1080. if (retClef.token === 'bass') {
  1081. //if (acc.note === 'A') acc.note = 'a';
  1082. //if (acc.note === 'B') acc.note = 'b';
  1083. if (acc.note === 'C') acc.note = 'c';
  1084. if (acc.note === 'D' && acc.acc !== 'flat') acc.note = 'd';
  1085. if (acc.note === 'E' && acc.acc !== 'flat') acc.note = 'e';
  1086. if (acc.note === 'F' && acc.acc !== 'flat') acc.note = 'f';
  1087. if (acc.note === 'G' && acc.acc !== 'flat') acc.note = 'g';
  1088. } else {
  1089. if (acc.note === 'a') acc.note = 'A';
  1090. if (acc.note === 'b') acc.note = 'B';
  1091. if (acc.note === 'C') acc.note = 'c';
  1092. }
  1093. });
  1094. multilineVars.key = ret;
  1095. result.foundKey = true;
  1096. }
  1097. return result;
  1098. };
  1099. this.addDirective = function(str) {
  1100. var getRequiredMeasurement = function(cmd, tokens) {
  1101. var points = tokenizer.getMeasurement(tokens);
  1102. if (points.used === 0 || tokens.length !== 0)
  1103. return { error: "Directive \"" + cmd + "\" requires a measurement as a parameter."};
  1104. return points.value;
  1105. };
  1106. var oneParameterMeasurement = function(cmd, tokens) {
  1107. var points = tokenizer.getMeasurement(tokens);
  1108. if (points.used === 0 || tokens.length !== 0)
  1109. return "Directive \"" + cmd + "\" requires a measurement as a parameter.";
  1110. tune.formatting[cmd] = points.value;
  1111. return null;
  1112. };
  1113. var getFontParameter = function(tokens) {
  1114. var font = {};
  1115. var token = tokens.last();
  1116. if (token.type === 'number') {
  1117. font.size = parseInt(token.token);
  1118. tokens.pop();
  1119. }
  1120. if (tokens.length > 0) {
  1121. var scratch = "";
  1122. tokens.each(function(tok) {
  1123. if (tok.token !== '-') {
  1124. if (scratch.length > 0) scratch += ' ';
  1125. scratch += tok.token;
  1126. }
  1127. });
  1128. font.font = scratch;
  1129. }
  1130. return font;
  1131. };
  1132. var getChangingFont = function(cmd, tokens) {
  1133. if (tokens.length === 0)
  1134. return "Directive \"" + cmd + "\" requires a font as a parameter.";
  1135. multilineVars[cmd] = getFontParameter(tokens);
  1136. return null;
  1137. };
  1138. var getGlobalFont = function(cmd, tokens) {
  1139. if (tokens.length === 0)
  1140. return "Directive \"" + cmd + "\" requires a font as a parameter.";
  1141. tune.formatting[cmd] = getFontParameter(tokens);
  1142. return null;
  1143. };
  1144. var tokens = tokenizer.tokenize(str, 0, str.length); // 3 or more % in a row, or just spaces after %% is just a comment
  1145. if (tokens.length === 0 || tokens[0].type !== 'alpha') return null;
  1146. var restOfString = str.substring(str.indexOf(tokens[0].token)+tokens[0].token.length);
  1147. restOfString = tokenizer.stripComment(restOfString);
  1148. var cmd = tokens.shift().token.toLowerCase();
  1149. var num;
  1150. var scratch = "";
  1151. switch (cmd)
  1152. {
  1153. // The following directives were added to abc_parser_lint, but haven't been implemented here.
  1154. // 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.
  1155. // alignbars: { type: "number", optional: true },
  1156. // aligncomposer: { type: "string", Enum: [ 'left', 'center','right' ], optional: true },
  1157. // annotationfont: fontType,
  1158. // barsperstaff: { type: "number", optional: true },
  1159. // bstemdown: { type: "boolean", optional: true },
  1160. // continueall: { type: "boolean", optional: true },
  1161. // dynalign: { type: "boolean", optional: true },
  1162. // exprabove: { type: "boolean", optional: true },
  1163. // exprbelow: { type: "boolean", optional: true },
  1164. // flatbeams: { type: "boolean", optional: true },
  1165. // footer: { type: "string", optional: true },
  1166. // footerfont: fontType,
  1167. // gchordbox: { type: "boolean", optional: true },
  1168. // graceslurs: { type: "boolean", optional: true },
  1169. // gracespacebefore: { type: "number", optional: true },
  1170. // gracespaceinside: { type: "number", optional: true },
  1171. // gracespaceafter: { type: "number", optional: true },
  1172. // header: { type: "string", optional: true },
  1173. // headerfont: fontType,
  1174. // historyfont: fontType,
  1175. // infofont: fontType,
  1176. // infospace: { type: "number", optional: true },
  1177. // lineskipfac: { type: "number", optional: true },
  1178. // maxshrink: { type: "number", optional: true },
  1179. // maxstaffsep: { type: "number", optional: true },
  1180. // maxsysstaffsep: { type: "number", optional: true },
  1181. // measurebox: { type: "boolean", optional: true },
  1182. // measurefont: fontType,
  1183. // notespacingfactor: { type: "number", optional: true },
  1184. // parskipfac: { type: "number", optional: true },
  1185. // partsbox: { type: "boolean", optional: true },
  1186. // repeatfont: fontType,
  1187. // rightmargin: { type: "number", optional: true },
  1188. // slurheight: { type: "number", optional: true },
  1189. // splittune: { type: "boolean", optional: true },
  1190. // squarebreve: { type: "boolean", optional: true },
  1191. // stemheight: { type: "number", optional: true },
  1192. // straightflags: { type: "boolean", optional: true },
  1193. // stretchstaff: { type: "boolean", optional: true },
  1194. // textfont: fontType,
  1195. // titleformat: { type: "string", optional: true },
  1196. // vocalabove: { type: "boolean", optional: true },
  1197. // vocalfont: fontType,
  1198. // wordsfont: fontType,
  1199. case "bagpipes":tune.formatting.bagpipes = true;break;
  1200. case "landscape":multilineVars.landscape = true;break;
  1201. case "papersize":multilineVars.papersize = restOfString;break;
  1202. case "slurgraces":tune.formatting.slurgraces = true;break;
  1203. case "stretchlast":tune.formatting.stretchlast = true;break;
  1204. case "titlecaps":multilineVars.titlecaps = true;break;
  1205. case "titleleft":tune.formatting.titleleft = true;break;
  1206. case "measurebox":tune.formatting.measurebox = true;break;
  1207. case "botmargin":
  1208. case "botspace":
  1209. case "composerspace":
  1210. case "indent":
  1211. case "leftmargin":
  1212. case "linesep":
  1213. case "musicspace":
  1214. case "partsspace":
  1215. case "pageheight":
  1216. case "pagewidth":
  1217. case "rightmargin":
  1218. case "staffsep":
  1219. case "staffwidth":
  1220. case "subtitlespace":
  1221. case "sysstaffsep":
  1222. case "systemsep":
  1223. case "textspace":
  1224. case "titlespace":
  1225. case "topmargin":
  1226. case "topspace":
  1227. case "vocalspace":
  1228. case "wordsspace":
  1229. return oneParameterMeasurement(cmd, tokens);
  1230. case "vskip":
  1231. var vskip = getRequiredMeasurement(cmd, tokens);
  1232. if (vskip.error)
  1233. return vskip.error;
  1234. tune.addSpacing(vskip);
  1235. return null;
  1236. break;
  1237. case "scale":
  1238. scratch = "";
  1239. tokens.each(function(tok) {
  1240. scratch += tok.token;
  1241. });
  1242. num = parseFloat(scratch);
  1243. if (isNaN(num) || num === 0)
  1244. return "Directive \"" + cmd + "\" requires a number as a parameter.";
  1245. tune.formatting.scale = num;
  1246. break;
  1247. case "sep":
  1248. if (tokens.length === 0)
  1249. tune.addSeparator();
  1250. else {
  1251. var points = tokenizer.getMeasurement(tokens);
  1252. if (points.used === 0)
  1253. return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
  1254. var spaceAbove = points.value;
  1255. points = tokenizer.getMeasurement(tokens);
  1256. if (points.used === 0)
  1257. return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
  1258. var spaceBelow = points.value;
  1259. points = tokenizer.getMeasurement(tokens);
  1260. if (points.used === 0 || tokens.length !== 0)
  1261. return "Directive \"" + cmd + "\" requires 3 numbers: space above, space below, length of line";
  1262. var lenLine = points.value;
  1263. tune.addSeparator(spaceAbove, spaceBelow, lenLine);
  1264. }
  1265. break;
  1266. case "measurenb":
  1267. if (tokens.length !== 1 || tokens[0].type !== 'number')
  1268. return "Directive \"" + cmd + "\" requires a number as a parameter.";
  1269. multilineVars.barNumbers = parseInt(tokens[0].token);
  1270. break;
  1271. case "barnumbers":
  1272. if (tokens.length !== 1 || tokens[0].type !== 'number')
  1273. return "Directive \"" + cmd + "\" requires a number as a parameter.";
  1274. multilineVars.barNumbers = parseInt(tokens[0].token);
  1275. break;
  1276. case "begintext":
  1277. multilineVars.inTextBlock = true;
  1278. break;
  1279. case "beginps":
  1280. multilineVars.inPsBlock = true;
  1281. warn("Postscript ignored", str, 0);
  1282. break;
  1283. case "deco":
  1284. if (restOfString.length > 0)
  1285. multilineVars.ignoredDecorations.push(restOfString.substring(0, restOfString.indexOf(' ')));
  1286. warn("Decoration redefinition ignored", str, 0);
  1287. break;
  1288. case "text":
  1289. var textstr = tokenizer.translateString(restOfString);
  1290. tune.addText(this.parseFontChangeLine(textstr));
  1291. break;
  1292. case "center":
  1293. var centerstr = tokenizer.translateString(restOfString);
  1294. tune.addCentered(this.parseFontChangeLine(centerstr));
  1295. break;
  1296. case "font":
  1297. // don't need to do anything for this; it is a useless directive
  1298. break;
  1299. case "setfont":
  1300. var sfTokens = tokenizer.tokenize(restOfString, 0, restOfString.length);
  1301. var sfDone = false;
  1302. if (sfTokens.length >= 4) {
  1303. if (sfTokens[0].token === '-' && sfTokens[1].type === 'number') {
  1304. var sfNum = parseInt(sfTokens[1].token);
  1305. if (sfNum >= 1 && sfNum <= 4) {
  1306. if (!multilineVars.setfont)
  1307. multilineVars.setfont = [];
  1308. var sfSize = sfTokens.pop();
  1309. if (sfSize.type === 'number') {
  1310. sfSize = parseInt(sfSize.token);
  1311. var sfFontName = '';
  1312. for (var sfi = 2; sfi < sfTokens.length; sfi++)
  1313. sfFontName += sfTokens[sfi].token;
  1314. multilineVars.setfont[sfNum] = { font: sfFontName, size: sfSize };
  1315. sfDone = true;
  1316. }
  1317. }
  1318. }
  1319. }
  1320. if (!sfDone)
  1321. return "Bad parameters: " + cmd;
  1322. break;
  1323. case "gchordfont":
  1324. case "partsfont":
  1325. case "vocalfont":
  1326. case "textfont":
  1327. return getChangingFont(cmd, tokens);
  1328. case "barlabelfont":
  1329. case "barnumberfont":
  1330. case "composerfont":
  1331. case "subtitlefont":
  1332. case "tempofont":
  1333. case "titlefont":
  1334. case "voicefont":
  1335. return getGlobalFont(cmd, tokens);
  1336. case "barnumfont":
  1337. return getGlobalFont("barnumberfont", tokens);
  1338. case "staves":
  1339. case "score":
  1340. multilineVars.score_is_present = true;
  1341. var addVoice = function(id, newStaff, bracket, brace, continueBar) {
  1342. if (newStaff || multilineVars.staves.length === 0) {
  1343. multilineVars.staves.push({index: multilineVars.staves.length, numVoices: 0});
  1344. }
  1345. var staff = multilineVars.staves.last();
  1346. if (bracket !== undefined) staff.bracket = bracket;
  1347. if (brace !== undefined) staff.brace = brace;
  1348. if (continueBar) staff.connectBarLines = 'end';
  1349. if (multilineVars.voices[id] === undefined) {
  1350. multilineVars.voices[id] = {staffNum: staff.index, index: staff.numVoices};
  1351. staff.numVoices++;
  1352. }
  1353. };
  1354. var openParen = false;
  1355. var openBracket = false;
  1356. var openBrace = false;
  1357. var justOpenParen = false;
  1358. var justOpenBracket = false;
  1359. var justOpenBrace = false;
  1360. var continueBar = false;
  1361. var lastVoice = undefined;
  1362. var addContinueBar = function() {
  1363. continueBar = true;
  1364. if (lastVoice) {
  1365. var ty = 'start';
  1366. if (lastVoice.staffNum > 0) {
  1367. if (multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'start' ||
  1368. multilineVars.staves[lastVoice.staffNum-1].connectBarLines === 'continue')
  1369. ty = 'continue';
  1370. }
  1371. multilineVars.staves[lastVoice.staffNum].connectBarLines = ty;
  1372. }
  1373. };
  1374. while (tokens.length) {
  1375. var t = tokens.shift();
  1376. switch (t.token) {
  1377. case '(':
  1378. if (openParen) warn("Can't nest parenthesis in %%score", str, t.start);
  1379. else {openParen = true;justOpenParen = true;}
  1380. break;
  1381. case ')':
  1382. if (!openParen || justOpenParen) warn("Unexpected close parenthesis in %%score", str, t.start);
  1383. else openParen = false;
  1384. break;
  1385. case '[':
  1386. if (openBracket) warn("Can't nest brackets in %%score", str, t.start);
  1387. else {openBracket = true;justOpenBracket = true;}
  1388. break;
  1389. case ']':
  1390. if (!openBracket || justOpenBracket) warn("Unexpected close bracket in %%score", str, t.start);
  1391. else {openBracket = false;multilineVars.staves[lastVoice.staffNum].bracket = 'end';}
  1392. break;
  1393. case '{':
  1394. if (openBrace ) warn("Can't nest braces in %%score", str, t.start);
  1395. else {openBrace = true;justOpenBrace = true;}
  1396. break;
  1397. case '}':
  1398. if (!openBrace || justOpenBrace) warn("Unexpected close brace in %%score", str, t.start);
  1399. else {openBrace = false;multilineVars.staves[lastVoice.staffNum].brace = 'end';}
  1400. break;
  1401. case '|':
  1402. addContinueBar();
  1403. break;
  1404. default:
  1405. var vc = "";
  1406. while (t.type === 'alpha' || t.type === 'number') {
  1407. vc += t.token;
  1408. if (t.continueId)
  1409. t = tokens.shift();
  1410. else
  1411. break;
  1412. }
  1413. var newStaff = !openParen || justOpenParen;
  1414. var bracket = justOpenBracket ? 'start' : openBracket ? 'continue' : undefined;
  1415. var brace = justOpenBrace ? 'start' : openBrace ? 'continue' : undefined;
  1416. addVoice(vc, newStaff, bracket, brace, continueBar);
  1417. justOpenParen = false;
  1418. justOpenBracket = false;
  1419. justOpenBrace = false;
  1420. continueBar = false;
  1421. lastVoice = multilineVars.voices[vc];
  1422. if (cmd === 'staves')
  1423. addContinueBar();
  1424. break;
  1425. }
  1426. }
  1427. break;
  1428. case "newpage":
  1429. var pgNum = tokenizer.getInt(restOfString);
  1430. tune.addNewPage(pgNum.digits === 0 ? -1 : pgNum.value);
  1431. break;
  1432. case "abc-copyright":
  1433. case "abc-creator":
  1434. case "abc-version":
  1435. case "abc-charset":
  1436. case "abc-edited-by":
  1437. tune.addMetaText(cmd, restOfString);
  1438. break;
  1439. case "header":
  1440. case "footer":
  1441. var footerStr = tokenizer.getMeat(restOfString, 0, restOfString.length);
  1442. footerStr = restOfString.substring(footerStr.start, footerStr.end);
  1443. if (footerStr.charAt(0) === '"' && footerStr.charAt(footerStr.length-1) === '"' )
  1444. footerStr = footerStr.substring(1, footerStr.length-2);
  1445. var footerArr = footerStr.split('\t');
  1446. var footer = {};
  1447. if (footerArr.length === 1)
  1448. footer = { left: "", center: footerArr[0], right: "" };
  1449. else if (footerArr.length === 2)
  1450. footer = { left: footerArr[0], center: footerArr[1], right: "" };
  1451. else
  1452. footer = { left: footerArr[0], center: footerArr[1], right: footerArr[2] };
  1453. if (footerArr.length > 3)
  1454. warn("Too many tabs in "+cmd+": "+footerArr.length+" found.", restOfString, 0);
  1455. tune.addMetaTextObj(cmd, footer);
  1456. break;
  1457. case "midi":
  1458. var midi = tokenizer.tokenize(restOfString, 0, restOfString.length);
  1459. if (midi.length > 0 && midi[0].token === '=')
  1460. midi.shift();
  1461. if (midi.length === 0)
  1462. warn("Expected midi command", restOfString, 0);
  1463. else {
  1464. // var midiCmd = restOfString.split(' ')[0];
  1465. // var midiParam = restOfString.substring(midiCmd.length+1);
  1466. // TODO-PER: make sure the command is legal
  1467. tune.formatting[cmd] = { cmd: midi.shift().token };
  1468. if (midi.length > 0)
  1469. tune.formatting[cmd].param = midi.shift().token;
  1470. // TODO-PER: save all the parameters, not just the first.
  1471. }
  1472. //%%MIDI barlines: deactivates %%nobarlines.
  1473. //%%MIDI bassprog n
  1474. //%%MIDI bassvol n
  1475. //%%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’.
  1476. //%%MIDI beataccents: reverts to normally emphasised notes. See also %%MIDI nobeat-
  1477. //%%MIDI beatmod ?int?: increments the velocities as defined by %%MIDI beat
  1478. //%%MIDI beatstring ?string?: similar to %%MIDI beat, but indicated with an fmp string.
  1479. //%%MIDI c ?int?: specifies the MIDI pitch which corresponds to . The default is 60.
  1480. //%%MIDI channel ?int?: selects the melody channel ?int? (1–16).
  1481. //%%MIDI chordattack ?int?: delays the start of chord notes by ?int? MIDI units.
  1482. //%%MIDI chordname ?string int1 int2 int3 int4 int5 int6?: defines new chords or re-defines existing ones as was seen in Section 12.8.
  1483. //%%MIDI chordprog 20 % Church organ
  1484. //%%MIDI chordvol ?int?: sets the volume (velocity) of the chord notes to ?int? (0–127).
  1485. //%%MIDI control ?bass/chord? ?int1 int2?: generates a MIDI control event. If %%control is followed by ?bass? or ?chord?, the event apply to the bass or chord channel, otherwise it will be applied to the melody channel. ?int1? is the MIDI control number (0–127) and ?int2? the value (0–127).
  1486. //%%MIDI deltaloudness?int?: bydefault,!crescendo!and!dimuendo!modifythebe- at variables ?vol1? ?vol2? ?vol3? 15 volume units. This command allows the user to change this default.
  1487. //%%MIDI drone ?int1 int2 int3 int4 int5?: specifies a two-note drone accompaniment. ?int1? is the drone MIDI instrument, ?int2? the MIDI pitch 1, ?int3? the MIDI pitch 2, ?int4? the MIDI volume 1, ?int5? the MIDI volume 2. Default values are 70 45 33 80 80.
  1488. //%%MIDI droneoff: turns the drone accompaniment off.
  1489. //%%MIDI droneon: turns the drone accompaniment on.
  1490. //%%MIDI drum string [drum programs] [drum velocities]
  1491. //%%MIDI drumbars ?int?: specifies the number of bars over which a drum pattern string is spread. Default is 1.
  1492. //%%MIDI drummap ?str? ?int?: associates the note ?str? (in ABC notation) to the a percussion instrument, as listed in Section H.2.
  1493. //%%MIDI drumoff turns drum accompaniment off.
  1494. //%%MIDI drumon turns drum accompaniment on.
  1495. //%%MIDI fermatafixed: expands a !fermata! by one unit length; that is, GC3 becomes
  1496. //%%MIDI fermataproportional: doubles the length of a note preceded by !fermata!;
  1497. //%%MIDI gchord string
  1498. //%%MIDI gchord str
  1499. //%%MIDI gchordon
  1500. //%%MIDI gchordoff
  1501. //%%MIDI grace ?float?: sets the fraction of the next note that grace notes will take up. ?float? must be a fraction such as 1/6.
  1502. //%%MIDI gracedivider ?int?: sets the grace note length as 1/?int?th of the following note.
  1503. //%%MIDI makechordchannels?int?: thisisaverycomplexcommandusedinchordscon-
  1504. //%%MIDI nobarlines
  1505. //%%MIDI nobeataccents: forces the ?int2? volume (see %%MIDI beat) for each note in a bar, regardless of their position.
  1506. //%%MIDI noportamento: turns off the portamento controller on the current channel.
  1507. //%%MIDI pitchbend [bass/chord] <high byte> <low byte>
  1508. //%%MIDI program 2 75
  1509. //%%MIDI portamento ?int?: turns on the portamento controller on the current channel and set it to ?int?. Experts only.
  1510. //%%MIDI randomchordattack: delays the start of chord notes by a random number of MIDI units.
  1511. //%%MIDI ratio n m
  1512. //%%MIDI rtranspose ?int1?: transposes relatively to a prior %%transpose command by ?int1? semitones; the total transposition will be ?int1 + int2? semitones.
  1513. //%%MIDI temperament ?int1? ?int2?: TO BE WRITTEN
  1514. //%%MIDI temperamentlinear ?float1 float2?: changes the temperament of the scale. ?fl- oat1? specifies the size of an octave in cents of a semitone, or 1/1200 of an octave. ?float2? specifies in the size of a fifth (normally 700 cents).
  1515. //%%MIDI temperamentnormal: restores normal temperament.
  1516. //%%MIDI transpose n
  1517. //%%MIDI voice [<ID>] [instrument=<integer> [bank=<integer>]] [mute]
  1518. break;
  1519. case "indent":
  1520. case "playtempo":
  1521. case "auquality":
  1522. case "continuous":
  1523. case "nobarcheck":
  1524. // TODO-PER: Actually handle the parameters of these
  1525. tune.formatting[cmd] = restOfString;
  1526. break;
  1527. default:
  1528. return "Unknown directive: " + cmd;
  1529. }
  1530. return null;
  1531. };
  1532. this.setCurrentVoice = function(id) {
  1533. multilineVars.currentVoice = multilineVars.voices[id];
  1534. tune.setCurrentVoice(multilineVars.currentVoice.staffNum, multilineVars.currentVoice.index);
  1535. };
  1536. this.parseVoice = function(line, i, e) {
  1537. //First truncate the string to the first non-space character after V: through either the
  1538. //end of the line or a % character. Then remove trailing spaces, too.
  1539. var ret = tokenizer.getMeat(line, i, e);
  1540. var start = ret.start;
  1541. var end = ret.end;
  1542. //The first thing on the line is the ID. It can be any non-space string and terminates at the
  1543. //first space.
  1544. var id = tokenizer.getToken(line, start, end);
  1545. if (id.length === 0) {
  1546. warn("Expected a voice id", line, start);
  1547. return;
  1548. }
  1549. var isNew = false;
  1550. if (multilineVars.voices[id] === undefined) {
  1551. multilineVars.voices[id] = {};
  1552. isNew = true;
  1553. if (multilineVars.score_is_present)
  1554. warn("Can't have an unknown V: id when the %score directive is present", line, start);
  1555. }
  1556. start += id.length;
  1557. start += tokenizer.eatWhiteSpace(line, start);
  1558. var staffInfo = {startStaff: isNew};
  1559. var addNextTokenToStaffInfo = function(name) {
  1560. var attr = tokenizer.getVoiceToken(line, start, end);
  1561. if (attr.warn !== undefined)
  1562. warn("Expected value for " + name + " in voice: " + attr.warn, line, start);
  1563. else if (attr.token.length === 0 && line.charAt(start) !== '"')
  1564. warn("Expected value for " + name + " in voice", line, start);
  1565. else
  1566. staffInfo[name] = attr.token;
  1567. start += attr.len;
  1568. };
  1569. var addNextTokenToVoiceInfo = function(id, name, type) {
  1570. var attr = tokenizer.getVoiceToken(line, start, end);
  1571. if (attr.warn !== undefined)
  1572. warn("Expected value for " + name + " in voice: " + attr.warn, line, start);
  1573. else if (attr.token.length === 0 && line.charAt(start) !== '"')
  1574. warn("Expected value for " + name + " in voice", line, start);
  1575. else {
  1576. if (type === 'number')
  1577. attr.token = parseFloat(attr.token);
  1578. multilineVars.voices[id][name] = attr.token;
  1579. }
  1580. start += attr.len;
  1581. };
  1582. //Then the following items can occur in any order:
  1583. while (start < end) {
  1584. var token = tokenizer.getVoiceToken(line, start, end);
  1585. start += token.len;
  1586. if (token.warn) {
  1587. warn("Error parsing voice: " + token.warn, line, start);
  1588. } else {
  1589. var attr = null;
  1590. switch (token.token) {
  1591. case 'clef':
  1592. case 'cl':
  1593. addNextTokenToStaffInfo('clef');
  1594. // TODO-PER: check for a legal clef; do octavizing
  1595. var oct = 0;
  1596. // for (var ii = 0; ii < staffInfo.clef.length; ii++) {
  1597. // if (staffInfo.clef[ii] === ',') oct -= 7;
  1598. // else if (staffInfo.clef[ii] === "'") oct += 7;
  1599. // }
  1600. if (staffInfo.clef !== undefined) {
  1601. staffInfo.clef = staffInfo.clef.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
  1602. if (staffInfo.clef.indexOf('+16') !== -1) {
  1603. oct += 14;
  1604. staffInfo.clef = staffInfo.clef.replace('+16', '');
  1605. }
  1606. staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct);
  1607. }
  1608. break;
  1609. case 'treble':
  1610. case 'bass':
  1611. case 'tenor':
  1612. case 'alto':
  1613. case 'none':
  1614. case 'treble\'':
  1615. case 'bass\'':
  1616. case 'tenor\'':
  1617. case 'alto\'':
  1618. case 'none\'':
  1619. case 'treble\'\'':
  1620. case 'bass\'\'':
  1621. case 'tenor\'\'':
  1622. case 'alto\'\'':
  1623. case 'none\'\'':
  1624. case 'treble,':
  1625. case 'bass,':
  1626. case 'tenor,':
  1627. case 'alto,':
  1628. case 'none,':
  1629. case 'treble,,':
  1630. case 'bass,,':
  1631. case 'tenor,,':
  1632. case 'alto,,':
  1633. case 'none,,':
  1634. // TODO-PER: handle the octave indicators on the clef by changing the middle property
  1635. var oct2 = 0;
  1636. // for (var iii = 0; iii < token.token.length; iii++) {
  1637. // if (token.token[iii] === ',') oct2 -= 7;
  1638. // else if (token.token[iii] === "'") oct2 += 7;
  1639. // }
  1640. staffInfo.clef = token.token.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
  1641. staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct2);
  1642. break;
  1643. case 'staves':
  1644. case 'stave':
  1645. case 'stv':
  1646. addNextTokenToStaffInfo('staves');
  1647. break;
  1648. case 'brace':
  1649. case 'brc':
  1650. addNextTokenToStaffInfo('brace');
  1651. break;
  1652. case 'bracket':
  1653. case 'brk':
  1654. addNextTokenToStaffInfo('bracket');
  1655. break;
  1656. case 'name':
  1657. case 'nm':
  1658. addNextTokenToStaffInfo('name');
  1659. break;
  1660. case 'subname':
  1661. case 'sname':
  1662. case 'snm':
  1663. addNextTokenToStaffInfo('subname');
  1664. break;
  1665. case 'merge':
  1666. staffInfo.startStaff = false;
  1667. break;
  1668. case 'stems':
  1669. attr = tokenizer.getVoiceToken(line, start, end);
  1670. if (attr.warn !== undefined)
  1671. warn("Expected value for stems in voice: " + attr.warn, line, start);
  1672. else if (attr.token === 'up' || attr.token === 'down')
  1673. multilineVars.voices[id].stem = attr.token;
  1674. else
  1675. warn("Expected up or down for voice stem", line, start);
  1676. start += attr.len;
  1677. break;
  1678. case 'up':
  1679. case 'down':
  1680. multilineVars.voices[id].stem = token.token;
  1681. break;
  1682. case 'middle':
  1683. case 'm':
  1684. addNextTokenToStaffInfo('verticalPos');
  1685. staffInfo.verticalPos = parseMiddle(staffInfo.verticalPos).mid;
  1686. break;
  1687. case 'gchords':
  1688. case 'gch':
  1689. multilineVars.voices[id].suppressChords = true;
  1690. break;
  1691. case 'space':
  1692. case 'spc':
  1693. addNextTokenToStaffInfo('spacing');
  1694. break;
  1695. case 'scale':
  1696. addNextTokenToVoiceInfo(id, 'scale', 'number');
  1697. break;
  1698. }
  1699. }
  1700. start += tokenizer.eatWhiteSpace(line, start);
  1701. }
  1702. // now we've filled up staffInfo, figure out what to do with this voice
  1703. // 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.
  1704. if (staffInfo.startStaff || multilineVars.staves.length === 0) {
  1705. multilineVars.staves.push({index: multilineVars.staves.length, meter: multilineVars.origMeter});
  1706. if (!multilineVars.score_is_present)
  1707. multilineVars.staves[multilineVars.staves.length-1].numVoices = 0;
  1708. }
  1709. if (multilineVars.voices[id].staffNum === undefined) {
  1710. // store where to write this for quick access later.
  1711. multilineVars.voices[id].staffNum = multilineVars.staves.length-1;
  1712. var vi = 0;
  1713. for(var v in multilineVars.voices) {
  1714. if(multilineVars.voices.hasOwnProperty(v)) {
  1715. if (multilineVars.voices[v].staffNum === multilineVars.voices[id].staffNum)
  1716. vi++;
  1717. }
  1718. }
  1719. multilineVars.voices[id].index = vi-1;
  1720. }
  1721. var s = multilineVars.staves[multilineVars.voices[id].staffNum];
  1722. if (!multilineVars.score_is_present)
  1723. s.numVoices++;
  1724. if (staffInfo.clef) s.clef = {type: staffInfo.clef, verticalPos: staffInfo.verticalPos};
  1725. if (staffInfo.spacing) s.spacing_below_offset = staffInfo.spacing;
  1726. if (staffInfo.verticalPos) s.verticalPos = staffInfo.verticalPos;
  1727. if (staffInfo.name) {if (s.name) s.name.push(staffInfo.name); else s.name = [ staffInfo.name ];}
  1728. if (staffInfo.subname) {if (s.subname) s.subname.push(staffInfo.subname); else s.subname = [ staffInfo.subname ];}
  1729. this.setCurrentVoice(id);
  1730. };
  1731. this.setTitle = function(title) {
  1732. if (multilineVars.hasMainTitle)
  1733. tune.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title))); // display secondary title
  1734. else
  1735. {
  1736. tune.addMetaText("title", tokenizer.translateString(tokenizer.theReverser(tokenizer.stripComment(title))));
  1737. multilineVars.hasMainTitle = true;
  1738. }
  1739. };
  1740. this.setMeter = function(line) {
  1741. line = tokenizer.stripComment(line);
  1742. if (line === 'C') {
  1743. if (multilineVars.havent_set_length === true)
  1744. multilineVars.default_length = 0.125;
  1745. return {type: 'common_time'};
  1746. } else if (line === 'C|') {
  1747. if (multilineVars.havent_set_length === true)
  1748. multilineVars.default_length = 0.125;
  1749. return {type: 'cut_time'};
  1750. } else if (line.length === 0 || line.toLowerCase() === 'none') {
  1751. if (multilineVars.havent_set_length === true)
  1752. multilineVars.default_length = 0.125;
  1753. return null;
  1754. }
  1755. else
  1756. {
  1757. var tokens = tokenizer.tokenize(line, 0, line.length);
  1758. // the form is [open_paren] decimal [ plus|dot decimal ]... [close_paren] slash decimal [plus same_as_before]
  1759. try {
  1760. var parseNum = function() {
  1761. // handles this much: [open_paren] decimal [ plus|dot decimal ]... [close_paren]
  1762. var ret = {value: 0, num: ""};
  1763. var tok = tokens.shift();
  1764. if (tok.token === '(')
  1765. tok = tokens.shift();
  1766. while (1) {
  1767. if (tok.type !== 'number') throw "Expected top number of meter";
  1768. ret.value += parseInt(tok.token);
  1769. ret.num += tok.token;
  1770. if (tokens.length === 0 || tokens[0].token === '/') return ret;
  1771. tok = tokens.shift();
  1772. if (tok.token === ')') {
  1773. if (tokens.length === 0 || tokens[0].token === '/') return ret;
  1774. throw "Unexpected paren in meter";
  1775. }
  1776. if (tok.token !== '.' && tok.token !== '+') throw "Expected top number of meter";
  1777. ret.num += tok.token;
  1778. if (tokens.length === 0) throw "Expected top number of meter";
  1779. tok = tokens.shift();
  1780. }
  1781. return ret; // just to suppress warning
  1782. };
  1783. var parseFraction = function() {
  1784. // handles this much: parseNum slash decimal
  1785. var ret = parseNum();
  1786. if (tokens.length === 0) throw "Expected slash in meter";
  1787. var tok = tokens.shift();
  1788. if (tok.token !== '/') throw "Expected slash in meter";
  1789. tok = tokens.shift();
  1790. if (tok.type !== 'number') throw "Expected bottom number of meter";
  1791. ret.den = tok.token;
  1792. ret.value = ret.value / parseInt(ret.den);
  1793. return ret;
  1794. };
  1795. if (tokens.length === 0) throw "Expected meter definition in M: line";
  1796. var meter = {type: 'specified', value: [ ]};
  1797. var totalLength = 0;
  1798. while (1) {
  1799. var ret = parseFraction();
  1800. totalLength += ret.value;
  1801. meter.value.push({num: ret.num, den: ret.den});
  1802. if (tokens.length === 0) break;
  1803. var tok = tokens.shift();
  1804. if (tok.token !== '+') throw "Extra characters in M: line";
  1805. }
  1806. if (multilineVars.havent_set_length === true) {
  1807. multilineVars.default_length = totalLength < 0.75 ? 0.0625 : 0.125;
  1808. }
  1809. return meter;
  1810. } catch (e) {
  1811. warn(e, line, 0);
  1812. }
  1813. }
  1814. return null;
  1815. };
  1816. this.calcTempo = function(relTempo) {
  1817. var dur = 1/4;
  1818. if (multilineVars.meter && multilineVars.meter.type === 'specified') {
  1819. dur = 1 / parseInt(multilineVars.meter.value[0].den);
  1820. } else if (multilineVars.origMeter && multilineVars.origMeter.type === 'specified') {
  1821. dur = 1 / parseInt(multilineVars.origMeter.value[0].den);
  1822. }
  1823. //var dur = multilineVars.default_length ? multilineVars.default_length : 1;
  1824. for (var i = 0; i < relTempo.duration; i++)
  1825. relTempo.duration[i] = dur * relTempo.duration[i];
  1826. return relTempo;
  1827. };
  1828. this.resolveTempo = function() {
  1829. if (multilineVars.tempo) { // If there's a tempo waiting to be resolved
  1830. this.calcTempo(multilineVars.tempo);
  1831. tune.metaText.tempo = multilineVars.tempo;
  1832. delete multilineVars.tempo;
  1833. }
  1834. };
  1835. this.addUserDefinition = function(line, start, end) {
  1836. var equals = line.indexOf('=', start);
  1837. if (equals === -1) {
  1838. warn("Need an = in a macro definition", line, start);
  1839. return;
  1840. }
  1841. var before = line.substring(start, equals).strip();
  1842. var after = line.substring(equals+1).strip();
  1843. if (before.length !== 1) {
  1844. warn("Macro definitions can only be one character", line, start);
  1845. return;
  1846. }
  1847. var legalChars = "HIJKLMNOPQRSTUVWXYhijklmnopqrstuvw~";
  1848. if (legalChars.indexOf(before) === -1) {
  1849. warn("Macro definitions must be H-Y, h-w, or tilde", line, start);
  1850. return;
  1851. }
  1852. if (after.length === 0) {
  1853. warn("Missing macro definition", line, start);
  1854. return;
  1855. }
  1856. if (multilineVars.macros === undefined)
  1857. multilineVars.macros = {};
  1858. multilineVars.macros[before] = after;
  1859. };
  1860. this.setDefaultLength = function(line, start, end) {
  1861. var len = line.substring(start, end).gsub(" ", "");
  1862. var len_arr = len.split('/');
  1863. if (len_arr.length === 2) {
  1864. var n = parseInt(len_arr[0]);
  1865. var d = parseInt(len_arr[1]);
  1866. if (d > 0) {
  1867. var q = n / d;
  1868. multilineVars.default_length = q; // a whole note is 1
  1869. multilineVars.havent_set_length = false;
  1870. }
  1871. }
  1872. };
  1873. this.setTempo = function(line, start, end) {
  1874. //Q - tempo; can be used to specify the notes per minute, e.g. If
  1875. //the meter denominator is a 4 note then Q:120 or Q:C=120
  1876. //is 120 quarter notes per minute. Similarly Q:C3=40 would be 40
  1877. //dotted half notes per minute. An absolute tempo may also be
  1878. //set, e.g. Q:1/8=120 is 120 eighth notes per minute,
  1879. //irrespective of the meter's denominator.
  1880. //
  1881. // This is either a number, "C=number", "Cnumber=number", or fraction [fraction...]=number
  1882. // It depends on the M: field, which may either not be present, or may appear after this.
  1883. // If M: is not present, an eighth note is used.
  1884. // 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.
  1885. // So, if it is the fourth type, set it here, otherwise, save the info in the multilineVars.
  1886. // The temporary variables we keep are the duration and the bpm. In the first two forms, the duration is 1.
  1887. // In addition, a quoted string may both precede and follow. If a quoted string is present, then the duration part is optional.
  1888. try {
  1889. var tokens = tokenizer.tokenize(line, start, end);
  1890. if (tokens.length === 0) throw "Missing parameter in Q: field";
  1891. var tempo = {};
  1892. var delaySet = true;
  1893. var token = tokens.shift();
  1894. if (token.type === 'quote') {
  1895. tempo.preString = token.token;
  1896. token = tokens.shift();
  1897. if (tokens.length === 0) { // It's ok to just get a string for the tempo
  1898. return {type: 'immediate', tempo: tempo};
  1899. }
  1900. }
  1901. if (token.type === 'alpha' && token.token === 'C') { // either type 2 or type 3
  1902. if (tokens.length === 0) throw "Missing tempo after C in Q: field";
  1903. token = tokens.shift();
  1904. if (token.type === 'punct' && token.token === '=') {
  1905. // This is a type 2 format. The duration is an implied 1
  1906. if (tokens.length === 0) throw "Missing tempo after = in Q: field";
  1907. token = tokens.shift();
  1908. if (token.type !== 'number') throw "Expected number after = in Q: field";
  1909. tempo.duration = [1];
  1910. tempo.bpm = parseInt(token.token);
  1911. } else if (token.type === 'number') {
  1912. // This is a type 3 format.
  1913. tempo.duration = [parseInt(token.token)];
  1914. if (tokens.length === 0) throw "Missing = after duration in Q: field";
  1915. token = tokens.shift();
  1916. if (token.type !== 'punct' || token.token !== '=') throw "Expected = after duration in Q: field";
  1917. if (tokens.length === 0) throw "Missing tempo after = in Q: field";
  1918. token = tokens.shift();
  1919. if (token.type !== 'number') throw "Expected number after = in Q: field";
  1920. tempo.bpm = parseInt(token.token);
  1921. } else throw "Expected number or equal after C in Q: field";
  1922. } else if (token.type === 'number') { // either type 1 or type 4
  1923. var num = parseInt(token.token);
  1924. if (tokens.length === 0 || tokens[0].type === 'quote') {
  1925. // This is type 1
  1926. tempo.duration = [1];
  1927. tempo.bpm = num;
  1928. } else { // This is type 4
  1929. delaySet = false;
  1930. token = tokens.shift();
  1931. if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field";
  1932. token = tokens.shift();
  1933. if (token.type !== 'number') throw "Expected fraction in Q: field";
  1934. var den = parseInt(token.token);
  1935. tempo.duration = [num/den];
  1936. // We got the first fraction, keep getting more as long as we find them.
  1937. while (tokens.length > 0 && tokens[0].token !== '=' && tokens[0].type !== 'quote') {
  1938. token = tokens.shift();
  1939. if (token.type !== 'number') throw "Expected fraction in Q: field";
  1940. num = parseInt(token.token);
  1941. token = tokens.shift();
  1942. if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field";
  1943. token = tokens.shift();
  1944. if (token.type !== 'number') throw "Expected fraction in Q: field";
  1945. den = parseInt(token.token);
  1946. tempo.duration.push(num/den);
  1947. }
  1948. token = tokens.shift();
  1949. if (token.type !== 'punct' && token.token !== '=') throw "Expected = in Q: field";
  1950. token = tokens.shift();
  1951. if (token.type !== 'number') throw "Expected tempo in Q: field";
  1952. tempo.bpm = parseInt(token.token);
  1953. }
  1954. } else throw "Unknown value in Q: field";
  1955. if (tokens.length !== 0) {
  1956. token = tokens.shift();
  1957. if (token.type === 'quote') {
  1958. tempo.postString = token.token;
  1959. token = tokens.shift();
  1960. }
  1961. if (tokens.length !== 0) throw "Unexpected string at end of Q: field";
  1962. }
  1963. return {type: delaySet?'delaySet':'immediate', tempo: tempo};
  1964. } catch (msg) {
  1965. warn(msg, line, start);
  1966. return {type: 'none'};
  1967. }
  1968. };
  1969. this.letter_to_inline_header = function(line, i)
  1970. {
  1971. var ws = tokenizer.eatWhiteSpace(line, i);
  1972. i +=ws;
  1973. if (line.length >= i+5 && line.charAt(i) === '[' && line.charAt(i+2) === ':') {
  1974. var e = line.indexOf(']', i);
  1975. switch(line.substring(i, i+3))
  1976. {
  1977. case "[I:":
  1978. var err = this.addDirective(line.substring(i+3, e));
  1979. if (err) warn(err, line, i);
  1980. return [ e-i+1+ws ];
  1981. case "[M:":
  1982. var meter = this.setMeter(line.substring(i+3, e));
  1983. if (tune.hasBeginMusic() && meter)
  1984. tune.appendStartingElement('meter', -1, -1, meter);
  1985. return [ e-i+1+ws ];
  1986. case "[K:":
  1987. var result = this.parseKey(line.substring(i+3, e));
  1988. if (result.foundClef && tune.hasBeginMusic())
  1989. tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
  1990. if (result.foundKey && tune.hasBeginMusic())
  1991. tune.appendStartingElement('key', -1, -1, this.fixKey(multilineVars.clef, multilineVars.key));
  1992. return [ e-i+1+ws ];
  1993. case "[P:":
  1994. tune.appendElement('part', -1, -1, {title: line.substring(i+3, e)});
  1995. return [ e-i+1+ws ];
  1996. case "[L:":
  1997. this.setDefaultLength(line, i+3, e);
  1998. return [ e-i+1+ws ];
  1999. case "[Q:":
  2000. if (e > 0) {
  2001. var tempo = this.setTempo(line, i+3, e);
  2002. if (tempo.type === 'delaySet') tune.appendElement('tempo', -1, -1, this.calcTempo(tempo.tempo));
  2003. else if (tempo.type === 'immediate') tune.appendElement('tempo', -1, -1, tempo.tempo);
  2004. return [ e-i+1+ws, line.charAt(i+1), line.substring(i+3, e)];
  2005. }
  2006. break;
  2007. case "[V:":
  2008. if (e > 0) {
  2009. this.parseVoice(line, i+3, e);
  2010. //startNewLine();
  2011. return [ e-i+1+ws, line.charAt(i+1), line.substring(i+3, e)];
  2012. }
  2013. break;
  2014. default:
  2015. // TODO: complain about unhandled header
  2016. }
  2017. }
  2018. return [ 0 ];
  2019. };
  2020. this.letter_to_body_header = function(line, i)
  2021. {
  2022. if (line.length >= i+3) {
  2023. switch(line.substring(i, i+2))
  2024. {
  2025. case "I:":
  2026. var err = this.addDirective(line.substring(i+2));
  2027. if (err) warn(err, line, i);
  2028. return [ line.length ];
  2029. case "M:":
  2030. var meter = this.setMeter(line.substring(i+2));
  2031. if (tune.hasBeginMusic() && meter)
  2032. tune.appendStartingElement('meter', -1, -1, meter);
  2033. return [ line.length ];
  2034. case "K:":
  2035. var result = this.parseKey(line.substring(i+2));
  2036. if (result.foundClef && tune.hasBeginMusic())
  2037. tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
  2038. if (result.foundKey && tune.hasBeginMusic())
  2039. tune.appendStartingElement('key', -1, -1, this.fixKey(multilineVars.clef, multilineVars.key));
  2040. return [ line.length ];
  2041. case "P:":
  2042. if (tune.hasBeginMusic())
  2043. tune.appendElement('part', -1, -1, {title: line.substring(i+2)});
  2044. return [ line.length ];
  2045. case "L:":
  2046. this.setDefaultLength(line, i+2, line.length);
  2047. return [ line.length ];
  2048. case "Q:":
  2049. var e = line.indexOf('\x12', i+2);
  2050. if (e === -1) e = line.length;
  2051. var tempo = this.setTempo(line, i+2, e);
  2052. if (tempo.type === 'delaySet') tune.appendElement('tempo', -1, -1, this.calcTempo(tempo.tempo));
  2053. else if (tempo.type === 'immediate') tune.appendElement('tempo', -1, -1, tempo.tempo);
  2054. return [ e, line.charAt(i), line.substring(i+2).strip()];
  2055. case "V:":
  2056. this.parseVoice(line, 2, line.length);
  2057. // startNewLine();
  2058. return [ line.length, line.charAt(i), line.substring(i+2).strip()];
  2059. default:
  2060. // TODO: complain about unhandled header
  2061. }
  2062. }
  2063. return [ 0 ];
  2064. };
  2065. var metaTextHeaders = {
  2066. A: 'author',
  2067. B: 'book',
  2068. C: 'composer',
  2069. D: 'discography',
  2070. F: 'url',
  2071. G: 'group',
  2072. I: 'instruction',
  2073. N: 'notes',
  2074. O: 'origin',
  2075. R: 'rhythm',
  2076. S: 'source',
  2077. W: 'unalignedWords',
  2078. Z: 'transcription'
  2079. };
  2080. this.parseHeader = function(line) {
  2081. if (line.startsWith('%%')) {
  2082. var err = this.addDirective(line.substring(2));
  2083. if (err) warn(err, line, 2);
  2084. return {};
  2085. }
  2086. line = tokenizer.stripComment(line);
  2087. if (line.length === 0)
  2088. return {};
  2089. if (line.length >= 2) {
  2090. if (line.charAt(1) === ':') {
  2091. var nextLine = "";
  2092. if (line.indexOf('\x12') >= 0 && line.charAt(0) !== 'w') { // w: is the only header field that can have a continuation.
  2093. nextLine = line.substring(line.indexOf('\x12')+1);
  2094. line = line.substring(0, line.indexOf('\x12')); //This handles a continuation mark on a header field
  2095. }
  2096. var field = metaTextHeaders[line.charAt(0)];
  2097. if (field !== undefined) {
  2098. if (field === 'unalignedWords')
  2099. tune.addMetaTextArray(field, this.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))));
  2100. else
  2101. tune.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
  2102. return {};
  2103. } else {
  2104. switch(line.charAt(0))
  2105. {
  2106. case 'H':
  2107. tune.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
  2108. multilineVars.is_in_history = true;
  2109. break;
  2110. case 'K':
  2111. // since the key is the last thing that can happen in the header, we can resolve the tempo now
  2112. this.resolveTempo();
  2113. var result = this.parseKey(line.substring(2));
  2114. if (!multilineVars.is_in_header && tune.hasBeginMusic()) {
  2115. if (result.foundClef)
  2116. tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
  2117. if (result.foundKey)
  2118. tune.appendStartingElement('key', -1, -1, this.fixKey(multilineVars.clef, multilineVars.key));
  2119. }
  2120. multilineVars.is_in_header = false; // The first key signifies the end of the header.
  2121. break;
  2122. case 'L':
  2123. this.setDefaultLength(line, 2, line.length);
  2124. break;
  2125. case 'M':
  2126. multilineVars.origMeter = multilineVars.meter = this.setMeter(line.substring(2));
  2127. break;
  2128. case 'P':
  2129. // TODO-PER: There is more to do with parts, but the writer doesn't care.
  2130. if (multilineVars.is_in_header)
  2131. tune.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
  2132. else
  2133. multilineVars.partForNextLine = tokenizer.translateString(tokenizer.stripComment(line.substring(2)));
  2134. break;
  2135. case 'Q':
  2136. var tempo = this.setTempo(line, 2, line.length);
  2137. if (tempo.type === 'delaySet') multilineVars.tempo = tempo.tempo;
  2138. else if (tempo.type === 'immediate') tune.metaText.tempo = tempo.tempo;
  2139. break;
  2140. case 'T':
  2141. this.setTitle(line.substring(2));
  2142. break;
  2143. case 'U':
  2144. this.addUserDefinition(line, 2, line.length);
  2145. break;
  2146. case 'V':
  2147. this.parseVoice(line, 2, line.length);
  2148. if (!multilineVars.is_in_header)
  2149. return {newline: true};
  2150. break;
  2151. case 's':
  2152. return {symbols: true};
  2153. case 'w':
  2154. return {words: true};
  2155. case 'X':
  2156. break;
  2157. case 'E':
  2158. case 'm':
  2159. warn("Ignored header", line, 0);
  2160. break;
  2161. default:
  2162. // It wasn't a recognized header value, so parse it as music.
  2163. if (nextLine.length)
  2164. nextLine = "\x12" + nextLine;
  2165. //parseRegularMusicLine(line+nextLine);
  2166. //nextLine = "";
  2167. return {regular: true, str: line+nextLine};
  2168. }
  2169. }
  2170. if (nextLine.length > 0)
  2171. return {recurse: true, str: nextLine};
  2172. return {};
  2173. }
  2174. }
  2175. // If we got this far, we have a regular line of mulsic
  2176. return {regular: true, str: line};
  2177. };
  2178. }
  2179. // abc_tokenizer.js: tokenizes an ABC Music Notation string to support abc_parse.
  2180. // Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
  2181. //
  2182. // This program is free software: you can redistribute it and/or modify
  2183. // it under the terms of the GNU General Public License as published by
  2184. // the Free Software Foundation, either version 3 of the License, or
  2185. // (at your option) any later version.
  2186. //
  2187. // This program is distributed in the hope that it will be useful,
  2188. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  2189. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  2190. // GNU General Public License for more details.
  2191. //
  2192. // You should have received a copy of the GNU General Public License
  2193. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  2194. /*extern AbcTokenizer */
  2195. // this is a series of functions that get a particular element out of the passed stream.
  2196. // the return is the number of characters consumed, so 0 means that the element wasn't found.
  2197. // also returned is the element found. This may be a different length because spaces may be consumed that aren't part of the string.
  2198. // The return structure for most calls is { len: num_chars_consumed, token: str }
  2199. function AbcTokenizer() {
  2200. this.skipWhiteSpace = function(str) {
  2201. for (var i = 0; i < str.length; i++) {
  2202. if (!this.isWhiteSpace(str.charAt(i)))
  2203. return i;
  2204. }
  2205. return str.length; // It must have been all white space
  2206. };
  2207. var finished = function(str, i) {
  2208. return i >= str.length;
  2209. };
  2210. this.eatWhiteSpace = function(line, index) {
  2211. for (var i = index; i < line.length; i++) {
  2212. if (!this.isWhiteSpace(line.charAt(i)))
  2213. return i-index;
  2214. }
  2215. return i-index;
  2216. };
  2217. // This just gets the basic pitch letter, ignoring leading spaces, and normalizing it to a capital
  2218. this.getKeyPitch = function(str) {
  2219. var i = this.skipWhiteSpace(str);
  2220. if (finished(str, i))
  2221. return {len: 0};
  2222. switch (str.charAt(i)) {
  2223. case 'A':return {len: i+1, token: 'A'};
  2224. case 'B':return {len: i+1, token: 'B'};
  2225. case 'C':return {len: i+1, token: 'C'};
  2226. case 'D':return {len: i+1, token: 'D'};
  2227. case 'E':return {len: i+1, token: 'E'};
  2228. case 'F':return {len: i+1, token: 'F'};
  2229. case 'G':return {len: i+1, token: 'G'};
  2230. // case 'a':return {len: i+1, token: 'A'};
  2231. // case 'b':return {len: i+1, token: 'B'};
  2232. // case 'c':return {len: i+1, token: 'C'};
  2233. // case 'd':return {len: i+1, token: 'D'};
  2234. // case 'e':return {len: i+1, token: 'E'};
  2235. // case 'f':return {len: i+1, token: 'F'};
  2236. // case 'g':return {len: i+1, token: 'G'};
  2237. }
  2238. return {len: 0};
  2239. };
  2240. // This just gets the basic accidental, ignoring leading spaces, and only the ones that appear in a key
  2241. this.getSharpFlat = function(str) {
  2242. if (str === 'bass')
  2243. return {len: 0};
  2244. switch (str.charAt(0)) {
  2245. case '#':return {len: 1, token: '#'};
  2246. case 'b':return {len: 1, token: 'b'};
  2247. }
  2248. return {len: 0};
  2249. };
  2250. this.getMode = function(str) {
  2251. var skipAlpha = function(str, start) {
  2252. // This returns the index of the next non-alphabetic char, or the entire length of the string if not found.
  2253. while (start < str.length && ((str.charAt(start) >= 'a' && str.charAt(start) <= 'z') || (str.charAt(start) >= 'A' && str.charAt(start) <= 'Z')))
  2254. start++;
  2255. return start;
  2256. };
  2257. var i = this.skipWhiteSpace(str);
  2258. if (finished(str, i))
  2259. return {len: 0};
  2260. var firstThree = str.substring(i,i+3).toLowerCase();
  2261. if (firstThree.length > 1 && firstThree.charAt(1) === ' ' || firstThree.charAt(1) === '^' || firstThree.charAt(1) === '_' || firstThree.charAt(1) === '=') firstThree = firstThree.charAt(0); // This will handle the case of 'm'
  2262. switch (firstThree) {
  2263. case 'mix':return {len: skipAlpha(str, i), token: 'Mix'};
  2264. case 'dor':return {len: skipAlpha(str, i), token: 'Dor'};
  2265. case 'phr':return {len: skipAlpha(str, i), token: 'Phr'};
  2266. case 'lyd':return {len: skipAlpha(str, i), token: 'Lyd'};
  2267. case 'loc':return {len: skipAlpha(str, i), token: 'Loc'};
  2268. case 'aeo':return {len: skipAlpha(str, i), token: 'm'};
  2269. case 'maj':return {len: skipAlpha(str, i), token: ''};
  2270. case 'ion':return {len: skipAlpha(str, i), token: ''};
  2271. case 'min':return {len: skipAlpha(str, i), token: 'm'};
  2272. case 'm':return {len: skipAlpha(str, i), token: 'm'};
  2273. }
  2274. return {len: 0};
  2275. };
  2276. this.getClef = function(str, bExplicitOnly) {
  2277. var strOrig = str;
  2278. var i = this.skipWhiteSpace(str);
  2279. if (finished(str, i))
  2280. return {len: 0};
  2281. // The word 'clef' is optional, but if it appears, a clef MUST appear
  2282. var needsClef = false;
  2283. var strClef = str.substring(i);
  2284. if (strClef.startsWith('clef=')) {
  2285. needsClef = true;
  2286. strClef = strClef.substring(5);
  2287. i += 5;
  2288. }
  2289. if (strClef.length === 0 && needsClef)
  2290. return {len: i+5, warn: "No clef specified: " + strOrig};
  2291. var j = this.skipWhiteSpace(strClef);
  2292. if (finished(strClef, j))
  2293. return {len: 0};
  2294. if (j > 0) {
  2295. i += j;
  2296. strClef = strClef.substring(j);
  2297. }
  2298. var name = null;
  2299. if (strClef.startsWith('treble'))
  2300. name = 'treble';
  2301. else if (strClef.startsWith('bass3'))
  2302. name = 'bass3';
  2303. else if (strClef.startsWith('bass'))
  2304. name = 'bass';
  2305. else if (strClef.startsWith('tenor'))
  2306. name = 'tenor';
  2307. else if (strClef.startsWith('alto2'))
  2308. name = 'alto2';
  2309. else if (strClef.startsWith('alto1'))
  2310. name = 'alto1';
  2311. else if (strClef.startsWith('alto'))
  2312. name = 'alto';
  2313. else if (!bExplicitOnly && (needsClef && strClef.startsWith('none')))
  2314. name = 'none';
  2315. else if (strClef.startsWith('perc'))
  2316. name = 'perc';
  2317. else if (!bExplicitOnly && (needsClef && strClef.startsWith('C')))
  2318. name = 'tenor';
  2319. else if (!bExplicitOnly && (needsClef && strClef.startsWith('F')))
  2320. name = 'bass';
  2321. else if (!bExplicitOnly && (needsClef && strClef.startsWith('G')))
  2322. name = 'treble';
  2323. else
  2324. return {len: i+5, warn: "Unknown clef specified: " + strOrig};
  2325. strClef = strClef.substring(name.length);
  2326. j = this.isMatch(strClef, '+8');
  2327. if (j > 0)
  2328. name += "+8";
  2329. else {
  2330. j = this.isMatch(strClef, '-8');
  2331. if (j > 0)
  2332. name += "-8";
  2333. }
  2334. return {len: i+name.length, token: name, explicit: needsClef};
  2335. };
  2336. // This returns one of the legal bar lines
  2337. // This is called alot and there is no obvious tokenable items, so this is broken apart.
  2338. this.getBarLine = function(line, i) {
  2339. switch (line.charAt(i)) {
  2340. case ']':
  2341. ++i;
  2342. switch (line.charAt(i)) {
  2343. case '|': return {len: 2, token: "bar_thick_thin"};
  2344. case '[':
  2345. ++i;
  2346. if ((line.charAt(i) >= '1' && line.charAt(i) <= '9') || line.charAt(i) === '"')
  2347. return {len: 2, token: "bar_invisible"};
  2348. return {len: 1, warn: "Unknown bar symbol"};
  2349. default:
  2350. return {len: 1, token: "bar_invisible"};
  2351. }
  2352. break;
  2353. case ':':
  2354. ++i;
  2355. switch (line.charAt(i)) {
  2356. case ':': return {len: 2, token: "bar_dbl_repeat"};
  2357. case '|': // :|
  2358. ++i;
  2359. switch (line.charAt(i)) {
  2360. case ']': // :|]
  2361. ++i;
  2362. switch (line.charAt(i)) {
  2363. case '|': // :|]|
  2364. ++i;
  2365. if (line.charAt(i) === ':') return {len: 5, token: "bar_dbl_repeat"};
  2366. return {len: 3, token: "bar_right_repeat"};
  2367. default:
  2368. return {len: 3, token: "bar_right_repeat"};
  2369. }
  2370. break;
  2371. case '|': // :||
  2372. ++i;
  2373. if (line.charAt(i) === ':') return {len: 4, token: "bar_dbl_repeat"};
  2374. return {len: 3, token: "bar_right_repeat"};
  2375. default:
  2376. return {len: 2, token: "bar_right_repeat"};
  2377. }
  2378. break;
  2379. default:
  2380. return {len: 1, warn: "Unknown bar symbol"};
  2381. }
  2382. break;
  2383. case '[': // [
  2384. ++i;
  2385. if (line.charAt(i) === '|') { // [|
  2386. ++i;
  2387. switch (line.charAt(i)) {
  2388. case ':': return {len: 3, token: "bar_left_repeat"};
  2389. case ']': return {len: 3, token: "bar_invisible"};
  2390. default: return {len: 2, token: "bar_thick_thin"};
  2391. }
  2392. } else {
  2393. if ((line.charAt(i) >= '1' && line.charAt(i) <= '9') || line.charAt(i) === '"')
  2394. return {len: 1, token: "bar_invisible"};
  2395. return {len: 0};
  2396. }
  2397. break;
  2398. case '|': // |
  2399. ++i;
  2400. switch (line.charAt(i)) {
  2401. case ']': return {len: 2, token: "bar_thin_thick"};
  2402. case '|': // ||
  2403. ++i;
  2404. if (line.charAt(i) === ':') return {len: 3, token: "bar_left_repeat"};
  2405. return {len: 2, token: "bar_thin_thin"};
  2406. case ':': // |:
  2407. var colons = 0;
  2408. while (line.charAt(i+colons) === ':') colons++;
  2409. return { len: 1+colons, token: "bar_left_repeat"};
  2410. default: return {len: 1, token: "bar_thin"};
  2411. }
  2412. break;
  2413. }
  2414. return {len: 0};
  2415. };
  2416. // this returns all the characters in the string that match one of the characters in the legalChars string
  2417. this.getTokenOf = function(str, legalChars) {
  2418. for (var i = 0; i < str.length; i++) {
  2419. if (legalChars.indexOf(str.charAt(i)) < 0)
  2420. return {len: i, token: str.substring(0, i)};
  2421. }
  2422. return {len: i, token: str};
  2423. };
  2424. this.getToken = function(str, start, end) {
  2425. // This returns the next set of chars that doesn't contain spaces
  2426. var i = start;
  2427. while (i < end && !this.isWhiteSpace(str.charAt(i)))
  2428. i++;
  2429. return str.substring(start, i);
  2430. };
  2431. // This just sees if the next token is the word passed in, with possible leading spaces
  2432. this.isMatch = function(str, match) {
  2433. var i = this.skipWhiteSpace(str);
  2434. if (finished(str, i))
  2435. return 0;
  2436. if (str.substring(i).startsWith(match))
  2437. return i+match.length;
  2438. return 0;
  2439. };
  2440. this.getPitchFromTokens = function(tokens) {
  2441. var ret = { };
  2442. 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};
  2443. ret.position = pitches[tokens[0].token];
  2444. if (ret.position === undefined)
  2445. return { warn: "Pitch expected. Found: " + tokens[0].token };
  2446. tokens.shift();
  2447. while (tokens.length) {
  2448. switch (tokens[0].token) {
  2449. case ',': ret.position -= 7; tokens.shift(); break;
  2450. case '\'': ret.position += 7; tokens.shift(); break;
  2451. default: return ret;
  2452. }
  2453. }
  2454. return ret;
  2455. }
  2456. this.getKeyAccidentals2 = function(tokens) {
  2457. var accs;
  2458. // find and strip off all accidentals in the token list
  2459. while (tokens.length > 0) {
  2460. var acc;
  2461. if (tokens[0].token === '^') {
  2462. acc = 'sharp';
  2463. tokens.shift();
  2464. if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
  2465. switch (tokens[0].token) {
  2466. case '^': acc = 'dblsharp'; tokens.shift(); break;
  2467. case '/': acc = 'quartersharp'; tokens.shift(); break;
  2468. }
  2469. } else if (tokens[0].token === '=') {
  2470. acc = 'natural';
  2471. tokens.shift();
  2472. } else if (tokens[0].token === '_') {
  2473. acc = 'flat';
  2474. tokens.shift();
  2475. if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
  2476. switch (tokens[0].token) {
  2477. case '_': acc = 'dblflat'; tokens.shift(); break;
  2478. case '/': acc = 'quarterflat'; tokens.shift(); break;
  2479. }
  2480. } else {
  2481. // Not an accidental, we'll assume that a later parse will recognize it.
  2482. return { accs: accs };
  2483. }
  2484. if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
  2485. switch (tokens[0].token.charAt(0))
  2486. {
  2487. case 'a':
  2488. case 'b':
  2489. case 'c':
  2490. case 'd':
  2491. case 'e':
  2492. case 'f':
  2493. case 'g':
  2494. case 'A':
  2495. case 'B':
  2496. case 'C':
  2497. case 'D':
  2498. case 'E':
  2499. case 'F':
  2500. case 'G':
  2501. if (accs === undefined)
  2502. accs = [];
  2503. accs.push({ acc: acc, note: tokens[0].token.charAt(0) });
  2504. if (tokens[0].token.length === 1)
  2505. tokens.shift();
  2506. else
  2507. tokens[0].token = tokens[0].token.substring(1);
  2508. break;
  2509. default:
  2510. return {accs: accs, warn: 'Expected note name after ' + acc + ' Found: ' + tokens[0].token };
  2511. break;
  2512. }
  2513. }
  2514. return { accs: accs };
  2515. };
  2516. // This gets an accidental marking for the key signature. It has the accidental then the pitch letter.
  2517. this.getKeyAccidental = function(str) {
  2518. var accTranslation = {
  2519. '^': 'sharp',
  2520. '^^': 'dblsharp',
  2521. '=': 'natural',
  2522. '_': 'flat',
  2523. '__': 'dblflat',
  2524. '_/': 'quarterflat',
  2525. '^/': 'quartersharp'
  2526. };
  2527. var i = this.skipWhiteSpace(str);
  2528. if (finished(str, i))
  2529. return {len: 0};
  2530. var acc = null;
  2531. switch (str.charAt(i))
  2532. {
  2533. case '^':
  2534. case '_':
  2535. case '=':
  2536. acc = str.charAt(i);
  2537. break;
  2538. default:return {len: 0};
  2539. }
  2540. i++;
  2541. if (finished(str, i))
  2542. return {len: 1, warn: 'Expected note name after accidental'};
  2543. switch (str.charAt(i))
  2544. {
  2545. case 'a':
  2546. case 'b':
  2547. case 'c':
  2548. case 'd':
  2549. case 'e':
  2550. case 'f':
  2551. case 'g':
  2552. case 'A':
  2553. case 'B':
  2554. case 'C':
  2555. case 'D':
  2556. case 'E':
  2557. case 'F':
  2558. case 'G':
  2559. return {len: i+1, token: {acc: accTranslation[acc], note: str.charAt(i)}};
  2560. case '^':
  2561. case '_':
  2562. case '/':
  2563. acc += str.charAt(i);
  2564. i++;
  2565. if (finished(str, i))
  2566. return {len: 2, warn: 'Expected note name after accidental'};
  2567. switch (str.charAt(i))
  2568. {
  2569. case 'a':
  2570. case 'b':
  2571. case 'c':
  2572. case 'd':
  2573. case 'e':
  2574. case 'f':
  2575. case 'g':
  2576. case 'A':
  2577. case 'B':
  2578. case 'C':
  2579. case 'D':
  2580. case 'E':
  2581. case 'F':
  2582. case 'G':
  2583. return {len: i+1, token: {acc: accTranslation[acc], note: str.charAt(i)}};
  2584. default:
  2585. return {len: 2, warn: 'Expected note name after accidental'};
  2586. }
  2587. break;
  2588. default:
  2589. return {len: 1, warn: 'Expected note name after accidental'};
  2590. }
  2591. };
  2592. this.isWhiteSpace = function(ch) {
  2593. return ch === ' ' || ch === '\t' || ch === '\x12';
  2594. };
  2595. this.getMeat = function(line, start, end) {
  2596. // This removes any comments starting with '%' and trims the ends of the string so that there are no leading or trailing spaces.
  2597. // it returns just the start and end characters that contain the meat.
  2598. var comment = line.indexOf('%', start);
  2599. if (comment >= 0 && comment < end)
  2600. end = comment;
  2601. while (start < end && (line.charAt(start) === ' ' || line.charAt(start) === '\t' || line.charAt(start) === '\x12'))
  2602. start++;
  2603. while (start < end && (line.charAt(end-1) === ' ' || line.charAt(end-1) === '\t' || line.charAt(end-1) === '\x12'))
  2604. end--;
  2605. return {start: start, end: end};
  2606. };
  2607. var isLetter = function(ch) {
  2608. return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
  2609. };
  2610. var isNumber = function(ch) {
  2611. return (ch >= '0' && ch <= '9');
  2612. };
  2613. this.tokenize = function(line, start, end) {
  2614. // this returns all the tokens inside the passed string. A token is a punctuation mark, a string of digits, a string of letters.
  2615. // Quoted strings are one token.
  2616. // The type of token is returned: quote, alpha, number, punct
  2617. var ret = this.getMeat(line, start, end);
  2618. start = ret.start;
  2619. end = ret.end;
  2620. var tokens = [];
  2621. var i;
  2622. while (start < end) {
  2623. if (line.charAt(start) === '"') {
  2624. i = start+1;
  2625. while (i < end && line.charAt(i) !== '"') i++;
  2626. tokens.push({ type: 'quote', token: line.substring(start+1, i), start: start+1, end: i});
  2627. i++;
  2628. } else if (isLetter(line.charAt(start))) {
  2629. i = start+1;
  2630. while (i < end && isLetter(line.charAt(i))) i++;
  2631. tokens.push({ type: 'alpha', token: line.substring(start, i), continueId: isNumber(line.charAt(i)), start: start, end: i});
  2632. start = i + 1;
  2633. } else if (isNumber(line.charAt(start))) {
  2634. i = start+1;
  2635. while (i < end && isNumber(line.charAt(i))) i++;
  2636. tokens.push({ type: 'number', token: line.substring(start, i), continueId: isLetter(line.charAt(i)), start: start, end: i});
  2637. start = i + 1;
  2638. } else if (line.charAt(start) === ' ' || line.charAt(start) === '\t') {
  2639. i = start+1;
  2640. } else {
  2641. tokens.push({ type: 'punct', token: line.charAt(start), start: start, end: start+1});
  2642. i = start+1;
  2643. }
  2644. start = i;
  2645. }
  2646. return tokens;
  2647. };
  2648. this.getVoiceToken = function(line, start, end) {
  2649. // 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.
  2650. var i = start;
  2651. while (i < end && this.isWhiteSpace(line.charAt(i)) || line.charAt(i) === '=')
  2652. i++;
  2653. if (line.charAt(i) === '"') {
  2654. var close = line.indexOf('"', i+1);
  2655. if (close === -1 || close >= end)
  2656. return {len: 1, err: "Missing close quote"};
  2657. return {len: close-start+1, token: this.translateString(line.substring(i+1, close))};
  2658. } else {
  2659. var ii = i;
  2660. while (ii < end && !this.isWhiteSpace(line.charAt(ii)) && line.charAt(ii) !== '=')
  2661. ii++;
  2662. return {len: ii-start+1, token: line.substring(i, ii)};
  2663. }
  2664. };
  2665. var charMap = {
  2666. "`a": 'ŕ', "'a": "á", "^a": "â", "~a": "ă", "\"a": "ä", "oa": "ĺ", "=a": "?", "ua": "?", ";a": "?",
  2667. "`e": 'č', "'e": "é", "^e": "ę", "\"e": "ë", "=e": "?", "ue": "?", ";e": "?", ".e": "?",
  2668. "`i": 'ě', "'i": "í", "^i": "î", "\"i": "ď", "=i": "?", "ui": "?", ";i": "?",
  2669. "`o": 'ň', "'o": "ó", "^o": "ô", "~o": "ő", "\"o": "ö", "=o": "?", "uo": "?", "/o": "ř",
  2670. "`u": 'ů', "'u": "ú", "^u": "ű", "~u": "?", "\"u": "ü", "ou": "?", "=u": "?", "uu": "?", ";u": "?",
  2671. "`A": 'Ŕ', "'A": "Á", "^A": "Â", "~A": "Ă", "\"A": "Ä", "oA": "Ĺ", "=A": "?", "uA": "?", ";A": "?",
  2672. "`E": 'Č', "'E": "É", "^E": "Ę", "\"E": "Ë", "=E": "?", "uE": "?", ";E": "?", ".E": "?",
  2673. "`I": 'Ě', "'I": "Í", "^I": "Î", "~I": "?", "\"I": "Ď", "=I": "?", "uI": "?", ";I": "?", ".I": "?",
  2674. "`O": 'Ň', "'O": "Ó", "^O": "Ô", "~O": "Ő", "\"O": "Ö", "=O": "?", "uO": "?", "/O": "Ř",
  2675. "`U": 'Ů', "'U": "Ú", "^U": "Ű", "~U": "?", "\"U": "Ü", "oU": "?", "=U": "?", "uU": "?", ";U": "?",
  2676. "ae": "ć", "AE": "Ć", "oe": "œ", "OE": "Œ", "ss": "ß",
  2677. "'c": "?", "^c": "?", "uc": "?", "cc": "ç", ".c": "?", "cC": "Ç", "'C": "?", "^C": "?", "uC": "?", ".C": "?",
  2678. "~n": "ń",
  2679. "=s": "š", "vs": "š",
  2680. "vz": 'ž'
  2681. // More chars: Ń ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Š ? ? ? ? ? ? ? ? ? ? Ÿ ˙ Ÿ ? ? ? ? Ž
  2682. };
  2683. var charMap1 = {
  2684. "#": "?",
  2685. "b": "?",
  2686. "=": "?"
  2687. };
  2688. var charMap2 = {
  2689. "201": "?",
  2690. "202": "?",
  2691. "203": "?",
  2692. "241": "Ą",
  2693. "242": "˘", "252": "a", "262": "2", "272": "o", "302": "Â", "312": "Ę", "322": "Ň", "332": "Ú", "342": "â", "352": "ę", "362": "ň", "372": "ú",
  2694. "243": "Ł", "253": "Ť", "263": "3", "273": "ť", "303": "Ă", "313": "Ë", "323": "Ó", "333": "Ű", "343": "ă", "353": "ë", "363": "ó", "373": "ű",
  2695. "244": "¤", "254": "Ź", "264": " ?", "274": "1?4", "304": "Ä", "314": "Ě", "324": "Ô", "334": "Ü", "344": "ä", "354": "ě", "364": "ô", "374": "ü",
  2696. "245": "Ľ", "255": "-", "265": "?", "275": "1?2", "305": "Ĺ", "315": "Í", "325": "Ő", "335": "Ý", "345": "ĺ", "355": "í", "365": "ő", "375": "ý",
  2697. "246": "Ś", "256": "Ž", "266": "ś", "276": "3?4", "306": "Ć", "316": "Î", "326": "Ö", "336": "Ţ", "346": "ć", "356": "î", "366": "ö", "376": "ţ",
  2698. "247": "§", "257": " ?", "267": "ˇ", "277": "ż", "307": "Ç", "317": "Ď", "327": "×", "337": "ß", "347": "ç", "357": "ď", "367": "÷", "377": "˙",
  2699. "250": " ?", "260": "°", "270": " ?", "300": "Ŕ", "310": "Č", "320": "Đ", "330": "Ř", "340": "ŕ", "350": "č", "360": "đ", "370": "ř",
  2700. "251": "Š", "261": "ą", "271": "1", "301": "Á", "311": "É", "321": "Ń", "331": "Ů", "341": "á", "351": "é", "361": "ń", "371": "ů" };
  2701. this.translateString = function(str) {
  2702. var arr = str.split('\\');
  2703. if (arr.length === 1) return str;
  2704. var out = null;
  2705. arr.each(function(s) {
  2706. if (out === null)
  2707. out = s;
  2708. else {
  2709. var c = charMap[s.substring(0, 2)];
  2710. if (c !== undefined)
  2711. out += c + s.substring(2);
  2712. else {
  2713. c = charMap2[s.substring(0, 3)];
  2714. if (c !== undefined)
  2715. out += c + s.substring(3);
  2716. else {
  2717. c = charMap1[s.substring(0, 1)];
  2718. if (c !== undefined)
  2719. out += c + s.substring(1);
  2720. else
  2721. out += "\\" + s;
  2722. }
  2723. }
  2724. }
  2725. });
  2726. return out;
  2727. };
  2728. this.getNumber = function(line, index) {
  2729. var num = 0;
  2730. while (index < line.length) {
  2731. switch (line.charAt(index)) {
  2732. case '0':num = num*10;index++;break;
  2733. case '1':num = num*10+1;index++;break;
  2734. case '2':num = num*10+2;index++;break;
  2735. case '3':num = num*10+3;index++;break;
  2736. case '4':num = num*10+4;index++;break;
  2737. case '5':num = num*10+5;index++;break;
  2738. case '6':num = num*10+6;index++;break;
  2739. case '7':num = num*10+7;index++;break;
  2740. case '8':num = num*10+8;index++;break;
  2741. case '9':num = num*10+9;index++;break;
  2742. default:
  2743. return {num: num, index: index};
  2744. }
  2745. }
  2746. return {num: num, index: index};
  2747. };
  2748. this.getFraction = function(line, index) {
  2749. var num = 1;
  2750. var den = 1;
  2751. if (line.charAt(index) !== '/') {
  2752. var ret = this.getNumber(line, index);
  2753. num = ret.num;
  2754. index = ret.index;
  2755. }
  2756. if (line.charAt(index) === '/') {
  2757. index++;
  2758. if (line.charAt(index) === '/') {
  2759. var div = 0.5;
  2760. while (line.charAt(index++) === '/')
  2761. div = div /2;
  2762. return {value: num * div, index: index-1};
  2763. } else {
  2764. var iSave = index;
  2765. var ret2 = this.getNumber(line, index);
  2766. if (ret2.num === 0 && iSave === index) // If we didn't use any characters, it is an implied 2
  2767. ret2.num = 2;
  2768. if (ret2.num !== 0)
  2769. den = ret2.num;
  2770. index = ret2.index;
  2771. }
  2772. }
  2773. return {value: num/den, index: index};
  2774. };
  2775. this.theReverser = function(str) {
  2776. if (str.endsWith(", The"))
  2777. return "The " + str.substring(0, str.length-5);
  2778. if (str.endsWith(", A"))
  2779. return "A " + str.substring(0, str.length-3);
  2780. return str;
  2781. };
  2782. this.stripComment = function(str) {
  2783. var i = str.indexOf('%');
  2784. if (i >= 0)
  2785. return str.substring(0, i).strip();
  2786. return str.strip();
  2787. };
  2788. this.getInt = function(str) {
  2789. // This parses the beginning of the string for a number and returns { value: num, digits: num }
  2790. // If digits is 0, then the string didn't point to a number.
  2791. var x = parseInt(str);
  2792. if (isNaN(x))
  2793. return {digits: 0};
  2794. var s = "" + x;
  2795. var i = str.indexOf(s); // This is to account for leading spaces
  2796. return {value: x, digits: i+s.length};
  2797. };
  2798. this.getFloat = function(str) {
  2799. // This parses the beginning of the string for a number and returns { value: num, digits: num }
  2800. // If digits is 0, then the string didn't point to a number.
  2801. var x = parseFloat(str);
  2802. if (isNaN(x))
  2803. return {digits: 0};
  2804. var s = "" + x;
  2805. var i = str.indexOf(s); // This is to account for leading spaces
  2806. return {value: x, digits: i+s.length};
  2807. };
  2808. this.getMeasurement = function(tokens) {
  2809. if (tokens.length === 0) return { used: 0 };
  2810. var used = 1;
  2811. var num = '';
  2812. if (tokens[0].token === '-') {
  2813. tokens.shift();
  2814. num = '-';
  2815. used++;
  2816. }
  2817. else if (tokens[0].type !== 'number') return { used: 0 };
  2818. num += tokens.shift().token;
  2819. if (tokens.length === 0) return { used: 1, value: parseInt(num) };
  2820. var x = tokens.shift();
  2821. if (x.token === '.') {
  2822. used++;
  2823. if (tokens.length === 0) return { used: used, value: parseInt(num) };
  2824. if (tokens[0].type === 'number') {
  2825. x = tokens.shift();
  2826. num = num + '.' + x.token;
  2827. used++;
  2828. if (tokens.length === 0) return { used: used, value: parseFloat(num) };
  2829. }
  2830. x = tokens.shift();
  2831. }
  2832. switch (x.token) {
  2833. case 'pt': return { used: used+1, value: parseFloat(num) };
  2834. case 'cm': return { used: used+1, value: parseFloat(num)/2.54*72 };
  2835. case 'in': return { used: used+1, value: parseFloat(num)*72 };
  2836. default: tokens.unshift(x); return { used: used, value: parseFloat(num) };
  2837. }
  2838. return { used: 0 };
  2839. };
  2840. var substInChord = function(str)
  2841. {
  2842. while ( str.indexOf("\\n") !== -1)
  2843. {
  2844. str = str.replace("\\n", "\n");
  2845. }
  2846. return str;
  2847. };
  2848. this.getBrackettedSubstring = function(line, i, maxErrorChars, _matchChar)
  2849. {
  2850. // This extracts the sub string by looking at the first character and searching for that
  2851. // character later in the line (or search for the optional _matchChar).
  2852. // For instance, if the first character is a quote it will look for
  2853. // the end quote. If the end of the line is reached, then only up to the default number
  2854. // of characters are returned, so that a missing end quote won't eat up the entire line.
  2855. // It returns the substring and the number of characters consumed.
  2856. // The number of characters consumed is normally two more than the size of the substring,
  2857. // but in the error case it might not be.
  2858. var matchChar = _matchChar || line.charAt(i);
  2859. var pos = i+1;
  2860. while ((pos < line.length) && (line.charAt(pos) !== matchChar))
  2861. ++pos;
  2862. if (line.charAt(pos) === matchChar)
  2863. return [pos-i+1,substInChord(line.substring(i+1, pos)), true];
  2864. else // we hit the end of line, so we'll just pick an arbitrary num of chars so the line doesn't disappear.
  2865. {
  2866. pos = i+maxErrorChars;
  2867. if (pos > line.length-1)
  2868. pos = line.length-1;
  2869. return [pos-i+1, substInChord(line.substring(i+1, pos)), false];
  2870. }
  2871. };
  2872. }
  2873. // abc_tune.js: a computer usable internal structure representing one tune.
  2874. // Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
  2875. //
  2876. // This program is free software: you can redistribute it and/or modify
  2877. // it under the terms of the GNU General Public License as published by
  2878. // the Free Software Foundation, either version 3 of the License, or
  2879. // (at your option) any later version.
  2880. //
  2881. // This program is distributed in the hope that it will be useful,
  2882. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  2883. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  2884. // GNU General Public License for more details.
  2885. //
  2886. // You should have received a copy of the GNU General Public License
  2887. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  2888. /*extern AbcTune */
  2889. // This is the data for a single ABC tune. It is created and populated by the AbcParse class.
  2890. function AbcTune() {
  2891. // The structure consists of a hash with the following two items:
  2892. // metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc...
  2893. // tempo: { noteLength: number (e.g. .125), bpm: number }
  2894. // lines: an array of elements, or one of the following:
  2895. //
  2896. // STAFF: array of elements
  2897. // SUBTITLE: string
  2898. //
  2899. // TODO: actually, the start and end char should modify each part of the note type
  2900. // The elements all have a type field and a start and end char
  2901. // field. The rest of the fields depend on the type and are listed below:
  2902. // REST: duration=1,2,4,8; chord: string
  2903. // NOTE: accidental=none,dbl_flat,flat,natural,sharp,dbl_sharp
  2904. // pitch: "C" is 0. The numbers refer to the pitch letter.
  2905. // duration: .5 (sixteenth), .75 (dotted sixteenth), 1 (eighth), 1.5 (dotted eighth)
  2906. // 2 (quarter), 3 (dotted quarter), 4 (half), 6 (dotted half) 8 (whole)
  2907. // chord: { name:chord, position: one of 'default', 'above', 'below' }
  2908. // end_beam = true or undefined if this is the last note in a beam.
  2909. // lyric: array of { syllable: xxx, divider: one of " -_" }
  2910. // startTie = true|undefined
  2911. // endTie = true|undefined
  2912. // startTriplet = num <- that is the number to print
  2913. // endTriplet = true|undefined (the last note of the triplet)
  2914. // TODO: actually, decoration should be an array.
  2915. // decoration: upbow, downbow, accent
  2916. // BAR: type=bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat
  2917. // number: 1 or 2: if it is the start of a first or second ending
  2918. // CLEF: type=treble,bass
  2919. // KEY-SIG:
  2920. // accidentals[]: { acc:sharp|dblsharp|natural|flat|dblflat, note:a|b|c|d|e|f|g }
  2921. // METER: type: common_time,cut_time,specified
  2922. // if specified, { num: 99, den: 99 }
  2923. this.reset = function () {
  2924. this.version = "1.0.1";
  2925. this.media = "screen";
  2926. this.metaText = {};
  2927. this.formatting = {};
  2928. this.lines = [];
  2929. this.staffNum = 0;
  2930. this.voiceNum = 0;
  2931. this.lineNum = 0;
  2932. };
  2933. this.cleanUp = function(defWidth, defLength) {
  2934. this.closeLine(); // Close the last line.
  2935. // Remove any blank lines
  2936. var anyDeleted = false;
  2937. for (var i = 0; i < this.lines.length; i++) {
  2938. if (this.lines[i].staff !== undefined) {
  2939. var hasAny = false;
  2940. for (var s = 0; s < this.lines[i].staff.length; s++) {
  2941. if (this.lines[i].staff[s] === undefined) {
  2942. anyDeleted = true;
  2943. this.lines[i].staff[s] = null;
  2944. //this.lines[i].staff[s] = { voices: []}; // TODO-PER: There was a part missing in the abc music. How should we recover?
  2945. } else {
  2946. for (var v = 0; v < this.lines[i].staff[s].voices.length; v++) {
  2947. if (this.lines[i].staff[s].voices[v] === undefined)
  2948. this.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover?
  2949. else
  2950. if (this.containsNotes(this.lines[i].staff[s].voices[v])) hasAny = true;
  2951. }
  2952. }
  2953. }
  2954. if (!hasAny) {
  2955. this.lines[i] = null;
  2956. anyDeleted = true;
  2957. }
  2958. }
  2959. }
  2960. if (anyDeleted) {
  2961. this.lines = this.lines.compact();
  2962. this.lines.each(function(line) {
  2963. if (line.staff)
  2964. line.staff = line.staff.compact();
  2965. });
  2966. }
  2967. function cleanUpSlursInLine(line) {
  2968. var currSlur = [];
  2969. var x;
  2970. // var lyr = null; // TODO-PER: debugging.
  2971. var addEndSlur = function(obj, num, chordPos) {
  2972. if (currSlur[chordPos] === undefined) {
  2973. // There isn't an exact match for note position, but we'll take any other open slur.
  2974. for (x = 0; x < currSlur.length; x++) {
  2975. if (currSlur[x] !== undefined) {
  2976. chordPos = x;
  2977. break;
  2978. }
  2979. }
  2980. if (currSlur[chordPos] === undefined) {
  2981. var offNum = chordPos*100;
  2982. obj.endSlur.each(function(x) { if (offNum === x) --offNum; })
  2983. currSlur[chordPos] = [offNum];
  2984. }
  2985. }
  2986. for (var i = 0; i < num; i++) {
  2987. var slurNum = currSlur[chordPos].pop();
  2988. obj.endSlur.push(slurNum);
  2989. // lyr.syllable += '<' + slurNum; // TODO-PER: debugging
  2990. }
  2991. if (currSlur[chordPos].length === 0)
  2992. delete currSlur[chordPos];
  2993. return slurNum;
  2994. };
  2995. var addStartSlur = function(obj, num, chordPos, usedNums) {
  2996. obj.startSlur = [];
  2997. if (currSlur[chordPos] === undefined) {
  2998. currSlur[chordPos] = [];
  2999. }
  3000. var nextNum = chordPos*100+1;
  3001. for (var i = 0; i < num; i++) {
  3002. if (usedNums) {
  3003. usedNums.each(function(x) { if (nextNum === x) ++nextNum; })
  3004. usedNums.each(function(x) { if (nextNum === x) ++nextNum; })
  3005. usedNums.each(function(x) { if (nextNum === x) ++nextNum; })
  3006. }
  3007. currSlur[chordPos].each(function(x) { if (nextNum === x) ++nextNum; })
  3008. currSlur[chordPos].each(function(x) { if (nextNum === x) ++nextNum; })
  3009. currSlur[chordPos].push(nextNum);
  3010. obj.startSlur.push({ label: nextNum });
  3011. // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
  3012. nextNum++;
  3013. }
  3014. };
  3015. for (var i = 0; i < line.length; i++) {
  3016. var el = line[i];
  3017. // if (el.lyric === undefined) // TODO-PER: debugging
  3018. // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
  3019. // lyr = el.lyric[0]; // TODO-PER: debugging
  3020. // lyr.syllable = ''; // TODO-PER: debugging
  3021. if (el.el_type === 'note') {
  3022. if (el.gracenotes) {
  3023. for (var g = 0; g < el.gracenotes.length; g++) {
  3024. if (el.gracenotes[g].endSlur) {
  3025. var gg = el.gracenotes[g].endSlur;
  3026. el.gracenotes[g].endSlur = [];
  3027. for (var ggg = 0; ggg < gg; ggg++)
  3028. addEndSlur(el.gracenotes[g], 1, 20);
  3029. }
  3030. if (el.gracenotes[g].startSlur) {
  3031. x = el.gracenotes[g].startSlur;
  3032. addStartSlur(el.gracenotes[g], x, 20);
  3033. }
  3034. }
  3035. }
  3036. if (el.endSlur) {
  3037. x = el.endSlur;
  3038. el.endSlur = [];
  3039. addEndSlur(el, x, 0);
  3040. }
  3041. if (el.startSlur) {
  3042. x = el.startSlur;
  3043. addStartSlur(el, x, 0);
  3044. }
  3045. if (el.pitches) {
  3046. var usedNums = [];
  3047. for (var p = 0; p < el.pitches.length; p++) {
  3048. if (el.pitches[p].endSlur) {
  3049. var k = el.pitches[p].endSlur;
  3050. el.pitches[p].endSlur = [];
  3051. for (var j = 0; j < k; j++) {
  3052. var slurNum = addEndSlur(el.pitches[p], 1, p+1);
  3053. usedNums.push(slurNum);
  3054. }
  3055. }
  3056. }
  3057. for (p = 0; p < el.pitches.length; p++) {
  3058. if (el.pitches[p].startSlur) {
  3059. x = el.pitches[p].startSlur;
  3060. addStartSlur(el.pitches[p], x, p+1, usedNums);
  3061. }
  3062. }
  3063. // Correct for the weird gracenote case where ({g}a) should match.
  3064. // The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
  3065. if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
  3066. if (el.gracenotes[0].endSlur)
  3067. el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);
  3068. else
  3069. el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
  3070. if (el.pitches[0].endSlur.length === 1)
  3071. delete el.pitches[0].endSlur;
  3072. else if (el.pitches[0].endSlur[0] === 100)
  3073. el.pitches[0].endSlur.shift();
  3074. else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length-1] === 100)
  3075. el.pitches[0].endSlur.pop();
  3076. if (currSlur[1].length === 1)
  3077. delete currSlur[1];
  3078. else
  3079. currSlur[1].pop();
  3080. }
  3081. }
  3082. }
  3083. }
  3084. }
  3085. // TODO-PER: This could be done faster as we go instead of as the last step.
  3086. function fixClefPlacement(el) {
  3087. //if (el.el_type === 'clef') {
  3088. var min = -2;
  3089. var max = 5;
  3090. switch(el.type) {
  3091. case 'treble+8':
  3092. case 'treble-8':
  3093. break;
  3094. case 'bass':
  3095. case 'bass+8':
  3096. case 'bass-8':
  3097. el.verticalPos = 20 + el.verticalPos; min += 6; max += 6; break;
  3098. break;
  3099. case 'tenor':
  3100. case 'tenor+8':
  3101. case 'tenor-8':
  3102. el.verticalPos = - el.verticalPos; min = -40; max = 40;
  3103. // el.verticalPos+=2; min += 6; max += 6;
  3104. break;
  3105. case 'alto':
  3106. case 'alto+8':
  3107. case 'alto-8':
  3108. el.verticalPos = - el.verticalPos; min = -40; max = 40;
  3109. // el.verticalPos-=2; min += 4; max += 4;
  3110. break;
  3111. }
  3112. if (el.verticalPos < min) {
  3113. while (el.verticalPos < min)
  3114. el.verticalPos += 7;
  3115. } else if (el.verticalPos > max) {
  3116. while (el.verticalPos > max)
  3117. el.verticalPos -= 7;
  3118. }
  3119. //}
  3120. }
  3121. for (this.lineNum = 0; this.lineNum < this.lines.length; this.lineNum++) {
  3122. if (this.lines[this.lineNum].staff) for (this.staffNum = 0; this.staffNum < this.lines[this.lineNum].staff.length; this.staffNum++) {
  3123. if (this.lines[this.lineNum].staff[this.staffNum].clef)
  3124. fixClefPlacement(this.lines[this.lineNum].staff[this.staffNum].clef);
  3125. for (this.voiceNum = 0; this.voiceNum < this.lines[this.lineNum].staff[this.staffNum].voices.length; this.voiceNum++) {
  3126. // var el = this.getLastNote();
  3127. // if (el) el.end_beam = true;
  3128. cleanUpSlursInLine(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]);
  3129. for (var j = 0; j < this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length; j++)
  3130. if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][j].el_type === 'clef')
  3131. fixClefPlacement(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][j]);
  3132. }
  3133. }
  3134. }
  3135. if (!this.formatting.pagewidth)
  3136. this.formatting.pagewidth = defWidth;
  3137. if (!this.formatting.pageheight)
  3138. this.formatting.pageheight = defLength;
  3139. // Remove temporary variables that the outside doesn't need to know about
  3140. delete this.staffNum;
  3141. delete this.voiceNum;
  3142. delete this.lineNum;
  3143. delete this.potentialStartBeam;
  3144. delete this.potentialEndBeam;
  3145. delete this.vskipPending;
  3146. };
  3147. this.reset();
  3148. this.getLastNote = function() {
  3149. if (this.lines[this.lineNum] && this.lines[this.lineNum].staff && this.lines[this.lineNum].staff[this.staffNum] &&
  3150. this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]) {
  3151. for (var i = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length-1; i >= 0; i--) {
  3152. var el = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][i];
  3153. if (el.el_type === 'note') {
  3154. return el;
  3155. }
  3156. }
  3157. }
  3158. return null;
  3159. };
  3160. this.addTieToLastNote = function() {
  3161. // TODO-PER: if this is a chord, which note?
  3162. var el = this.getLastNote();
  3163. if (el && el.pitches && el.pitches.length > 0) {
  3164. el.pitches[0].startTie = {};
  3165. return true;
  3166. }
  3167. return false;
  3168. };
  3169. this.getDuration = function(el) {
  3170. if (el.duration) return el.duration;
  3171. //if (el.pitches && el.pitches.length > 0) return el.pitches[0].duration;
  3172. return 0;
  3173. };
  3174. this.closeLine = function() {
  3175. if (this.potentialStartBeam && this.potentialEndBeam) {
  3176. this.potentialStartBeam.startBeam = true;
  3177. this.potentialEndBeam.endBeam = true;
  3178. }
  3179. delete this.potentialStartBeam;
  3180. delete this.potentialEndBeam;
  3181. };
  3182. this.appendElement = function(type, startChar, endChar, hashParams)
  3183. {
  3184. var This = this;
  3185. var pushNote = function(hp) {
  3186. if (hp.pitches !== undefined) {
  3187. var mid = This.lines[This.lineNum].staff[This.staffNum].clef.verticalPos;
  3188. hp.pitches.each(function(p) { p.verticalPos = p.pitch - mid; });
  3189. }
  3190. if (hp.gracenotes !== undefined) {
  3191. var mid2 = This.lines[This.lineNum].staff[This.staffNum].clef.verticalPos;
  3192. hp.gracenotes.each(function(p) { p.verticalPos = p.pitch - mid2; });
  3193. }
  3194. This.lines[This.lineNum].staff[This.staffNum].voices[This.voiceNum].push(hp);
  3195. };
  3196. hashParams.el_type = type;
  3197. if (startChar !== null)
  3198. hashParams.startChar = startChar;
  3199. if (endChar !== null)
  3200. hashParams.endChar = endChar;
  3201. var endBeamHere = function() {
  3202. This.potentialStartBeam.startBeam = true;
  3203. hashParams.endBeam = true;
  3204. delete This.potentialStartBeam;
  3205. delete This.potentialEndBeam;
  3206. };
  3207. var endBeamLast = function() {
  3208. if (This.potentialStartBeam !== undefined && This.potentialEndBeam !== undefined) { // Do we have a set of notes to beam?
  3209. This.potentialStartBeam.startBeam = true;
  3210. This.potentialEndBeam.endBeam = true;
  3211. }
  3212. delete This.potentialStartBeam;
  3213. delete This.potentialEndBeam;
  3214. };
  3215. if (type === 'note') { // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
  3216. // Now, add the startBeam and endBeam where it is needed.
  3217. // end_beam is already set on the places where there is a forced end_beam. We'll remove that here after using that info.
  3218. // this.potentialStartBeam either points to null or the start beam.
  3219. // this.potentialEndBeam either points to null or the start beam.
  3220. // 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.
  3221. // reset the variables for the next notes.
  3222. var dur = This.getDuration(hashParams);
  3223. if (dur >= 0.25) { // The beam ends on the note before this.
  3224. endBeamLast();
  3225. } else if (hashParams.force_end_beam_last && This.potentialStartBeam !== undefined) {
  3226. endBeamLast();
  3227. } else if (hashParams.end_beam && This.potentialStartBeam !== undefined) { // the beam is forced to end on this note, probably because of a space in the ABC
  3228. if (hashParams.rest === undefined)
  3229. endBeamHere();
  3230. else
  3231. endBeamLast();
  3232. } else if (hashParams.rest === undefined) { // this a short note and we aren't about to end the beam
  3233. if (This.potentialStartBeam === undefined) { // We aren't collecting notes for a beam, so start here.
  3234. if (!hashParams.end_beam) {
  3235. This.potentialStartBeam = hashParams;
  3236. delete This.potentialEndBeam;
  3237. }
  3238. } else {
  3239. This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
  3240. }
  3241. }
  3242. // 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)
  3243. // if (hashParams.rest !== undefined)
  3244. // {
  3245. // hashParams.end_beam = true;
  3246. // var el2 = this.getLastNote();
  3247. // if (el2) el2.end_beam = true;
  3248. // // TODO-PER: implement exception mentioned in the comment.
  3249. // }
  3250. } else { // It's not a note, so there definitely isn't beaming after it.
  3251. endBeamLast();
  3252. }
  3253. delete hashParams.end_beam; // We don't want this temporary variable hanging around.
  3254. delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
  3255. pushNote(hashParams);
  3256. };
  3257. this.appendStartingElement = function(type, startChar, endChar, hashParams2)
  3258. {
  3259. // Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
  3260. var hashParams = Object.clone(hashParams2);
  3261. // These elements should not be added twice, so if the element exists on this line without a note or bar before it, just replace the staff version.
  3262. var voice = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
  3263. for (var i = 0; i < voice.length; i++) {
  3264. if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
  3265. hashParams.el_type = type;
  3266. hashParams.startChar = startChar;
  3267. hashParams.endChar = endChar;
  3268. voice.push(hashParams);
  3269. return;
  3270. }
  3271. if (voice[i].el_type === type) {
  3272. hashParams.el_type = type;
  3273. hashParams.startChar = startChar;
  3274. hashParams.endChar = endChar;
  3275. voice[i] = hashParams;
  3276. return;
  3277. }
  3278. }
  3279. // We didn't see either that type or a note, so replace the element to the staff.
  3280. this.lines[this.lineNum].staff[this.staffNum][type] = hashParams2;
  3281. };
  3282. this.getNumLines = function() {
  3283. return this.lines.length;
  3284. };
  3285. this.pushLine = function(hash) {
  3286. if (this.vskipPending) {
  3287. hash.vskip = this.vskipPending;
  3288. delete this.vskipPending;
  3289. }
  3290. this.lines.push(hash);
  3291. }
  3292. this.addSubtitle = function(str) {
  3293. this.pushLine({subtitle: str});
  3294. };
  3295. this.addSpacing = function(num) {
  3296. this.vskipPending = num;
  3297. };
  3298. this.addNewPage = function(num) {
  3299. this.pushLine({newpage: num});
  3300. };
  3301. this.addSeparator = function(spaceAbove, spaceBelow, lineLength) {
  3302. this.pushLine({separator: {spaceAbove: spaceAbove, spaceBelow: spaceBelow, lineLength: lineLength}});
  3303. };
  3304. this.addText = function(str) {
  3305. this.pushLine({text: str});
  3306. };
  3307. this.addCentered = function(str) {
  3308. this.pushLine({text: [{text: str, center: true }]});
  3309. };
  3310. this.containsNotes = function(voice) {
  3311. for (var i = 0; i < voice.length; i++) {
  3312. if (voice[i].el_type === 'note' || voice[i].el_type === 'bar')
  3313. return true;
  3314. }
  3315. return false;
  3316. };
  3317. // anyVoiceContainsNotes: function(line) {
  3318. // for (var i = 0; i < line.staff.voices.length; i++) {
  3319. // if (this.containsNotes(line.staff.voices[i]))
  3320. // return true;
  3321. // }
  3322. // return false;
  3323. // },
  3324. this.startNewLine = function(params) {
  3325. // 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.
  3326. // If it does exist and has music, then increment the line number. If the new element doesn't exist, create it.
  3327. var This = this;
  3328. this.closeLine(); // Close the previous line.
  3329. var createVoice = function(params) {
  3330. This.lines[This.lineNum].staff[This.staffNum].voices[This.voiceNum] = [];
  3331. if (This.isFirstLine(This.lineNum)) {
  3332. if (params.name) {if (!This.lines[This.lineNum].staff[This.staffNum].title) This.lines[This.lineNum].staff[This.staffNum].title = [];This.lines[This.lineNum].staff[This.staffNum].title[This.voiceNum] = params.name;}
  3333. } else {
  3334. if (params.subname) {if (!This.lines[This.lineNum].staff[This.staffNum].title) This.lines[This.lineNum].staff[This.staffNum].title = [];This.lines[This.lineNum].staff[This.staffNum].title[This.voiceNum] = params.subname;}
  3335. }
  3336. if (params.style)
  3337. This.appendElement('style', null, null, {head: params.style});
  3338. if (params.stem)
  3339. This.appendElement('stem', null, null, {direction: params.stem});
  3340. else if (This.voiceNum > 0) {
  3341. if (This.lines[This.lineNum].staff[This.staffNum].voices[0]!== undefined) {
  3342. var found = false;
  3343. for (var i = 0; i < This.lines[This.lineNum].staff[This.staffNum].voices[0].length; i++) {
  3344. if (This.lines[This.lineNum].staff[This.staffNum].voices[0].el_type === 'stem')
  3345. found = true;
  3346. }
  3347. if (!found) {
  3348. var stem = { el_type: 'stem', direction: 'up' };
  3349. This.lines[This.lineNum].staff[This.staffNum].voices[0].splice(0,0,stem);
  3350. }
  3351. }
  3352. This.appendElement('stem', null, null, {direction: 'down'});
  3353. }
  3354. if (params.scale)
  3355. This.appendElement('scale', null, null, { size: params.scale} );
  3356. };
  3357. var createStaff = function(params) {
  3358. This.lines[This.lineNum].staff[This.staffNum] = {voices: [ ], clef: params.clef, key: params.key};
  3359. if (params.vocalfont) This.lines[This.lineNum].staff[This.staffNum].vocalfont = params.vocalfont;
  3360. if (params.bracket) This.lines[This.lineNum].staff[This.staffNum].bracket = params.bracket;
  3361. if (params.brace) This.lines[This.lineNum].staff[This.staffNum].brace = params.brace;
  3362. if (params.connectBarLines) This.lines[This.lineNum].staff[This.staffNum].connectBarLines = params.connectBarLines;
  3363. createVoice(params);
  3364. // Some stuff just happens for the first voice
  3365. if (params.part)
  3366. This.appendElement('part', params.startChar, params.endChar, {title: params.part});
  3367. if (params.meter !== undefined) This.lines[This.lineNum].staff[This.staffNum].meter = params.meter;
  3368. };
  3369. var createLine = function(params) {
  3370. This.lines[This.lineNum] = {staff: []};
  3371. createStaff(params);
  3372. };
  3373. if (this.lines[this.lineNum] === undefined) createLine(params);
  3374. else if (this.lines[this.lineNum].staff === undefined) {
  3375. this.lineNum++;
  3376. this.startNewLine(params);
  3377. } else if (this.lines[this.lineNum].staff[this.staffNum] === undefined) createStaff(params);
  3378. else if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] === undefined) createVoice(params);
  3379. else if (!this.containsNotes(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum])) return;
  3380. else {
  3381. this.lineNum++;
  3382. this.startNewLine(params);
  3383. }
  3384. };
  3385. this.hasBeginMusic = function() {
  3386. return this.lines.length > 0;
  3387. };
  3388. this.isFirstLine = function(index) {
  3389. for (var i = index-1; i >= 0; i--) {
  3390. if (this.lines[i].staff !== undefined) return false;
  3391. }
  3392. return true;
  3393. };
  3394. this.getCurrentVoice = function() {
  3395. if (this.lines[this.lineNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] !== undefined)
  3396. return this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
  3397. else return null;
  3398. };
  3399. this.setCurrentVoice = function(staffNum, voiceNum) {
  3400. this.staffNum = staffNum;
  3401. this.voiceNum = voiceNum;
  3402. for (var i = 0; i < this.lines.length; i++) {
  3403. if (this.lines[i].staff) {
  3404. if (this.lines[i].staff[staffNum] === undefined || this.lines[i].staff[staffNum].voices[voiceNum] === undefined ||
  3405. !this.containsNotes(this.lines[i].staff[staffNum].voices[voiceNum] )) {
  3406. this.lineNum = i;
  3407. return;
  3408. }
  3409. }
  3410. }
  3411. this.lineNum = i;
  3412. };
  3413. this.addMetaText = function(key, value) {
  3414. if (this.metaText[key] === undefined)
  3415. this.metaText[key] = value;
  3416. else
  3417. this.metaText[key] += "\n" + value;
  3418. };
  3419. this.addMetaTextArray = function(key, value) {
  3420. if (this.metaText[key] === undefined)
  3421. this.metaText[key] = [value];
  3422. else
  3423. this.metaText[key].push(value);
  3424. };
  3425. this.addMetaTextObj = function(key, value) {
  3426. this.metaText[key] = value;
  3427. };
  3428. }
  3429. // abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
  3430. // Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
  3431. //
  3432. // This program is free software: you can redistribute it and/or modify
  3433. // it under the terms of the GNU General Public License as published by
  3434. // the Free Software Foundation, either version 3 of the License, or
  3435. // (at your option) any later version.
  3436. //
  3437. // This program is distributed in the hope that it will be useful,
  3438. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  3439. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  3440. // GNU General Public License for more details.
  3441. //
  3442. // You should have received a copy of the GNU General Public License
  3443. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  3444. /*global AbcParseHeader, AbcTokenizer, AbcTune */
  3445. /*extern AbcParse */
  3446. function AbcParse() {
  3447. var tune = new AbcTune();
  3448. var tokenizer = new AbcTokenizer();
  3449. this.getTune = function() {
  3450. return tune;
  3451. };
  3452. var multilineVars = {
  3453. reset: function() {
  3454. for (var property in this) {
  3455. if (this.hasOwnProperty(property) && typeof this[property] !== "function") {
  3456. delete this[property];
  3457. }
  3458. }
  3459. this.iChar = 0;
  3460. this.key = {accidentals: [], root: 'none', acc: '', mode: '' };
  3461. this.meter = {type: 'specified', value: [{num: '4', den: '4'}]}; // if no meter is specified, there is an implied one.
  3462. this.origMeter = {type: 'specified', value: [{num: '4', den: '4'}]}; // this is for new voices that are created after we set the meter.
  3463. this.hasMainTitle = false;
  3464. this.default_length = 0.125;
  3465. this.clef = { type: 'treble', verticalPos: 0 };
  3466. this.next_note_duration = 0;
  3467. this.start_new_line = true;
  3468. this.is_in_header = true;
  3469. this.is_in_history = false;
  3470. this.partForNextLine = "";
  3471. this.havent_set_length = true;
  3472. this.voices = {};
  3473. this.staves = [];
  3474. this.macros = {};
  3475. this.currBarNumber = 1;
  3476. this.inTextBlock = false;
  3477. this.inPsBlock = false;
  3478. this.ignoredDecorations = [];
  3479. this.textBlock = "";
  3480. this.score_is_present = false; // Can't have original V: lines when there is the score directive
  3481. this.inEnding = false;
  3482. this.inTie = false;
  3483. this.inTieChord = {};
  3484. }
  3485. };
  3486. var addWarning = function(str) {
  3487. if (!multilineVars.warnings)
  3488. multilineVars.warnings = [];
  3489. multilineVars.warnings.push(str);
  3490. };
  3491. var warn = function(str, line, col_num) {
  3492. var bad_char = line.charAt(col_num);
  3493. if (bad_char === ' ')
  3494. bad_char = "SPACE";
  3495. var clean_line = line.substring(0, col_num).gsub('\x12', ' ') + '\n' + bad_char + '\n' + line.substring(col_num+1).gsub('\x12', ' ');
  3496. clean_line = clean_line.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').replace('\n', '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">').replace('\n', '</span>');
  3497. addWarning("Music Line:" + tune.getNumLines() + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
  3498. };
  3499. var header = new AbcParseHeader(tokenizer, warn, multilineVars, tune);
  3500. this.getWarnings = function() {
  3501. return multilineVars.warnings;
  3502. };
  3503. var letter_to_chord = function(line, i)
  3504. {
  3505. if (line.charAt(i) === '"')
  3506. {
  3507. var chord = tokenizer.getBrackettedSubstring(line, i, 5);
  3508. if (!chord[2])
  3509. warn("Missing the closing quote while parsing the chord symbol", line , i);
  3510. // If it starts with ^, then the chord appears above.
  3511. // If it starts with _ then the chord appears below.
  3512. // (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
  3513. if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
  3514. chord[1] = chord[1].substring(1);
  3515. chord[2] = 'above';
  3516. } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
  3517. chord[1] = chord[1].substring(1);
  3518. chord[2] = 'below';
  3519. } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
  3520. chord[1] = chord[1].substring(1);
  3521. chord[2] = 'left';
  3522. } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
  3523. chord[1] = chord[1].substring(1);
  3524. chord[2] = 'right';
  3525. } else {
  3526. chord[1] = chord[1].replace(/([ABCDEFG])b/g, "$1?");
  3527. chord[1] = chord[1].replace(/([ABCDEFG])#/g, "$1?");
  3528. chord[2] = 'default';
  3529. }
  3530. return chord;
  3531. }
  3532. return [0, ""];
  3533. };
  3534. var legalAccents = [ "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
  3535. "emphasis", "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
  3536. "open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
  3537. "segno", "coda", "D.S.", "D.C.", "fine", "crescendo(", "crescendo)", "diminuendo(", "diminuendo)",
  3538. "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz", "repeatbar", "repeatbar2", "slide",
  3539. "upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
  3540. "turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem",
  3541. "style=normal", "style=harmonic", "style=rhythm", "style=x"
  3542. ];
  3543. var accentPsuedonyms = [ ["<", "accent"], [">", "accent"], ["tr", "trill"], ["<(", "crescendo("], ["<)", "crescendo)"],
  3544. [">(", "diminuendo("], [">)", "diminuendo)"], ["plus", "+"] ];
  3545. var letter_to_accent = function(line, i)
  3546. {
  3547. var macro = multilineVars.macros[line.charAt(i)];
  3548. if (macro !== undefined) {
  3549. if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
  3550. macro = macro.substring(1);
  3551. if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
  3552. macro = macro.substring(0, macro.length-1);
  3553. if (legalAccents.detect(function(acc) {
  3554. return (macro === acc);
  3555. }))
  3556. return [ 1, macro ];
  3557. else {
  3558. if (!multilineVars.ignoredDecorations.detect(function(dec) {
  3559. return (macro === dec);
  3560. }))
  3561. warn("Unknown macro: " + macro, line, i);
  3562. return [1, '' ];
  3563. }
  3564. }
  3565. switch (line.charAt(i))
  3566. {
  3567. case '.':return [1, 'staccato'];
  3568. case 'u':return [1, 'upbow'];
  3569. case 'v':return [1, 'downbow'];
  3570. case '~':return [1, 'roll'];
  3571. case '!':
  3572. case '+':
  3573. var ret = tokenizer.getBrackettedSubstring(line, i, 5);
  3574. // Be sure that the accent is recognizable.
  3575. if (ret[1].length > 0 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
  3576. ret[1] = ret[1].substring(1); // TODO-PER: The test files have indicators forcing the orniment to the top or bottom, but that isn't in the standard. We'll just ignore them.
  3577. if (legalAccents.detect(function(acc) {
  3578. return (ret[1] === acc);
  3579. }))
  3580. return ret;
  3581. if (accentPsuedonyms.detect(function(acc) {
  3582. if (ret[1] === acc[0]) {
  3583. ret[1] = acc[1];
  3584. return true;
  3585. } else
  3586. return false;
  3587. }))
  3588. return ret;
  3589. // We didn't find the accent in the list, so consume the space, but don't return an accent.
  3590. // Although it is possible that ! was used as a line break, so accept that.
  3591. if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
  3592. return [1, null ];
  3593. warn("Unknown decoration: " + ret[1], line, i);
  3594. ret[1] = "";
  3595. return ret;
  3596. case 'H':return [1, 'fermata'];
  3597. case 'J':return [1, 'slide'];
  3598. case 'L':return [1, 'accent'];
  3599. case 'M':return [1, 'mordent'];
  3600. case 'O':return[1, 'coda'];
  3601. case 'P':return[1, 'pralltriller'];
  3602. case 'R':return [1, 'roll'];
  3603. case 'S':return [1, 'segno'];
  3604. case 'T':return [1, 'trill'];
  3605. }
  3606. return [0, 0];
  3607. };
  3608. var letter_to_spacer = function(line, i)
  3609. {
  3610. var start = i;
  3611. while (tokenizer.isWhiteSpace(line.charAt(i)))
  3612. i++;
  3613. return [ i-start ];
  3614. };
  3615. // returns the class of the bar line
  3616. // the number of the repeat
  3617. // and the number of characters used up
  3618. // if 0 is returned, then the next element was not a bar line
  3619. var letter_to_bar = function(line, curr_pos)
  3620. {
  3621. var ret = tokenizer.getBarLine(line, curr_pos);
  3622. if (ret.len === 0)
  3623. return [0,""];
  3624. if (ret.warn) {
  3625. warn(ret.warn, line, curr_pos);
  3626. return [ret.len,""];
  3627. }
  3628. // Now see if this is a repeated ending
  3629. // A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
  3630. // It can also optionally start with '[', which is ignored.
  3631. // Also, it can have white space before the '['.
  3632. for (var ws = 0; ws < line.length; ws++)
  3633. if (line.charAt(curr_pos+ret.len+ws) !== ' ')
  3634. break;
  3635. var orig_bar_len = ret.len;
  3636. if (line.charAt(curr_pos+ret.len+ws) === '[') {
  3637. ret.len += ws + 1;
  3638. // 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.
  3639. if (line.charAt(curr_pos+ret.len) === '"') {
  3640. var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
  3641. return [ret.len+ending[0], ret.token, ending[1]];
  3642. }
  3643. }
  3644. var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
  3645. if (retRep.len === 0 || retRep.token[0] === '-')
  3646. return [orig_bar_len, ret.token];
  3647. return [ret.len+retRep.len, ret.token, retRep.token];
  3648. };
  3649. var letter_to_open_slurs_and_triplets = function(line, i) {
  3650. // consume spaces, and look for all the open parens. If there is a number after the open paren,
  3651. // that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
  3652. var ret = {};
  3653. var start = i;
  3654. while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
  3655. if (line.charAt(i) === '(') {
  3656. if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
  3657. if (ret.triplet !== undefined)
  3658. warn("Can't nest triplets", line, i);
  3659. else {
  3660. ret.triplet = line.charAt(i+1) - '0';
  3661. if (i+2 < line.length && line.charAt(i+2) === ':') {
  3662. // We are expecting "(p:q:r" or "(p:q" or "(p::r" we are only interested in the first number (p) and the number of notes (r)
  3663. // if r is missing, then it is equal to p.
  3664. if (i+3 < line.length && line.charAt(i+3) === ':') {
  3665. if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
  3666. ret.num_notes = line.charAt(i+4) - '0';
  3667. i += 3;
  3668. } else
  3669. warn("expected number after the two colons after the triplet to mark the duration", line, i);
  3670. } else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
  3671. // ignore this middle number
  3672. if (i+4 < line.length && line.charAt(i+4) === ':') {
  3673. if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
  3674. ret.num_notes = line.charAt(i+5) - '0';
  3675. i += 4;
  3676. }
  3677. } else {
  3678. ret.num_notes = ret.triplet;
  3679. i += 3;
  3680. }
  3681. } else
  3682. warn("expected number after the triplet to mark the duration", line, i);
  3683. }
  3684. }
  3685. i++;
  3686. }
  3687. else {
  3688. if (ret.startSlur === undefined)
  3689. ret.startSlur = 1;
  3690. else
  3691. ret.startSlur++;
  3692. }
  3693. }
  3694. i++;
  3695. }
  3696. ret.consumed = i-start;
  3697. return ret;
  3698. };
  3699. var addWords = function(line, words) {
  3700. if (!line) { warn("Can't add words before the first line of mulsic", line, 0); return; }
  3701. words = words.strip();
  3702. if (words.charAt(words.length-1) !== '-')
  3703. words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it.
  3704. var word_list = [];
  3705. // first make a list of words from the string we are passed. A word is divided on either a space or dash.
  3706. var last_divider = 0;
  3707. var replace = false;
  3708. var addWord = function(i) {
  3709. var word = words.substring(last_divider, i).strip();
  3710. last_divider = i+1;
  3711. if (word.length > 0) {
  3712. if (replace)
  3713. word = word.gsub('~', ' ');
  3714. var div = words.charAt(i);
  3715. if (div !== '_' && div !== '-')
  3716. div = ' ';
  3717. word_list.push({syllable: tokenizer.translateString(word), divider: div});
  3718. replace = false;
  3719. return true;
  3720. }
  3721. return false;
  3722. };
  3723. for (var i = 0; i < words.length; i++) {
  3724. switch (words.charAt(i)) {
  3725. case ' ':
  3726. case '\x12':
  3727. addWord(i);
  3728. break;
  3729. case '-':
  3730. if (!addWord(i) && word_list.length > 0) {
  3731. word_list.last().divider = '-';
  3732. word_list.push({skip: true, to: 'next'});
  3733. }
  3734. break;
  3735. case '_':
  3736. addWord(i);
  3737. word_list.push({skip: true, to: 'slur'});
  3738. break;
  3739. case '*':
  3740. addWord(i);
  3741. word_list.push({skip: true, to: 'next'});
  3742. break;
  3743. case '|':
  3744. addWord(i);
  3745. word_list.push({skip: true, to: 'bar'});
  3746. break;
  3747. case '~':
  3748. replace = true;
  3749. break;
  3750. }
  3751. }
  3752. var inSlur = false;
  3753. line.each(function(el) {
  3754. if (word_list.length !== 0) {
  3755. if (word_list[0].skip) {
  3756. switch (word_list[0].to) {
  3757. case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break;
  3758. case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break;
  3759. case 'bar': if (el.el_type === 'bar') word_list.shift(); break;
  3760. }
  3761. } else {
  3762. if (el.el_type === 'note' && el.rest === undefined && !inSlur) {
  3763. var lyric = word_list.shift();
  3764. if (el.lyric === undefined)
  3765. el.lyric = [ lyric ];
  3766. else
  3767. el.lyric.push(lyric);
  3768. }
  3769. }
  3770. }
  3771. });
  3772. };
  3773. var addSymbols = function(line, words) {
  3774. // TODO-PER: Currently copied from w: line. This needs to be read as symbols instead.
  3775. if (!line) { warn("Can't add symbols before the first line of mulsic", line, 0); return; }
  3776. words = words.strip();
  3777. if (words.charAt(words.length-1) !== '-')
  3778. words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it.
  3779. var word_list = [];
  3780. // first make a list of words from the string we are passed. A word is divided on either a space or dash.
  3781. var last_divider = 0;
  3782. var replace = false;
  3783. var addWord = function(i) {
  3784. var word = words.substring(last_divider, i).strip();
  3785. last_divider = i+1;
  3786. if (word.length > 0) {
  3787. if (replace)
  3788. word = word.gsub('~', ' ');
  3789. var div = words.charAt(i);
  3790. if (div !== '_' && div !== '-')
  3791. div = ' ';
  3792. word_list.push({syllable: tokenizer.translateString(word), divider: div});
  3793. replace = false;
  3794. return true;
  3795. }
  3796. return false;
  3797. };
  3798. for (var i = 0; i < words.length; i++) {
  3799. switch (words.charAt(i)) {
  3800. case ' ':
  3801. case '\x12':
  3802. addWord(i);
  3803. break;
  3804. case '-':
  3805. if (!addWord(i) && word_list.length > 0) {
  3806. word_list.last().divider = '-';
  3807. word_list.push({skip: true, to: 'next'});
  3808. }
  3809. break;
  3810. case '_':
  3811. addWord(i);
  3812. word_list.push({skip: true, to: 'slur'});
  3813. break;
  3814. case '*':
  3815. addWord(i);
  3816. word_list.push({skip: true, to: 'next'});
  3817. break;
  3818. case '|':
  3819. addWord(i);
  3820. word_list.push({skip: true, to: 'bar'});
  3821. break;
  3822. case '~':
  3823. replace = true;
  3824. break;
  3825. }
  3826. }
  3827. var inSlur = false;
  3828. line.each(function(el) {
  3829. if (word_list.length !== 0) {
  3830. if (word_list[0].skip) {
  3831. switch (word_list[0].to) {
  3832. case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break;
  3833. case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break;
  3834. case 'bar': if (el.el_type === 'bar') word_list.shift(); break;
  3835. }
  3836. } else {
  3837. if (el.el_type === 'note' && el.rest === undefined && !inSlur) {
  3838. var lyric = word_list.shift();
  3839. if (el.lyric === undefined)
  3840. el.lyric = [ lyric ];
  3841. else
  3842. el.lyric.push(lyric);
  3843. }
  3844. }
  3845. }
  3846. });
  3847. };
  3848. var getBrokenRhythm = function(line, index) {
  3849. switch (line.charAt(index)) {
  3850. case '>':
  3851. if (index < line.length - 1 && line.charAt(index+1) === '>') // double >>
  3852. return [2, 1.75, 0.25];
  3853. else
  3854. return [1, 1.5, 0.5];
  3855. break;
  3856. case '<':
  3857. if (index < line.length - 1 && line.charAt(index+1) === '<') // double <<
  3858. return [2, 0.25, 1.75];
  3859. else
  3860. return [1, 0.5, 1.5];
  3861. break;
  3862. }
  3863. return null;
  3864. };
  3865. // TODO-PER: make this a method in el.
  3866. var addEndBeam = function(el) {
  3867. if (el.duration !== undefined && el.duration < 0.25)
  3868. el.end_beam = true;
  3869. return el;
  3870. };
  3871. 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};
  3872. var rests = {x: 'invisible', y: 'spacer', z: 'rest', Z: 'multimeasure' };
  3873. var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
  3874. //var el = { startChar: index };
  3875. var isComplete = function(state) {
  3876. return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
  3877. };
  3878. var state = 'startSlur';
  3879. var durationSetByPreviousNote = false;
  3880. while (1) {
  3881. switch(line.charAt(index)) {
  3882. case '(':
  3883. if (state === 'startSlur') {
  3884. if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
  3885. } else if (isComplete(state)) {el.endChar = index;return el;}
  3886. else return null;
  3887. break;
  3888. case ')':
  3889. if (isComplete(state)) {
  3890. if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
  3891. } else return null;
  3892. break;
  3893. case '^':
  3894. if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
  3895. else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
  3896. else if (isComplete(state)) {el.endChar = index;return el;}
  3897. else return null;
  3898. break;
  3899. case '_':
  3900. if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
  3901. else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
  3902. else if (isComplete(state)) {el.endChar = index;return el;}
  3903. else return null;
  3904. break;
  3905. case '=':
  3906. if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
  3907. else if (isComplete(state)) {el.endChar = index;return el;}
  3908. else return null;
  3909. break;
  3910. case 'A':
  3911. case 'B':
  3912. case 'C':
  3913. case 'D':
  3914. case 'E':
  3915. case 'F':
  3916. case 'G':
  3917. case 'a':
  3918. case 'b':
  3919. case 'c':
  3920. case 'd':
  3921. case 'e':
  3922. case 'f':
  3923. case 'g':
  3924. if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
  3925. el.pitch = pitches[line.charAt(index)];
  3926. state = 'octave';
  3927. // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
  3928. if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
  3929. el.duration = multilineVars.next_note_duration;
  3930. multilineVars.next_note_duration = 0;
  3931. durationSetByPreviousNote = true;
  3932. } else
  3933. el.duration = multilineVars.default_length;
  3934. } else if (isComplete(state)) {el.endChar = index;return el;}
  3935. else return null;
  3936. break;
  3937. case ',':
  3938. if (state === 'octave') {el.pitch -= 7;}
  3939. else if (isComplete(state)) {el.endChar = index;return el;}
  3940. else return null;
  3941. break;
  3942. case '\'':
  3943. if (state === 'octave') {el.pitch += 7;}
  3944. else if (isComplete(state)) {el.endChar = index;return el;}
  3945. else return null;
  3946. break;
  3947. case 'x':
  3948. case 'y':
  3949. case 'z':
  3950. case 'Z':
  3951. if (state === 'startSlur') {
  3952. el.rest = { type: rests[line.charAt(index)] };
  3953. // There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
  3954. // just nix them here.
  3955. delete el.accidental;
  3956. delete el.startSlur;
  3957. delete el.startTie;
  3958. delete el.endSlur;
  3959. delete el.endTie;
  3960. delete el.end_beam;
  3961. delete el.grace_notes;
  3962. // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
  3963. if (el.rest.type === 'multimeasure') {
  3964. el.duration = 1;
  3965. state = 'Zduration';
  3966. } else {
  3967. if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
  3968. el.duration = multilineVars.next_note_duration;
  3969. multilineVars.next_note_duration = 0;
  3970. durationSetByPreviousNote = true;
  3971. } else
  3972. el.duration = multilineVars.default_length;
  3973. state = 'duration';
  3974. }
  3975. } else if (isComplete(state)) {el.endChar = index;return el;}
  3976. else return null;
  3977. break;
  3978. case '1':
  3979. case '2':
  3980. case '3':
  3981. case '4':
  3982. case '5':
  3983. case '6':
  3984. case '7':
  3985. case '8':
  3986. case '9':
  3987. case '0':
  3988. case '/':
  3989. if (state === 'octave' || state === 'duration') {
  3990. var fraction = tokenizer.getFraction(line, index);
  3991. if (!durationSetByPreviousNote)
  3992. el.duration = el.duration * fraction.value;
  3993. // TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
  3994. el.endChar = fraction.index;
  3995. while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
  3996. if (line.charAt(fraction.index) === '-')
  3997. el.startTie = {};
  3998. else
  3999. el = addEndBeam(el);
  4000. fraction.index++;
  4001. }
  4002. index = fraction.index-1;
  4003. state = 'broken_rhythm';
  4004. } else if (state === 'sharp2') {
  4005. el.accidental = 'quartersharp';state = 'pitch';
  4006. } else if (state === 'flat2') {
  4007. el.accidental = 'quarterflat';state = 'pitch';
  4008. } else if (state === 'Zduration') {
  4009. var num = tokenizer.getNumber(line, index);
  4010. el.duration = num.num;
  4011. el.endChar = num.index;
  4012. return el;
  4013. } else return null;
  4014. break;
  4015. case '-':
  4016. if (state === 'startSlur') {
  4017. // This is the first character, so it must have been meant for the previous note. Correct that here.
  4018. tune.addTieToLastNote();
  4019. el.endTie = true;
  4020. } else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
  4021. el.startTie = {};
  4022. if (!durationSetByPreviousNote && canHaveBrokenRhythm)
  4023. state = 'broken_rhythm';
  4024. else {
  4025. // Peek ahead to the next character. If it is a space, then we have an end beam.
  4026. if (tokenizer.isWhiteSpace(line.charAt(index+1)))
  4027. addEndBeam(el);
  4028. el.endChar = index+1;
  4029. return el;
  4030. }
  4031. } else if (state === 'broken_rhythm') {el.endChar = index;return el;}
  4032. else return null;
  4033. break;
  4034. case ' ':
  4035. case '\t':
  4036. if (isComplete(state)) {
  4037. el.end_beam = true;
  4038. // look ahead to see if there is a tie
  4039. do {
  4040. if (line.charAt(index) === '-')
  4041. el.startTie = {};
  4042. index++;
  4043. } while (index < line.length && (tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-'));
  4044. el.endChar = index;
  4045. if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line.charAt(index) === '<' || line.charAt(index) === '>')) { // TODO-PER: Don't need the test for < and >, but that makes the endChar work out for the regression test.
  4046. index--;
  4047. state = 'broken_rhythm';
  4048. } else
  4049. return el;
  4050. }
  4051. else return null;
  4052. break;
  4053. case '>':
  4054. case '<':
  4055. if (isComplete(state)) {
  4056. if (canHaveBrokenRhythm) {
  4057. var br2 = getBrokenRhythm(line, index);
  4058. index += br2[0] - 1; // index gets incremented below, so we'll let that happen
  4059. multilineVars.next_note_duration = br2[2]*el.duration;
  4060. el.duration = br2[1]*el.duration;
  4061. state = 'end_slur';
  4062. } else {
  4063. el.endChar = index;
  4064. return el;
  4065. }
  4066. } else
  4067. return null;
  4068. break;
  4069. default:
  4070. if (isComplete(state)) {
  4071. el.endChar = index;
  4072. return el;
  4073. }
  4074. return null;
  4075. }
  4076. index++;
  4077. if (index === line.length) {
  4078. if (isComplete(state)) {el.endChar = index;return el;}
  4079. else return null;
  4080. }
  4081. }
  4082. return null;
  4083. };
  4084. function startNewLine() {
  4085. var params = { startChar: -1, endChar: -1};
  4086. if (multilineVars.partForNextLine.length)
  4087. params.part = multilineVars.partForNextLine;
  4088. params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.clone(multilineVars.clef) ;
  4089. params.key = header.deepCopyKey(multilineVars.key);
  4090. header.addPosToKey(params.clef, params.key);
  4091. if (multilineVars.meter !== null) {
  4092. if (multilineVars.currentVoice) {
  4093. multilineVars.staves.each(function(st) {
  4094. st.meter = multilineVars.meter;
  4095. });
  4096. params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
  4097. multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
  4098. } else
  4099. params.meter = multilineVars.meter;
  4100. multilineVars.meter = null;
  4101. } else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
  4102. // Make sure that each voice gets the meter marking.
  4103. params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
  4104. multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
  4105. }
  4106. if (multilineVars.currentVoice && multilineVars.currentVoice.name)
  4107. params.name = multilineVars.currentVoice.name;
  4108. if (multilineVars.vocalfont)
  4109. params.vocalfont = multilineVars.vocalfont;
  4110. if (multilineVars.style)
  4111. params.style = multilineVars.style;
  4112. if (multilineVars.currentVoice) {
  4113. var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
  4114. if (staff.brace) params.brace = staff.brace;
  4115. if (staff.bracket) params.bracket = staff.bracket;
  4116. if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
  4117. if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
  4118. if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
  4119. if (multilineVars.currentVoice.stem)
  4120. params.stem = multilineVars.currentVoice.stem;
  4121. if (multilineVars.currentVoice.scale)
  4122. params.scale = multilineVars.currentVoice.scale;
  4123. if (multilineVars.currentVoice.style)
  4124. params.style = multilineVars.currentVoice.style;
  4125. }
  4126. tune.startNewLine(params);
  4127. multilineVars.partForNextLine = "";
  4128. if (multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === multilineVars.staves.length-1 && multilineVars.staves[multilineVars.currentVoice.staffNum].numVoices-1 === multilineVars.currentVoice.index)) {
  4129. //multilineVars.meter = null;
  4130. if (multilineVars.barNumbers === 0)
  4131. multilineVars.barNumOnNextNote = multilineVars.currBarNumber;
  4132. }
  4133. }
  4134. var letter_to_grace = function(line, i) {
  4135. // Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
  4136. if (line.charAt(i) === '{') {
  4137. // fetch the gracenotes string and consume that into the array
  4138. var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
  4139. if (!gra[2])
  4140. warn("Missing the closing '}' while parsing grace note", line, i);
  4141. // If there is a slur after the grace construction, then move it to the last note inside the grace construction
  4142. if (line[i+gra[0]] === ')') {
  4143. gra[0]++;
  4144. gra[1] += ')';
  4145. }
  4146. var gracenotes = [];
  4147. var ii = 0;
  4148. var inTie = false;
  4149. while (ii < gra[1].length) {
  4150. var note = getCoreNote(gra[1], ii, {}, false);
  4151. if (note !== null) {
  4152. gracenotes.push(note);
  4153. if (inTie) {
  4154. note.endTie = true;
  4155. inTie = false;
  4156. }
  4157. if (note.startTie)
  4158. inTie = true;
  4159. ii = note.endChar;
  4160. delete note.endChar;
  4161. }
  4162. else {
  4163. // We shouldn't get anything but notes or a space here, so report an error
  4164. if (gra[1].charAt(ii) === ' ') {
  4165. if (gracenotes.length > 0)
  4166. gracenotes[gracenotes.length-1].end_beam = true;
  4167. } else
  4168. warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
  4169. ii++;
  4170. }
  4171. }
  4172. if (gracenotes.length)
  4173. return [gra[0], gracenotes];
  4174. // for (var ret = letter_to_pitch(gra[1], ii); ret[0]>0 && ii<gra[1].length;
  4175. // ret = letter_to_pitch(gra[1], ii)) {
  4176. // //todo get other stuff that could be in a grace note
  4177. // ii += ret[0];
  4178. // gracenotes.push({el_type:"gracenote",pitch:ret[1]});
  4179. // }
  4180. // return [ gra[0], gracenotes ];
  4181. }
  4182. return [ 0 ];
  4183. };
  4184. //
  4185. // Parse line of music
  4186. //
  4187. // This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
  4188. // core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
  4189. // chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
  4190. // grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
  4191. // note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
  4192. // bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
  4193. // header is <open-bracket:required, K|M|L|V:required, colon:required, field:required, close-bracket:required> spaces can occur between the colon, in the field, and before the close bracket
  4194. // 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.
  4195. // a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
  4196. // Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
  4197. // Then, all leading and trailing spaces are ignored.
  4198. // If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
  4199. // of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
  4200. // TODO-PER: How to handle ! for line break?
  4201. // TODO-PER: dots before bar, dots before slur
  4202. // TODO-PER: U: redefinable symbols.
  4203. // Ambiguous symbols:
  4204. // "[" can be the start of a chord, the start of a header element or part of a bar line.
  4205. // --- if it is immediately followed by "|", it is a bar line
  4206. // --- 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.)
  4207. // --- otherwise it is the beginning of a chord
  4208. // "(" can be the start of a slur or a triplet
  4209. // --- if it is followed by a number from 2-9, then it is a triplet
  4210. // --- otherwise it is a slur
  4211. // "]"
  4212. // --- if there is a chord open, then this is the close
  4213. // --- if it is after a [|, then it is an invisible bar line
  4214. // --- otherwise, it is par of a bar
  4215. // "." can be a bar modifier or a slur modifier, or a decoration
  4216. // --- if it comes immediately before a bar, it is a bar modifier
  4217. // --- if it comes immediately before a slur, it is a slur modifier
  4218. // --- otherwise it is a decoration for the next note.
  4219. // number:
  4220. // --- if it is after a bar, with no space, it is an ending marker
  4221. // --- if it is after a ( with no space, it is a triplet count
  4222. // --- if it is after a pitch or octave or slash, then it is a duration
  4223. // Unambiguous symbols (except inside quoted strings):
  4224. // vertical-bar, colon: part of a bar
  4225. // ABCDEFGabcdefg: pitch
  4226. // xyzZ: rest
  4227. // comma, prime: octave
  4228. // close-paren: end-slur
  4229. // hyphen: tie
  4230. // tilde, v, u, bang, plus, THLMPSO: decoration
  4231. // carat, underscore, equal: accidental
  4232. // ampersand: time reset
  4233. // open-curly, close-curly: grace notes
  4234. // double-quote: chord symbol
  4235. // less-than, greater-than, slash: duration
  4236. // back-tick, space, tab: space
  4237. var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
  4238. var parseRegularMusicLine = function(line) {
  4239. header.resolveTempo();
  4240. //multilineVars.havent_set_length = false; // To late to set this now.
  4241. 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.
  4242. var i = 0;
  4243. var startOfLine = multilineVars.iChar;
  4244. // 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 %
  4245. while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
  4246. i++;
  4247. if (i === line.length || line.charAt(i) === '%')
  4248. return;
  4249. // Start with the standard staff, clef and key symbols on each line
  4250. var delayStartNewLine = multilineVars.start_new_line;
  4251. // if (multilineVars.start_new_line) {
  4252. // startNewLine();
  4253. // }
  4254. multilineVars.start_new_line = true;
  4255. var tripletNotesLeft = 0;
  4256. //var tripletMultiplier = 0;
  4257. // var inTie = false;
  4258. // var inTieChord = {};
  4259. // See if the line starts with a header field
  4260. var retHeader = header.letter_to_body_header(line, i);
  4261. if (retHeader[0] > 0) {
  4262. i += retHeader[0];
  4263. // TODO-PER: Handle inline headers
  4264. }
  4265. var el = { };
  4266. while (i < line.length)
  4267. {
  4268. var startI = i;
  4269. if (line.charAt(i) === '%')
  4270. break;
  4271. var retInlineHeader = header.letter_to_inline_header(line, i);
  4272. if (retInlineHeader[0] > 0) {
  4273. i += retInlineHeader[0];
  4274. // TODO-PER: Handle inline headers
  4275. //multilineVars.start_new_line = false;
  4276. } else {
  4277. // Wait until here to actually start the line because we know we're past the inline statements.
  4278. if (delayStartNewLine) {
  4279. startNewLine();
  4280. delayStartNewLine = false;
  4281. }
  4282. // var el = { };
  4283. // We need to decide if the following characters are a bar-marking or a note-group.
  4284. // Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
  4285. // If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
  4286. // If there is a bar marker, it is definitely a bar-marking.
  4287. // If there is either a core-note or chord, it is definitely a note-group.
  4288. // 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]
  4289. // Then, if there is a grace-note, we know where to go.
  4290. // Else see if we have a chord, core-note, slur, triplet, or bar.
  4291. while (1) {
  4292. var ret = tokenizer.eatWhiteSpace(line, i);
  4293. if (ret > 0) {
  4294. i += ret;
  4295. }
  4296. if (i > 0 && line.charAt(i-1) === '\x12') {
  4297. // 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.
  4298. ret = header.letter_to_body_header(line, i);
  4299. if (ret[0] > 0) {
  4300. // TODO: insert header here
  4301. i = ret[0];
  4302. multilineVars.start_new_line = false;
  4303. }
  4304. }
  4305. // gather all the grace notes, chord symbols and decorations
  4306. ret = letter_to_spacer(line, i);
  4307. if (ret[0] > 0) {
  4308. i += ret[0];
  4309. }
  4310. ret = letter_to_chord(line, i);
  4311. if (ret[0] > 0) {
  4312. // There could be more than one chord here if they have different positions.
  4313. // If two chords have the same position, then connect them with newline.
  4314. if (!el.chord)
  4315. el.chord = [];
  4316. var chordName = tokenizer.translateString(ret[1]);
  4317. chordName = chordName.replace(/;/g, "\n");
  4318. var addedChord = false;
  4319. for (var ci = 0; ci < el.chord.length; ci++) {
  4320. if (el.chord[ci].position === ret[2]) {
  4321. addedChord = true;
  4322. el.chord[ci].name += "\n" + chordName;
  4323. }
  4324. }
  4325. if (addedChord === false)
  4326. el.chord.push({name: chordName, position: ret[2]});
  4327. i += ret[0];
  4328. var ii = tokenizer.skipWhiteSpace(line.substring(i));
  4329. if (ii > 0)
  4330. el.force_end_beam_last = true;
  4331. i += ii;
  4332. } else {
  4333. if (nonDecorations.indexOf(line.charAt(i)) === -1)
  4334. ret = letter_to_accent(line, i);
  4335. else ret = [ 0 ];
  4336. if (ret[0] > 0) {
  4337. if (ret[1] === null) {
  4338. if (i+1 < line.length)
  4339. startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
  4340. } else if (ret[1].length > 0) {
  4341. if (el.decoration === undefined)
  4342. el.decoration = [];
  4343. el.decoration.push(ret[1]);
  4344. }
  4345. i += ret[0];
  4346. } else {
  4347. ret = letter_to_grace(line, i);
  4348. // TODO-PER: Be sure there aren't already grace notes defined. That is an error.
  4349. if (ret[0] > 0) {
  4350. el.gracenotes = ret[1];
  4351. i += ret[0];
  4352. } else
  4353. break;
  4354. }
  4355. }
  4356. }
  4357. ret = letter_to_bar(line, i);
  4358. if (ret[0] > 0) {
  4359. // This is definitely a bar
  4360. if (el.gracenotes !== undefined) {
  4361. // Attach the grace note to an invisible note
  4362. el.rest = { type: 'spacer' };
  4363. el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
  4364. tune.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
  4365. multilineVars.measureNotEmpty = true;
  4366. el = {};
  4367. }
  4368. var bar = {type: ret[1]};
  4369. if (bar.type.length === 0)
  4370. warn("Unknown bar type", line, i);
  4371. else {
  4372. if (multilineVars.inEnding && bar.type !== 'bar_thin') {
  4373. bar.endEnding = true;
  4374. multilineVars.inEnding = false;
  4375. }
  4376. if (ret[2]) {
  4377. bar.startEnding = ret[2];
  4378. if (multilineVars.inEnding)
  4379. bar.endEnding = true;
  4380. multilineVars.inEnding = true;
  4381. }
  4382. if (el.decoration !== undefined)
  4383. bar.decoration = el.decoration;
  4384. if (el.chord !== undefined)
  4385. bar.chord = el.chord;
  4386. if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
  4387. multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
  4388. else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
  4389. multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
  4390. else if (bar.endEnding)
  4391. multilineVars.barFirstEndingNum = undefined;
  4392. if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
  4393. multilineVars.currBarNumber++;
  4394. if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
  4395. multilineVars.barNumOnNextNote = multilineVars.currBarNumber;
  4396. }
  4397. tune.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
  4398. multilineVars.measureNotEmpty = false;
  4399. el = {};
  4400. }
  4401. i += ret[0];
  4402. } else if (line[i] === '&') { // backtrack to beginning of measure
  4403. warn("Overlay not yet supported", line, i);
  4404. i++;
  4405. } else {
  4406. // This is definitely a note group
  4407. //
  4408. // Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
  4409. ret = letter_to_open_slurs_and_triplets(line, i);
  4410. if (ret.consumed > 0) {
  4411. if (ret.startSlur !== undefined)
  4412. el.startSlur = ret.startSlur;
  4413. if (ret.triplet !== undefined) {
  4414. if (tripletNotesLeft > 0)
  4415. warn("Can't nest triplets", line, i);
  4416. else {
  4417. el.startTriplet = ret.triplet;
  4418. tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
  4419. }
  4420. }
  4421. i += ret.consumed;
  4422. }
  4423. // handle chords.
  4424. if (line.charAt(i) === '[') {
  4425. i++;
  4426. var chordDuration = null;
  4427. var done = false;
  4428. while (!done) {
  4429. var chordNote = getCoreNote(line, i, {}, false);
  4430. if (chordNote !== null) {
  4431. if (chordNote.end_beam) {
  4432. el.end_beam = true;
  4433. delete chordNote.end_beam;
  4434. }
  4435. if (el.pitches === undefined) {
  4436. el.duration = chordNote.duration;
  4437. el.pitches = [ chordNote ];
  4438. } else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
  4439. el.pitches.push(chordNote);
  4440. delete chordNote.duration;
  4441. if (multilineVars.inTieChord[el.pitches.length]) {
  4442. chordNote.endTie = true;
  4443. multilineVars.inTieChord[el.pitches.length] = undefined;
  4444. }
  4445. if (chordNote.startTie)
  4446. multilineVars.inTieChord[el.pitches.length] = true;
  4447. i = chordNote.endChar;
  4448. delete chordNote.endChar;
  4449. } else if (line.charAt(i) === ' ') {
  4450. // Spaces are not allowed in chords, but we can recover from it by ignoring it.
  4451. warn("Spaces are not allowed in chords", line, i);
  4452. i++;
  4453. } else {
  4454. if (i < line.length && line.charAt(i) === ']') {
  4455. // consume the close bracket
  4456. i++;
  4457. if (multilineVars.next_note_duration !== 0) {
  4458. el.duration = el.duration * multilineVars.next_note_duration;
  4459. // el.pitches.each(function(p) {
  4460. // p.duration = p.duration * multilineVars.next_note_duration;
  4461. // });
  4462. multilineVars.next_note_duration = 0;
  4463. }
  4464. if (multilineVars.inTie) {
  4465. el.pitches.each(function(pitch) { pitch.endTie = true; });
  4466. multilineVars.inTie = false;
  4467. }
  4468. if (tripletNotesLeft > 0) {
  4469. tripletNotesLeft--;
  4470. if (tripletNotesLeft === 0) {
  4471. el.endTriplet = true;
  4472. }
  4473. }
  4474. // if (el.startSlur !== undefined) {
  4475. // el.pitches.each(function(pitch) { if (pitch.startSlur === undefined) pitch.startSlur = el.startSlur; else pitch.startSlur += el.startSlur; });
  4476. // delete el.startSlur;
  4477. // }
  4478. var postChordDone = false;
  4479. while (i < line.length && !postChordDone) {
  4480. switch (line.charAt(i)) {
  4481. case ' ':
  4482. case '\t':
  4483. addEndBeam(el);
  4484. break;
  4485. case ')':
  4486. if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
  4487. //el.pitches.each(function(pitch) { if (pitch.endSlur === undefined) pitch.endSlur = 1; else pitch.endSlur++; });
  4488. break;
  4489. case '-':
  4490. el.pitches.each(function(pitch) { pitch.startTie = {}; });
  4491. multilineVars.inTie = true;
  4492. break;
  4493. case '>':
  4494. case '<':
  4495. var br2 = getBrokenRhythm(line, i);
  4496. i += br2[0] - 1; // index gets incremented below, so we'll let that happen
  4497. multilineVars.next_note_duration = br2[2];
  4498. chordDuration = br2[1];
  4499. break;
  4500. case '1':
  4501. case '2':
  4502. case '3':
  4503. case '4':
  4504. case '5':
  4505. case '6':
  4506. case '7':
  4507. case '8':
  4508. case '9':
  4509. case '/':
  4510. var fraction = tokenizer.getFraction(line, i);
  4511. chordDuration = fraction.value;
  4512. i = fraction.index;
  4513. postChordDone = true;
  4514. break;
  4515. default:
  4516. postChordDone = true;
  4517. break;
  4518. }
  4519. if (!postChordDone) {
  4520. i++;
  4521. }
  4522. }
  4523. } else
  4524. warn("Expected ']' to end the chords", line, i);
  4525. if (el.pitches !== undefined) {
  4526. if (chordDuration !== null) {
  4527. el.duration = el.duration * chordDuration;
  4528. // el.pitches.each(function(p) {
  4529. // p.duration = p.duration * chordDuration;
  4530. // });
  4531. }
  4532. if (multilineVars.barNumOnNextNote) {
  4533. el.barNumber = multilineVars.barNumOnNextNote;
  4534. multilineVars.barNumOnNextNote = null;
  4535. }
  4536. tune.appendElement('note', startOfLine+i, startOfLine+i, el);
  4537. multilineVars.measureNotEmpty = true;
  4538. el = {};
  4539. }
  4540. done = true;
  4541. }
  4542. }
  4543. } else {
  4544. // Single pitch
  4545. var el2 = {};
  4546. var core = getCoreNote(line, i, el2, true);
  4547. if (el2.endTie !== undefined) multilineVars.inTie = true;
  4548. if (core !== null) {
  4549. if (core.pitch !== undefined) {
  4550. el.pitches = [ { } ];
  4551. // TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
  4552. if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
  4553. el.pitches[0].pitch = core.pitch;
  4554. if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
  4555. if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
  4556. if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
  4557. if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
  4558. if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
  4559. if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
  4560. } else {
  4561. el.rest = core.rest;
  4562. if (core.endSlur !== undefined) el.endSlur = core.endSlur;
  4563. if (core.endTie !== undefined) el.rest.endTie = core.endTie;
  4564. if (core.startSlur !== undefined) el.startSlur = core.startSlur;
  4565. if (el.startSlur !== undefined) el.startSlur = el.startSlur;
  4566. if (core.startTie !== undefined) el.rest.startTie = core.startTie;
  4567. if (el.startTie !== undefined) el.rest.startTie = el.startTie;
  4568. }
  4569. if (core.chord !== undefined) el.chord = core.chord;
  4570. if (core.duration !== undefined) el.duration = core.duration;
  4571. if (core.decoration !== undefined) el.decoration = core.decoration;
  4572. if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
  4573. delete el.startSlur;
  4574. if (multilineVars.inTie) {
  4575. if (el.pitches !== undefined)
  4576. el.pitches[0].endTie = true;
  4577. else
  4578. el.rest.endTie = true;
  4579. multilineVars.inTie = false;
  4580. }
  4581. if (core.startTie || el.startTie)
  4582. multilineVars.inTie = true;
  4583. i = core.endChar;
  4584. if (tripletNotesLeft > 0) {
  4585. tripletNotesLeft--;
  4586. if (tripletNotesLeft === 0) {
  4587. el.endTriplet = true;
  4588. }
  4589. }
  4590. if (core.end_beam)
  4591. addEndBeam(el);
  4592. if (multilineVars.barNumOnNextNote) {
  4593. el.barNumber = multilineVars.barNumOnNextNote;
  4594. multilineVars.barNumOnNextNote = null;
  4595. }
  4596. tune.appendElement('note', startOfLine+startI, startOfLine+i, el);
  4597. multilineVars.measureNotEmpty = true;
  4598. el = {};
  4599. }
  4600. }
  4601. if (i === startI) { // don't know what this is, so ignore it.
  4602. if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
  4603. warn("Unknown character ignored", line, i);
  4604. // warn("Unknown character ignored (" + line.charCodeAt(i) + ")", line, i);
  4605. i++;
  4606. }
  4607. }
  4608. }
  4609. }
  4610. };
  4611. var parseLine = function(line) {
  4612. var ret = header.parseHeader(line);
  4613. if (ret.regular)
  4614. parseRegularMusicLine(ret.str);
  4615. if (ret.newline)
  4616. startNewLine();
  4617. if (ret.words)
  4618. addWords(tune.getCurrentVoice(), line.substring(2));
  4619. if (ret.symbols)
  4620. addSymbols(tune.getCurrentVoice(), line.substring(2));
  4621. if (ret.recurse)
  4622. parseLine(ret.str);
  4623. };
  4624. this.parse = function(strTune, switches) {
  4625. // the switches are optional and cause a difference in the way the tune is parsed.
  4626. // switches.header_only : stop parsing when the header is finished
  4627. // switches.stop_on_warning : stop at the first warning encountered.
  4628. // switches.print: format for the page instead of the browser.
  4629. tune.reset();
  4630. if (switches && switches.print)
  4631. tune.media = 'print';
  4632. multilineVars.reset();
  4633. // Take care of whatever line endings come our way
  4634. strTune = strTune.gsub('\r\n', '\n');
  4635. strTune = strTune.gsub('\r', '\n');
  4636. strTune += '\n'; // Tacked on temporarily to make the last line continuation work
  4637. strTune = strTune.replace(/\n\\.*\n/g, "\n"); // get rid of latex commands.
  4638. var continuationReplacement = function(all, backslash, comment){
  4639. var spaces = " ";
  4640. var padding = comment ? spaces.substring(0, comment.length) : "";
  4641. return backslash + " \x12" + padding;
  4642. };
  4643. strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, continuationReplacement); // take care of line continuations right away, but keep the same number of characters
  4644. var lines = strTune.split('\n');
  4645. if (lines.last().length === 0) // remove the blank line we added above.
  4646. lines.pop();
  4647. try {
  4648. lines.each( function(line) {
  4649. if (switches) {
  4650. if (switches.header_only && multilineVars.is_in_header === false)
  4651. throw "normal_abort";
  4652. if (switches.stop_on_warning && multilineVars.warnings)
  4653. throw "normal_abort";
  4654. }
  4655. if (multilineVars.is_in_history) {
  4656. if (line.charAt(1) === ':') {
  4657. multilineVars.is_in_history = false;
  4658. parseLine(line);
  4659. } else
  4660. tune.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)));
  4661. } else if (multilineVars.inTextBlock) {
  4662. if (line.startsWith("%%endtext")) {
  4663. //tune.addMetaText("textBlock", multilineVars.textBlock);
  4664. tune.addText(multilineVars.textBlock);
  4665. multilineVars.inTextBlock = false;
  4666. }
  4667. else {
  4668. if (line.startsWith("%%"))
  4669. multilineVars.textBlock += ' ' + line.substring(2);
  4670. else
  4671. multilineVars.textBlock += ' ' + line;
  4672. }
  4673. } else if (multilineVars.inPsBlock) {
  4674. if (line.startsWith("%%endps")) {
  4675. // Just ignore postscript
  4676. multilineVars.inPsBlock = false;
  4677. }
  4678. else
  4679. multilineVars.textBlock += ' ' + line;
  4680. } else
  4681. parseLine(line);
  4682. multilineVars.iChar += line.length + 1;
  4683. });
  4684. var ph = 11*72;
  4685. var pl = 8.5*72;
  4686. switch (multilineVars.papersize) {
  4687. //case "letter": ph = 11*72; pl = 8.5*72; break;
  4688. case "legal": ph = 14*72; pl = 8.5*72; break;
  4689. case "A4": ph = 11.7*72; pl = 8.3*72; break;
  4690. }
  4691. if (multilineVars.landscape) {
  4692. var x = ph;
  4693. ph = pl;
  4694. pl = x;
  4695. }
  4696. tune.cleanUp(pl, ph);
  4697. } catch (err) {
  4698. if (err !== "normal_abort")
  4699. throw err;
  4700. }
  4701. };
  4702. }
  4703. var tools = {
  4704. letterForPitch : function(pitch) {
  4705. return "CDEFGAB".charAt((pitch + 700) % 7);
  4706. },
  4707. symbolForAcc : function(acc) {
  4708. if (acc == 'dbl_flat') return 'bb';
  4709. if (acc == 'flat') return 'b';
  4710. if (acc == 'natural') return '=';
  4711. if (acc == 'sharp') return '#';
  4712. if (acc == 'dbl_sharp') return '##';
  4713. return '';
  4714. },
  4715. accidentalForKey : function(pitch, key) {
  4716. var acc = null;
  4717. var note = this.letterForPitch(pitch).toLowerCase();
  4718. key.accidentals.each(function(value) {
  4719. if (value.note == note) acc = value.acc;
  4720. });
  4721. return acc;
  4722. },
  4723. accidentalForNote : function(note, key) {
  4724. var pitch = note.pitch;
  4725. var acc = note.accidental;
  4726. if (null == key) acc || null;
  4727. return (acc && acc != "none") ? acc : this.accidentalForKey(pitch, key);
  4728. },
  4729. // converts name-indexed pitch (C=0 D=1 etc) to halfstep-indexed height (C=0 D=2 E=4 F=5)
  4730. heightForPitch : function(pitch) {
  4731. pitch += 700; // get rid of negative numbers
  4732. var quotient = Math.floor(pitch/7);
  4733. var remainder = pitch % 7;
  4734. var strictHeight = [0, 2, 4, 5, 7, 9, 11][remainder];
  4735. return (quotient * 12 + strictHeight) - 1200;
  4736. },
  4737. frequency : function(height, acc) {
  4738. n = height - 5; // half steps up from A 440
  4739. if ('dbl_flat' == acc) n -= 2;
  4740. if ('flat' == acc) n -= 1;
  4741. if ('sharp' == acc) n += 1;
  4742. if ('dbl_flat' == acc) n += 2;
  4743. return Math.pow(2, n/12) * 440;
  4744. },
  4745. frequencyForNote : function(note, key) {
  4746. var height = this.heightForPitch(note.pitch);
  4747. var acc = this.accidentalForNote(note, key);
  4748. return this.frequency(height, acc);
  4749. },
  4750. millisecondsForNote : function(note, tempo) {
  4751. var bpm = tempo.bpm;
  4752. var beatLength = tempo.duration[0]; // why is this an array?
  4753. var beats = note.duration / beatLength;
  4754. return 60000 * beats/bpm;
  4755. },
  4756. round : function(num) {
  4757. return Math.floor(num * 1000)/1000;
  4758. },
  4759. isCompound : function(meter) {
  4760. return (meter && meter.num && meter.num % 3 == 0);
  4761. },
  4762. printTune : function(abc) {
  4763. var parser = new AbcParse();
  4764. parser.parse(abc);
  4765. var tune = parser.getTune();
  4766. print(JSON.stringify(tune));
  4767. },
  4768. };
  4769. function abc2pd(abc) {
  4770. var parser = new AbcParse();
  4771. parser.parse(abc);
  4772. var tune = parser.getTune();
  4773. //print(JSON.stringify(tune));
  4774. var key = tune.lines[0].staff[0].key;
  4775. var meter = tune.lines[0].staff[0].meter;
  4776. var tempo = tune.metaText.tempo || { bpm:100, duration:[0.25] };
  4777. var elements = tune.lines[0].staff[0].voices[0];
  4778. var results = [];
  4779. var tying = 0;
  4780. var slurDepth = 0;
  4781. var tupletRatio = 1;
  4782. tune.lines.each(function(line) {
  4783. meter = line.staff[0].meter || meter;
  4784. key = line.staff[0].key || key
  4785. line.staff[0].voices[0].each(function(elem) {
  4786. //console.log(elem);
  4787. if (elem.el_type == "key") key = elem;
  4788. if (elem.el_type == "meter") meter = elem;
  4789. if (elem.el_type == "note") {
  4790. var ms = tools.millisecondsForNote(elem, tempo);
  4791. var freq;
  4792. var symbol;
  4793. // adjust length for tuplets
  4794. if (elem.startTriplet) {
  4795. //print('startTriplet: ' + elem.startTriplet);
  4796. var n = tools.isCompound(meter) ? 2 : 3;
  4797. tupletRatio = [1, 1, 3/2, 2/3, 3/4, n/5, 2/6, n/7, 3/8, n/9][elem.startTriplet];
  4798. // (tuplet interpretation according to http://www.lesession.co.uk/abc/abc_notation_part2.htm#ets)
  4799. }
  4800. ms *= tupletRatio;
  4801. if (elem.endTriplet) {
  4802. //print('endTriplet');
  4803. tupletRatio = 1;
  4804. }
  4805. if (elem.rest) {
  4806. freq = 0;
  4807. symbol = '%';
  4808. } else {
  4809. freq = tools.frequencyForNote(elem.pitches[0], key);
  4810. var letter = tools.letterForPitch(elem.pitches[0].pitch);
  4811. var acc = tools.accidentalForNote(elem.pitches[0], key);
  4812. var accSymbol = tools.symbolForAcc(acc);
  4813. symbol = letter + accSymbol;
  4814. if (elem.pitches[0].endTie) tying = false;
  4815. if (elem.pitches[0].startTie) tying = true;
  4816. if (elem.pitches[0].endSlur) slurDepth -= 1;
  4817. if (elem.pitches[0].startSlur) slurDepth += 1;
  4818. }
  4819. results.push(tools.round(freq), tools.round(ms));
  4820. if (tying || slurDepth > 0) results.push("TIE");
  4821. }
  4822. });
  4823. });
  4824. if (results[results.length-1] == "TIE") results.pop();
  4825. return results.join(" ");
  4826. }
  4827. var abc = arguments[0];
  4828. print(abc2pd(abc));
  4829. // TODO: move this to a separate file
  4830. // ASSERT
  4831. function AssertException(message) { this.message = message; }
  4832. AssertException.prototype.toString = function () { return 'AssertException: ' + this.message; }
  4833. function assert(exp, message) {
  4834. if (!exp) {
  4835. print(message);
  4836. throw new AssertException(message);
  4837. }
  4838. }
  4839. // TODO: move tests to a separate file
  4840. function testHeightForPitch()
  4841. {
  4842. assert(tools.heightForPitch(-1) == -1);
  4843. assert(tools.heightForPitch(0) == 0);
  4844. assert(tools.heightForPitch(1) == 2);
  4845. assert(tools.heightForPitch(2) == 4);
  4846. assert(tools.heightForPitch(3) == 5);
  4847. assert(tools.heightForPitch(4) == 7);
  4848. assert(tools.heightForPitch(5) == 9);
  4849. assert(tools.heightForPitch(6) == 11);
  4850. assert(tools.heightForPitch(7) == 12);
  4851. assert(tools.heightForPitch(8) == 14);
  4852. assert(tools.heightForPitch(15) == 26);
  4853. }
  4854. testHeightForPitch();
  4855. function testAbc2pd(abc, pd)
  4856. {
  4857. var actual = abc2pd(abc);
  4858. if (actual != pd) tools.printTune(abc);
  4859. assert(abc2pd(abc) == pd, "For abc:\n" + abc + "\n expected " + pd + "\n got " + abc2pd(abc));
  4860. }
  4861. // basic
  4862. var abc = "CGEA";
  4863. var pd = "329.627 300 493.883 300 415.304 300 554.365 300";
  4864. testAbc2pd(abc, pd);
  4865. // longer last note
  4866. var abc = "CGEA2";
  4867. var pd = "329.627 300 493.883 300 415.304 300 554.365 600";
  4868. testAbc2pd(abc, pd);
  4869. // '<' notation
  4870. var abc = "CGE<A";
  4871. var pd = "329.627 300 493.883 300 415.304 150 554.365 450";
  4872. testAbc2pd(abc, pd);
  4873. // with a rest
  4874. var abc = "CGEzA";
  4875. var pd = "329.627 300 493.883 300 415.304 300 0 300 554.365 300";
  4876. testAbc2pd(abc, pd);
  4877. // tempo
  4878. abc = "Q:1/8=1\nCGEA2";
  4879. pd = "329.627 60000 493.883 60000 415.304 60000 554.365 120000";
  4880. testAbc2pd(abc, pd);
  4881. // tuplet
  4882. abc = "(3CGE A";
  4883. pd = "329.627 200 493.883 200 415.304 200 554.365 300";
  4884. testAbc2pd(abc, pd);
  4885. // slur (tie notation)
  4886. abc = "G-C-E A2";
  4887. pd = "493.883 300 TIE 329.627 300 TIE 415.304 300 554.365 600";
  4888. testAbc2pd(abc, pd);
  4889. // slur (slur notation)
  4890. abc = "(GCE)A";
  4891. pd = "493.883 300 TIE 329.627 300 TIE 415.304 300 554.365 300";
  4892. testAbc2pd(abc, pd);