not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

494 lines
24 KiB

  1. // Copyright (c) 2014-2016 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // This header is prepended to the Lilypond output.
  12. const LILYPONDHEADER = '\\version "2.18.2"\n\n% ****************************************************************\n% \n% WHAT IS THIS? -- This is a LilyPond file generated from Music\n% Blocks software (Read about it at www.musicblocks.net).\n% \n% DOWNLOAD LILYPOND -- In order to create notation with this file,\n% you will need to download and install LilyPond software onto your\n% computer (http://lilypond.org/download.html). Frescobaldi\n% software is also handy for editing LilyPond files\n% (http://frescobaldi.org/download).\n% \n% LILYPOND INSTRUCTIONS -- For instructions on how to further\n% manipulate musical notation using LilyPond software, please\n% read the Introduction (http://lilypond.org/text-input.html) and\n% the Manual\n% (http://lilypond.org/doc/v2.18/Documentation/learning/index.html).\n% \n% GLOSSARY -- A glossary with helpful examples may be found here\n% (http://www.lilypond.org/doc/v2.19/Documentation/music-glossary/).\n% \n% MUTOPIA -- You may also benefit from studying scores from the\n% Mutopia Project website, which has freely sharable music notation\n% generated with LilyPond (http://www.mutopiaproject.org/).\n% \n% LILYBIN -- You can explore your Lilypond output in a web browser at\n% (http://lilybin.com/).\n% \n% COMMENTS -- Some of the code below is commented out. You can\n% enable it by deleting the % that precedes the text or, in the\n% case of a commented section, deleting the %{ and %} that surrounds\n% the section.\n% \n% ****************************************************************\n\n% Please add your own name, the title of your musical creation,\n% and the intended copyright below.\n% The copyright is great for sharing (and re-sharing)!\n% Read more about it here (http://creativecommons.org/licenses/by-sa/4.0/).\n% Of course, you can use any copyright you like -- you made it!\n\\header {\n dedication = \\markup {\n \\abs-fontsize #8 \\sans "Made with LilyPond and Music Blocks" \\with-url #"http://walterbender.github.io/musicblocks/" {\n \\abs-fontsize #8 \\sans "(http://walterbender.github.io/musicblocks/)"\n }\n }\n title = "My Music Blocks Creation"\n% subtitle = "Subtitle"\n% instrument = "Instrument"\n composer = "Mr. Mouse"\n% arranger = "Arranger"\n copyright = "Mr. Mouse (c) 2015 -- CC-BY-SA"\n tagline = "Made from Music Blocks v.0.9"\n footer = \\markup {\n \\with-url #"http://walterbender.github.io/musicblocks/" "Made with Music Blocks Software v.0.9." Engraved on \\simple #(strftime "%Y-%m-%d" (localtime (current-time)))\n }\n currentYear = \\markup {\n \\simple #(strftime "%Y" (localtime (current-time)))\n }\n copyTag = " free to distribute, modify, and perform"\n copyType = \\markup {\n \\with-url #"http://creativecommons.org/licenses/by-sa/3.0/" "Creative Commons Attribution ShareAlike 3.0 (Unported) License "\n }\n copyright = \\markup {\n \\override #\'(baseline-skip . 0 ) \\right-column {\n \\sans \\bold \\with-url #"http://musicblocks.net" {\n \\abs-fontsize #9 "Music " \\concat {\n \\abs-fontsize #12 \\with-color #white \\char ##x01C0 \\abs-fontsize #9 "Blocks "\n }\n }\n }\n \\override #\'(baseline-skip . 0 ) \\center-column {\n \\abs-fontsize #11.9 \\with-color #grey \\bold {\n \\char ##x01C0 \\char ##x01C0\n }\n }\n \\override #\'(baseline-skip . 0 ) \\column {\n \\abs-fontsize #8 \\sans \\concat {\n " Typeset using " \\with-url #"http://www.lilypond.org" "LilyPond software " \\char ##x00A9 " " \\currentYear " by " \\composer " " \\char ##x2014 " " \\footer\n }\n \\concat {\n \\concat {\n \\abs-fontsize #8 \\sans {\n " " \\copyType \\char ##x2014 \\copyTag\n }\n }\n \\abs-fontsize #13 \\with-color #white \\char ##x01C0\n }\n }\n }\n tagline = ##f\n}\n\n% To change the meter make adjustments in the following section.\n% You must also delete the % before \\meter e
  13. //.TRANS Animal names used in Lilypond output
  14. const RODENTS = [_('mouse'), _('brown rat'), _('mole'), _('chipmunk'), _('red squirrel'), _('guinea pig'), _('capybara'), _('coypu'), _('black rat'), _('grey squirrel'), _('flying squirrel'), _('bat')];
  15. const RODENTSEN = ['mouse', 'brown rat', 'mole', 'chipmunk', 'red squirrel', 'guinea pig', 'capybara', 'coypu', 'black rat', 'grey squirrel', 'flying squirrel', 'bat'];
  16. //.TRANS Abbreviations for names used in Lilypind output, e.g., m for mouse
  17. const RODENTSSHORT = [_('m'), _('br'), _('ml'), _('ch'), _('rs'), _('gp'), _('cb'), _('cp'), _('bk'), _('gs'), _('fs'), _('bt')];
  18. const RODENTSSHORTEN = ['m', 'br', 'ml', 'ch', 'rs', 'gp', 'cb', 'cp', 'bk', 'gs', 'fs', 'bt'];
  19. const LYNOTE = 0;
  20. const LYDURATION = 1;
  21. const LYDOTCOUNT = 2;
  22. const LYTUPLETVALUE = 3;
  23. const LYROUNDDOWN = 4;
  24. const LYINSIDECHORD = 5;
  25. const LYSTACCATO = 6;
  26. const CLEFS = ['treble', 'bass', 'bass_8', 'percussion'];
  27. getLilypondHeader = function () {
  28. return LILYPONDHEADER;
  29. }
  30. updateLilypondNotation = function (logo, note, duration, turtle, insideChord) {
  31. var obj = durationToNoteValue(duration);
  32. if (!(turtle in logo.lilypondStaging)) {
  33. logo.lilypondStaging[turtle] = [];
  34. }
  35. if (logo.turtles.turtleList[turtle].drum) {
  36. note = "c'";
  37. }
  38. logo.lilypondStaging[turtle].push([note, obj[0], obj[1], obj[2], obj[3], insideChord, logo.staccato[turtle].length > 0 && last(logo.staccato[turtle]) > 0]);
  39. };
  40. processLilypondNotes = function (logo, turtle) {
  41. // obj = [instructions] or
  42. // obj = [note, duration, dotCount, tupletValue, roundDown, insideChord, staccato, tie]
  43. logo.lilypondNotes[turtle] = '';
  44. function __toLilynote (note) {
  45. // Lilypond notes use is for sharp, es for flat,
  46. // , and ' for shifts in octave.
  47. // Also, notes must be lowercase.
  48. // And the octave bounday is at C, not A.
  49. // Convert frequencies here.
  50. if (typeof(note) === 'number') {
  51. var pitchObj = frequencyToPitch(note);
  52. note = pitchObj[0] + pitchObj[1];
  53. }
  54. return note.replace(/♯/g, 'is').replace(/♭/g, 'es').replace(/10/g, "''''''''").replace(/1/g, ',,').replace(/2/g, ',').replace(/3/g, '').replace(/4/g, "'").replace(/5/g, "''").replace(/6/g, "'''").replace(/7/g, "''''").replace(/8/g, "''''''").replace(/9/g, "'''''''").toLowerCase();
  55. };
  56. var counter = 0;
  57. var queueSlur = false;
  58. var articulation = false;
  59. var targetDuration = 0;
  60. var tupletDuration = 0;
  61. for (var i = 0; i < logo.lilypondStaging[turtle].length; i++) {
  62. obj = logo.lilypondStaging[turtle][i];
  63. if (typeof(obj) === 'string') {
  64. if (obj === 'break') {
  65. if (i > 0) {
  66. logo.lilypondNotes[turtle] += '\n';
  67. }
  68. counter = 0;
  69. } else {
  70. if (obj === '(') {
  71. // The ( is added after the first note.
  72. queueSlur = true;
  73. } else if (obj === 'begin articulation') {
  74. articulation = true;
  75. } else if (obj === 'end articulation') {
  76. articulation = false;
  77. } else {
  78. logo.lilypondNotes[turtle] += obj;
  79. }
  80. }
  81. } else {
  82. if (counter % 8 === 0 && counter > 0) {
  83. logo.lilypondNotes[turtle] += '\n';
  84. }
  85. counter += 1;
  86. var note = __toLilynote(obj[LYNOTE]);
  87. var incompleteTuplet = 0; // An incomplete tuplet
  88. // If it is a tuplet, look ahead to see if it is complete.
  89. // While you are at it, add up the durations.
  90. if (obj[LYTUPLETVALUE] != null) {
  91. targetDuration = (1 / logo.lilypondStaging[turtle][i][LYDURATION]);
  92. tupletDuration = (1 / logo.lilypondStaging[turtle][i][LYROUNDDOWN]);
  93. var j = 1;
  94. var k = 1;
  95. while (k < obj[LYTUPLETVALUE]) {
  96. if (i + j >= logo.lilypondStaging[turtle].length) {
  97. incompleteTuplet = j;
  98. break;
  99. }
  100. if (logo.lilypondStaging[turtle][i + j][LYINSIDECHORD] > 0 && logo.lilypondStaging[turtle][i + j][LYINSIDECHORD] === logo.lilypondStaging[turtle][i + j - 1][LYINSIDECHORD]) {
  101. // In a chord, so jump to next note.
  102. j++;
  103. } else if (logo.lilypondStaging[turtle][i + j][LYTUPLETVALUE] !== obj[LYTUPLETVALUE]) {
  104. incompleteTuplet = j;
  105. break;
  106. } else {
  107. targetDuration += (1 / logo.lilypondStaging[turtle][i + j][LYDURATION]);
  108. tupletDuration += (1 / logo.lilypondStaging[turtle][i + j][LYROUNDDOWN]);
  109. j++; // Jump to next note.
  110. k++; // Increment notes in tuplet.
  111. }
  112. }
  113. }
  114. function __processTuplet(logo, turtle, i, count) {
  115. var j = 0;
  116. var k = 0;
  117. while (k < count) {
  118. var tupletDuration = 2 * logo.lilypondStaging[turtle][i + j][LYDURATION];
  119. // Are we in a chord?
  120. if (logo.lilypondStaging[turtle][i + j][LYINSIDECHORD] > 0) {
  121. // Is logo the first note in the chord?
  122. if ((i === 0 && j === 0) || logo.lilypondStaging[turtle][i + j - 1][LYINSIDECHORD] !== logo.lilypondStaging[turtle][i + j][LYINSIDECHORD]) {
  123. logo.lilypondNotes[turtle] += '<';
  124. }
  125. logo.lilypondNotes[turtle] += __toLilynote(logo.lilypondStaging[turtle][i + j][LYNOTE]);
  126. if (obj[LYSTACCATO]) {
  127. logo.lilypondNotes[turtle] += '\\staccato';
  128. }
  129. logo.lilypondNotes[turtle] += ' ';
  130. // Is logo the last note in the chord?
  131. if (i + j === logo.lilypondStaging[turtle].length - 1 || logo.lilypondStaging[turtle][i + j + 1][LYINSIDECHORD] !== logo.lilypondStaging[turtle][i + j][LYINSIDECHORD]) {
  132. logo.lilypondNotes[turtle] += '>' + logo.lilypondStaging[turtle][i + j + 1][LYROUNDDOWN] + ' ';
  133. k++; // Increment notes in tuplet.
  134. }
  135. j++;
  136. } else {
  137. logo.lilypondNotes[turtle] += __toLilynote(logo.lilypondStaging[turtle][i + j][LYNOTE]) + logo.lilypondStaging[turtle][i + j][LYROUNDDOWN];
  138. if (obj[LYSTACCATO]) {
  139. logo.lilypondNotes[turtle] += '\\staccato';
  140. }
  141. logo.lilypondNotes[turtle] += ' ';
  142. j++; // Jump to next note.
  143. k++; // Increment notes in tuplet.
  144. }
  145. }
  146. // Workaround to a Lilypond "feature": if a slur
  147. // ends on a tuplet, the closing ) must be inside
  148. // the closing } of the tuplet.
  149. if (i + j - 1 < logo.lilypondStaging[turtle].length - 1) {
  150. var nextObj = logo.lilypondStaging[turtle][i + j];
  151. if (typeof(nextObj) === 'string' && nextObj === ')') {
  152. logo.lilypondNotes[turtle] += ')} ';
  153. i += 1;
  154. } else {
  155. logo.lilypondNotes[turtle] += '} ';
  156. }
  157. } else {
  158. logo.lilypondNotes[turtle] += '} ';
  159. }
  160. return j;
  161. };
  162. if (obj[LYTUPLETVALUE] > 0) {
  163. // lilypond tuplets look like logo: \tuplet 3/2 { f8 g a }
  164. // multiplier = tupletDuration / targetDuration
  165. // e.g., (3/8) / (1/4) = (3/8) * 4 = 12/8 = 3/2
  166. // There may be chords embedded.
  167. if (incompleteTuplet === 0) {
  168. var tupletFraction = toFraction(tupletDuration / targetDuration);
  169. logo.lilypondNotes[turtle] += '\\tuplet ' + tupletFraction[0] + '/' + tupletFraction[1] + ' { ';
  170. i += __processTuplet(logo, turtle, i, obj[LYTUPLETVALUE]) - 1;
  171. } else {
  172. var tupletFraction = toFraction(obj[LYTUPLETVALUE] / incompleteTuplet);
  173. logo.lilypondNotes[turtle] += '\\tuplet ' + tupletFraction[0] + '/' + tupletFraction[1] + ' { ';
  174. i += __processTuplet(logo, turtle, i, incompleteTuplet) - 1;
  175. }
  176. targetDuration = 0;
  177. tupletDuration = 0;
  178. } else {
  179. if (obj[LYINSIDECHORD] > 0) {
  180. // Is logo the first note in the chord?
  181. if (i === 0 || logo.lilypondStaging[turtle][i - 1][LYINSIDECHORD] !== obj[LYINSIDECHORD]) {
  182. // Open the chord.
  183. logo.lilypondNotes[turtle] += '<';
  184. }
  185. logo.lilypondNotes[turtle] += (note);
  186. // Is logo the last note in the chord?
  187. if (i === logo.lilypondStaging[turtle].length - 1 || logo.lilypondStaging[turtle][i + 1][LYINSIDECHORD] !== obj[LYINSIDECHORD]) {
  188. // Close the chord and add note duration.
  189. logo.lilypondNotes[turtle] += '>';
  190. logo.lilypondNotes[turtle] += obj[LYDURATION];
  191. for (var d = 0; d < obj[LYDOTCOUNT]; d++) {
  192. logo.lilypondNotes[turtle] += '.';
  193. }
  194. if (articulation) {
  195. logo.lilypondNotes[turtle] += '->';
  196. }
  197. logo.lilypondNotes[turtle] += ' ';
  198. }
  199. } else {
  200. logo.lilypondNotes[turtle] += (note + obj[LYDURATION]);
  201. for (var d = 0; d < obj[LYDOTCOUNT]; d++) {
  202. logo.lilypondNotes[turtle] += '.';
  203. }
  204. if (articulation) {
  205. logo.lilypondNotes[turtle] += '->';
  206. }
  207. }
  208. if (obj[LYSTACCATO]) {
  209. logo.lilypondNotes[turtle] += '\\staccato';
  210. }
  211. targetDuration = 0;
  212. tupletDuration = 0;
  213. }
  214. logo.lilypondNotes[turtle] += ' ';
  215. if (queueSlur) {
  216. queueSlur = false;
  217. logo.lilypondNotes[turtle] += '(';
  218. }
  219. }
  220. }
  221. };
  222. lilypondLineBreak = function(logo, turtle) {
  223. if (logo.lilypondStaging[turtle] == undefined) {
  224. logo.lilypondStaging[turtle] = [];
  225. }
  226. logo.lilypondStaging[turtle].push('break');
  227. };
  228. lilypondBeginArticulation = function(logo, turtle) {
  229. if (logo.lilypondStaging[turtle] == undefined) {
  230. logo.lilypondStaging[turtle] = [];
  231. }
  232. logo.lilypondStaging[turtle].push('begin articulation');
  233. };
  234. lilypondEndArticulation = function(logo, turtle) {
  235. logo.lilypondStaging[turtle].push('end articulation');
  236. };
  237. lilypondBeginCrescendo = function(logo, turtle, factor) {
  238. if (logo.lilypondStaging[turtle] == undefined) {
  239. logo.lilypondStaging[turtle] = [];
  240. }
  241. if (factor > 0) {
  242. logo.lilypondStaging[turtle].push('\\< ');
  243. } else {
  244. logo.lilypondStaging[turtle].push('\\> ');
  245. }
  246. };
  247. lilypondEndCrescendo = function(logo, turtle) {
  248. logo.lilypondStaging[turtle].push('\\! ');
  249. };
  250. lilypondBeginSlur = function(logo, turtle) {
  251. if (logo.lilypondStaging[turtle] == undefined) {
  252. logo.lilypondStaging[turtle] = [];
  253. }
  254. logo.lilypondStaging[turtle].push('(');
  255. };
  256. lilypondEndSlur = function(logo, turtle) {
  257. logo.lilypondStaging[turtle].push(') ');
  258. };
  259. lilypondInsertTie = function(logo, turtle) {
  260. if (logo.lilypondStaging[turtle] == undefined) {
  261. logo.lilypondStaging[turtle] = [];
  262. }
  263. logo.lilypondStaging[turtle].push('~');
  264. };
  265. lilypondRemoveTie = function(logo, turtle) {
  266. logo.lilypondStaging[turtle].pop();
  267. };
  268. saveLilypondOutput = function(logo, saveName) {
  269. var turtleCount = 0;
  270. var clef = [];
  271. for (var t in logo.lilypondStaging) {
  272. turtleCount += 1;
  273. }
  274. console.log('saving as lilypond: ' + turtleCount);
  275. logo.lilypondOutput += '% You can change the MIDI instruments below to anything on logo list:\n% (http://lilypond.org/doc/v2.18/documentation/notation/midi-instruments)\n\n';
  276. var c = 0;
  277. for (var t in logo.lilypondStaging) {
  278. if (logo.lilypondStaging[t].length > 0) {
  279. var octaveTotal = 0;
  280. var noteCount = 0;
  281. for (var i = 0; i < logo.lilypondStaging[t].length; i++) {
  282. obj = logo.lilypondStaging[t][i];
  283. if (typeof(obj) === 'object') {
  284. if (obj[0].length < 2) {
  285. // Test for rests
  286. } else {
  287. if (typeof(obj[0]) === 'string') {
  288. octaveTotal += Number(obj[0].substr(-1));
  289. } else {
  290. var pitchObj = frequencyToPitch(obj[0]);
  291. octaveTotal += pitchObj[1];
  292. }
  293. noteCount += 1;
  294. }
  295. }
  296. }
  297. if (logo.turtles.turtleList[t].drum) {
  298. clef.push('percussion');
  299. } else if (noteCount > 0) {
  300. // console.log(t + ': ' + octaveTotal + ' ' + noteCount);
  301. switch (Math.floor(octaveTotal / noteCount)) {
  302. case 0:
  303. case 1:
  304. case 2:
  305. clef.push('bass_8');
  306. break;
  307. case 3:
  308. clef.push('bass');
  309. break;
  310. default:
  311. clef.push('treble');
  312. break;
  313. }
  314. } else {
  315. clef.push('treble');
  316. }
  317. processLilypondNotes(logo, t);
  318. var instrumentName = logo.turtles.turtleList[t].name;
  319. if (instrumentName === _('start') || instrumentName === _('start drum')) {
  320. instrumentName = RODENTS[t % 12];
  321. } else if (instrumentName === t.toString()) {
  322. instrumentName = RODENTS[t % 12];
  323. }
  324. if (instrumentName === "") {
  325. instrumentName = RODENTSEN[t % 12];
  326. }
  327. instrumentName = instrumentName.replace(/ /g, '_');
  328. logo.lilypondOutput += instrumentName + ' = {\n';
  329. logo.lilypondOutput += '% \\meter\n';
  330. logo.lilypondOutput += logo.lilypondNotes[t];
  331. // Add bar to last turtle's output.
  332. if (c === turtleCount - 1) {
  333. logo.lilypondOutput += ' \\bar "|."'
  334. }
  335. logo.lilypondOutput += '\n}\n\n';
  336. var shortInstrumentName = RODENTSSHORT[t % 12];
  337. if (shortInstrumentName === "") {
  338. shortInstrumentName = RODENTSSHORTEN[t % 12];
  339. }
  340. logo.lilypondOutput += instrumentName.replace(/ /g, '_') + 'Voice = ';
  341. if (logo.turtles.turtleList[t].drum) {
  342. logo.lilypondOutput += '\\new DrumStaff \\with {\n';
  343. // logo.lilypondOutput += ' \\drummode {\n hihat4 hh bassdrum bd\n }\n';
  344. } else {
  345. logo.lilypondOutput += '\\new Staff \\with {\n';
  346. }
  347. logo.lilypondOutput += ' \\clef "' + last(clef) + '"\n';
  348. logo.lilypondOutput += ' instrumentName = "' + instrumentName + '"\n';
  349. logo.lilypondOutput += ' shortInstrumentName = "' + shortInstrumentName + '"\n';
  350. logo.lilypondOutput += ' midiInstrument = "acoustic grand"\n';
  351. // Automatic note splitting
  352. logo.lilypondOutput += '\n \\remove "Note_heads_engraver"\n \\consists "Completion_heads_engraver"\n \\remove "Rest_engraver"\n \\consists "Completion_rest_engraver"\n'
  353. logo.lilypondOutput += '} { \\clef "' + last(clef) + '" \\' + instrumentName.replace(/ /g, '_') + ' }\n\n';
  354. }
  355. c += 1;
  356. }
  357. // Begin the SCORE section.
  358. logo.lilypondOutput += '\n\\score {\n';
  359. logo.lilypondOutput += ' <<\n';
  360. // Sort the staffs, treble on top, bass_8 on the bottom.
  361. for (var c = 0; c < CLEFS.length; c++) {
  362. var i = 0;
  363. for (var t in logo.lilypondNotes) {
  364. if (clef[i] === CLEFS[c]) {
  365. if (logo.lilypondStaging[t].length > 0) {
  366. var instrumentName = logo.turtles.turtleList[t].name;
  367. if (instrumentName === _('start') || instrumentName === _('start drum')) {
  368. instrumentName = RODENTS[t % 12];
  369. } else if (instrumentName === t.toString()) {
  370. instrumentName = RODENTS[t % 12];
  371. }
  372. instrumentName = instrumentName.replace(/ /g, '_');
  373. logo.lilypondOutput += ' \\' + instrumentName + 'Voice\n';
  374. }
  375. }
  376. }
  377. }
  378. // Add GUITAR TAB in comments.
  379. logo.lilypondOutput += '\n\n% GUITAR TAB SECTION\n% Delete the %{ and %} below to include guitar tablature output.\n%{\n \\new TabStaff = "guitar tab" \n <<\n \\clef moderntab\n';
  380. for (var c = 0; c < CLEFS.length; c++) {
  381. var i = 0;
  382. for (var t in logo.lilypondNotes) {
  383. if (clef[i] === CLEFS[c]) {
  384. if (logo.lilypondStaging[t].length > 0) {
  385. var instrumentName = logo.turtles.turtleList[t].name;
  386. if (instrumentName === _('start') || instrumentName === _('start drum')) {
  387. instrumentName = RODENTS[t % 12]
  388. } else if (instrumentName === t.toString()) {
  389. instrumentName = RODENTS[t % 12];
  390. }
  391. instrumentName = instrumentName.replace(/ /g, '_');
  392. logo.lilypondOutput += ' \\context TabVoice = "'+ instrumentName + '" \\' + instrumentName.replace(/ /g, '_') + '\n';
  393. }
  394. }
  395. }
  396. }
  397. // Close the SCORE sections.
  398. logo.lilypondOutput += ' >>\n%}\n';
  399. logo.lilypondOutput += '\n >>\n \\layout {}\n\n';
  400. // Add MIDI OUTPUT in comments.
  401. logo.lilypondOutput += '% MIDI SECTION\n% Delete the %{ and %} below to include MIDI output.\n%{\n\\midi {\n \\tempo 4=90\n}\n%}\n\n}\n\n';
  402. // ADD TURTLE BLOCKS CODE HERE
  403. logo.lilypondOutput += '% MUSIC BLOCKS CODE\n';
  404. logo.lilypondOutput += '% Below is the code for the Music Blocks project that generated logo Lilypond file.\n%{\n\n';
  405. // prepareExport() returns json-encoded project data.
  406. var projectData = prepareExport();
  407. logo.lilypondOutput += projectData.replace(/]],/g, ']],\n');
  408. logo.lilypondOutput += '\n%}\n\n';
  409. doSaveLilypond(logo, saveName);
  410. };