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.
- package/README.md +6 -0
- package/RELEASE.md +44 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +1198 -150
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/package.json +1 -1
- package/src/api/abc_timing_callbacks.js +88 -13
- package/src/const/relative-major.js +24 -19
- package/src/data/abc_tune.js +12 -1
- package/src/data/deline-tune.js +1 -1
- package/src/edit/abc_editarea.js +53 -50
- package/src/edit/abc_editor.js +176 -157
- package/src/midi/abc_midi_create.js +2 -2
- package/src/parse/abc_parse.js +20 -0
- package/src/parse/chord-grid.js +364 -0
- package/src/parse/tune-builder.js +4 -4
- package/src/str/output.js +97 -48
- package/src/synth/abc_midi_sequencer.js +20 -30
- package/src/synth/create-synth.js +1 -1
- package/src/synth/repeats.js +200 -0
- package/src/write/creation/abstract-engraver.js +4 -1
- package/src/write/draw/chord-grid.js +252 -0
- package/src/write/draw/draw.js +48 -35
- package/src/write/draw/text.js +1 -1
- package/src/write/engraver-controller.js +4 -2
- package/src/write/layout/beam.js +16 -1
- package/src/write/renderer.js +4 -0
- package/src/write/svg.js +8 -1
- package/types/index.d.ts +36 -3
- package/version.js +1 -1
package/src/edit/abc_editor.js
CHANGED
|
@@ -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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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.
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
if (time.den
|
|
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();
|
package/src/parse/abc_parse.js
CHANGED
|
@@ -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
|
|