abcjs 6.4.1 → 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 (39) hide show
  1. package/RELEASE.md +34 -0
  2. package/dist/abcjs-basic-min.js +2 -2
  3. package/dist/abcjs-basic.js +1246 -1136
  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 +81 -18
  20. package/src/synth/create-synth-control.js +1 -2
  21. package/src/tablatures/abc_tablatures.js +184 -0
  22. package/src/tablatures/instruments/string-patterns.js +266 -268
  23. package/src/tablatures/instruments/string-tablature.js +38 -35
  24. package/src/tablatures/instruments/tab-note.js +186 -181
  25. package/src/tablatures/instruments/tab-notes.js +30 -35
  26. package/src/tablatures/instruments/tab-string.js +43 -25
  27. package/src/tablatures/render/tab-absolute-elements.js +303 -0
  28. package/src/tablatures/render/tab-renderer.js +244 -0
  29. package/src/test/abc_parser_lint.js +2 -3
  30. package/src/write/creation/abstract-engraver.js +1 -1
  31. package/src/write/engraver-controller.js +1 -1
  32. package/temp.txt +12 -0
  33. package/types/index.d.ts +1 -1
  34. package/version.js +1 -1
  35. package/src/api/abc_tablatures.js +0 -184
  36. package/src/tablatures/instruments/tab-string-patterns.js +0 -23
  37. package/src/tablatures/tab-absolute-elements.js +0 -301
  38. package/src/tablatures/tab-common.js +0 -29
  39. 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]);
@@ -338,25 +383,34 @@ function resolvePitch(currentChord, type, firstBoom, newBass) {
338
383
  ret.push(firstBoom ? currentChord.boom : currentChord.boom2)
339
384
  else if (newBass)
340
385
  ret.push(currentChord.boom)
386
+ var numChordNotes = currentChord.chick.length
341
387
  if (type.indexOf('chick') >= 0) {
342
- for (var i = 0; i < currentChord.chick.length; i++)
388
+ for (var i = 0; i < numChordNotes; i++)
343
389
  ret.push(currentChord.chick[i])
344
390
  }
345
391
  switch (type) {
346
392
  case 'DO': ret.push(currentChord.chick[0]); break;
347
393
  case 'MI': ret.push(currentChord.chick[1]); break;
348
- case 'SOL': ret.push(currentChord.chick[2]); break;
349
- case 'TI': currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]) : ret.push(currentChord.chick[0]+12); break;
350
- 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;
351
397
  case 'do': ret.push(currentChord.chick[0]+12); break;
352
398
  case 'mi': ret.push(currentChord.chick[1]+12); break;
353
- case 'sol': ret.push(currentChord.chick[2]+12); break;
354
- case 'ti': currentChord.chick.length > 3 ? ret.push(currentChord.chick[2]+12) : ret.push(currentChord.chick[0]+24); break;
355
- 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;
356
402
  }
357
403
  return ret
358
404
  }
359
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
+
360
414
  function parseGChord(gchord) {
361
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.
362
416
  var pattern = []
@@ -532,7 +586,12 @@ ChordTrack.prototype.chordIntervals = {
532
586
  'maj7#5#11': [0, 4, 8, 11, 18],
533
587
  '9(#5)': [0, 4, 8, 10, 14],
534
588
  '13(#5)': [0, 4, 8, 10, 14, 21],
535
- '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
+
536
595
  };
537
596
 
538
597
  ChordTrack.prototype.rhythmPatterns = {
@@ -547,8 +606,12 @@ ChordTrack.prototype.rhythmPatterns = {
547
606
  "6/4": ['boom', '', 'chick', '', 'boom', '', 'chick', '', 'boom', '', 'chick', ''],
548
607
 
549
608
  "3/8": ['boom', '', 'chick'],
609
+ "5/8": ['boom', 'chick', 'chick', 'boom', 'chick'],
550
610
  "6/8": ['boom', '', 'chick', 'boom', '', 'chick'],
611
+ "7/8": ['boom', 'chick', 'chick', 'boom', 'chick', 'boom', 'chick'],
551
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'],
552
615
  "12/8": ['boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick', 'boom', '', 'chick'],
553
616
  };
554
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
@@ -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;