abcjs 6.4.0 → 6.4.2

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 (40) hide show
  1. package/RELEASE.md +42 -0
  2. package/dist/abcjs-basic-min.js +2 -2
  3. package/dist/abcjs-basic.js +1252 -1139
  4. package/dist/abcjs-basic.js.map +1 -1
  5. package/dist/abcjs-plugin-min.js +2 -2
  6. package/package.json +1 -1
  7. package/src/api/abc_tunebook.js +1 -2
  8. package/src/api/abc_tunebook_svg.js +0 -1
  9. package/src/midi/abc_midi_create.js +22 -7
  10. package/src/parse/abc_common.js +3 -11
  11. package/src/parse/abc_parse.js +1 -1
  12. package/src/parse/abc_parse_directive.js +44 -3
  13. package/src/parse/abc_parse_header.js +6 -4
  14. package/src/parse/abc_parse_key_voice.js +10 -6
  15. package/src/parse/abc_parse_music.js +22 -5
  16. package/src/parse/tune-builder.js +675 -643
  17. package/src/synth/abc_midi_flattener.js +3 -1
  18. package/src/synth/abc_midi_sequencer.js +18 -3
  19. package/src/synth/chord-track.js +86 -20
  20. package/src/synth/create-synth-control.js +1 -2
  21. package/src/synth/create-synth.js +1 -1
  22. package/src/tablatures/abc_tablatures.js +184 -0
  23. package/src/tablatures/instruments/string-patterns.js +266 -268
  24. package/src/tablatures/instruments/string-tablature.js +38 -35
  25. package/src/tablatures/instruments/tab-note.js +186 -181
  26. package/src/tablatures/instruments/tab-notes.js +30 -35
  27. package/src/tablatures/instruments/tab-string.js +43 -25
  28. package/src/tablatures/render/tab-absolute-elements.js +303 -0
  29. package/src/tablatures/render/tab-renderer.js +244 -0
  30. package/src/test/abc_parser_lint.js +2 -3
  31. package/src/write/creation/abstract-engraver.js +1 -1
  32. package/src/write/engraver-controller.js +1 -1
  33. package/temp.txt +9 -0
  34. package/types/index.d.ts +1 -1
  35. package/version.js +1 -1
  36. package/src/api/abc_tablatures.js +0 -184
  37. package/src/tablatures/instruments/tab-string-patterns.js +0 -23
  38. package/src/tablatures/tab-absolute-elements.js +0 -301
  39. package/src/tablatures/tab-common.js +0 -29
  40. package/src/tablatures/tab-renderer.js +0 -259
@@ -209,6 +209,7 @@ var pitchesToPerc = require('./pitches-to-perc');
209
209
  case "chordprog":
210
210
  case "bassvol":
211
211
  case "chordvol":
212
+ case "gchordbars":
212
213
  chordTrack.paramChange(element)
213
214
  break
214
215
  default:
@@ -347,7 +348,8 @@ var pitchesToPerc = require('./pitches-to-perc');
347
348
  return 0;
348
349
 
349
350
  var volume;
