abcjs 6.5.1 → 6.6.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.
@@ -96,51 +96,60 @@ function gatherAbcParams(params) {
96
96
  return abcjsParams;
97
97
  }
98
98
 
99
- var Editor = function(editarea, params) {
99
+ var Editor = function (editarea, params) {
100
100
  // Copy all the options that will be passed through
101
101
  this.abcjsParams = gatherAbcParams(params);
102
102
 
103
103
  if (params.indicate_changed)
104
104
  this.indicate_changed = true;
105
- if (typeof editarea === "string") {
106
- this.editarea = new EditArea(editarea);
107
- } else {
108
- this.editarea = editarea;
109
- }
110
- this.editarea.addSelectionListener(this);
111
- this.editarea.addChangeListener(this);
112
-
113
- if (params.canvas_id) {
114
- this.div = params.canvas_id;
115
- } else if (params.paper_id) {
116
- this.div = params.paper_id;
117
- } else {
118
- this.div = document.createElement("DIV");
119
- this.editarea.getElem().parentNode.insertBefore(this.div, this.editarea.getElem());
120
- }
121
- if (typeof this.div === 'string')
122
- this.div = document.getElementById(this.div);
123
-
124
- if (params.selectionChangeCallback) {
125
- this.selectionChangeCallback = params.selectionChangeCallback;
126
- }
127
-
128
- this.clientClickListener = this.abcjsParams.clickListener;
129
- this.abcjsParams.clickListener = this.highlight.bind(this);
130
-
131
- if (params.synth) {
132
- if (supportsAudio()) {
133
- this.synth = {
134
- el: params.synth.el,
135
- cursorControl: params.synth.cursorControl,
136
- options: params.synth.options
137
- };
138
- }
139
- }
105
+
106
+ // If a string is passed in then it could either be an element's ID or a selector
107
+ // If an object is passed in then it could either be an EditArea or a textarea.
108
+ if (typeof editarea === "string") {
109
+ // EditArea handles both the ID and the selector
110
+ this.editarea = new EditArea(editarea);
111
+ } else {
112
+ // If an edit area was passed in, just use it
113
+ if (editarea.isEditArea)
114
+ this.editarea = editarea;
115
+ else
116
+ // Hopefully we were passed in a textarea or equivalent.
117
+ this.editarea = new EditArea(editarea)
118
+ }
119
+ this.editarea.addSelectionListener(this);
120
+ this.editarea.addChangeListener(this);
121
+
122
+ if (params.canvas_id) {
123
+ this.div = params.canvas_id;
124
+ } else if (params.paper_id) {
125
+ this.div = params.paper_id;
126
+ } else {
127
+ this.div = document.createElement("DIV");
128
+ this.editarea.getElem().parentNode.insertBefore(this.div, this.editarea.getElem());
129
+ }
130
+ if (typeof this.div === 'string')
131
+ this.div = document.getElementById(this.div);
132
+
133
+ if (params.selectionChangeCallback) {
134
+ this.selectionChangeCallback = params.selectionChangeCallback;
135
+ }
136
+
137
+ this.clientClickListener = this.abcjsParams.clickListener;
138
+ this.abcjsParams.clickListener = this.highlight.bind(this);
139
+
140
+ if (params.synth) {
141
+ if (supportsAudio()) {
142
+ this.synth = {
143
+ el: params.synth.el,
144
+ cursorControl: params.synth.cursorControl,
145
+ options: params.synth.options
146
+ };
147
+ }
148
+ }
140
149
  // If the user wants midi, then store the elements that it will be written to. The element could either be passed in as an id,
141
150
  // an element, or nothing. If nothing is passed in, then just put the midi on top of the generated music.
142
151
  if (params.generate_midi) {
143
- this.generate_midi = params.generate_midi;
152
+ this.generate_midi = params.generate_midi;
144
153
  if (this.abcjsParams.generateDownload) {
145
154
  if (typeof params.midi_download_id === 'string')
146
155
  this.downloadMidi = document.getElementById(params.midi_download_id);
@@ -155,56 +164,57 @@ var Editor = function(editarea, params) {
155
164
  }
156
165
  }
157
166
 
158
- if (params.warnings_id) {
159
- if (typeof(params.warnings_id) === "string")
160
- this.warningsdiv = document.getElementById(params.warnings_id);
161
- else
162
- this.warningsdiv = params.warnings_id;
163
- } else if (params.generate_warnings) {
164
- this.warningsdiv = document.createElement("div");
165
- this.div.parentNode.insertBefore(this.warningsdiv, this.div);
166
- }
167
-
168
- this.onchangeCallback = params.onchange;
169
-
170
- this.currentAbc = "";
171
- this.tunes = [];
172
- this.bReentry = false;
173
- this.parseABC();
174
- this.modelChanged();
175
-
176
- this.addClassName = function(element, className) {
177
- var hasClassName = function(element, className) {
178
- var elementClassName = element.className;
179
- return (elementClassName.length > 0 && (elementClassName === className ||
180
- new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
181
- };
182
-
183
- if (!hasClassName(element, className))
184
- element.className += (element.className ? ' ' : '') + className;
185
- return element;
186
- };
187
-
188
- this.removeClassName = function(element, className) {
189
- element.className = parseCommon.strip(element.className.replace(
190
- new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
191
- return element;
192
- };
193
-
194
- this.setReadOnly = function(readOnly) {
195
- var readonlyClass = 'abc_textarea_readonly';
196
- var el = this.editarea.getElem();
197
- if (readOnly) {
198
- el.setAttribute('readonly', 'yes');
199
- this.addClassName(el, readonlyClass);
200
- } else {
201
- el.removeAttribute('readonly');
202
- this.removeClassName(el, readonlyClass);
203
- }
204
- };
167
+ if (params.warnings_id) {
168
+ if (typeof (params.warnings_id) === "string")
169
+ this.warningsdiv = document.getElementById(params.warnings_id);
170
+ else
171
+ this.warningsdiv = params.warnings_id;
172
+ } else if (params.generate_warnings) {
173
+ this.warningsdiv = document.createElement("div");
174
+ this.div.parentNode.insertBefore(this.warningsdiv, this.div);
175
+ }
176
+
177
+ this.onchangeCallback = params.onchange;
178
+ this.redrawCallback = params.redrawCallback;
179
+
180
+ this.currentAbc = "";
181
+ this.tunes = [];
182
+ this.bReentry = false;
183
+ this.parseABC();
184
+ this.modelChanged();
185
+
186
+ this.addClassName = function (element, className) {
187
+ var hasClassName = function (element, className) {
188
+ var elementClassName = element.className;
189
+ return (elementClassName.length > 0 && (elementClassName === className ||
190
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
191
+ };
192
+
193
+ if (!hasClassName(element, className))
194
+ element.className += (element.className ? ' ' : '') + className;
195
+ return element;
196
+ };
197
+
198
+ this.removeClassName = function (element, className) {
199
+ element.className = parseCommon.strip(element.className.replace(
200
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
201
+ return element;
202
+ };
203
+
204
+ this.setReadOnly = function (readOnly) {
205
+ var readonlyClass = 'abc_textarea_readonly';
206
+ var el = this.editarea.getElem();
207
+ if (readOnly) {
208
+ el.setAttribute('readonly', 'yes');
209
+ this.addClassName(el, readonlyClass);
210
+ } else {
211
+ el.removeAttribute('readonly');
212
+ this.removeClassName(el, readonlyClass);
213
+ }
214
+ };
205
215
  };
206
216
 
207
- Editor.prototype.redrawMidi = function() {
217
+ Editor.prototype.redrawMidi = function () {
208
218
  if (this.generate_midi && !this.midiPause) {
209
219
  var event = new window.CustomEvent("generateMidi", {
210
220
  detail: {
@@ -227,12 +237,14 @@ Editor.prototype.redrawMidi = function() {
227
237
  }
228
238
  };
229
239
 
230
- Editor.prototype.modelChanged = function() {
231
- if (this.bReentry)
232
- return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc
240
+ Editor.prototype.modelChanged = function () {
241
+ if (this.bReentry)
242
+ return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc
233
243
  this.bReentry = true;
234
244
  try {
235
245
  this.timerId = null;
246
+ if (this.redrawCallback)
247
+ this.redrawCallback(true)
236
248
  if (this.synth && this.synth.synthControl)
237
249
  this.synth.synthControl.disable(true);
238
250
 
@@ -241,22 +253,24 @@ Editor.prototype.modelChanged = function() {
241
253
  this.warnings = this.tunes[0].warnings;
242
254
  }
243
255
  this.redrawMidi();
244
- } catch(error) {
256
+ if (this.redrawCallback)
257
+ this.redrawCallback(false)
258
+ } catch (error) {
245
259
  console.error("ABCJS error: ", error);
246
260
  if (!this.warnings)
247
261
  this.warnings = [];
248
262
  this.warnings.push(error.message);
249
263
  }
250
264
 
251
- if (this.warningsdiv) {
252
- this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
253
- }
254
- this.updateSelection();
255
- this.bReentry = false;
265
+ if (this.warningsdiv) {
266
+ this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
267
+ }
268
+ this.updateSelection();
269
+ this.bReentry = false;
256
270
  };
257
271
 
258
272
  // Call this to reparse in response to the client changing the parameters on the fly
259
- Editor.prototype.paramChanged = function(engraverParams) {
273
+ Editor.prototype.paramChanged = function (engraverParams) {
260
274
  if (engraverParams) {
261
275
  for (var key in engraverParams) {
262
276
  if (engraverParams.hasOwnProperty(key)) {
@@ -268,7 +282,11 @@ Editor.prototype.paramChanged = function(engraverParams) {
268
282
  this.fireChanged();
269
283
  };
270
284
 
271
- Editor.prototype.synthParamChanged = function(options) {
285
+ Editor.prototype.getTunes = function () {
286
+ return this.tunes
287
+ };
288
+
289
+ Editor.prototype.synthParamChanged = function (options) {
272
290
  if (!this.synth)
273
291
  return;
274
292
  this.synth.options = {};
@@ -284,52 +302,53 @@ Editor.prototype.synthParamChanged = function(options) {
284
302
  };
285
303
 
286
304
  // return true if the model has changed
287
- Editor.prototype.parseABC = function() {
288
- var t = this.editarea.getString();
289
- if (t===this.currentAbc) {
290
- this.updateSelection();
291
- return false;
292
- }
293
-
294
- this.currentAbc = t;
295
- return true;
305
+ Editor.prototype.parseABC = function () {
306
+ var t = this.editarea.getString();
307
+ if (t === this.currentAbc) {
308
+ this.updateSelection();
309
+ return false;
310
+ }
311
+
312
+ this.currentAbc = t;
313
+ return true;
296
314
  };
297
315
 
298
- Editor.prototype.updateSelection = function() {
299
- var selection = this.editarea.getSelection();
300
- try {
301
- if (this.tunes.length > 0 && this.tunes[0].engraver)
302
- this.tunes[0].engraver.rangeHighlight(selection.start, selection.end);
303
- } catch (e) {} // maybe printer isn't defined yet?
316
+ Editor.prototype.updateSelection = function () {
317
+ var selection = this.editarea.getSelection();
318
+ try {
319
+ if (this.tunes.length > 0 && this.tunes[0].engraver)
320
+ this.tunes[0].engraver.rangeHighlight(selection.start, selection.end);
321
+ } catch (e) {
322
+ } // maybe printer isn't defined yet?
304
323
  if (this.selectionChangeCallback)
305
324
  this.selectionChangeCallback(selection.start, selection.end);
306
325
  };
307
326
 
308
327
  // Called when the textarea's selection is in the process of changing (after mouse down, dragging, or keyboard arrows)
309
- Editor.prototype.fireSelectionChanged = function() {
310
- this.updateSelection();
328
+ Editor.prototype.fireSelectionChanged = function () {
329
+ this.updateSelection();
311
330
  };
312
331
 
313
- Editor.prototype.setDirtyStyle = function(isDirty) {
332
+ Editor.prototype.setDirtyStyle = function (isDirty) {
314
333
  if (this.indicate_changed === undefined)
315
334
  return;
316
- var addClassName = function(element, className) {
317
- var hasClassName = function(element, className) {
318
- var elementClassName = element.className;
319
- return (elementClassName.length > 0 && (elementClassName === className ||
320
- new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
321
- };
322
-
323
- if (!hasClassName(element, className))
324
- element.className += (element.className ? ' ' : '') + className;
325
- return element;
326
- };
327
-
328
- var removeClassName = function(element, className) {
329
- element.className = parseCommon.strip(element.className.replace(
330
- new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
331
- return element;
332
- };
335
+ var addClassName = function (element, className) {
336
+ var hasClassName = function (element, className) {
337
+ var elementClassName = element.className;
338
+ return (elementClassName.length > 0 && (elementClassName === className ||
339
+ new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
340
+ };
341
+
342
+ if (!hasClassName(element, className))
343
+ element.className += (element.className ? ' ' : '') + className;
344
+ return element;
345
+ };
346
+
347
+ var removeClassName = function (element, className) {
348
+ element.className = parseCommon.strip(element.className.replace(
349
+ new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
350
+ return element;
351
+ };
333
352
 
334
353
  var readonlyClass = 'abc_textarea_dirty';
335
354
  var el = this.editarea.getElem();
@@ -337,66 +356,66 @@ Editor.prototype.setDirtyStyle = function(isDirty) {
337
356
  addClassName(el, readonlyClass);
338
357
  } else {
339
358
  removeClassName(el, readonlyClass);
340
- }
359
+ }
341
360
  };
342
361
 
343
362
  // call when the textarea alerts us that the abc text is changed and needs re-parsing
344
- Editor.prototype.fireChanged = function() {
345
- if (this.bIsPaused)
346
- return;
347
- if (this.parseABC()) {
348
- var self = this;
349
- if (this.timerId) // If the user is still typing, cancel the update
350
- clearTimeout(this.timerId);
351
- this.timerId = setTimeout(function () {
352
- self.modelChanged();
353
- }, 300); // Is this a good compromise between responsiveness and not redrawing too much?
354
- var isDirty = this.isDirty();
355
- if (this.wasDirty !== isDirty) {
356
- this.wasDirty = isDirty;
357
- this.setDirtyStyle(isDirty);
358
- }
359
- if (this.onchangeCallback)
360
- this.onchangeCallback(this);
361
- }
363
+ Editor.prototype.fireChanged = function () {
364
+ if (this.bIsPaused)
365
+ return;
366
+ if (this.parseABC()) {
367
+ var self = this;
368
+ if (this.timerId) // If the user is still typing, cancel the update
369
+ clearTimeout(this.timerId);
370
+ this.timerId = setTimeout(function () {
371
+ self.modelChanged();
372
+ }, 300); // Is this a good compromise between responsiveness and not redrawing too much?
373
+ var isDirty = this.isDirty();
374
+ if (this.wasDirty !== isDirty) {
375
+ this.wasDirty = isDirty;
376
+ this.setDirtyStyle(isDirty);
377
+ }
378
+ if (this.onchangeCallback)
379
+ this.onchangeCallback(this);
380
+ }
362
381
  };
363
382
 
364
- Editor.prototype.setNotDirty = function() {
383
+ Editor.prototype.setNotDirty = function () {
365
384
  this.editarea.initialText = this.editarea.getString();
366
385
  this.wasDirty = false;
367
386
  this.setDirtyStyle(false);
368
387
  };
369
388
 
370
- Editor.prototype.isDirty = function() {
389
+ Editor.prototype.isDirty = function () {
371
390
  if (this.indicate_changed === undefined)
372
391
  return false;
373
392
  return this.editarea.initialText !== this.editarea.getString();
374
393
  };
375
394
 
376
- Editor.prototype.highlight = function(abcelem, tuneNumber, classes, analysis, drag, mouseEvent) {
395
+ Editor.prototype.highlight = function (abcelem, tuneNumber, classes, analysis, drag, mouseEvent) {
377
396
  // TODO-PER: The marker appears to get off by one for each tune parsed. I'm not sure why, but adding the tuneNumber in corrects it for the time being.
378
397
  // var offset = (tuneNumber !== undefined) ? this.startPos[tuneNumber] + tuneNumber : 0;
379
398
 
380
- this.editarea.setSelection(abcelem.startChar, abcelem.endChar);
399
+ this.editarea.setSelection(abcelem.startChar, abcelem.endChar);
381
400
  if (this.selectionChangeCallback)
382
401
  this.selectionChangeCallback(abcelem.startChar, abcelem.endChar);
383
402
  if (this.clientClickListener)
384
403
  this.clientClickListener(abcelem, tuneNumber, classes, analysis, drag, mouseEvent);
385
404
  };
386
405
 
387
- Editor.prototype.pause = function(shouldPause) {
406
+ Editor.prototype.pause = function (shouldPause) {
388
407
  this.bIsPaused = shouldPause;
389
408
  if (!shouldPause)
390
409
  this.fireChanged();
391
410
  };
392
411
 
393
- Editor.prototype.millisecondsPerMeasure = function() {
412
+ Editor.prototype.millisecondsPerMeasure = function () {
394
413
  if (!this.synth || !this.synth.synthControl || !this.synth.synthControl.visualObj)
395
414
  return 0;
396
415
  return this.synth.synthControl.visualObj.millisecondsPerMeasure();
397
416
  };
398
417
 
399
- Editor.prototype.pauseMidi = function(shouldPause) {
418
+ Editor.prototype.pauseMidi = function (shouldPause) {
400
419
  this.midiPause = shouldPause;
401
420
  if (!shouldPause)
402
421
  this.redrawMidi();
@@ -23,8 +23,8 @@ var create;
23
23
 
24
24
  var beatsPerSecond = tempo / 60;
25
25
 
26
- // Fix tempo for */8 meters
27
- if (time.den == 8){
26
+ // Fix tempo for compound meters
27
+ if (time.den === 8 && time.num !== 5 && time.num !== 7){
28
28
 
29
29
  // Compute the tempo based on the actual milliseconds per measure, scaled by the number of eight notes and halved to get tempo in bpm.
30
30
  var msPerMeasure = abcTune.millisecondsPerMeasure();
@@ -6,6 +6,7 @@ var ParseHeader = require('./abc_parse_header');
6
6
  var ParseMusic = require('./abc_parse_music');
7
7
  var Tokenizer = require('./abc_tokenizer');
8
8
  var wrap = require('./wrap_lines');
9
+ var chordGrid = require('./chord-grid');
9
10
 
10
11
  var Tune = require('../data/abc_tune');
11
12
  var TuneBuilder = require('../parse/tune-builder');
@@ -53,6 +54,8 @@ var Parse = function() {
53
54
  t.lineBreaks = tune.lineBreaks;
54
55
  if (tune.visualTranspose)
55
56
  t.visualTranspose = tune.visualTranspose;
57
+ if (tune.chordGrid)
58
+ t.chordGrid = tune.chordGrid
56
59
  return t;
57
60
  };
58
61
 
@@ -593,6 +596,23 @@ var Parse = function() {
593
596
  }
594
597
 
595
598
  wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers);
599
+ if (switches.chordGrid) {
600
+ try {
601
+ tune.chordGrid = chordGrid(tune)
602
+ } catch(err) {
603
+ switch (err.message) {
604
+ case "notCommonTime":
605
+ warn("Chord grid only works for 2/2 and 4/4 time.", 0,0)
606
+ break;
607
+ case "noChords":
608
+ warn("No chords are found in the tune.", 0,0)
609
+ break;
610
+ default:
611
+ warn(err.message, 0,0)
612
+ }
613
+
614
+ }
615
+ }
596
616
  };
597
617
  };
598
618