abcjs-rails 2.3 → 3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/abcjs/api/abc_animation.js +166 -4
  3. data/app/assets/javascripts/abcjs/api/abc_tunebook.js +170 -15
  4. data/app/assets/javascripts/abcjs/data/abc_tune.js +69 -47
  5. data/app/assets/javascripts/abcjs/edit/abc_editor.js +78 -37
  6. data/app/assets/javascripts/abcjs/midi/abc_midi_controls.js +513 -0
  7. data/app/assets/javascripts/abcjs/midi/abc_midi_create.js +29 -7
  8. data/app/assets/javascripts/abcjs/midi/abc_midi_flattener.js +21 -18
  9. data/app/assets/javascripts/abcjs/midi/abc_midi_js_preparer.js +233 -0
  10. data/app/assets/javascripts/abcjs/midi/abc_midi_renderer.js +3 -229
  11. data/app/assets/javascripts/abcjs/midi/abc_midi_sequencer.js +11 -3
  12. data/app/assets/javascripts/abcjs/parse/abc_common.js +24 -0
  13. data/app/assets/javascripts/abcjs/parse/abc_parse.js +58 -16
  14. data/app/assets/javascripts/abcjs/parse/abc_parse_header.js +4 -1
  15. data/app/assets/javascripts/abcjs/parse/abc_parse_key_voice.js +5 -0
  16. data/app/assets/javascripts/abcjs/write/abc_absolute_element.js +9 -2
  17. data/app/assets/javascripts/abcjs/write/abc_abstract_engraver.js +71 -19
  18. data/app/assets/javascripts/abcjs/write/abc_beam_element.js +11 -3
  19. data/app/assets/javascripts/abcjs/write/abc_brace_element.js +51 -0
  20. data/app/assets/javascripts/abcjs/write/abc_create_clef.js +3 -3
  21. data/app/assets/javascripts/abcjs/write/abc_create_key_signature.js +3 -3
  22. data/app/assets/javascripts/abcjs/write/abc_create_time_signature.js +3 -3
  23. data/app/assets/javascripts/abcjs/write/abc_crescendo_element.js +4 -1
  24. data/app/assets/javascripts/abcjs/write/abc_decoration.js +1 -1
  25. data/app/assets/javascripts/abcjs/write/abc_engraver_controller.js +10 -9
  26. data/app/assets/javascripts/abcjs/write/abc_glyphs.js +1 -1
  27. data/app/assets/javascripts/abcjs/write/abc_relative_element.js +1 -1
  28. data/app/assets/javascripts/abcjs/write/abc_renderer.js +85 -13
  29. data/app/assets/javascripts/abcjs/write/abc_staff_group_element.js +16 -0
  30. data/app/assets/javascripts/abcjs/write/abc_tempo_element.js +31 -11
  31. data/app/assets/javascripts/abcjs/write/abc_tie_element.js +8 -1
  32. data/lib/abcjs-rails/version.rb +1 -1
  33. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92eedfe222f72fee3b313a63bd3c2965c198dfd2
4
- data.tar.gz: 0a2092fb7c1c2b99bf67a628d310be52ac376816
3
+ metadata.gz: 2c266341445b98625e0ab4b98abcb6cdf4af18ae
4
+ data.tar.gz: c24e3d239349217bb149896dc3ff3221a949117e
5
5
  SHA512:
6
- metadata.gz: e9368ee3727320885cb1a5acede442e1256d3e8c2158d3a7ad7219e73adaddbf92a2c885d49dc57090f5f831e15e25d848cf2f332dff52818caee10b04d72484
7
- data.tar.gz: c1c82feb61cac98aa0ff32b75f02095c04278651682e21f2de515ae2bb6b067da31ca0d5c92cf66cb71248dee6930c05eba8e538b406ee4b48569b73432f9960
6
+ metadata.gz: 3ef48f03107fe57ba9c878decadacaf0c311a53db60f3a8f789b7387cd8d9304b5cdffcbf88e10db17a67794e83c34c380303078367d6995b5ea9dbfae121f94
7
+ data.tar.gz: e6fe7d8d56bc43083acb6b37f72a966b7803b546ebb84cb3b0f0da8e8caf8f1d1f4dd2ba5b513c8a8312a5aec76ce6513aa2cebca140669e7252e902d2d5d9d3
@@ -39,6 +39,28 @@ if (!window.ABCJS)
39
39
  return ret;
