abcjs 6.4.4 → 6.5.1
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/RELEASE.md +50 -0
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic.js +337 -125
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/package.json +1 -1
- package/src/parse/abc_parse_directive.js +36 -4
- package/src/parse/abc_parse_header.js +3 -3
- package/src/parse/abc_parse_key_voice.js +41 -6
- package/src/parse/abc_parse_music.js +2 -0
- package/src/parse/abc_parse_settings.js +1 -0
- package/src/parse/abc_tokenizer.js +11 -3
- package/src/parse/abc_transpose.js +1 -1
- package/src/parse/transpose-chord.js +9 -1
- package/src/parse/tune-builder.js +3 -1
- package/src/str/output.js +4 -4
- package/src/synth/abc_midi_flattener.js +52 -11
- package/src/synth/abc_midi_sequencer.js +27 -6
- package/src/synth/chord-track.js +1 -1
- package/src/synth/create-synth.js +74 -65
- package/src/synth/place-note.js +1 -1
- package/src/test/abc_parser_lint.js +1 -1
- package/src/write/creation/abstract-engraver.js +9 -6
- package/src/write/creation/decoration.js +2 -0
- package/src/write/creation/elements/free-text.js +6 -1
- package/src/write/draw/draw.js +8 -0
- package/src/write/draw/ending.js +12 -3
- package/src/write/draw/text.js +8 -1
- package/src/write/interactive/selection.js +7 -1
- package/src/write/svg.js +23 -2
- package/types/index.d.ts +1 -1
- package/version.js +1 -1
package/package.json
CHANGED
|
@@ -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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -677,9 +677,17 @@ function getTitleNumber(str){
|
|
|
677
677
|
}
|
|
678
678
|
|
|
679
679
|
var thePatterns = [
|
|
680
|
-
{ match: /,\s*
|
|
681
|
-
{ match: /,\s*
|
|
682
|
-
{ match: /,\s*
|
|
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
|
|
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
|
|
|
@@ -804,6 +804,8 @@ function wrapMusicLines(lines, barsperstaff) {
|
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
function getPrevMusicLine(lines, currentLine) {
|
|
807
|
+
if (lines.length <= currentLine)
|
|
808
|
+
return null
|
|
807
809
|
// If the current line doesn't have music, search backwards until one is found.
|
|
808
810
|
while (currentLine >= 0) {
|
|
809
811
|
if (lines[currentLine].staff)
|
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[
|
|
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
|
|
365
|
+
if (nextVolume !== undefined) {
|
|
353
366
|
volume = nextVolume;
|
|
354
367
|
nextVolume = undefined;
|
|
355
368
|
} else if (!doBeatAccents) {
|
|
356
|
-
volume =
|
|
369
|
+
volume = pitchStressBeatDown;
|
|
357
370
|
} else if (pickupLength > beat) {
|
|
358
|
-
volume =
|
|
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 =
|
|
376
|
+
volume = pitchStressBeat1;
|
|
364
377
|
else if (parseInt(barBeat,10) === barBeat)
|
|
365
|
-
volume =
|
|
378
|
+
volume = pitchStressBeatDown;
|
|
366
379
|
else
|
|
367
|
-
volume =
|
|
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 = "
|
|
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;
|
|
@@ -423,7 +440,25 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
423
440
|
start += shortestNote;
|
|
424
441
|
}
|
|
425
442
|
break;
|
|
426
|
-
|
|
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;
|
|
@@ -432,6 +467,7 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
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;
|
|
@@ -532,6 +568,11 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
532
568
|
if (elem.elem)
|
|
533
569
|
elem.elem.midiPitches = [];
|
|
534
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
|
+
}
|
|
535
576
|
var note = ePitches[i];
|
|
536
577
|
if (!note)
|
|
537
578
|
continue;
|
|
@@ -545,7 +586,7 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
545
586
|
if (name && percmap[name])
|
|
546
587
|
actualPitch = percmap[name].sound;
|
|
547
588
|
}
|
|
548
|
-
var p = { cmd: 'note', pitch: actualPitch, volume:
|
|
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};
|
|
549
590
|
p = adjustForMicroTone(p);
|
|
550
591
|
if (elem.gracenotes) {
|
|
551
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({
|
|
195
|
-
|
|
196
|
-
|
|
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,15 @@ var parseCommon = require("../parse/abc_common");
|
|
|
467
480
|
|
|
468
481
|
if (dynamicType) {
|
|
469
482
|
currentVolume = volumes[dynamicType].slice(0);
|
|
470
|
-
|
|
483
|
+
let volumesPerNotePitch = [currentVolume];
|
|
484
|
+
if(Array.isArray(elem.decoration)){
|
|
485
|
+
volumesPerNotePitch = [];
|
|
486
|
+
elem.decoration.forEach(d=>{
|
|
487
|
+
if (d in volumes)
|
|
488
|
+
volumesPerNotePitch.push(volumes[d].slice(0));
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
voices[voiceNumber].push({ el_type: 'beat', beats: currentVolume.slice(0), volumesPerNotePitch: volumesPerNotePitch, });
|
|
471
492
|
inCrescendo[k] = false;
|
|
472
493
|
inDiminuendo[k] = false;
|
|
473
494
|
}
|
package/src/synth/chord-track.js
CHANGED
|
@@ -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 -
|
|
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);
|
|
@@ -26,7 +26,7 @@ function CreateSynth() {
|
|
|
26
26
|
self.audioBuffers = []; // cache of the buffers so starting play can be fast.
|
|
27
27
|
self.duration = undefined; // the duration of the tune in seconds.
|
|
28
28
|
self.isRunning = false; // whether there is currently a sound buffer running.
|
|
29
|
-
self.options =
|
|
29
|
+
self.options = {} // Thx tomohirohiratsuka
|
|
30
30
|
self.pickupLength = 0
|
|
31
31
|
|
|
32
32
|
// Load and cache all needed sounds
|
|
@@ -299,82 +299,91 @@ function CreateSynth() {
|
|
|
299
299
|
return Promise.reject(new Error(notSupportedMessage));
|
|
300
300
|
if (self.debugCallback)
|
|
301
301
|
self.debugCallback("prime called");
|
|
302
|
-
return new Promise(function(resolve) {
|
|
303
|
-
var startTime = activeAudioContext().currentTime;
|
|
304
|
-
var tempoMultiplier = self.millisecondsPerMeasure / 1000 / self.meterSize;
|
|
305
|
-
self.duration = self.flattened.totalDuration * tempoMultiplier;
|
|
306
|
-
if(self.duration <= 0) {
|
|
307
|
-
self.audioBuffers = [];
|
|
308
|
-
return resolve({ status: "empty", seconds: 0});
|
|
309
|
-
}
|
|
310
|
-
self.duration += fadeTimeSec;
|
|
311
|
-
var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration);
|
|
312
302
|
|
|
313
|
-
|
|
314
|
-
|
|
303
|
+
return new Promise(function(resolve, reject) {
|
|
304
|
+
try {
|
|
305
|
+
var startTime = activeAudioContext().currentTime;
|
|
306
|
+
var tempoMultiplier = self.millisecondsPerMeasure / 1000 / self.meterSize;
|
|
307
|
+
self.duration = self.flattened.totalDuration * tempoMultiplier;
|
|
308
|
+
if (self.duration <= 0) {
|
|
309
|
+
self.audioBuffers = [];
|
|
310
|
+
return resolve({status: "empty", seconds: 0});
|
|
311
|
+
}
|
|
312
|
+
self.duration += fadeTimeSec;
|
|
313
|
+
var totalSamples = Math.floor(activeAudioContext().sampleRate * self.duration);
|
|
315
314
|
|
|
316
|
-
|
|
315
|
+
// There might be a previous run that needs to be turned off.
|
|
316
|
+
self.stop();
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength)
|
|
318
|
+
var noteMapTracks = createNoteMap(self.flattened);
|
|
320
319
|
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
if (self.options.swing)
|
|
321
|
+
addSwing(noteMapTracks, self.options.swing, self.meterFraction, self.pickupLength)
|
|
323
322
|
|
|
324
|
-
|
|
323
|
+
if (self.sequenceCallback)
|
|
324
|
+
self.sequenceCallback(noteMapTracks, self.callbackContext);
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
var
|
|
331
|
-
|
|
332
|
-
var
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
326
|
+
var panDistances = setPan(noteMapTracks.length, self.pan);
|
|
327
|
+
|
|
328
|
+
// Create a simple list of all the unique sounds in this music and where they should be placed.
|
|
329
|
+
// There appears to be a limit on how many audio buffers can be created at once so this technique limits the number needed.
|
|
330
|
+
var uniqueSounds = {};
|
|
331
|
+
noteMapTracks.forEach(function (noteMap, trackNumber) {
|
|
332
|
+
var panDistance = panDistances && panDistances.length > trackNumber ? panDistances[trackNumber] : 0;
|
|
333
|
+
noteMap.forEach(function (note) {
|
|
334
|
+
var key = note.instrument + ':' + note.pitch + ':' + note.volume + ':' + Math.round((note.end - note.start) * 1000) / 1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + (note.cents ? note.cents : 0);
|
|
335
|
+
if (self.debugCallback)
|
|
336
|
+
self.debugCallback("noteMapTrack " + key)
|
|
337
|
+
if (!uniqueSounds[key])
|
|
338
|
+
uniqueSounds[key] = [];
|
|
339
|
+
uniqueSounds[key].push(note.start);
|
|
340
|
+
});
|
|
338
341
|
});
|
|
339
|
-
});
|
|
340
342
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
343
|
+
// Now that we know what we are trying to create, construct the audio buffer by creating each sound and placing it.
|
|
344
|
+
var allPromises = [];
|
|
345
|
+
var audioBuffer = activeAudioContext().createBuffer(2, totalSamples, activeAudioContext().sampleRate);
|
|
346
|
+
for (var key2 = 0; key2 < Object.keys(uniqueSounds).length; key2++) {
|
|
347
|
+
var k = Object.keys(uniqueSounds)[key2];
|
|
348
|
+
var parts = k.split(":");
|
|
349
|
+
var cents = parts[6] !== undefined ? parseFloat(parts[6]) : 0;
|
|
350
|
+
parts = {instrument: parts[0], pitch: parseInt(parts[1], 10), volume: parseInt(parts[2], 10), len: parseFloat(parts[3]), pan: parseFloat(parts[4]), tempoMultiplier: parseFloat(parts[5]), cents: cents};
|
|
351
|
+
allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k], self.soundFontVolumeMultiplier, self.programOffsets[parts.instrument], fadeTimeSec, self.noteEnd / 1000, self.debugCallback));
|
|
352
|
+
}
|
|
353
|
+
self.audioBuffers = [audioBuffer];
|
|
352
354
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
})
|
|
368
|
-
} else if (activeAudioContext().state === "interrupted") {
|
|
369
|
-
activeAudioContext().suspend().then(function () {
|
|
355
|
+
if (self.debugCallback) {
|
|
356
|
+
self.debugCallback("sampleRate = " + activeAudioContext().sampleRate);
|
|
357
|
+
self.debugCallback("totalSamples = " + totalSamples);
|
|
358
|
+
self.debugCallback("creationTime = " + Math.floor((activeAudioContext().currentTime - startTime) * 1000) + "ms");
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function resolveData(me) {
|
|
362
|
+
var duration = me && me.audioBuffers && me.audioBuffers.length > 0 ? me.audioBuffers[0].duration : 0;
|
|
363
|
+
return {status: activeAudioContext().state, duration: duration}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Promise.all(allPromises).then(function () {
|
|
367
|
+
// Safari iOS can mess with the audioContext state, so resume if needed.
|
|
368
|
+
if (activeAudioContext().state === "suspended") {
|
|
370
369
|
activeAudioContext().resume().then(function () {
|
|
371
370
|
resolve(resolveData(self));
|
|
372
371
|
})
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
372
|
+
} else if (activeAudioContext().state === "interrupted") {
|
|
373
|
+
activeAudioContext().suspend().then(function () {
|
|
374
|
+
activeAudioContext().resume().then(function () {
|
|
375
|
+
resolve(resolveData(self));
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
} else {
|
|
379
|
+
resolve(resolveData(self));
|
|
380
|
+
}
|
|
381
|
+
}).catch(function (error) {
|
|
382
|
+
reject(error)
|
|
383
|
+
});
|
|
384
|
+
} catch (error) {
|
|
385
|
+
reject(error)
|
|
386
|
+
}
|
|
378
387
|
});
|
|
379
388
|
};
|
|
380
389
|
|