abcjs 6.4.3 → 6.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abcjs",
3
- "version": "6.4.3",
3
+ "version": "6.5.0",
4
4
  "description": "Renderer for abc music notation",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -938,14 +938,27 @@ var parseDirective = {};
938
938
  }
939
939
  multilineVars.currBarNumber = tuneBuilder.setBarNumberImmediate(tokens[0].intt);
940
940
  break;
941
+ case "keywarn":
942
+ if (tokens.length !== 1 || tokens[0].type !== 'number' || (tokens[0].intt !== 1 && tokens[0].intt !== 0)) {
943
+ return 'Directive ' + cmd + ' requires 0 or 1 as a parameter.';
944
+ }
945
+ multilineVars[cmd] = tokens[0].intt === 1
946
+ break;
941
947
  case "begintext":
942
948
  var textBlock = '';
943
949
  line = tokenizer.nextLine();
944
950
  while(line && line.indexOf('%%endtext') !== 0) {
945
- if (parseCommon.startsWith(line, "%%"))
946
- textBlock += line.substring(2) + "\n";
947
- else
948
- textBlock += line + "\n";
951
+ // MAE 9 May 2025 - for text blocks with just white space
952
+ if (parseCommon.startsWith(line, "%%")){
953
+
954
+ var theLine = line.substring(2);
955
+ theLine = theLine.trim() + "\n";
956
+ textBlock += theLine;
957
+
958
+ }
959
+ else{
960
+ textBlock += line.trim() + "\n";
961
+ }
949
962
  line = tokenizer.nextLine();
950
963
  }
951
964
  tuneBuilder.addText(textBlock, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+textBlock.length+7});
@@ -1119,6 +1132,17 @@ var parseDirective = {};
1119
1132
  }
1120
1133
  break;
1121
1134
 
1135
+ case "maxstaves":
1136
+ var nStaves = tokenizer.getInt(restOfString)
1137
+ if (nStaves.digits === 0)
1138
+ warn("Expected number of staves in maxstaves")
1139
+ else{
1140
+ if (nStaves.value > 0){
1141
+ tune.formatting.maxStaves = nStaves.value;
1142
+ }
1143
+ }
1144
+ break;
1145
+
1122
1146
  case "newpage":
1123
1147
  var pgNum = tokenizer.getInt(restOfString);
1124
1148
  tuneBuilder.addNewPage(pgNum.digits === 0 ? -1 : pgNum.value);
@@ -1179,6 +1203,14 @@ var parseDirective = {};
1179
1203
  }
1180
1204
  break;
1181
1205
 
1206
+ case "visualtranspose":
1207
+ var halfSteps = tokenizer.getInt(restOfString)
1208
+ if (halfSteps.digits === 0)
1209
+ warn("Expected number of half steps in visualTranspose")
1210
+ else
1211
+ multilineVars.globalTranspose = halfSteps.value
1212
+ break;
1213
+
1182
1214
  case "map":
1183
1215
  case "playtempo":
1184
1216
  case "auquality":
@@ -429,9 +429,9 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
429
429
  return [ line.length ];
430
430
  case "K:":
431
431
  var result = parseKeyVoice.parseKey(line.substring(i+2), tuneBuilder.hasBeginMusic());
432
- if (result.foundClef && tuneBuilder.hasBeginMusic())
432
+ if (result.foundClef && tuneBuilder.hasBeginMusic() && multilineVars.keywarn !== false)
433
433
  tuneBuilder.appendStartingElement('clef', multilineVars.iChar + i, multilineVars.iChar + line.length, multilineVars.clef);