40
40
  }
41
41
 
42
+ // This finds the place in the stylesheets that contain the rule that matches the selector.
43
+ // If that selector is not found, then it creates the rule.
44
+ // We are doing this so that we can use a transition for animating the scrolling.
45
+ function getCssRule(selector) {
46
+ var rule;
47
+ for (var i = 0; i < document.styleSheets.length && rule === undefined; i++) {
48
+ var css = document.styleSheets[i];
49
+ var rules = css.rules;
50
+ if (rules) {
51
+ for (var j = 0; j < rules.length && rule === undefined; j++) {
52
+ if (rules[j].selectorText && rules[j].selectorText === selector)
53
+ rule = rules[j];
54
+ }
55
+ }
56
+ }
57
+ if (!rule) {
58
+ document.styleSheets[0].insertRule(selector + " { }", 1);
59
+ return getCssRule(selector);
60
+ }
61
+ return rule;
62
+ }
63
+
42
64
  function getBeatsPerMinute(tune, options) {
43
65
  // We either want to run the timer once per measure or once per beat. If we run it once per beat we need a multiplier for the measures.
44
66
  // So, first we figure out the beats per minute and the beats per measure, then depending on the type of animation, we can
@@ -55,6 +77,13 @@ if (!window.ABCJS)
55
77
  return bpm;
56
78
  }
57
79
 
80
+ var scrollTimer;
81
+ var animateTimer;
82
+ var cssRule;
83
+ var currentMargin;
84
+ var animationTarget;
85
+ var shouldResetOverflow;
86
+
58
87
  // This is a way to manipulate the written music on a timer. Their are two ways to manipulate the music: turn off each measure as it goes by,
59
88
  // and put a vertical cursor before the next note to play. The timer works at the speed of the original tempo of the music unless it is overwritten
60
89
  // in the options parameter.
@@ -66,8 +95,20 @@ if (!window.ABCJS)
66
95
  // hideFinishedMeasures: true or false [ false is the default ]
67
96
  // showCursor: true or false [ false is the default ]
68
97
  // bpm: number of beats per minute [ the default is whatever is in the Q: field ]
98
+ // scrollHorizontal: true or false [ false is the default ]
99
+ // scrollVertical: true or false [ false is the default ]
100
+ // scrollHint: true or false [ false is the default ]
101
+ //
102
+ // If scrollHorizontal is present, then we expect that the music was rendered with the viewportHorizontal parameter so there is a viewport wrapping the music div. (Note that this only works when there is a single line of music and there are no repeats, signo, or codas.)
103
+ // If scrollVertical or scrollHint is present, then we expect that the music was rendered with the viewportVertical parameter so there is a viewport wrapping the music div.
104
+ // If the music is larger than the viewport, then it scrolls as the music is being played.
69
105
  var stopNextTime = false;
70
106
  var cursor;
