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.
Files changed (72) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +15 -6
  3. package/RELEASE.md +67 -1
  4. package/dist/abcjs-basic-min.js +2 -2
  5. package/dist/abcjs-basic-min.js.LICENSE +2 -2
  6. package/dist/abcjs-basic.js +2587 -334
  7. package/dist/abcjs-basic.js.map +1 -1
  8. package/dist/abcjs-plugin-min.js +2 -2
  9. package/dist/abcjs-plugin-min.js.LICENSE +2 -2
  10. package/dist/report-basic.html +37 -0
  11. package/dist/report-before-glyph-compress.html +37 -0
  12. package/dist/report-brown-ts-target-es5.html +37 -0
  13. package/dist/report-dev-orig-no-babel.html +37 -0
  14. package/dist/report-synth.html +37 -0
  15. package/docker-build.sh +1 -0
  16. package/glyphs.json +1 -0
  17. package/index.js +23 -1
  18. package/license.js +1 -1
  19. package/package.json +2 -1
  20. package/plugin.js +23 -1
  21. package/src/api/abc_tablatures.js +144 -0
  22. package/src/api/abc_tunebook.js +10 -1
  23. package/src/api/abc_tunebook_svg.js +18 -6
  24. package/src/data/abc_tune.js +26 -24
  25. package/src/edit/abc_editor.js +29 -11
  26. package/src/parse/abc_parse.js +4 -2
  27. package/src/parse/abc_parse_directive.js +12 -6
  28. package/src/parse/tune-builder.js +1 -1
  29. package/src/synth/abc_midi_flattener.js +11 -2
  30. package/src/synth/abc_midi_sequencer.js +4 -1
  31. package/src/synth/create-synth.js +87 -30
  32. package/src/synth/load-note.js +34 -65
  33. package/src/synth/place-note.js +63 -59
  34. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  35. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  36. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  37. package/src/tablatures/instruments/string-patterns.js +277 -0
  38. package/src/tablatures/instruments/string-tablature.js +56 -0
  39. package/src/tablatures/instruments/tab-note.js +282 -0
  40. package/src/tablatures/instruments/tab-notes.js +41 -0
  41. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  42. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  43. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  44. package/src/tablatures/tab-absolute-elements.js +310 -0
  45. package/src/tablatures/tab-common.js +29 -0
  46. package/src/tablatures/tab-renderer.js +243 -0
  47. package/src/tablatures/transposer.js +110 -0
  48. package/src/test/abc_parser_lint.js +3 -0
  49. package/src/write/abc_absolute_element.js +2 -2
  50. package/src/write/abc_engraver_controller.js +19 -11
  51. package/src/write/abc_glyphs.js +2 -0
  52. package/src/write/abc_relative_element.js +5 -3
  53. package/src/write/abc_renderer.js +5 -1
  54. package/src/write/draw/absolute.js +5 -1
  55. package/src/write/draw/draw.js +5 -6
  56. package/src/write/draw/non-music.js +3 -1
  57. package/src/write/draw/print-line.js +24 -0
  58. package/src/write/draw/relative.js +14 -2
  59. package/src/write/draw/selectables.js +9 -6
  60. package/src/write/draw/staff-group.js +44 -8
  61. package/src/write/draw/staff-line.js +3 -19
  62. package/src/write/draw/staff.js +15 -2
  63. package/src/write/draw/tab-line.js +40 -0
  64. package/src/write/draw/text.js +3 -0
  65. package/src/write/draw/voice.js +9 -1
  66. package/src/write/format-jazz-chord.js +2 -2
  67. package/src/write/layout/staffGroup.js +23 -1
  68. package/src/write/layout/voice.js +2 -1
  69. package/src/write/svg.js +2 -1
  70. package/test.js +23 -0
  71. package/types/index.d.ts +73 -25
  72. package/version.js +1 -1
