abcjs-rails 2.3 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
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);