107
+
108
+ function setMargin(margin) {
109
+ cssRule.style.marginTop = -margin + "px";
110
+ currentMargin = margin;
111
+ }
71
112
  ABCJS.startAnimation = function(paper, tune, options) {
72
113
  if (paper.getElementsByClassName === undefined) {
73
114
  console.error("ABCJS.startAnimation: The first parameter must be a regular DOM element. (Did you pass a jQuery object or an ID?)");
@@ -77,6 +118,25 @@ if (!window.ABCJS)
77
118
  console.error("ABCJS.startAnimation: The second parameter must be a single tune. (Did you pass the entire array of tunes?)");
78
119
  return;
79
120
  }
121
+ if (options.scrollHorizontal || options.scrollVertical || options.scrollHint) {
122
+ // We assume that there is an extra div in this case, so adjust the paper if needed.
123
+ // This can be called either with the outer div or the inner div.
124
+ if (!hasClass(paper, 'abcjs-inner')) {
125
+ // Must be the outer div; hide the scrollbar and move in.
126
+ paper.scrollTop = 0; // In case the user has repositioned the scrollbar.
127
+ paper.style.overflow = "hidden";
128
+ paper = paper.children[0];
129
+ }
130
+ if (!hasClass(paper, 'abcjs-inner')) {
131
+ console.error("ABCJS.startAnimation: When using scrollHorizontal/scrollVertical/scrollHint, the music must have been rendered using viewportHorizontal/viewportVertical.");
132
+ return;
133
+ }
134
+ }
135
+ // Can only have one animation at a time, so make sure that it has been stopped.
136
+ ABCJS.stopAnimation();
137
+ animationTarget = paper;
138
+ shouldResetOverflow = options.scrollVertical || options.scrollHint;
139
+
80
140
  if (options.showCursor) {
81
141
  cursor = $('<div class="cursor" style="position: absolute;"></div>');
82
142
  $(paper).append(cursor);
@@ -87,9 +147,40 @@ if (!window.ABCJS)
87
147
  var beatsPerMinute = getBeatsPerMinute(tune, options);
88
148
  var beatsPerMillisecond = beatsPerMinute / 60000;
89
149
  var beatLength = tune.getBeatLength(); // This is the same units as the duration is stored in.
150
+ var totalBeats = 0;
151
+
152
+ var millisecondsPerHalfMeasure;
153
+ if (options.scrollVertical) {
154
+ var millisecondsPerBeat = 1/beatsPerMillisecond;
155
+ var beatsPerMeasure = 1/beatLength;
156
+ var millisecondsPerMeasure = millisecondsPerBeat * beatsPerMeasure;
157
+ millisecondsPerHalfMeasure = millisecondsPerMeasure / 2;
158
+ cssRule = getCssRule(".abcjs-inner");
159
+ }
90
160
 
91
161
  var startTime;
92
162
 
163
+ var initialWait = 2700;
164
+ var interval = 11;
165
+ var distance = 1;
166
+ var outer = paper.parentNode;
167
+ function scrolling() {
168
+ var currentPosition = paper.style.marginLeft;
169
+ if (currentPosition === "")
170
+ currentPosition = 0;
171
+ else
172
+ currentPosition = parseInt(currentPosition);
173
+ currentPosition -= distance;
174
+ paper.style.marginLeft = currentPosition + "px";
175
+ if (currentPosition > outer.offsetWidth - paper.scrollWidth)
176
+ scrollTimer = setTimeout(scrolling, interval);
177
+ }
178
+
179
+ if (options.scrollHorizontal) {
180
+ paper.style.marginLeft = "0px";
181
+ scrollTimer = setTimeout(scrolling, initialWait);
182
+ }
183
+
93
184
  function processMeasureHider(lineNum, measureNum) {
94
185
  var els = getAllElementsByClasses(paper, "l"+lineNum, "m"+measureNum);
95
186
 
@@ -102,6 +193,29 @@ if (!window.ABCJS)
102
193
  }
103
194
  }
104
195
 
196
+ function addVerticalInfo(timingEvents) {
197
+ // Add vertical info to the bar events: put the next event's top, and the event after the next measure's top.
198
+ var lastBarTop;
199
+ var lastBarBottom;
200
+ var lastEventTop;
201
+ var lastEventBottom;
202
+ for (var e = timingEvents.length-1; e >= 0; e--) {
203
+ var ev = timingEvents[e];
204
+ if (ev.type === 'bar') {
205
+ ev.top = lastEventTop;
206
+ ev.nextTop = lastBarTop;
207
+ lastBarTop = lastEventTop;
208
+
209
+ ev.bottom = lastEventBottom;
210
+ ev.nextBottom = lastBarBottom;
211
+ lastBarBottom = lastEventBottom;
212
+ } else if (ev.type === 'event') {
213
+ lastEventTop = ev.top;
214
+ lastEventBottom = ev.top +ev.height;
215
+ }
216
+ }
217
+ }
218
+
105
219
  function makeSortedArray(hash) {
106
220
  var arr = [];
107
221
  for (var k in hash) {
@@ -144,6 +258,8 @@ if (!window.ABCJS)
144
258
  var elements = voices[v].children;
145
259
  for (var elem=0; elem<elements.length; elem++) {
146
260
  var element = elements[elem];
261
+ if (element.hint)
262
+ break;
147
263
  if (element.duration > 0) {
148
264
  // There are 3 possibilities here: the note could stand on its own, the note could be tied to the next,
149
265
  // the note could be tied to the previous, and the note could be tied on both sides.
@@ -191,9 +307,32 @@ if (!window.ABCJS)
191
307
  }
192
308
  // now we have all the events, but if there are multiple voices then there may be events out of order or duplicated, so normalize it.
193
309
  timingEvents = makeSortedArray(eventHash);
310
+ totalBeats = timingEvents[timingEvents.length-1].time / beatLength;
311
+ if (options.scrollVertical) {
312
+ addVerticalInfo(timingEvents);
313
+ }
194
314
  }
195
315
  setupEvents(tune.engraver);
196
316
 
317
+ function isEndOfLine(currentNote) {
318
+ return currentNote.top !== currentNote.nextTop && currentNote.nextTop !== undefined;
319
+ }
320
+
321
+ function shouldScroll(outer, scrollPos, currentNote) {
322
+ var height = parseInt(outer.clientHeight, 10);
323
+ var isVisible = currentNote.nextBottom - scrollPos < height;
324
+ //console.log("SCROLL: ", height, scrollPos, currentNote.nextTop, currentNote.nextBottom, isVisible);
325
+ return !isVisible;
326
+ }
327
+
328
+ var lastTop = -1;
329
+ var inner = $(outer).find(".abcjs-inner");
330
+ currentMargin = 0;
331
+
332
+ if (options.scrollVertical) {
333
+ setMargin(0); // In case we are calling this a second time.
334
+ }
335
+
197
336
  function processShowCursor() {
198
337
  var currentNote = timingEvents.shift();
199
338
  if (!currentNote) {
@@ -201,14 +340,31 @@ if (!window.ABCJS)
201
340
  return 0;
202
341
  }
203
342
  if (currentNote.type === "bar") {
343
+ if (options.scrollVertical) {
344
+ if (isEndOfLine(currentNote) && shouldScroll(outer, currentMargin, currentNote)) {
345
+ setTimeout(function() {
346
+ setMargin(currentNote.nextTop);
347
+ }, millisecondsPerHalfMeasure);
348
+ }
349
+ }
204
350
  if (options.hideFinishedMeasures)
205
351
  processMeasureHider(currentNote.lineNum, currentNote.measureNum);
206
352
  if (timingEvents.length > 0)
207
353
  return timingEvents[0].time / beatLength;
208
354
  return 0;
209
355
  }
210
- if (options.showCursor)
211
- cursor.css({ left: currentNote.left + "px", top: currentNote.top + "px", width: currentNote.width + "px", height: currentNote.height + "px" });
356
+ if (options.scrollHint && lastTop !== currentNote.top) {
357
+ lastTop = currentNote.top;
358
+ setMargin(lastTop);
359
+ }
360
+ if (options.showCursor) {
361
+ cursor.css({
362
+ left: currentNote.left + "px",
363
+ top: currentNote.top + "px",
364
+ width: currentNote.width + "px",
365
+ height: currentNote.height + "px"
366
+ });
367
+ }
212
368
  if (timingEvents.length > 0)
213
369
  return timingEvents[0].time / beatLength;
214
370
  stopNextTime = true;
@@ -228,7 +384,7 @@ if (!window.ABCJS)
228
384
  if (interval <= 0)
229
385
  processNext();
230
386
  else
231
- setTimeout(processNext, interval);
387
+ animateTimer = setTimeout(processNext, interval);
232
388
  }
233
389
  startTime = new Date();
234
390
  startTime = startTime.getTime();
@@ -236,10 +392,16 @@ if (!window.ABCJS)
236
392
  };
237
393
 
238
394
  ABCJS.stopAnimation = function() {
239
- stopNextTime = true;
395
+ clearTimeout(animateTimer);
396
+ clearTimeout(scrollTimer);
240
397
  if (cursor) {
241
398
  cursor.remove();
242
399
  cursor = null;
243
400
  }
401
+ if (shouldResetOverflow) {
402
+ if (animationTarget && animationTarget.parentNode) // If the music was redrawn or otherwise disappeared before the animation was finished, this might be null.
403
+ animationTarget.parentNode.style.overflowY = "auto";
404
+ setMargin(0);
405
+ }
244
406
  };
245
407
  })();
