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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.4.0",
3
+ "version": "6.4.2",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -2,7 +2,7 @@
2
2
 
3
3
  var Parse = require('../parse/abc_parse');
4
4
  var bookParser = require('../parse/abc_parse_book');
5
- var tablatures = require('./abc_tablatures');
5
+ var tablatures = require('../tablatures/abc_tablatures');
6
6
 
7
7
 
8
8
  var tunebook = {};
@@ -87,7 +87,6 @@ var tunebook = {};
87
87
  // Init tablatures plugins
88
88
  //
89
89
  if (params.tablature) {
90
- tablatures.init();
91
90
  tune.tablatures = tablatures.preparePlugins(tune, currentTune, params);
92
91
  }
93
92
  var warnings = abcParser.getWarnings();
@@ -4,7 +4,6 @@ var Tune = require('../data/abc_tune');
4
4
  var EngraverController = require('../write/engraver-controller');
5
5
  var Parse = require('../parse/abc_parse');
6
6
  var wrap = require('../parse/wrap_lines');
7
- // var tablatures = require('./abc_tablatures');
8
7
 
9
8
 
10
9
  var resizeDivs = {};
@@ -9,20 +9,35 @@ var create;
9
9
 
10
10
  var baseDuration = 480*4; // nice and divisible, equals 1 whole note
11
11
 