350
- if (nextVolume) {
351
+ // MAE 21 Jun 2024 - This previously wasn't allowing zero volume to be applied
352
+ if (nextVolume != undefined) {
351
353
  volume = nextVolume;
352
354
  nextVolume = undefined;
353
355
  } else if (!doBeatAccents) {
@@ -315,7 +315,7 @@ var parseCommon = require("../parse/abc_common");
315
315
  if (!e) e = voices[voiceNumber].length; // If there wasn't a first ending marker, then we copy everything.
316
316
  // duplicate each of the elements - this has to be a deep copy.
317
317
  for (var z = s; z < e; z++) {
318
- var item = parseCommon.clone(voices[voiceNumber][z]);
318
+ var item = Object.assign({},voices[voiceNumber][z]);
319
319
  if (item.pitches)
320
320
  item.pitches = parseCommon.cloneArray(item.pitches);
321
321
  voices[voiceNumber].push(item);
@@ -389,12 +389,27 @@ var parseCommon = require("../parse/abc_common");
389
389
  break;
390
390
  case "swing":
391
391
  case "gchord":
392
- case "bassprog":
393
- case "chordprog":
394
392
  case "bassvol":
395
393
  case "chordvol":
396
394
  voices[voiceNumber].push({ el_type: elem.cmd, param: elem.params[0] });
397
395
  break;
396
+
397
+ case "bassprog": // MAE 22 May 2024
398
+ case "chordprog": // MAE 22 May 2024
399
+ voices[voiceNumber].push({
400
+ el_type: elem.cmd,
401
+ value: elem.params[0],
402
+ octaveShift: elem.params[1]
403
+ });
404
+ break;
405
+
406
+ // MAE 23 Jun 2024
407
+ case "gchordbars":
408
+ voices[voiceNumber].push({
409
+ el_type: elem.cmd,
410
+ param: elem.params[0]
411
+ });
412
+ break;
398
413
  default:
399
414
  console.log("MIDI seq: midi cmd not handled: ", elem.cmd, elem);
400
415
  }
@@ -17,8 +17,6 @@
17
17
  //
18
18
  // If there is any note in the melody that has a rhythm head, then assume the melody controls the rhythm, so there is no chord added for that entire measure.
19
19
 
20
- var parseCommon = require("../parse/abc_common");
21
-
22
20
  var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
23
21
  this.chordTrack = [];
24
22
  this.chordTrackFinished = false;
@@ -34,12 +32,24 @@ var ChordTrack = function ChordTrack(numVoices, chordsOff, midiOptions, meter) {
34
32
  this.meter = meter;
35
33
  this.tempoChangeFactor = 1;
36
34
 
37
- this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length === 1 ? midiOptions.bassprog[0] : 0;
38
- this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length === 1 ? midiOptions.chordprog[0] : 0;
35
+ // MAE 17 Jun 2024 - To allow for bass and chord instrument octave shifts
36
+ this.bassInstrument = midiOptions.bassprog && midiOptions.bassprog.length >= 1 ? midiOptions.bassprog[0] : 0;
37
+ this.chordInstrument = midiOptions.chordprog && midiOptions.chordprog.length >= 1 ? midiOptions.chordprog[0] : 0;
38
+
39
+ // MAE For octave shifted bass and chords
40
+ this.bassOctaveShift = midiOptions.bassprog && midiOptions.bassprog.length === 2 ? midiOptions.bassprog[1] : 0;
41
+ this.chordOctaveShift = midiOptions.chordprog && midiOptions.chordprog.length === 2 ? midiOptions.chordprog[1] : 0;
42
+
39
43
  this.boomVolume = midiOptions.bassvol && midiOptions.bassvol.length === 1 ? midiOptions.bassvol[0] : 64;
40
44
  this.chickVolume = midiOptions.chordvol && midiOptions.chordvol.length === 1 ? midiOptions.chordvol[0] : 48;
41
45
 
42
- this.overridePattern = midiOptions.gchord ? parseGChord(midiOptions.gchord[0]) : undefined
46
+ // This allows for an initial %%MIDI gchord with no string
47
+ if (midiOptions.gchord && (midiOptions.gchord.length > 0)) {
48
+ this.overridePattern = parseGChord(midiOptions.gchord[0])
49
+ }
50
+ else {
51
+ this.overridePattern = undefined;
52
+ }
43
53
  };
44
54
 
45
55
  ChordTrack.prototype.setMeter = function (meter) {
@@ -64,7 +74,7 @@ ChordTrack.prototype.setRhythmHead = function (isRhythmHead, elem) {
64
74
  if (isRhythmHead) {
65
75
  if (this.lastChord && this.lastChord.chick) {
66
76
  for (var i2 = 0; i2 < this.lastChord.chick.length; i2++) {
67
- var note2 = parseCommon.clone(elem.pitches[0]);
77
+ var note2 = Object.assign({},elem.pitches[0]);
68
78
  note2.actualPitch = this.lastChord.chick[i2];
69
79
  ePitches.push(note2);
70
80
  }
@@ -89,13 +99,32 @@ ChordTrack.prototype.gChordOn = function (element) {
89
99
  ChordTrack.prototype.paramChange = function (element) {
90
100
  switch (element.el_type) {
91
101
  case "gchord":
92
- this.overridePattern = parseGChord(element.param);
102
+ // Skips gchord elements that don't have pattern strings
103
+ if (element.param && element.param.length > 0) {
104
+ this.overridePattern = parseGChord(element.param);
105
+
106
+ // Generate a default duration scale based on the pattern
107
+ //this.gchordduration = generateDefaultDurationScale(element.param);
108
+ } else
109
+ this.overridePattern = undefined;
93
110
  break;
94
111
  case "bassprog":
95
- this.bassInstrument = element.param;
112
+ this.bassInstrument = element.value;
113
+ if ((element.octaveShift != undefined) && (element.octaveShift != null)) {
114
+ this.bassOctaveShift = element.octaveShift;
115
+ }
116
+ else {
117
+ this.bassOctaveShift = 0;
118
+ }
96
119
  break;
97
120
  case "chordprog":
98
- this.chordInstrument = element.param;
121
+ this.chordInstrument = element.value;
122
+ if ((element.octaveShift != undefined) && (element.octaveShift != null)) {
123
+ this.chordOctaveShift = element.octaveShift;
124
+ }
125
+ else {
126
+ this.chordOctaveShift = 0;
127
+ }
99
128
  break;
100
129
  case "bassvol":
101
130
  this.boomVolume = element.param;
@@ -169,6 +198,12 @@ ChordTrack.prototype.interpretChord = function (name) {
169
198
  while (chordTranspose > 8)
170
199
  chordTranspose -= 12;
171
200
  bass += chordTranspose;
201
+
202
+ // MAE 17 Jun 2024 - Supporting octave shifted bass and chords
203
+ var unshiftedBass = bass;
204
+
205
+ bass += this.bassOctaveShift * 12;
206
+
172
207
  var bass2 = bass - 5; // The alternating bass is a 4th below
173
208
  var chick;
174
209
  if (name.length === 1)
@@ -176,16 +211,18 @@ ChordTrack.prototype.interpretChord = function (name) {
176
211
  var remaining = name.substring(1);
177
212
  var acc = remaining.substring(0, 1);
178
213
  if (acc === 'b' || acc === '♭') {
214
+ unshiftedBass--;
179
215
  bass--;
180
216
  bass2--;
181
217
  remaining = remaining.substring(1);
182
218
  } else if (acc === '#' || acc === '♯') {
219
+ unshiftedBass++;
183
220
  bass++;
184
221
  bass2++;
185
222
  remaining = remaining.substring(1);
186
223
  }
187
224
  var arr = remaining.split('/');
188
- chick = this.chordNotes(bass, arr[0]);
225
+ chick = this.chordNotes(unshiftedBass, arr[0]);
189
226
  // If the 5th is altered then the bass is altered. Normally the bass is 7 from the root, so adjust if it isn't.
190
227
  if (chick.length >= 3) {
191
228
  var fifth = chick[2] - chick[0];
@@ -198,6 +235,10 @@ ChordTrack.prototype.interpretChord = function (name) {
198
235
  var bassAcc = arr[1].substring(1);
199
236
  var bassShift = { '#': 1, '♯': 1, 'b': -1, '♭': -1 }[bassAcc] || 0;
200
237
  bass = this.basses[arr[1].substring(0, 1)] + bassShift + chordTranspose;
238
+
239
+ // MAE 22 May 2024 - Supporting octave shifted bass and chords
240
+ bass += this.bassOctaveShift * 12;
241
+
201
242
  bass2 = bass;
202
243
  }
203
244
  }
@@ -215,6 +256,10 @@ ChordTrack.prototype.chordNotes = function (bass, modifier) {
215
256
  intervals = this.chordIntervals.M;
216
257
  }
217
258
  bass += 12; // the chord is an octave above the bass note.
259
+
260
+ // MAE 22 May 2024 - For chick octave shift
261
+ bass += (this.chordOctaveShift * 12);
262
+
218
263
  var notes = [];
219
264
  for (var i = 0; i < intervals.length; i++) {
220
265
  notes.push(bass + intervals[i]);
@@ -284,8 +329,11 @@ ChordTrack.prototype.resolveChords = function (startTime, endTime) {
284
329
  firstBoom = true
285
330
  var type = thisPattern[p]
286
331
  var isBoom = type.indexOf('boom') >= 0
287
- // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in.
288
- var newBass = !isBoom && p !== 0 && (!currentChordsExpanded[p-1] || currentChordsExpanded[p-1].boom !== currentChordsExpanded[p].boom)
332
+ // If we changed chords at a time when we're not expecting a bass note, then add an extra bass note in if the first thing in the pattern is a bass note.
333
+ var newBass = !isBoom &&
334
+ p !== 0 &&
335
+ thisPattern[0].indexOf('boom') >= 0 &&
336
+ (!currentChordsExpanded[p-1] || currentChordsExpanded[p-1].boom !== currentChordsExpanded[p].boom)
289
337
  var pitches = resolvePitch(currentChordsExpanded[p], type, firstBoom, newBass)
290
338
  if (isBoom)
291
339
  firstBoom = false
@@ -335,25 +383,34 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
335
383
  ret.push(firstBoom ? currentChord.boom : currentChord.boom2)
336
384
  else if (newBass)
337
385
  ret.push(currentChord.boom)
386
+ var numChordNotes = currentChord.chick.length
338
387
  if (type.indexOf('chick') >= 0) {
339
- for (var i = 0; i < currentChord.chick.length; i++)
388
+ for (var i = 0; i < numChordNotes; i++)
340
389
  ret.push(currentChord.chick[i])
341
390
  }
342
391
  switch (type) {
343
392
  case 'DO': ret.push(currentChord.chick[0]); break;
344
393
  case 'MI': ret.push(currentChord.chick[1]); break;
345
- case 'SOL': ret.push(currentChord.chick[2]); break;
346
- case 'TI': currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0]+12); break;
347
- case 'TOP': currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[1]+12); break;
394
+ case 'SOL': ret.push(extractNote(currentChord,2)); break;
395
+ case 'TI': ret.push(extractNote(currentChord,3)); break;
396
+ case 'TOP': ret.push(extractNote(currentChord,4)); break;
348
397
  case 'do': ret.push(currentChord.chick[0]+12); break;
349
398
  case 'mi': ret.push(currentChord.chick[1]+12); break;
350
- case 'sol': ret.push(currentChord.chick[2]+12); break;
351
- case 'ti': currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]+12) : ret.push(currentChord.chick[0]+24); break;
352
- case 'top': currentChord.chick.length > 4 ? ret.push(currentChord.chick[2]+12) : ret.push(currentChord.chick[1]+24); break;
399
+ case 'sol': ret.push(extractNote(currentChord,2)+12); break;
400
+ case 'ti': ret.push(extractNote(currentChord,3)+12); break;
401
+ case 'top': ret.push(extractNote(currentChord,4)+12); break;
353
402
  }
354
403
  return ret
355
404
  }
356
405
 
406
+ function extractNote(chord, index) {
407
+ // This creates an arpeggio note no matter how many notes are in the chord - if it runs out of notes it continues in the next octave
408
+ var octave = Math.floor(index / chord.chick.length)
409
+ var note = chord.chick[index % chord.chick.length]
410
+ //console.log(chord.chick, {index, octave, note}, index % chord.chick.length)
411
+ return note + octave * 12
412
+ }
413
+
357
414
  function parseGChord(gchord) {
358
415
  // TODO-PER: The spec is more complicated than this but for now this will not try to do anything with error cases like the wrong number of beats.
359
416
  var pattern = []
@@ -529,7 +586,12 @@ ChordTrack.prototype.chordIntervals = {
529
586
  'maj7#5#11': [0, 4, 8, 11, 18],
530
587
  '9(#5)': [0, 4, 8, 10, 14],
531
588
  '13(#5)': [0, 4, 8, 10, 14, 21],
532
- '13#5': [0, 4, 8, 10, 14, 21]
589
+ '13#5': [0, 4, 8, 10, 14, 21],
590
+ // MAE Power chords added 10 April 2024
591
+ '5': [0, 7],
592
+ '5(8)': [0, 7, 12],
593
+ '5add8': [0, 7, 12]
594
+
533
595
  };
534
596
 
535
597
  ChordTrack.prototype.rhythmPatterns = {
@@ -544,8 +606,12 @@ ChordTrack.prototype.rhythmPatterns = {
544
606
  "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
545
607
 
546
608
  "3/8": ['boom', '', 'chick'],
609
+ "5/8": ['boom', 'chick', 'chick', 'boom', 'chick'],
547
610
  "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
611
+ "7/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
548
612
  "9/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
613
+ "10/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
614
+ "11/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick', 'chick'],
549
615
  "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
550
616
  };
551
617
 
@@ -1,7 +1,6 @@
1
1
  var supportsAudio = require('./supports-audio');
2
2
  var registerAudioContext = require('./register-audio-context');
3
3
  var activeAudioContext = require('./active-audio-context');
4
- var parseCommon = require('../parse/abc_common');
5
4
 
6
5
  var loopImage = require('./images/loop.svg.js');
7
6
  var playImage = require('./images/play.svg.js');
@@ -23,7 +22,7 @@ function CreateSynthControl(parent, options) {
23
22
  self.parent = parent;
24
23
  self.options = {};
25
24
  if (options)
26
- self.options = parseCommon.clone(options);
25
+ self.options = Object.assign({},options);
27
26
 
28
27
  // This can be called in the following cases:
29
28
  // AC already registered and not suspended
@@ -127,7 +127,7 @@ function CreateSynth() {
127
127
  self.sequenceCallback = params.sequenceCallback;
128
128
  self.callbackContext = params.callbackContext;
129
129
  self.onEnded = params.onEnded;
130
- self.meterFraction = options.visualObj.getMeterFraction();
130
+ self.meterFraction = options.visualObj ? options.visualObj.getMeterFraction() : {den: 1} // If we are given a sequence instead of a regular visual obj, then don't do the swing
131
131
 
132
132
  var allNotes = {};
133
133
  var cached = [];
@@ -0,0 +1,184 @@
1
+ /*
2
+ * Tablature Plugins
3
+ * tablature are defined dynamically and registered inside abcjs
4
+ * by calling abcTablatures.register(plugin)
5
+ * where plugin represents a plugin instance
6
+ *
7
+ */
8
+
9
+ // This is the only entry point to the tablatures. It is called both after parsing a tune and just before engraving
10
+
11
+ var TabString = require('./instruments/tab-string');
12
+
13
+ /* extend the table below when adding a new instrument plugin */
14
+
15
+ // Existing tab classes
16
+ var pluginTab = {
17
+ 'violin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 },
18
+ 'fiddle': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 },
19
+ 'mandolin': { name: 'StringTab', defaultTuning: ['G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: 0 },
20
+ 'guitar': { name: 'StringTab', defaultTuning: ['E,', 'A,', 'D', 'G', 'B', 'e'], isTabBig: true, tabSymbolOffset: 0 },
21
+ 'fiveString': { name: 'StringTab', defaultTuning: ['C,', 'G,', 'D', 'A', 'e'], isTabBig: false, tabSymbolOffset: -.95 },
22
+ };
23
+
24
+ var abcTablatures = {
25
+
26
+ inited: false,
27
+ plugins: {},
28
+
29
+
30
+ /**
31
+ * to be called once per plugin for registration
32
+ * @param {*} plugin
33
+ */
34
+ register: function (plugin) {
35
+ var name = plugin.name;
36
+ var tablature = plugin.tablature;
37
+ this.plugins[name] = tablature;
38
+ },
39
+
40
+ setError: function (tune, msg) {
41
+ if (tune.warnings) {
42
+ tune.warning.push(msg);
43
+ } else {
44
+ tune.warnings = [msg];
45
+ }
46
+ },
47
+
48
+ /**
49
+ * handle params for current processed score
50
+ * @param {*} tune current tune
51
+ * @param {*} tuneNumber number in tune list
52
+ * @param {*} params params to be processed for tablature
53
+ * @return prepared tablatures plugin instances for current tune
54
+ */
55
+ preparePlugins: function (tune, tuneNumber, params) {
56
+ // Called after parsing a tune and before engraving it
57
+ if (!this.inited) {
58
+ // TODO-PER: I don't think this is needed - the plugin array can be hard coded, right?
59
+ this.register(new TabString());
60
+ this.inited = true;
61
+ }
62
+ var returned = null;
63
+ var nbPlugins = 0;
64
+ if (params.tablature) {
65
+ // validate requested plugins
66
+ var tabs = params.tablature;
67
+ returned = [];
68
+ for (var ii = 0; ii < tabs.length; ii++) {
69
+ var args = tabs[ii];
70
+ var instrument = args['instrument'];
71
+ if (instrument == null) {
72
+ this.setError(tune, "tablature 'instrument' is missing");
73
+ return returned;
74
+ }
75
+ var tabName = pluginTab[instrument];
76
+ var plugin = null;
77
+ if (tabName) {
78
+ plugin = this.plugins[tabName.name];
79
+ }
80
+ if (plugin) {
81
+ if (params.visualTranspose != 0) {
82
+ // populate transposition request to tabs
83
+ args.visualTranspose = params.visualTranspose;
84
+ }
85
+ args.abcSrc = params.tablature.abcSrc;
86
+ var pluginInstance = {
87
+ classz: plugin,
88
+ tuneNumber: tuneNumber,
89
+ params: args,
90
+ instance: null,
91
+ tabType: tabName,
92
+ };
93
+ // proceed with tab plugin init
94
+ // plugin.init(tune, tuneNumber, args, ii);
95
+ returned.push(pluginInstance);
96
+ nbPlugins++;
97
+ } else if (instrument === '') {
98
+ // create a placeholder - there is no tab for this staff
99
+ returned.push(null)
100
+ } else {
101
+ // unknown tab plugin
102
+ //this.emit_error('Undefined tablature plugin: ' + tabName)
103
+ this.setError(tune, 'Undefined tablature plugin: ' + instrument);
104
+ return returned;
105
+ }
106
+ }
107
+ }
108
+ return returned;
109
+ },
110
+
111
+ /**
112
+ * Call requested plugin
113
+ * @param {*} renderer
114
+ * @param {*} abcTune
115
+ */
116
+ layoutTablatures: function layoutTablatures(renderer, abcTune) {
117
+ var tabs = abcTune.tablatures;
118
+
119
+ // chack tabs request for each staffs
120
+ var staffLineCount = 0;
121
+
122
+ // Clear the suppression flag
123
+ if (tabs && (tabs.length > 0)) {
124
+ var nTabs = tabs.length;
125
+ for (var kk = 0; kk < nTabs; ++kk) {
126
+ if (tabs[kk] && tabs[kk].params.firstStaffOnly) {
127
+ tabs[kk].params.suppress = false;
128
+ }
129
+ }
130
+ }
131
+
132
+ for (var ii = 0; ii < abcTune.lines.length; ii++) {
133
+ var line = abcTune.lines[ii];
134
+
135
+ if (line.staff) {
136
+ staffLineCount++;
137
+ }
138
+
139
+ // MAE 27Nov2023
140
+ // If tab param "firstStaffOnly", remove the tab label after the first staff
141
+ if (staffLineCount > 1) {
142
+ if (tabs && (tabs.length > 0)) {
143
+ var nTabs = tabs.length;
144
+ for (var kk = 0; kk < nTabs; ++kk) {
145
+ if (tabs[kk].params.firstStaffOnly) {
146
+ // Set the staff draw suppression flag
147
+ tabs[kk].params.suppress = true;
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ var curStaff = line.staff;
154
+ if (curStaff) {
155
+ var maxStaves = curStaff.length
156
+ for (var jj = 0; jj < curStaff.length; jj++) {
157
+
158
+ if (tabs[jj] && jj < maxStaves) {
159
+ // tablature requested for staff
160
+ var tabPlugin = tabs[jj];
161
+ if (tabPlugin.instance == null) {
162
+ //console.log("★★★★ Tab Init line: " + ii + " staff: " + jj)
163
+ tabPlugin.instance = new tabPlugin.classz();
164
+ // plugin.init(tune, tuneNumber, args, ii);
165
+ // call initer first
166
+ tabPlugin.instance.init(abcTune,
167
+ tabPlugin.tuneNumber,
168
+ tabPlugin.params,
169
+ tabPlugin.tabType
170
+ );
171
+ }
172
+ // render next
173
+ //console.log("★★★★ Tab Render line: " + ii + " staff: " + jj)
174
+ tabPlugin.instance.render(renderer, line, jj);
175
+ }
176
+ }
177
+ }
178
+ }
179
+ },
180
+
181
+ };
182
+
183
+
184
+ module.exports = abcTablatures;