@@ -21,6 +21,8 @@ if (!window.ABCJS)
21
21
  window.ABCJS = {};
22
22
 
23
23
  (function() {
24
+ "use strict";
25
+
24
26
  ABCJS.numberOfTunes = function(abc) {
25
27
  var tunes = abc.split("\nX:");
26
28
  var num = tunes.length;
@@ -125,7 +127,7 @@ if (!window.ABCJS)
125
127
  abcParser.parse(book.tunes[currentTune].abc, parserParams);
126
128
  var tune = abcParser.getTune();
127
129
  ret.push(tune);
128
- callback(div, tune);
130
+ callback(div, tune, i);
129
131
  }
130
132
  }
131
133
  currentTune++;
@@ -133,6 +135,132 @@ if (!window.ABCJS)
133
135
  return ret;
134
136
  }
135
137
 
138
+ var resizeDivs = {};
139
+ function resizeOuter() {
140
+ var width = window.innerWidth;
141
+ for (var id in resizeDivs) {
142
+ if (resizeDivs.hasOwnProperty(id)) {
143
+ var outer = resizeDivs[id];
144
+ var ofs = outer.offsetLeft;
145
+ width -= ofs * 2;
146
+ outer.style.width = width + "px";
147
+ }
148
+ }
149
+ }
150
+
151
+ window.addEventListener("resize", resizeOuter);
152
+ window.addEventListener("orientationChange", resizeOuter);
153
+
154
+ function renderOne(div, tune, renderParams, engraverParams) {
155
+ var width = renderParams.width ? renderParams.width : 800;
156
+ if (renderParams.viewportHorizontal) {
157
+ // Create an inner div that holds the music, so that the passed in div will be the viewport.
158
+ div.innerHTML = '<div class="abcjs-inner"></div>';
159
+ if (renderParams.scrollHorizontal) {
160
+ div.style.overflowX = "auto";
161
+ div.style.overflowY = "hidden";
162
+ } else
163
+ div.style.overflow = "hidden";
164
+ resizeDivs[div.id] = div; // We use a hash on the element's id so that multiple calls won't keep adding to the list.
165
+ div = div.children[0]; // The music should be rendered in the inner div.
166
+ }
167
+ else if (renderParams.viewportVertical) {
168
+ // Create an inner div that holds the music, so that the passed in div will be the viewport.
169
+ div.innerHTML = '<div class="abcjs-inner scroll-amount"></div>';
170
+ div.style.overflowX = "hidden";
171
+ div.style.overflowY = "auto";
172
+ div = div.children[0]; // The music should be rendered in the inner div.
173
+ }
174
+ /* jshint -W064 */ var paper = Raphael(div, width, 400); /* jshint +W064 */
175
+ if (engraverParams === undefined)
176
+ engraverParams = {};
177
+ var engraver_controller = new ABCJS.write.EngraverController(paper, engraverParams);
178
+ engraver_controller.engraveABC(tune);
179
+ tune.engraver = engraver_controller;
180
+ if (renderParams.viewportVertical || renderParams.viewportHorizontal) {
181
+ // If we added a wrapper around the div, then we need to size the wrapper, too.
182
+ var parent = div.parentNode;
183
+ parent.style.width = div.style.width;
184
+ }
185
+ }
186
+
187
+ function renderEachLineSeparately(div, tune, renderParams, engraverParams) {
188
+ function initializeTuneLine(tune) {
189
+ return {
190
+ formatting: tune.formatting,
191
+ media: tune.media,
192
+ version: tune.version,
193
+ metaText: {},
194
+ lines: []
195
+ };
196
+ }
197
+
198
+ // Before rendering, chop up the returned tune into an array where each element is a line.
199
+ // The first element of the array gets the title and other items that go on top, the last element
200
+ // of the array gets the extra text that goes on bottom. Each element gets any non-music info that comes before it.
201
+ var tunes = [];
202
+ var tuneLine;
203
+ for (var i = 0; i < tune.lines.length; i++) {
204
+ var line = tune.lines[i];
205
+ if (!tuneLine)
206
+ tuneLine = initializeTuneLine(tune);
207
+
208
+ if (i === 0) {
209
+ // These items go on top of the music
210
+ tuneLine.metaText.tempo = tune.metaText.tempo;
211
+ tuneLine.metaText.title = tune.metaText.title;
212
+ tuneLine.metaText.header = tune.metaText.header;
213
+ tuneLine.metaText.rhythm = tune.metaText.rhythm;
214
+ tuneLine.metaText.origin = tune.metaText.origin;
215
+ tuneLine.metaText.composer = tune.metaText.composer;
216
+ tuneLine.metaText.author = tune.metaText.author;
217
+ tuneLine.metaText.partOrder = tune.metaText.partOrder;
218
+ }
219
+
220
+ // push the lines until we get to a music line
221
+ tuneLine.lines.push(line);
222
+ if (line.staff) {
223
+ tunes.push(tuneLine);
224
+ tuneLine = undefined;
225
+ }
226
+ }
227
+ // Add any extra stuff to the last line.
228
+ if (tuneLine) {
229
+ var lastLine = tunes[tunes.length-1];
230
+ for (var j = 0; j < tuneLine.lines.length; j++)
231
+ lastLine.lines.push(tuneLine.lines[j]);
232
+ }
233
+
234
+ // These items go below the music
235
+ tuneLine = tunes[tunes.length-1];
236
+ tuneLine.metaText.unalignedWords = tune.metaText.unalignedWords;
237
+ tuneLine.metaText.book = tune.metaText.book;
238
+ tuneLine.metaText.source = tune.metaText.source;
239
+ tuneLine.metaText.discography = tune.metaText.discography;
240
+ tuneLine.metaText.notes = tune.metaText.notes;
241
+ tuneLine.metaText.transcription = tune.metaText.transcription;
242
+ tuneLine.metaText.history = tune.metaText.history;
243
+ tuneLine.metaText['abc-copyright'] = tune.metaText['abc-copyright'];
244
+ tuneLine.metaText['abc-creator'] = tune.metaText['abc-creator'];
245
+ tuneLine.metaText['abc-edited-by'] = tune.metaText['abc-edited-by'];
246
+ tuneLine.metaText.footer = tune.metaText.footer;
247
+
248
+ // Now create sub-divs and render each line
249
+ var eParamsFirst = JSON.parse(JSON.stringify(engraverParams));
250
+ var eParamsMid = JSON.parse(JSON.stringify(engraverParams));
251
+ var eParamsLast = JSON.parse(JSON.stringify(engraverParams));
252
+ eParamsFirst.paddingbottom = -20;
253
+ eParamsMid.paddingbottom = -20;
254
+ eParamsMid.paddingtop = 10;
255
+ eParamsLast.paddingtop = 10;
256
+ for (var k = 0; k < tunes.length; k++) {
257
+ var lineEl = document.createElement("div");
258
+ div.appendChild(lineEl);
259
+ var ep = (k === 0) ? eParamsFirst : (k === tunes.length-1) ? eParamsLast : eParamsMid;
260
+ renderOne(lineEl, tunes[k], renderParams, ep);
261
+ }
262
+ }
263
+
136
264
  // A quick way to render a tune from javascript when interactivity is not required.
