abcjs 6.0.0-beta.34 → 6.0.0-beta.38
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/LICENSE.md +1 -1
- package/README.md +15 -6
- package/RELEASE.md +67 -1
- package/dist/abcjs-basic-min.js +2 -2
- package/dist/abcjs-basic-min.js.LICENSE +2 -2
- package/dist/abcjs-basic.js +2587 -334
- package/dist/abcjs-basic.js.map +1 -1
- package/dist/abcjs-plugin-min.js +2 -2
- package/dist/abcjs-plugin-min.js.LICENSE +2 -2
- package/dist/report-basic.html +37 -0
- package/dist/report-before-glyph-compress.html +37 -0
- package/dist/report-brown-ts-target-es5.html +37 -0
- package/dist/report-dev-orig-no-babel.html +37 -0
- package/dist/report-synth.html +37 -0
- package/docker-build.sh +1 -0
- package/glyphs.json +1 -0
- package/index.js +23 -1
- package/license.js +1 -1
- package/package.json +2 -1
- package/plugin.js +23 -1
- package/src/api/abc_tablatures.js +144 -0
- package/src/api/abc_tunebook.js +10 -1
- package/src/api/abc_tunebook_svg.js +18 -6
- package/src/data/abc_tune.js +26 -24
- package/src/edit/abc_editor.js +29 -11
- package/src/parse/abc_parse.js +4 -2
- package/src/parse/abc_parse_directive.js +12 -6
- package/src/parse/tune-builder.js +1 -1
- package/src/synth/abc_midi_flattener.js +11 -2
- package/src/synth/abc_midi_sequencer.js +4 -1
- package/src/synth/create-synth.js +87 -30
- package/src/synth/load-note.js +34 -65
- package/src/synth/place-note.js +63 -59
- package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
- package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
- package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
- package/src/tablatures/instruments/string-patterns.js +277 -0
- package/src/tablatures/instruments/string-tablature.js +56 -0
- package/src/tablatures/instruments/tab-note.js +282 -0
- package/src/tablatures/instruments/tab-notes.js +41 -0
- package/src/tablatures/instruments/violin/tab-violin.js +47 -0
- package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
- package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
- package/src/tablatures/tab-absolute-elements.js +310 -0
- package/src/tablatures/tab-common.js +29 -0
- package/src/tablatures/tab-renderer.js +243 -0
- package/src/tablatures/transposer.js +110 -0
- package/src/test/abc_parser_lint.js +3 -0
- package/src/write/abc_absolute_element.js +2 -2
- package/src/write/abc_engraver_controller.js +19 -11
- package/src/write/abc_glyphs.js +2 -0
- package/src/write/abc_relative_element.js +5 -3
- package/src/write/abc_renderer.js +5 -1
- package/src/write/draw/absolute.js +5 -1
- package/src/write/draw/draw.js +5 -6
- package/src/write/draw/non-music.js +3 -1
- package/src/write/draw/print-line.js +24 -0
- package/src/write/draw/relative.js +14 -2
- package/src/write/draw/selectables.js +9 -6
- package/src/write/draw/staff-group.js +44 -8
- package/src/write/draw/staff-line.js +3 -19
- package/src/write/draw/staff.js +15 -2
- package/src/write/draw/tab-line.js +40 -0
- package/src/write/draw/text.js +3 -0
- package/src/write/draw/voice.js +9 -1
- package/src/write/format-jazz-chord.js +2 -2
- package/src/write/layout/staffGroup.js +23 -1
- package/src/write/layout/voice.js +2 -1
- package/src/write/svg.js +2 -1
- package/test.js +23 -0
- package/types/index.d.ts +73 -25
- package/version.js +1 -1
package/src/data/abc_tune.js
CHANGED
|
@@ -388,30 +388,32 @@ var Tune = function() {
|
|
|
388
388
|
var tempos = {};
|
|
389
389
|
for (var line = 0; line < this.engraver.staffgroups.length; line++) {
|
|
390
390
|
var group = this.engraver.staffgroups[line];
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
var
|
|
402
|
-
|
|
403
|
-
voicesArr[v]
|
|
404
|
-
|
|
405
|
-
measureNumber[v]
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
391
|
+
if (group && group.staffs && group.staffs.length > 0) {
|
|
392
|
+
var firstStaff = group.staffs[0];
|
|
393
|
+
var middleC = firstStaff.absoluteY;
|
|
394
|
+
var top = middleC - firstStaff.top * spacing.STEP;
|
|
395
|
+
var lastStaff = group.staffs[group.staffs.length - 1];
|
|
396
|
+
middleC = lastStaff.absoluteY;
|
|
397
|
+
var bottom = middleC - lastStaff.bottom * spacing.STEP;
|
|
398
|
+
var height = bottom - top;
|
|
399
|
+
|
|
400
|
+
var voices = group.voices;
|
|
401
|
+
for (var v = 0; v < voices.length; v++) {
|
|
402
|
+
var noteFound = false;
|
|
403
|
+
if (!voicesArr[v])
|
|
404
|
+
voicesArr[v] = [];
|
|
405
|
+
if (measureNumber[v] === undefined)
|
|
406
|
+
measureNumber[v] = 0;
|
|
407
|
+
var elements = voices[v].children;
|
|
408
|
+
for (var elem = 0; elem < elements.length; elem++) {
|
|
409
|
+
if (elements[elem].type === "tempo")
|
|
410
|
+
tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem);
|
|
411
|
+
voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]});
|
|
412
|
+
if (elements[elem].type === 'bar' && noteFound) // Count the measures by counting the bar lines, but skip a bar line that appears at the left of the music, before any notes.
|
|
413
|
+
measureNumber[v]++;
|
|
414
|
+
if (elements[elem].type === 'note' || elements[elem].type === 'rest')
|
|
415
|
+
noteFound = true;
|
|
416
|
+
}
|
|
415
417
|
}
|
|
416
418
|
}
|
|
417
419
|
}
|
package/src/edit/abc_editor.js
CHANGED
|
@@ -82,6 +82,17 @@ function gatherAbcParams(params) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
/*
|
|
86
|
+
if (params.tablature_options) {
|
|
87
|
+
abcjsParams['tablatures'] = params.tablature_options;
|
|
88
|
+
}
|
|
89
|
+
*/
|
|
90
|
+
if (abcjsParams.tablature) {
|
|
91
|
+
if (params.warnings_id) {
|
|
92
|
+
// store for plugin error handling
|
|
93
|
+
abcjsParams.tablature.warnings_id = params.warnings_id;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
85
96
|
return abcjsParams;
|
|
86
97
|
}
|
|
87
98
|
|
|
@@ -123,7 +134,7 @@ var Editor = function(editarea, params) {
|
|
|
123
134
|
el: params.synth.el,
|
|
124
135
|
cursorControl: params.synth.cursorControl,
|
|
125
136
|
options: params.synth.options
|
|
126
|
-
}
|
|
137
|
+
};
|
|
127
138
|
}
|
|
128
139
|
}
|
|
129
140
|
// If the user wants midi, then store the elements that it will be written to. The element could either be passed in as an id,
|
|
@@ -219,16 +230,23 @@ Editor.prototype.redrawMidi = function() {
|
|
|
219
230
|
Editor.prototype.modelChanged = function() {
|
|
220
231
|
if (this.bReentry)
|
|
221
232
|
return; // TODO is this likely? maybe, if we rewrite abc immediately w/ abc2abc
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
this.bReentry = true;
|
|
234
|
+
try {
|
|
235
|
+
this.timerId = null;
|
|
236
|
+
if (this.synth && this.synth.synthControl)
|
|
237
|
+
this.synth.synthControl.disable(true);
|
|
238
|
+
|
|
239
|
+
this.tunes = renderAbc(this.div, this.currentAbc, this.abcjsParams);
|
|
240
|
+
if (this.tunes.length > 0) {
|
|
241
|
+
this.warnings = this.tunes[0].warnings;
|
|
242
|
+
}
|
|
243
|
+
this.redrawMidi();
|
|
244
|
+
} catch(error) {
|
|
245
|
+
console.error("ABCJS error: ", error);
|
|
246
|
+
if (!this.warnings)
|
|
247
|
+
this.warnings = [];
|
|
248
|
+
this.warnings.push(error.message);
|
|
249
|
+
}
|
|
232
250
|
|
|
233
251
|
if (this.warningsdiv) {
|
|
234
252
|
this.warningsdiv.innerHTML = (this.warnings) ? this.warnings.join("<br />") : "No errors";
|
package/src/parse/abc_parse.js
CHANGED
|
@@ -138,8 +138,10 @@ var Parse = function() {
|
|
|
138
138
|
};
|
|
139
139
|
for (var i = 0; i < this.inTie.length; i++) {
|
|
140
140
|
this.endingHoldOver.inTie.push([]);
|
|
141
|
-
|
|
142
|
-
this.
|
|
141
|
+
if (this.inTie[i]) { // if a voice is suppressed there might be a gap in the array.
|
|
142
|
+
for (var j = 0; j < this.inTie[i].length; j++) {
|
|
143
|
+
this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
|
|
144
|
+
}
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
for (var key in this.inTieChord) {
|
|
@@ -39,6 +39,9 @@ var parseDirective = {};
|
|
|
39
39
|
tune.formatting.footerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" };
|
|
40
40
|
tune.formatting.headerfont = { face: "\"Times New Roman\"", size: 12, weight: "normal", style: "normal", decoration: "none" };
|
|
41
41
|
tune.formatting.voicefont = { face: "\"Times New Roman\"", size: 13, weight: "bold", style: "normal", decoration: "none" };
|
|
42
|
+
tune.formatting.tablabelfont = { face: "\"Trebuchet MS\"", size: 16, weight: "normal", style: "normal", decoration: "none" };
|
|
43
|
+
tune.formatting.tabnumberfont = { face: "\"Arial\"", size: 11, weight: "normal", style: "normal", decoration: "none" };
|
|
44
|
+
tune.formatting.tabgracefont = { face: "\"Arial\"", size: 8, weight: "normal", style: "normal", decoration: "none" };
|
|
42
45
|
|
|
43
46
|
// these are the default fonts for these element types. In the printer, these fonts might change as the tune progresses.
|
|
44
47
|
tune.formatting.annotationfont = multilineVars.annotationfont;
|
|
@@ -390,7 +393,7 @@ var parseDirective = {};
|
|
|
390
393
|
var interpretPercMap = function(restOfString) {
|
|
391
394
|
var tokens = restOfString.split(/\s+/); // Allow multiple spaces.
|
|
392
395
|
if (tokens.length !== 2 && tokens.length !== 3)
|
|
393
|
-
return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'}
|
|
396
|
+
return { error: 'Expected parameters "abc-note", "drum-sound", and optionally "note-head"'};
|
|
394
397
|
var key = tokens[0];
|
|
395
398
|
// The percussion sound can either be a MIDI number or a drum name. If it is not a number then check for a name.
|
|
396
399
|
var pitch = parseInt(tokens[1], 10);
|
|
@@ -403,7 +406,7 @@ var parseDirective = {};
|
|
|
403
406
|
if (tokens.length === 3)
|
|
404
407
|
value.noteHead = tokens[2];
|
|
405
408
|
return { key: key, value: value };
|
|
406
|
-
}
|
|
409
|
+
};
|
|
407
410
|
|
|
408
411
|
var getRequiredMeasurement = function(cmd, tokens) {
|
|
409
412
|
var points = tokenizer.getMeasurement(tokens);
|
|
@@ -875,13 +878,13 @@ var parseDirective = {};
|
|
|
875
878
|
break;
|
|
876
879
|
case "begintext":
|
|
877
880
|
var textBlock = '';
|
|
878
|
-
line = tokenizer.nextLine()
|
|
881
|
+
line = tokenizer.nextLine();
|
|
879
882
|
while(line && line.indexOf('%%endtext') !== 0) {
|
|
880
883
|
if (parseCommon.startsWith(line, "%%"))
|
|
881
884
|
textBlock += line.substring(2) + "\n";
|
|
882
885
|
else
|
|
883
886
|
textBlock += line + "\n";
|
|
884
|
-
line = tokenizer.nextLine()
|
|
887
|
+
line = tokenizer.nextLine();
|
|
885
888
|
}
|
|
886
889
|
tuneBuilder.addText(textBlock, { startChar: multilineVars.iChar, endChar: multilineVars.iChar+textBlock.length+7});
|
|
887
890
|
break;
|
|
@@ -889,9 +892,9 @@ var parseDirective = {};
|
|
|
889
892
|
multilineVars.continueall = true;
|
|
890
893
|
break;
|
|
891
894
|
case "beginps":
|
|
892
|
-
line = tokenizer.nextLine()
|
|
895
|
+
line = tokenizer.nextLine();
|
|
893
896
|
while(line && line.indexOf('%%endps') !== 0) {
|
|
894
|
-
tokenizer.nextLine()
|
|
897
|
+
tokenizer.nextLine();
|
|
895
898
|
}
|
|
896
899
|
warn("Postscript ignored", str, 0);
|
|
897
900
|
break;
|
|
@@ -1152,6 +1155,9 @@ var parseDirective = {};
|
|
|
1152
1155
|
case "vocalfont":
|
|
1153
1156
|
case "wordsfont":
|
|
1154
1157
|
case "annotationfont":
|
|
1158
|
+
case "tablabelfont":
|
|
1159
|
+
case "tabnumberfont":
|
|
1160
|
+
case "tabgracefont":
|
|
1155
1161
|
getChangingFont(cmd, tokens, value);
|
|
1156
1162
|
break;
|
|
1157
1163
|
case "scale":
|
|
@@ -686,7 +686,7 @@ var TuneBuilder = function(tune) {
|
|
|
686
686
|
|
|
687
687
|
this.containsNotesStrict = function(voice) {
|
|
688
688
|
for (var i = 0; i < voice.length; i++) {
|
|
689
|
-
if (voice[i].el_type === 'note' && voice[i].rest === undefined)
|
|
689
|
+
if (voice[i].el_type === 'note' && (voice[i].rest === undefined || voice[i].chord !== undefined))
|
|
690
690
|
return true;
|
|
691
691
|
}
|
|
692
692
|
return false;
|
|
@@ -220,7 +220,7 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
220
220
|
if (currentTrackName)
|
|
221
221
|
currentTrack.unshift(currentTrackName);
|
|
222
222
|
tracks.push(currentTrack);
|
|
223
|
-
if (
|
|
223
|
+
if (!chordTrackEmpty()) // Don't do chords on more than one track, so turn off chord detection after we create it.
|
|
224
224
|
chordTrackFinished = true;
|
|
225
225
|
if (drumTrack.length > 0) // Don't do drums on more than one track, so turn off drum after we create it.
|
|
226
226
|
drumTrackFinished = true;
|
|
@@ -229,7 +229,7 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
229
229
|
if (options.detuneOctave)
|
|
230
230
|
findOctaves(tracks, parseInt(options.detuneOctave, 10));
|
|
231
231
|
|
|
232
|
-
if (
|
|
232
|
+
if (!chordTrackEmpty())
|
|
233
233
|
tracks.push(chordTrack);
|
|
234
234
|
if (drumTrack.length > 0)
|
|
235
235
|
tracks.push(drumTrack);
|
|
@@ -246,6 +246,15 @@ var pitchesToPerc = require('./pitches-to-perc');
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
function chordTrackEmpty() {
|
|
250
|
+
var isEmpty = true;
|
|
251
|
+
for (var i = 0; i < chordTrack.length && isEmpty; i++) {
|
|
252
|
+
if (chordTrack[i].cmd === 'note')
|
|
253
|
+
isEmpty = false
|
|
254
|
+
}
|
|
255
|
+
return isEmpty;
|
|
256
|
+
}
|
|
257
|
+
|
|
249
258
|
function timeToRealTime(time) {
|
|
250
259
|
return time/1000000;
|
|
251
260
|
}
|
|
@@ -152,6 +152,9 @@ var parseCommon = require("../parse/abc_common");
|
|
|
152
152
|
var voiceNumber = 0;
|
|
153
153
|
for (var j = 0; j < staves.length; j++) {
|
|
154
154
|
var staff = staves[j];
|
|
155
|
+
if (staff.clef && staff.clef.type === "TAB")
|
|
156
|
+
continue;
|
|
157
|
+
|
|
155
158
|
// For each staff line
|
|
156
159
|
for (var k = 0; k < staff.voices.length; k++) {
|
|
157
160
|
// For each voice in a staff line
|
|
@@ -235,7 +238,7 @@ var parseCommon = require("../parse/abc_common");
|
|
|
235
238
|
if (elem.startTriplet) {
|
|
236
239
|
tripletMultiplier = elem.tripletMultiplier;
|
|
237
240
|
tripletDurationTotal = elem.startTriplet * tripletMultiplier * elem.duration;
|
|
238
|
-
if (elem.startTriplet
|
|
241
|
+
if (elem.startTriplet !== elem.tripletR) { // most commonly (3:2:2
|
|
239
242
|
if (v + elem.tripletR <= voice.length) {
|
|
240
243
|
var durationTotal = 0;
|
|
241
244
|
for (var w = v; w < v + elem.tripletR; w++) {
|
|
@@ -12,10 +12,10 @@ var soundsCache = require('./sounds-cache');
|
|
|
12
12
|
// TODO-PER: remove the midi tests from here: I don't think the object can be constructed unless it passes.
|
|
13
13
|
var notSupportedMessage = "MIDI is not supported in this browser.";
|
|
14
14
|
|
|
15
|
-
var
|
|
15
|
+
var originalSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/";
|
|
16
16
|
// These are the original soundfonts supplied. They will need a volume boost:
|
|
17
|
-
var
|
|
18
|
-
var
|
|
17
|
+
var defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/";
|
|
18
|
+
var alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/";
|
|
19
19
|
|
|
20
20
|
function CreateSynth() {
|
|
21
21
|
var self = this;
|
|
@@ -43,20 +43,61 @@ function CreateSynth() {
|
|
|
43
43
|
self.soundFontUrl = params.soundFontUrl ? params.soundFontUrl : defaultSoundFontUrl;
|
|
44
44
|
if (self.soundFontUrl[self.soundFontUrl.length-1] !== '/')
|
|
45
45
|
self.soundFontUrl += '/';
|
|
46
|
-
if (params.soundFontVolumeMultiplier)
|
|
46
|
+
if (params.soundFontVolumeMultiplier || params.soundFontVolumeMultiplier === 0)
|
|
47
47
|
self.soundFontVolumeMultiplier = params.soundFontVolumeMultiplier;
|
|
48
|
-
else if (self.soundFontUrl ===
|
|
49
|
-
self.soundFontVolumeMultiplier =
|
|
50
|
-
else if (self.soundFontUrl ===
|
|
51
|
-
self.soundFontVolumeMultiplier = 0.
|
|
48
|
+
else if (self.soundFontUrl === defaultSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl)
|
|
49
|
+
self.soundFontVolumeMultiplier = 3.0;
|
|
50
|
+
else if (self.soundFontUrl === originalSoundFontUrl)
|
|
51
|
+
self.soundFontVolumeMultiplier = 0.4;
|
|
52
52
|
else
|
|
53
53
|
self.soundFontVolumeMultiplier = 1.0;
|
|
54
54
|
if (params.programOffsets)
|
|
55
55
|
self.programOffsets = params.programOffsets;
|
|
56
|
-
else if (self.soundFontUrl ===
|
|
56
|
+
else if (self.soundFontUrl === originalSoundFontUrl)
|
|
57
57
|
self.programOffsets = {
|
|
58
|
-
"
|
|
59
|
-
"
|
|
58
|
+
"bright_acoustic_piano": 20,
|
|
59
|
+
"honkytonk_piano": 20,
|
|
60
|
+
"electric_piano_1": 30,
|
|
61
|
+
"electric_piano_2": 30,
|
|
62
|
+
"harpsichord": 40,
|
|
63
|
+
"clavinet": 20,
|
|
64
|
+
"celesta": 20,
|
|
65
|
+
"glockenspiel": 40,
|
|
66
|
+
"vibraphone": 30,
|
|
67
|
+
"marimba": 35,
|
|
68
|
+
"xylophone": 30,
|
|
69
|
+
"tubular_bells": 35,
|
|
70
|
+
"dulcimer": 30,
|
|
71
|
+
"drawbar_organ": 20,
|
|
72
|
+
"percussive_organ": 25,
|
|
73
|
+
"rock_organ": 20,
|
|
74
|
+
"church_organ": 40,
|
|
75
|
+
"reed_organ": 40,
|
|
76
|
+
"accordion": 40,
|
|
77
|
+
"harmonica": 40,
|
|
78
|
+
"acoustic_guitar_nylon": 20,
|
|
79
|
+
"acoustic_guitar_steel": 30,
|
|
80
|
+
"electric_guitar_jazz": 25,
|
|
81
|
+
"electric_guitar_clean": 15,
|
|
82
|
+
"electric_guitar_muted": 35,
|
|
83
|
+
"overdriven_guitar": 25,
|
|
84
|
+
"distortion_guitar": 20,
|
|
85
|
+
"guitar_harmonics": 30,
|
|
86
|
+
"electric_bass_finger": 15,
|
|
87
|
+
"electric_bass_pick": 30,
|
|
88
|
+
"fretless_bass": 40,
|
|
89
|
+
"violin": 105,
|
|
90
|
+
"viola": 50,
|
|
91
|
+
"cello": 40,
|
|
92
|
+
"contrabass": 60,
|
|
93
|
+
"trumpet": 10,
|
|
94
|
+
"trombone": 90,
|
|
95
|
+
"alto_sax": 20,
|
|
96
|
+
"tenor_sax": 20,
|
|
97
|
+
"clarinet": 20,
|
|
98
|
+
"flute": 50,
|
|
99
|
+
"banjo": 50,
|
|
100
|
+
"woodblock": 20,
|
|
60
101
|
};
|
|
61
102
|
else
|
|
62
103
|
self.programOffsets = {};
|
|
@@ -84,6 +125,7 @@ function CreateSynth() {
|
|
|
84
125
|
|
|
85
126
|
var allNotes = {};
|
|
86
127
|
var cached = [];
|
|
128
|
+
var errorNotes = [];
|
|
87
129
|
var currentInstrument = instrumentIndexToName[0];
|
|
88
130
|
self.flattened.tracks.forEach(function(track) {
|
|
89
131
|
track.forEach(function(event) {
|
|
@@ -97,10 +139,17 @@ function CreateSynth() {
|
|
|
97
139
|
allNotes[currentInstrument] = {};
|
|
98
140
|
if (!soundsCache[currentInstrument] || !soundsCache[currentInstrument][noteName])
|
|
99
141
|
allNotes[currentInstrument][noteName] = true;
|
|
100
|
-
else
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
142
|
+
else {
|
|
143
|
+
var label2 = currentInstrument+":"+noteName
|
|
144
|
+
if (cached.indexOf(label2) < 0)
|
|
145
|
+
cached.push(label2);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
var label = currentInstrument+":"+noteName
|
|
149
|
+
console.log("Can't find note: ", pitchNumber, label);
|
|
150
|
+
if (errorNotes.indexOf(label) < 0)
|
|
151
|
+
errorNotes.push(label)
|
|
152
|
+
}
|
|
104
153
|
}
|
|
105
154
|
});
|
|
106
155
|
});
|
|
@@ -124,7 +173,7 @@ function CreateSynth() {
|
|
|
124
173
|
return new Promise(function(resolve, reject) {
|
|
125
174
|
var results = {
|
|
126
175
|
cached: cached,
|
|
127
|
-
error:
|
|
176
|
+
error: errorNotes,
|
|
128
177
|
loaded: []
|
|
129
178
|
};
|
|
130
179
|
|
|
@@ -245,7 +294,7 @@ function CreateSynth() {
|
|
|
245
294
|
noteMapTracks.forEach(function(noteMap, trackNumber) {
|
|
246
295
|
var panDistance = panDistances && panDistances.length > trackNumber ? panDistances[trackNumber] : 0;
|
|
247
296
|
noteMap.forEach(function(note) {
|
|
248
|
-
var key = note.instrument + ':' + note.pitch + ':' +note.volume + ':' + Math.round((note.end-note.start)*1000)/1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + note.cents;
|
|
297
|
+
var key = note.instrument + ':' + note.pitch + ':' +note.volume + ':' + Math.round((note.end-note.start)*1000)/1000 + ':' + panDistance + ':' + tempoMultiplier + ':' + (note.cents ? note.cents : 0);
|
|
249
298
|
if (!uniqueSounds[key])
|
|
250
299
|
uniqueSounds[key] = [];
|
|
251
300
|
uniqueSounds[key].push(note.start);
|
|
@@ -259,7 +308,7 @@ function CreateSynth() {
|
|
|
259
308
|
var k = Object.keys(uniqueSounds)[key2];
|
|
260
309
|
var parts = k.split(":");
|
|
261
310
|
var cents = parts[6] !== undefined ? parseFloat(parts[6]) : 0;
|
|
262
|
-
|
|
311
|
+
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};
|
|
263
312
|
allPromises.push(placeNote(audioBuffer, activeAudioContext().sampleRate, parts, uniqueSounds[k], self.soundFontVolumeMultiplier, self.programOffsets[parts.instrument], fadeTimeSec, self.noteEnd/1000));
|
|
264
313
|
}
|
|
265
314
|
self.audioBuffers = [audioBuffer];
|
|
@@ -279,23 +328,29 @@ function CreateSynth() {
|
|
|
279
328
|
};
|
|
280
329
|
|
|
281
330
|
function setPan(numTracks, panParam) {
|
|
331
|
+
// panParam, if it is set, can be either a number representing the separation between each track,
|
|
332
|
+
// or an array, which is the absolute pan position for each track.
|
|
282
333
|
if (panParam === null || panParam === undefined)
|
|
283
334
|
return null;
|
|
284
335
|
|
|
285
336
|
var panDistances = [];
|
|
286
337
|
if (panParam.length) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
338
|
+
// We received an array. If there are the same number of items in the pan array as the number of tracks,
|
|
339
|
+
// it all lines up perfectly. If there are more items in the pan array than the tracks then the excess items are ignored.
|
|
340
|
+
// If there are more tracks than items in the pan array then the remaining tracks are placed in the middle.
|
|
341
|
+
// If any of the pan numbers are out of range then they are adjusted.
|
|
342
|
+
for (var pp = 0; pp < numTracks; pp++) {
|
|
343
|
+
if (pp < panParam.length) {
|
|
290
344
|
var x = parseFloat(panParam[pp]);
|
|
291
|
-
if (x
|
|
292
|
-
|
|
293
|
-
else
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
345
|
+
if (x < -1)
|
|
346
|
+
x = -1;
|
|
347
|
+
else if (x > 1)
|
|
348
|
+
x = 1;
|
|
349
|
+
panDistances.push(x);
|
|
350
|
+
} else
|
|
351
|
+
panDistances.push(0)
|
|
298
352
|
}
|
|
353
|
+
return panDistances;
|
|
299
354
|
} else {
|
|
300
355
|
var panNumber = parseFloat(panParam);
|
|
301
356
|
// the separation needs to be no further than 2 (i.e. -1 to 1) so test to see if there are too many tracks for the passed in distance
|
|
@@ -345,8 +400,8 @@ function CreateSynth() {
|
|
|
345
400
|
if (self.debugCallback)
|
|
346
401
|
self.debugCallback("pause called");
|
|
347
402
|
|
|
348
|
-
|
|
349
|
-
self.pausedTimeSec
|
|
403
|
+
self.pausedTimeSec = self.stop();
|
|
404
|
+
return self.pausedTimeSec;
|
|
350
405
|
};
|
|
351
406
|
|
|
352
407
|
self.resume = function() {
|
|
@@ -395,6 +450,8 @@ function CreateSynth() {
|
|
|
395
450
|
}
|
|
396
451
|
});
|
|
397
452
|
self.directSource = [];
|
|
453
|
+
var elapsed = activeAudioContext().currentTime - self.startTimeSec;
|
|
454
|
+
return elapsed;
|
|
398
455
|
};
|
|
399
456
|
self.finished = function() {
|
|
400
457
|
self.startTimeSec = undefined;
|
package/src/synth/load-note.js
CHANGED
|
@@ -2,74 +2,43 @@
|
|
|
2
2
|
// url = the base url for the soundfont
|
|
3
3
|
// instrument = the instrument name (e.g. "acoustic_grand_piano")
|
|
4
4
|
// name = the pitch name (e.g. "A3")
|
|
5
|
-
var soundsCache = require(
|
|
5
|
+
var soundsCache = require("./sounds-cache");
|
|
6
6
|
|
|
7
|
-
var getNote = function(url, instrument, name, audioContext) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
soundsCache[instrument] = {};
|
|
11
|
-
var instrumentCache = soundsCache[instrument];
|
|
7
|
+
var getNote = function (url, instrument, name, audioContext) {
|
|
8
|
+
if (!soundsCache[instrument]) soundsCache[instrument] = {};
|
|
9
|
+
var instrumentCache = soundsCache[instrument];
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
var xhr = new XMLHttpRequest();
|
|
27
|
-
xhr.open('GET', url+instrument+'-mp3/'+name+'.mp3', true);
|
|
28
|
-
xhr.responseType = 'arraybuffer';
|
|
29
|
-
|
|
30
|
-
var self = this;
|
|
31
|
-
function onSuccess(audioBuffer) {
|
|
32
|
-
instrumentCache[name] = audioBuffer;
|
|
33
|
-
// if (self.debugCallback)
|
|
34
|
-
// self.debugCallback(`Sound loaded: ${instrument} ${name} ${url}`);
|
|
35
|
-
resolve({instrument: instrument, name: name, status: "loaded"});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function onFailure(error) {
|
|
39
|
-
error = "Can't decode sound. " + url + ' ' + instrument + ' ' + name + ' ' + error;
|
|
40
|
-
if (self.debugCallback)
|
|
41
|
-
self.debugCallback(error);
|
|
42
|
-
return resolve({instrument: instrument, name: name, status: "error", message: error });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
xhr.onload = function (e) {
|
|
46
|
-
if (this.status === 200) {
|
|
47
|
-
try {
|
|
48
|
-
var promise = audioContext.decodeAudioData(this.response, onSuccess, onFailure);
|
|
49
|
-
// older browsers only have the callback. Newer ones will report an unhandled
|
|
50
|
-
// rejection if catch isn't handled so we need both. We don't need to report it twice, though.
|
|
51
|
-
if (promise && promise.catch)
|
|
52
|
-
promise.catch(function () {});
|
|
53
|
-
} catch(error) {
|
|
54
|
-
reject(error);
|
|
11
|
+
if (!instrumentCache[name])
|
|
12
|
+
instrumentCache[name] = new Promise(function (resolve, reject) {
|
|
13
|
+
var xhr = new XMLHttpRequest();
|
|
14
|
+
let noteUrl = url + instrument + "-mp3/" + name + ".mp3";
|
|
15
|
+
xhr.open("GET", noteUrl, true);
|
|
16
|
+
xhr.responseType = "arraybuffer";
|
|
17
|
+
xhr.onload = function () {
|
|
18
|
+
if (xhr.status !== 200) {
|
|
19
|
+
reject(Error("Can't load sound at " + noteUrl));
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
var noteDecoded = function(audioBuffer) {
|
|
23
|
+
resolve({instrument: instrument, name: name, status: "loaded", audioBuffer: audioBuffer})
|
|
55
24
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
25
|
+
var maybePromise = audioContext.decodeAudioData(xhr.response, noteDecoded, function () {
|
|
26
|
+
reject(Error("Can't decode sound at " + noteUrl));
|
|
27
|
+
});
|
|
28
|
+
// In older browsers `BaseAudioContext.decodeAudio()` did not return a promise
|
|
29
|
+
if (maybePromise && typeof maybePromise.catch === "function") maybePromise.catch(reject);
|
|
30
|
+
};
|
|
31
|
+
xhr.onerror = function () {
|
|
32
|
+
reject(Error("Can't load sound at " + noteUrl));
|
|
33
|
+
};
|
|
34
|
+
xhr.send();
|
|
35
|
+
})
|
|
36
|
+
.catch(err => {
|
|
37
|
+
console.error("Didn't load note", instrument, name, ":", err.message);
|
|
38
|
+
throw err;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return instrumentCache[name];
|
|
73
42
|
};
|
|
74
43
|
|
|
75
44
|
module.exports = getNote;
|