@@ -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
- var firstStaff = group.staffs[0];
392
- var middleC = firstStaff.absoluteY;
393
- var top = middleC - firstStaff.top * spacing.STEP;
394
- var lastStaff = group.staffs[group.staffs.length - 1];
395
- middleC = lastStaff.absoluteY;
396
- var bottom = middleC - lastStaff.bottom * spacing.STEP;
397
- var height = bottom - top;
398
-
399
- var voices = group.voices;
400
- for (var v = 0; v < voices.length; v++) {
401
- var noteFound = false;
402
- if (!voicesArr[v])
403
- voicesArr[v] = [];
404
- if (measureNumber[v] === undefined)
405
- measureNumber[v] = 0;
406
- var elements = voices[v].children;
407
- for (var elem = 0; elem < elements.length; elem++) {
408
- if (elements[elem].type === "tempo")
409
- tempos[measureNumber[v]] = this.getBpm(elements[elem].abcelem);
410
- voicesArr[v].push({top: top, height: height, line: group.line, measureNumber: measureNumber[v], elem: elements[elem]});
411
- 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.
412
- measureNumber[v]++;
413
- if (elements[elem].type === 'note' || elements[elem].type === 'rest')
414
- noteFound = true;
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
  }
@@ -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
- this.bReentry = true;
223
- this.timerId = null;
224
- if (this.synth && this.synth.synthControl)
225
- this.synth.synthControl.disable(true);
226
-
227
- this.tunes = renderAbc(this.div, this.currentAbc, this.abcjsParams);
228
- if (this.tunes.length > 0) {
229
- this.warnings = this.tunes[0].warnings;
230
- }
231
- this.redrawMidi();
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";
@@ -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
- for (var j = 0; j < this.inTie[i].length; j++) {
142
- this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
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 (chordTrack.length > 0) // Don't do chords on more than one track, so turn off chord detection after we create it.
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 (chordTrack.length > 0)
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 != elem.tripletR) { // most commonly (3:2:2
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 defaultSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/abcjs/";
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 alternateSoundFontUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/";
18
- var alternateSoundFontUrl2 = "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/";
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 === alternateSoundFontUrl || self.soundFontUrl === alternateSoundFontUrl2)
49
- self.soundFontVolumeMultiplier = 5.0;
50
- else if (self.soundFontUrl === defaultSoundFontUrl)
51
- self.soundFontVolumeMultiplier = 0.5;
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 === defaultSoundFontUrl)
56
+ else if (self.soundFontUrl === originalSoundFontUrl)
57
57
  self.programOffsets = {
58
- "violin": 113,
59
- "trombone": 200,
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
- cached.push(currentInstrument+":"+noteName);
102
- } else
103
- console.log("Can't find note: ", pitchNumber);
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
- 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};
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
- if (numTracks === panParam.length) {
288
- var ok = true;
289
- for (var pp = 0; pp < panParam.length; pp++){
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 >= -1 && x <= 1)
292
- panDistances.push(x);
293
- else
294
- ok = false;
295
- }
296
- if (ok)
297
- return panDistances;
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
- self.stop();
349
- self.pausedTimeSec = activeAudioContext().currentTime - self.startTimeSec;
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;
@@ -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('./sounds-cache');
5
+ var soundsCache = require("./sounds-cache");
6
6
 
7
- var getNote = function(url, instrument, name, audioContext) {
8
- return new Promise(function (resolve, reject) {
9
- if (!soundsCache[instrument])
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
- if (instrumentCache[name] === 'error') {
14
- return resolve({instrument: instrument, name: name, status: "error", message: "Unable to load sound font" + ' ' + url + ' ' + instrument + ' ' + name });
15
- }
16
- if (instrumentCache[name] === 'pending') {
17
- return resolve({instrument: instrument, name: name, status: "pending"});
18
- }
19
- if (instrumentCache[name]) {
20
- return resolve({instrument: instrument, name: name, status: "cached"});
21
- }
22
-
23
- // if (this.debugCallback)
24
- // this.debugCallback(`Loading sound: ${instrument} ${name}`);
25
- instrumentCache[name] = "pending"; // This can be called in parallel, so don't call it a second time before the first one has loaded.
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
- } else {
57
- instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.
58
- var cantLoadMp3 = "Onload error loading sound: " + name + " " + url + " " + e.currentTarget.status + " " + e.currentTarget.statusText;
59
- if (self.debugCallback)
60
- self.debugCallback(cantLoadMp3);
61
- return resolve({instrument: instrument, name: name, status: "error", message: cantLoadMp3 });
62
- }
63
- };
64
- xhr.addEventListener("error", function () {
65
- instrumentCache[name] = "error"; // To keep this from trying to load repeatedly.
66
- var cantLoadMp3 = "Error in loading sound: " + " " + url;
67
- if (self.debugCallback)
68
- self.debugCallback(cantLoadMp3);
69
- return resolve({instrument: instrument, name: name, status: "error", message: cantLoadMp3 });
70
- }, false);
71
- xhr.send();
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;