137
265
  // This is used when a javascript routine has some abc text that it wants to render
138
266
  // in a div or collection of divs. One tune or many can be rendered.
@@ -151,14 +279,13 @@ if (!window.ABCJS)
151
279
  // (If this element is not present, then rendering starts at zero.)
152
280
  // width: 800 by default. The width in pixels of the output paper
153
281
  ABCJS.renderAbc = function(output, abc, parserParams, engraverParams, renderParams) {
282
+ if (renderParams === undefined)
283
+ renderParams = {};
154
284
  function callback(div, tune) {
155
- var width = renderParams ? renderParams.width ? renderParams.width : 800 : 800;
156
- /* jshint -W064 */ var paper = Raphael(div, width, 400); /* jshint +W064 */
157
- if (engraverParams === undefined)
158
- engraverParams = {};
159
- var engraver_controller = new ABCJS.write.EngraverController(paper, engraverParams);
160
- engraver_controller.engraveABC(tune);
161
- tune.engraver = engraver_controller;
285
+ if (!renderParams.oneSvgPerLine || tune.lines.length < 2)
286
+ renderOne(div, tune, renderParams, engraverParams);
287
+ else
288
+ renderEachLineSeparately(div, tune, renderParams, engraverParams);
162
289
  }
163
290
 
164
291
  return renderEngine(callback, output, abc, parserParams, renderParams);
@@ -181,14 +308,42 @@ if (!window.ABCJS)
181
308
  // startingTune: an index, starting at zero, representing which tune to start rendering at.
182
309
  // (If this element is not present, then rendering starts at zero.)
183
310
  ABCJS.renderMidi = function(output, abc, parserParams, midiParams, renderParams) {
184
- function callback(div, tune) {
185
- if (midiParams === undefined)
186
- midiParams = {};
311
+ if (midiParams === undefined)
312
+ midiParams = {};
313
+ if (midiParams.generateInline === undefined) // default is to generate inline controls.
314
+ midiParams.generateInline = true;
315
+ if (midiParams.inlineControls)
316
+ midiParams.inlineControls.selectionToggle = false; // Override the selection option because there is no selection in the Basic call.
317
+
318
+ function callback(div, tune, index) {
319
+ var html = "";
187
320
  var midi = window.ABCJS.midi.create(tune, midiParams);
188
- var title = tune.metaText.title;
189
- if (title)
190
- title = title.toLowerCase().replace(/\W/g, '_');
191
- div.innerHTML = '<a download="' + title + '.midi" href="' + midi + '">download midi</a>';
321
+ if (midiParams.generateInline) {
322
+ var inlineMidi = midi.inline ? midi.inline : midi;
323
+ html += window.ABCJS.midi.generateMidiControls(tune, midiParams, inlineMidi, index);
324
+ }
325
+ if (midiParams.generateDownload) {
326
+ var downloadMidi = midi.download ? midi.download : midi;
327
+ html += window.ABCJS.midi.generateMidiDownloadLink(tune, midiParams, downloadMidi, index);
328
+ }
329
+ div.innerHTML = html;
330
+ var find = function(element, cls) {
331
+ var els = element.getElementsByClassName(cls);
332
+ if (els.length === 0)
333
+ return null;
334
+ return els[0];
335
+ };
336
+ if (midiParams.generateInline && (midiParams.animate || midiParams.listener)) {
337
+ var parent = find(div, "abcjs-inline-midi");
338
+ parent.abcjsTune = tune;
339
+ parent.abcjsListener = midiParams.listener;
340
+ parent.abcjsAnimate = midiParams.animate;
341
+ }
342
+ if (midiParams.generateInline && midiParams.inlineControls && midiParams.inlineControls.startPlaying) {
343
+ var startButton = find(div, "abcjs-midi-start");
344
+ window.ABCJS.midi.startPlaying(startButton);
345
+ }
346
+
192
347
  }
193
348
 
194
349
  return renderEngine(callback, output, abc, parserParams, renderParams);