12
- create = function(abcTune, options) {
12
+ create = function create(abcTune, options) {
13
13
  if (options === undefined) options = {};
14
14
  var commands = abcTune.setUpAudio(options);
15
15
  var midi = rendererFactory();
16
-
17
16
  var title = abcTune.metaText ? abcTune.metaText.title : undefined;
18
- if (title && title.length > 128)
19
- title = title.substring(0,124) + '...';
17
+ if (title && title.length > 128) title = title.substring(0, 124) + '...';
20
18
  var key = abcTune.getKeySignature();
21
19
  var time = abcTune.getMeterFraction();
22
- var beatsPerSecond = commands.tempo / 60;
23
- //var beatLength = abcTune.getBeatLength();
24
- midi.setGlobalInfo(commands.tempo, title, key, time);
25
20
 
21
+ // MAE 7 July 2024 - Fix for */8 meter tempos
22
+ var tempo = commands.tempo;
23
+
24
+ var beatsPerSecond = tempo / 60;
25
+
26
+ // Fix tempo for */8 meters
27
+ if (time.den == 8){
28
+
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
+ var msPerMeasure = abcTune.millisecondsPerMeasure();
31
+
32
+ tempo = (60000 / (msPerMeasure/time.num)) / 2;
33
+
34
+ beatsPerSecond = tempo/60;
35
+
36
+ }
37
+
38
+ //var beatLength = abcTune.getBeatLength();
39
+ midi.setGlobalInfo(tempo, title, key, time);
40
+
26
41
  for (var i = 0; i < commands.tracks.length; i++) {
27
42
  midi.startTrack();
28
43
  var notePlacement = {};
@@ -1,19 +1,11 @@
1
- // abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
1
+ // abc_common.js: Some common utility functions.
2
2
 
3
3
  var parseCommon = {};
4
4
 
5
- parseCommon.clone = function(source) {
6
- var destination = {};
7
- for (var property in source)
8
- if (source.hasOwnProperty(property))
9
- destination[property] = source[property];
10
- return destination;
11
- };
12
-
13
5
  parseCommon.cloneArray = function(source) {
14
6
  var destination = [];
15
7
  for (var i = 0; i < source.length; i++) {
16
- destination.push(parseCommon.clone(source[i]));
8
+ destination.push(Object.assign({},source[i]));
17
9
  }
18
10
  return destination;
19
11
  };
@@ -22,7 +14,7 @@ parseCommon.cloneHashOfHash = function(source) {
22
14
  var destination = {};
23
15
  for (var property in source)
24
16
  if (source.hasOwnProperty(property))
25
- destination[property] = parseCommon.clone(source[property]);
17
+ destination[property] = Object.assign({},source[property]);
26
18
  return destination;
27
19
  };
28
20
 
@@ -440,7 +440,7 @@ var Parse = function() {
440
440
  });
441
441
  for (var i = 0; i < nextVoice.length; i++) {
442
442
  var element = nextVoice[i];
443
- var hint = parseCommon.clone(element);
443
+ var hint = Object.assign({},element);
444
444
  voice.push(hint);
445
445
  if (element.el_type === 'bar')
446
446
  return;
@@ -485,8 +485,6 @@ var parseDirective = {};
485
485
  var midiCmdParam1Integer = [
486
486
  "bassvol",
487
487
  "chordvol",
488
- "bassprog",
489
- "chordprog",
490
488
  "c",
491
489
  "channel",
492
490
  "beatmod",
@@ -500,7 +498,8 @@ var parseDirective = {};
500
498
  "transpose",
501
499
  "rtranspose",
502
500
  "vol",
503
- "volinc"
501
+ "volinc",
502
+ "gchordbars"
504
503
  ];
505
504
  var midiCmdParam1Integer1OptionalInteger = [
506
505
  "program"
@@ -531,6 +530,10 @@ var parseDirective = {};
531
530
  "drum",
532
531
  "chordname"
533
532
  ];
533
+ var midiCmdParam1Integer1OptionalString = [
534
+ "bassprog", "chordprog"
535
+ ];
536
+
534
537
 
535
538
  var parseMidiCommand = function(midi, tune, restOfString) {
536
539
  var midi_cmd = midi.shift().token;
@@ -673,6 +676,44 @@ var parseDirective = {};
673
676
  }
674
677
  }
675
678
  }
679
+ else if (midiCmdParam1Integer1OptionalString.indexOf(midi_cmd) >= 0){
680
+
681
+ // ONE INT PARAMETER, ONE OPTIONAL string
682
+ if (midi.length !== 1 && midi.length !== 2)
683
+ warn("Expected one or two parameters in MIDI " + midi_cmd, restOfString, 0);
684
+ else if (midi[0].type !== "number")
685
+ warn("Expected integer parameter in MIDI " + midi_cmd, restOfString, 0);
686
+ else if (midi.length === 2 && midi[1].type !== "alpha")
687
+ warn("Expected alpha parameter in MIDI " + midi_cmd, restOfString, 0);
688
+ else {
689
+ midi_params.push(midi[0].intt);
690
+
691
+ // Currently only bassprog and chordprog with optional octave shifts use this path
692
+ if (midi.length === 2){
693
+ var cmd = midi[1].token;
694
+ if (cmd.indexOf("octave=") != -1){
695
+ cmd = cmd.replace("octave=","");
696
+ cmd = parseInt(cmd);
697
+ if (!isNaN(cmd)){
698
+ // Limit range from -1 to 3 octaves
699
+ if (cmd < -1){
700
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be >= -1 (recv:'+cmd+')');
701
+ cmd = -1;
702
+ }
703
+ if (cmd > 3){
704
+ warn("Expected octave= in MIDI " + midi_cmd + ' to be <= 3 (recv:'+cmd+')');
705
+ cmd = 3;
706
+ }
707
+ midi_params.push(cmd);
708
+ } else
709
+ warn("Expected octave value in MIDI" + midi_cmd);
710
+ }
711
+ else{
712
+ warn("Expected octave= in MIDI" + midi_cmd);
713
+ }
714
+ }
715
+ }
716
+ }
676
717
 
677
718
  if (tuneBuilder.hasBeginMusic())
678
719
  tuneBuilder.appendElement('midi', -1, -1, { cmd: midi_cmd, params: midi_params });
@@ -341,6 +341,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
341
341
 
342
342
  this.letter_to_inline_header = function(line, i, startLine)
343
343
  {
344
+ var needsNewLine = false
344
345
  var ws = tokenizer.eatWhiteSpace(line, i);
345
346
  i +=ws;
346
347
  if (line.length >= i+5 && line[i] === '[' && line[i+2] === ':') {
@@ -396,9 +397,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
396
397
  break;
397
398
  case "[V:":
398
399
  if (e > 0) {
399
- parseKeyVoice.parseVoice(line, i+3, e);
400
+ needsNewLine = parseKeyVoice.parseVoice(line, i+3, e);
400
401
  //startNewLine();
401
- return [ e-i+1+ws, line[i+1], line.substring(i+3, e)];
402
+ return [ e-i+1+ws, line[i+1], line.substring(i+3, e), needsNewLine];
402
403
  }
403
404
  break;
404
405
  case "[r:":
@@ -413,6 +414,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
413
414
 
414
415
  this.letter_to_body_header = function(line, i)
415
416
  {
417
+ var needsNewLine = false
416
418
  if (line.length >= i+3) {
417
419
  switch(line.substring(i, i+2))
418
420
  {
@@ -447,9 +449,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
447
449
  else if (tempo.type === 'immediate') tuneBuilder.appendElement('tempo', multilineVars.iChar + i, multilineVars.iChar + line.length, tempo.tempo);
448
450
  return [ e, line[i], parseCommon.strip(line.substring(i+2))];
449
451
  case "V:":
450
- parseKeyVoice.parseVoice(line, i+2, line.length);
452
+ needsNewLine = parseKeyVoice.parseVoice(line, i+2, line.length);
451
453
  // startNewLine();
452
- return [ line.length, line[i], parseCommon.strip(line.substring(i+2))];
454
+ return [ line.length, line[i], parseCommon.strip(line.substring(i+2)), needsNewLine];
453
455
  default:
454
456
  // TODO: complain about unhandled header
455
457
  }
@@ -1,4 +1,3 @@
1
- var parseCommon = require('./abc_common');
2
1
  var parseDirective = require('./abc_parse_directive');
3
2
  var transpose = require('./abc_transpose');
4
3
 
@@ -84,7 +83,7 @@ var parseKeyVoice = {};
84
83
  parseKeyVoice.deepCopyKey = function(key) {
85
84
  var ret = { accidentals: [], root: key.root, acc: key.acc, mode: key.mode };
86
85
  key.accidentals.forEach(function(k) {
87
- ret.accidentals.push(parseCommon.clone(k));
86
+ ret.accidentals.push(Object.assign({},k));
88
87
  });
89
88
  return ret;
90
89
  };
@@ -151,7 +150,7 @@ var parseKeyVoice = {};
151
150
  };
152
151
 
153
152
  parseKeyVoice.fixKey = function(clef, key) {
154
- var fixedKey = parseCommon.clone(key);
153
+ var fixedKey = Object.assign({},key);
155
154
  parseKeyVoice.addPosToKey(clef, fixedKey);
156
155
  return fixedKey;
157
156
  };
@@ -494,8 +493,13 @@ var parseKeyVoice = {};
494
493
  };
495
494
 
496
495
  var setCurrentVoice = function(id) {
497
- multilineVars.currentVoice = multilineVars.voices[id];
498
- tuneBuilder.setCurrentVoice(multilineVars.currentVoice.staffNum, multilineVars.currentVoice.index);
496
+ var currentVoice = multilineVars.voices[id]
497
+ if (multilineVars.currentVoice) {
498
+ if (multilineVars.currentVoice.index === currentVoice.index && multilineVars.currentVoice.staffNum === currentVoice.staffNum)
499
+ return // there was no change so don't reset it.
500
+ }
501
+ multilineVars.currentVoice = currentVoice;
502
+ return tuneBuilder.setCurrentVoice(currentVoice.staffNum, currentVoice.index, id);
499
503
  };
500
504
 
501
505
  parseKeyVoice.parseVoice = function(line, i, e) {
@@ -788,7 +792,7 @@ var parseKeyVoice = {};
788
792
  if (staffInfo.name) {if (s.name) s.name.push(staffInfo.name); else s.name = [ staffInfo.name ];}
789
793
  if (staffInfo.subname) {if (s.subname) s.subname.push(staffInfo.subname); else s.subname = [ staffInfo.subname ];}
790
794
 
791
- setCurrentVoice(id);
795
+ return setCurrentVoice(id);
792
796
  };
793
797
 
794
798
  })();
@@ -1,4 +1,3 @@
1
- var parseCommon = require('./abc_common');
2
1
  var parseKeyVoice = require('./abc_parse_key_voice');
3
2
  var transpose = require('./abc_transpose');
4
3
 
@@ -145,6 +144,7 @@ MusicParser.prototype.parseMusic = function(line) {
145
144
  var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine);
146
145
  if (retInlineHeader[0] > 0) {
147
146
  i += retInlineHeader[0];
147
+ //console.log("inline header", retInlineHeader)
148
148
  if (retInlineHeader[1] === 'V')
149
149
  delayStartNewLine = true; // fixes bug on this: c[V:2]d
150
150
  // TODO-PER: Handle inline headers
@@ -457,6 +457,9 @@ MusicParser.prototype.parseMusic = function(line) {
457
457
  else
458
458
  postChordDone = true;
459
459
  break;
460
+ case '0':
461
+ chordDuration = 0;
462
+ break;
460
463
  default:
461
464
  postChordDone = true;
462
465
  break;
@@ -554,13 +557,17 @@ MusicParser.prototype.parseMusic = function(line) {
554
557
  // The first item on a line is a regular note value, each item after that represents a dot placed after the previous note.
555
558
  // Only durations less than a whole note are tested because whole note durations have some tricky rules.
556
559
 
557
- if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
560
+ if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
558
561
  if (!el.rest || el.rest.type !== 'spacer')
559
562
  warn("Duration not representable: " + line.substring(startI, i), line, i);
560
563
  }
561
564
 
562
565
  multilineVars.addFormattingOptions(el, tune.formatting, 'note');
563
- tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
566
+ var succeeded = tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
567
+ if (!succeeded) {
568
+ this.startNewLine()
569
+ tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
570
+ }
564
571
  multilineVars.measureNotEmpty = true;
565
572
  el = {};
566
573
  }
@@ -670,7 +677,11 @@ var letter_to_grace = function(line, i) {
670
677
  note.duration = note.duration / (multilineVars.default_length * 8);
671
678
  if (acciaccatura)
672
679
  note.acciaccatura = true;
673
- gracenotes.push(note);
680
+ if (note.rest) {
681
+ // don't allow rests inside gracenotes
682
+ warn("Rests not allowed as grace notes '" + gra[1][ii] + "' while parsing grace note", line, i);
683
+ } else
684
+ gracenotes.push(note);
674
685
 
675
686
  if (inTie) {
676
687
  note.endTie = true;
@@ -927,7 +938,7 @@ MusicParser.prototype.startNewLine = function() {
927
938
  var params = { startChar: -1, endChar: -1};
928
939
  if (multilineVars.partForNextLine.title)
929
940
  params.part = multilineVars.partForNextLine;
930
- params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
941
+ params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? Object.assign({},multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : Object.assign({},multilineVars.clef);
931
942
  var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
932
943
  params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
933
944
  params.key.mode = multilineVars.key.mode;
@@ -997,6 +1008,12 @@ MusicParser.prototype.startNewLine = function() {
997
1008
  params.style = multilineVars.currentVoice.style;
998
1009
  if (multilineVars.currentVoice.transpose)
999
1010
  params.clef.transpose = multilineVars.currentVoice.transpose;
1011
+ params.currentVoice = multilineVars.currentVoice
1012
+ var voices = Object.keys(multilineVars.voices)
1013
+ for (var mv = 0; mv < voices.length; mv++) {
1014
+ if (params.currentVoice.staffNum === multilineVars.voices[voices[mv]].staffNum && params.currentVoice.index === multilineVars.voices[voices[mv]].index)
1015
+ params.currentVoiceName = voices[mv]
1016
+ }
1000
1017
  }
1001
1018
  var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
1002
1019
  if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)