434
- if (result.foundKey && tuneBuilder.hasBeginMusic())
434
+ if (result.foundKey && tuneBuilder.hasBeginMusic() && multilineVars.keywarn !== false)
435
435
  tuneBuilder.appendStartingElement('key', multilineVars.iChar + i, multilineVars.iChar + line.length, parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
436
436
  return [ line.length ];
437
437
  case "P:":
@@ -504,7 +504,7 @@ var ParseHeader = function(tokenizer, warn, multilineVars, tune, tuneBuilder) {
504
504
  // since the key is the last thing that can happen in the header, we can resolve the tempo now
505
505
  this.resolveTempo();
506
506
  var result = parseKeyVoice.parseKey(line.substring(2), false);
507
- if (!multilineVars.is_in_header && tuneBuilder.hasBeginMusic()) {
507
+ if (!multilineVars.is_in_header && tuneBuilder.hasBeginMusic() && multilineVars.keywarn !== false) {
508
508
  if (result.foundClef)
509
509
  tuneBuilder.appendStartingElement('clef', startChar, endChar, multilineVars.clef);
510
510
  if (result.foundKey)
@@ -292,7 +292,7 @@ var parseKeyVoice = {};
292
292
  if (isInline)
293
293
  multilineVars.globalTransposeOrigKeySig = savedOrigKey
294
294
  multilineVars.key.mode = mode;
295
- if (oldKey) {
295
+ if (oldKey && multilineVars.keywarn !== false) {
296
296
  // Add natural in all places that the old key had an accidental.
297
297
  var kk;
298
298
  for (var k = 0; k < multilineVars.key.accidentals.length; k++) {
@@ -644,13 +644,48 @@ var parseKeyVoice = {};
644
644
  case 'tenor,,':
645
645
  case 'alto,,':
646
646
  case 'none,,':
647
+ // MAE 26 May 2025 Start of additional clefs
648
+ case 'treble+8':
649
+ case 'treble-8':
650
+ case 'treble^8':
651
+ case 'treble_8':
652
+ case 'treble1':
653
+ case 'treble2':
654
+ case 'treble3':
655
+ case 'treble4':
656
+ case 'treble5':
657
+ case 'bass+8':
658
+ case 'bass-8':
659
+ case 'bass^8':
660
+ case 'bass_8':
661
+ case 'bass+16':
662
+ case 'bass-16':
663
+ case 'bass^16':
664
+ case 'bass_16':
665
+ case 'bass1':
666
+ case 'bass2':
667
+ case 'bass3':
668
+ case 'bass4':
669
+ case 'bass5':
670
+ case 'tenor1':
671
+ case 'tenor2':
672
+ case 'tenor3':
673
+ case 'tenor4':
674
+ case 'tenor5':
675
+ case 'alto1':
676
+ case 'alto2':
677
+ case 'alto3':
678
+ case 'alto4':
679
+ case 'alto5':
680
+ case 'alto+8':
681
+ case 'alto-8':
682
+ case 'alto^8':
683
+ case 'alto_8':
684
+ // MAE 26 May 2025 End of additional clefs
685
+
647
686
  // TODO-PER: handle the octave indicators on the clef by changing the middle property
648
687
  var oct2 = 0;
649
- // for (var iii = 0; iii < token.token.length; iii++) {
650
- // if (token.token[iii] === ',') oct2 -= 7;
651
- // else if (token.token[iii] === "'") oct2 += 7;
652
- // }
653
- staffInfo.clef = token.token.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
688
+ staffInfo.clef = token.token.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
654
689
  staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct2);
655
690
  multilineVars.voices[id].clef = token.token;
656
691
  break;
@@ -294,8 +294,7 @@ MusicParser.prototype.parseMusic = function(line) {
294
294
  else if (bar.endEnding)
295
295
  multilineVars.barFirstEndingNum = undefined;
296
296
  if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
297
- var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
298
- if (isFirstVoice) {
297
+ if (isFirstVoice()) {
299
298
  multilineVars.currBarNumber++;
300
299
  if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
301
300
  bar.barNumber = multilineVars.currBarNumber;
@@ -510,6 +509,8 @@ MusicParser.prototype.parseMusic = function(line) {
510
509
  if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
511
510
  } else {
512
511
  el.rest = core.rest;
512
+ if (core.rest.type === 'multimeasure' && isFirstVoice())
513
+ multilineVars.currBarNumber += core.rest.text - 1 // The minus one is because the measure with the rest is already counted once normally.
513
514
  if (core.endSlur !== undefined) el.endSlur = core.endSlur;
514
515
  if (core.endTie !== undefined) el.rest.endTie = core.endTie;
515
516
  if (core.startSlur !== undefined) el.startSlur = core.startSlur;
@@ -834,6 +835,8 @@ var letter_to_accent = function(line, i) {
834
835
  case 'R':return [1, 'roll'];
835
836
  case 'S':return [1, 'segno'];
836
837
  case 'T':return [1, 'trill'];
838
+ case 't':return [1, 'trillh'];
839
+
837
840
  }
838
841
  return [0, 0];
839
842
  };
@@ -1030,8 +1033,7 @@ MusicParser.prototype.startNewLine = function() {
1030
1033
  params.currentVoiceName = voices[mv]
1031
1034
  }
1032
1035
  }
1033
- var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
1034
- if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)
1036
+ if (multilineVars.barNumbers === 0 && isFirstVoice() && multilineVars.currBarNumber !== 1)
1035
1037
  params.barNumber = multilineVars.currBarNumber;
1036
1038
  tuneBuilder.startNewLine(params);
1037
1039
  if (multilineVars.key.impliedNaturals)
@@ -1315,4 +1317,8 @@ var getBrokenRhythm = function(line, index) {
1315
1317
  return null;
1316
1318
  };
1317
1319
 
1320
+ function isFirstVoice() {
1321
+ return multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
1322
+ }
1323
+
1318
1324
  module.exports = MusicParser;
@@ -1,5 +1,6 @@
1
1
  module.exports.legalAccents = [
2
2
  'trill',
3
+ 'trillh',
3
4
  'lowermordent',
4
5
  'uppermordent',
5
6
  'mordent',
@@ -677,9 +677,17 @@ function getTitleNumber(str){
677
677
  }
678
678
 
679
679
  var thePatterns = [
680
- { match: /,\s*[Tt]he$/, replace: "The " },
681
- { match: /,\s*[Aa]$/, replace: "A " },
682
- { match: /,\s*[Aa]n$/, replace: "An " },
680
+ { match: /,\s*The$/, replace: "The " },
681
+ { match: /,\s*the$/, replace: "the " },
682
+ { match: /,\s*A$/, replace: "A " },
683
+ { match: /,\s*a$/, replace: "a " },
684
+ { match: /,\s*An$/, replace: "An " },
685
+ { match: /,\s*an$/, replace: "an " },
686
+ { match: /,\s*Da$/, replace: "Da " },
687
+ { match: /,\s*La$/, replace: "La " },
688
+ { match: /,\s*Le$/, replace: "Le " },
689
+ { match: /,\s*Les$/, replace: "Les " },
690
+ { match: /,\s*Ye$/, replace: "Ye " },
683
691
  ]
684
692
 
685
693
  this.theReverser = function (str) {
@@ -65,7 +65,7 @@ transpose.keySignature = function(multilineVars, keyName, root, acc, localTransp
65
65
  var newKeyName = (keyName[0] === 'm' ? newKeyMinor[index] : newKey[index]);
66
66
  var transposedKey = newKeyName + keyName;
67
67
  var newKeySig = keyAccidentals(transposedKey);
68
- if (newKeySig.length > 0 && newKeySig[0].acc === 'flat')
68
+ if (newKeySig.length === 0 || newKeySig[0].acc === 'flat') // key of C and all keys with flats should have chords with flats
69
69
  multilineVars.localTransposePreferFlats = true;
70
70
  var distance = transposedKey.charCodeAt(0) - baseKey.charCodeAt(0);
71
71
  if (multilineVars.localTranspose > 0) {
@@ -45,6 +45,14 @@ function transposeChordName(chord, steps, preferFlats, freeGCchord) {
45
45
  else chord = sharpChords[index]
46
46
  }
47
47
 
48
+ var isDim = extra1 && (extra1.indexOf('dim') >= 0 || extra1.indexOf('°') >= 0)
49
+ console.log(isDim, chord, extra1)
50
+ // We never want A#dim or D#dim
51
+ if (isDim && chord === 'A#') chord = 'Bb'
52
+ if (isDim && chord === 'D#') chord = 'Eb'
53
+ if (isDim && chord === 'A♯') chord = 'B♭'
54
+ if (isDim && chord === 'D♯') chord = 'E♭'
55
+
48
56
  if (extra1)
49
57
  chord += extra1
50
58
 
@@ -77,4 +85,4 @@ function transposeChordName(chord, steps, preferFlats, freeGCchord) {
77
85
  return chord;
78
86
  }
79
87
 
80
- module.exports = transposeChordName
88
+ module.exports = transposeChordName
@@ -9,7 +9,7 @@ var TuneBuilder = function (tune) {
9
9
  tune.reset();
10
10
 
11
11
  this.setVisualTranspose = function (visualTranspose) {
12
- if (visualTranspose)
12
+ if (visualTranspose!==undefined)
13
13
  tune.visualTranspose = visualTranspose;
14
14
  };
15
15
 
@@ -382,7 +382,7 @@ var TuneBuilder = function (tune) {
382
382
 
383
383
  this.getCurrentVoice = function () {
384
384
  //console.log("getCurrentVoice", tune.lineNum)
385
- var currLine = tune.lines[tune.lineNum];
385
+ var currLine = getPrevMusicLine(tune.lines, tune.lineNum)
386
386
  if (!currLine)
387
387
  return null;
388
388
  var currStaff = currLine.staff[tune.staffNum];
@@ -803,6 +803,18 @@ function wrapMusicLines(lines, barsperstaff) {
803
803
  return false;
804
804
  }
805
805
 
806
+ function getPrevMusicLine(lines, currentLine) {
807
+ if (lines.length <= currentLine)
808
+ return null
809
+ // If the current line doesn't have music, search backwards until one is found.
810
+ while (currentLine >= 0) {
811
+ if (lines[currentLine].staff)
812
+ return lines[currentLine];
813
+ currentLine--;
814
+ }
815
+ return null;
816
+ }
817
+
806
818
  function getNextMusicLine(lines, currentLine) {
807
819
  currentLine++;
808
820
  while (lines.length > currentLine) {
package/src/str/output.js CHANGED
@@ -58,12 +58,12 @@ var strTranspose;
58
58
  var count = arr[0].length
59
59
  for (var i = 1; i < arr.length; i++) {
60
60
  var segment = arr[i]
61
- var match = segment.match(/^( *)([A-G])([#b]?)(\w*)/)
61
+ var match = segment.match(/^( *)([A-G])([#b]?)( ?)(\w*)/)
62
62
  if (match) {
63
63
  var start = count + 2 + match[1].length // move past the 'K:' and optional white space
64
- var key = match[2] + match[3] + match[4] // key name, accidental, and mode
65
- var destinationKey = newKey({ root: match[2], acc: match[3], mode: match[4] }, steps)
66
- var dest = destinationKey.root + destinationKey.acc + destinationKey.mode
64
+ var key = match[2] + match[3] + match[4] + match[5] // key name, accidental, optional space, and mode
65
+ var destinationKey = newKey({ root: match[2], acc: match[3], mode: match[5] }, steps)
66
+ var dest = destinationKey.root + destinationKey.acc + match[4] + destinationKey.mode
67
67
  changes.push({ start: start, end: start + key.length, note: dest })
68
68
  }
69
69
  count += segment.length + 2
@@ -35,6 +35,7 @@ var pitchesToPerc = require('./pitches-to-perc');
35
35
  var stressBeat1 = 105;
36
36
  var stressBeatDown = 95;
37
37
  var stressBeatUp = 85;
38
+ var volumesPerNotePitch = [[stressBeat1, stressBeatDown, stressBeatUp]];
38
39
  var beatFraction = 0.25;
39
40
  var nextVolume;
40
41
  var nextVolumeDelta;
@@ -77,6 +78,7 @@ var pitchesToPerc = require('./pitches-to-perc');
77
78
  stressBeat1 = 105;
78
79
  stressBeatDown = 95;
79
80
  stressBeatUp = 85;
81
+ volumesPerNotePitch = [];
80
82
  beatFraction = 0.25;
81
83
  nextVolume = undefined;
82
84
  nextVolumeDelta = undefined;
@@ -193,6 +195,10 @@ var pitchesToPerc = require('./pitches-to-perc');
193
195
  stressBeat1 = element.beats[0];
194
196
  stressBeatDown = element.beats[1];
195
197
  stressBeatUp = element.beats[2];
198
+ if (!element.volumesPerNotePitch)
199
+ volumesPerNotePitch = []
200
+ else
201
+ volumesPerNotePitch = element.volumesPerNotePitch;
196
202
  // TODO-PER: also use the last parameter - which changes which beats are strong.
197
203
  break;
198
204
  case "vol":
@@ -343,28 +349,35 @@ var pitchesToPerc = require('./pitches-to-perc');
343
349
  return distanceFromStart / beatLength;
344
350
  }
345
351
 
346
- function processVolume(beat, voiceOff) {
352
+ function processVolume(beat, voiceOff, pitchIndexOfNote) {
347
353
  if (voiceOff)
348
354
  return 0;
349
-
355
+ let pitchStressBeat1 = stressBeat1;
356
+ let pitchStressBeatDown = stressBeatDown;
357
+ let pitchStressBeatUp = stressBeatUp;
358
+ if(pitchIndexOfNote !== undefined && volumesPerNotePitch.length >= pitchIndexOfNote+1){
359
+ pitchStressBeat1 = volumesPerNotePitch[pitchIndexOfNote][0];
360
+ pitchStressBeatDown = volumesPerNotePitch[pitchIndexOfNote][1];
361
+ pitchStressBeatUp = volumesPerNotePitch[pitchIndexOfNote][2];
362
+ }
350
363
  var volume;
351
364
  // MAE 21 Jun 2024 - This previously wasn't allowing zero volume to be applied
352
- if (nextVolume != undefined) {
365
+ if (nextVolume !== undefined) {
353
366
  volume = nextVolume;
354
367
  nextVolume = undefined;
355
368
  } else if (!doBeatAccents) {
356
- volume = stressBeatDown;
369
+ volume = pitchStressBeatDown;
357
370
  } else if (pickupLength > beat) {
358
- volume = stressBeatUp;
371
+ volume = pitchStressBeatUp;
359
372
  } else {
360
373
  //var barLength = meter.num / meter.den;
361
374
  var barBeat = calcBeat(lastBarTime, getBeatFraction(meter), beat);
362
375
  if (barBeat === 0)
363
- volume = stressBeat1;
376
+ volume = pitchStressBeat1;
364
377
  else if (parseInt(barBeat,10) === barBeat)
365
- volume = stressBeatDown;
378
+ volume = pitchStressBeatDown;
366
379
  else
367
- volume = stressBeatUp;
380
+ volume = pitchStressBeatUp;
368
381
  }
369
382
  if (nextVolumeDelta) {
370
383
  volume += nextVolumeDelta;
@@ -393,13 +406,17 @@ var pitchesToPerc = require('./pitches-to-perc');
393
406
  else if (elem.decoration[d] === 'lowermordent')
394
407
  ret.noteModification = "lowermordent";
395
408
  else if (elem.decoration[d] === 'uppermordent')
396
- ret.noteModification = "mordent";
409
+ ret.noteModification = "pralltriller";
397
410
  else if (elem.decoration[d] === 'mordent')
398
411
  ret.noteModification = "mordent";
399
412
  else if (elem.decoration[d] === 'turn')
400
413
  ret.noteModification = "turn";
401
414
  else if (elem.decoration[d] === 'roll')
402
415
  ret.noteModification = "roll";
416
+ else if (elem.decoration[d] === 'pralltriller')
417
+ ret.noteModification = "pralltriller";
418
+ else if (elem.decoration[d] === 'trillh')
419
+ ret.noteModification = "trillh";
403
420
  }
404
421
  }
405
422
  return ret;
@@ -415,39 +432,57 @@ var pitchesToPerc = require('./pitches-to-perc');
415
432
 
416
433
  switch (noteModification) {
417
434
  case "trill":
418
- var note = 1;
435
+ var note = 2;
419
436
  while (runningDuration > 0) {
420
437
  currentTrack.push({ cmd: 'note', pitch: p.pitch+note, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
421
- note = (note === 1) ? 0 : 1;
438
+ note = (note === 2) ? 0 : 2;
422
439
  runningDuration -= shortestNote;
423
440
  start += shortestNote;
424
441
  }
425
442
  break;
426
- case "mordent":
443
+ case "trillh":
444
+ var note = 1;
445
+ while (runningDuration > 0) {
446
+ currentTrack.push({
447
+ cmd: 'note',
448
+ pitch: p.pitch + note,
449
+ volume: p.volume,
450
+ start: start,
451
+ duration: shortestNote,
452
+ gap: 0,
453
+ instrument: currentInstrument,
454
+ style: 'decoration'
455
+ });
456
+ note = note === 1 ? 0 : 1;
457
+ runningDuration -= shortestNote;
458
+ start += shortestNote;
459
+ }
460
+ break;
461
+ case "pralltriller":
427
462
  currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
428
463
  runningDuration -= shortestNote;
429
464
  start += shortestNote;
430
- currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
465
+ currentTrack.push({ cmd: 'note', pitch: p.pitch+2, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
431
466
  runningDuration -= shortestNote;
432
467
  start += shortestNote;
433
468
  currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument });
434
469
  break;
470
+ case "mordent":
435
471
  case "lowermordent":
436
472
  currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
437
473
  runningDuration -= shortestNote;
438
474
  start += shortestNote;
439
- currentTrack.push({ cmd: 'note', pitch: p.pitch-1, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
475
+ currentTrack.push({ cmd: 'note', pitch: p.pitch-2, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
440
476
  runningDuration -= shortestNote;
441
477
  start += shortestNote;
442
478
  currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: runningDuration, gap: 0, instrument: currentInstrument });
443
479
  break;
444
480
  case "turn":
445
- shortestNote = p.duration / 5;
446
- currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
447
- currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
448
- currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*2, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
449
- currentTrack.push({ cmd: 'note', pitch: p.pitch+1, volume: p.volume, start: start+shortestNote*3, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
450
- currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*4, duration: shortestNote, gap: 0, instrument: currentInstrument });
481
+ shortestNote = p.duration / 4;
482
+ currentTrack.push({ cmd: 'note', pitch: p.pitch+2, volume: p.volume, start: start, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
483
+ currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
484
+ currentTrack.push({ cmd: 'note', pitch: p.pitch-1, volume: p.volume, start: start+shortestNote*2, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
485
+ currentTrack.push({ cmd: 'note', pitch: p.pitch, volume: p.volume, start: start+shortestNote*3, duration: shortestNote, gap: 0, instrument: currentInstrument, style: 'decoration' });
451
486
  break;
452
487
  case "roll":
453
488
  while (runningDuration > 0) {
@@ -533,6 +568,11 @@ var pitchesToPerc = require('./pitches-to-perc');
533
568
  if (elem.elem)
534
569
  elem.elem.midiPitches = [];
535
570
  for (var i=0; i<ePitches.length; i++) {
571
+ //here we can set the volume for each note in a chord, if specified
572
+ let pitchVelocity = velocity;
573
+ if(!ret.velocity && Array.isArray(elem.decoration) && elem.decoration.length > i){
574
+ pitchVelocity = processVolume(timeToRealTime(elem.time), voiceOff, i)
575
+ }
536
576
  var note = ePitches[i];
537
577
  if (!note)
538
578
  continue;
@@ -546,7 +586,7 @@ var pitchesToPerc = require('./pitches-to-perc');
546
586
  if (name && percmap[name])
547
587
  actualPitch = percmap[name].sound;
548
588
  }
549
- var p = { cmd: 'note', pitch: actualPitch, volume: velocity, start: timeToRealTime(elem.time), duration: durationRounded(note.duration), instrument: currentInstrument, startChar: elem.elem.startChar, endChar: elem.elem.endChar};
589
+ var p = { cmd: 'note', pitch: actualPitch, volume: pitchVelocity, start: timeToRealTime(elem.time), duration: durationRounded(note.duration), instrument: currentInstrument, startChar: elem.elem.startChar, endChar: elem.elem.endChar};
550
590
  p = adjustForMicroTone(p);
551
591
  if (elem.gracenotes) {
552
592
  p.duration = p.duration / 2;
@@ -135,6 +135,7 @@ var parseCommon = require("../parse/abc_common");
135
135
 
136
136
  // visit each voice completely in turn
137
137
  var voices = [];
138
+ var clefTransposeActive = []
138
139
  var inCrescendo = [];
139
140
  var inDiminuendo = [];
140
141
  var durationCounter = [0];
@@ -188,12 +189,24 @@ var parseCommon = require("../parse/abc_common");
188
189
  if (staff.clef && staff.clef.type !== "perc" && staff.clef.transpose) {
189
190
  staff.clef.el_type = 'clef';
190
191
  voices[voiceNumber].push({ el_type: 'transpose', transpose: staff.clef.transpose });
192
+ clefTransposeActive[voiceNumber] = false
191
193
  }
192
194
  if (staff.clef && staff.clef.type) {
193
- if (staff.clef.type.indexOf("-8") >= 0)
194
- voices[voiceNumber].push({ el_type: 'transpose', transpose: -12 });
195
- else if (staff.clef.type.indexOf("+8") >= 0)
196
- voices[voiceNumber].push({ el_type: 'transpose', transpose: 12 });
195
+ if (staff.clef.type.indexOf("-8") >= 0) {
196
+ voices[voiceNumber].push({el_type: 'transpose', transpose: -12});
197
+ clefTransposeActive[voiceNumber] = true
198
+ }
199
+ else if (staff.clef.type.indexOf("+8") >= 0) {
200
+ voices[voiceNumber].push({el_type: 'transpose', transpose: 12});
201
+ clefTransposeActive[voiceNumber] = true
202
+ }
203
+ else {
204
+ // if we had a previous treble+8 and now have a regular clef, then cancel the transposition
205
+ if (clefTransposeActive[voiceNumber]) {
206
+ voices[voiceNumber].push({ el_type: 'transpose', transpose: 0 });
207
+ clefTransposeActive[voiceNumber] = false
208
+ }
209
+ }
197
210
  }
198
211
 
199
212
  if (abctune.formatting.midi && abctune.formatting.midi.drumoff) {
@@ -429,7 +442,7 @@ var parseCommon = require("../parse/abc_common");
429
442
  }
430
443
 
431
444
  function setDynamics(elem) {
432
- var volumes = {
445
+ var volumes = {//stressBeat1, stressBeatDown, stressBeatUp
433
446
  'pppp': [15, 10, 5, 1],
434
447
  'ppp': [30, 20, 10, 1],
435
448
  'pp': [45, 35, 20, 1],
@@ -467,7 +480,14 @@ var parseCommon = require("../parse/abc_common");
467
480
 
468
481
  if (dynamicType) {
469
482
  currentVolume = volumes[dynamicType].slice(0);
470
- voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0) });
483
+ let volumesPerNotePitch = [currentVolume];
484
+ if(Array.isArray(elem.decoration)){
485
+ volumesPerNotePitch = [];
486
+ elem.decoration.forEach(d=>{
487
+ volumesPerNotePitch.push(volumes[d].slice(0));
488
+ });
489
+ }
490
+ voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0), volumesPerNotePitch: volumesPerNotePitch, });
471
491
  inCrescendo[k] = false;
472
492
  inDiminuendo[k] = false;
473
493
  }
@@ -183,7 +183,7 @@ ChordTrack.prototype.interpretChord = function (name) {
183
183
  return { chick: [] };
184
184
  var root = name.substring(0, 1);
185
185
  if (root === '(') {
186
- name = name.substring(1, name.length - 2);
186
+ name = name.substring(1, name.length - 1);
187
187
  if (name.length === 0)
188
188
  return undefined;
189
189
  root = name.substring(0, 1);