abcjs 6.0.0-beta.7 → 6.0.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.
Files changed (204) hide show
  1. package/.github/workflows/tests.yml +29 -0
  2. package/CODE_OF_CONDUCT.md +76 -0
  3. package/CONTRIBUTING.md +1 -0
  4. package/LICENSE.md +1 -1
  5. package/README.md +92 -3
  6. package/RELEASE.md +957 -1
  7. package/abcjs-audio.css +14 -5
  8. package/dist/.gitignore +1 -2
  9. package/dist/abcjs-basic-min.js +3 -0
  10. package/dist/abcjs-basic-min.js.LICENSE +23 -0
  11. package/dist/abcjs-basic.js +28231 -0
  12. package/dist/abcjs-basic.js.map +1 -0
  13. package/dist/abcjs-plugin-min.js +3 -0
  14. package/dist/abcjs-plugin-min.js.LICENSE +23 -0
  15. package/dist/report-basic.html +37 -0
  16. package/dist/report-before-glyph-compress.html +37 -0
  17. package/dist/report-brown-ts-target-es5.html +37 -0
  18. package/dist/report-dev-orig-no-babel.html +37 -0
  19. package/dist/report-synth.html +37 -0
  20. package/docker-build.sh +1 -0
  21. package/glyphs.json +1 -0
  22. package/index.js +27 -2
  23. package/{static-wrappers/license.js → license.js} +1 -1
  24. package/package.json +26 -29
  25. package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
  26. package/src/api/abc_animation.js +1 -17
  27. package/src/api/abc_tablatures.js +144 -0
  28. package/src/api/abc_timing_callbacks.js +234 -116
  29. package/src/api/abc_tunebook.js +18 -67
  30. package/src/api/abc_tunebook_svg.js +38 -46
  31. package/src/data/abc_tune.js +232 -972
  32. package/src/data/deline-tune.js +199 -0
  33. package/src/edit/abc_editarea.js +112 -0
  34. package/src/edit/abc_editor.js +95 -221
  35. package/src/midi/abc_midi_create.js +48 -50
  36. package/src/parse/abc_common.js +0 -14
  37. package/src/parse/abc_parse.js +167 -1321
  38. package/src/parse/abc_parse_book.js +62 -0
  39. package/src/parse/abc_parse_directive.js +164 -41
  40. package/src/parse/abc_parse_header.js +116 -145
  41. package/src/parse/abc_parse_key_voice.js +26 -20
  42. package/src/parse/abc_parse_music.js +1337 -0
  43. package/src/parse/abc_tokenizer.js +21 -15
  44. package/src/parse/abc_transpose.js +3 -15
  45. package/src/parse/tune-builder.js +896 -0
  46. package/src/parse/wrap_lines.js +205 -453
  47. package/src/synth/abc_midi_flattener.js +1292 -0
  48. package/src/{midi → synth}/abc_midi_renderer.js +44 -17
  49. package/src/synth/abc_midi_sequencer.js +648 -0
  50. package/src/synth/active-audio-context.js +3 -14
  51. package/src/synth/cents-to-factor.js +10 -0
  52. package/src/synth/create-note-map.js +21 -32
  53. package/src/synth/create-synth-control.js +20 -103
  54. package/src/synth/create-synth.js +185 -77
  55. package/src/synth/download-buffer.js +7 -21
  56. package/src/synth/get-midi-file.js +13 -20
  57. package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
  58. package/src/synth/images/loop.svg.js +65 -0
  59. package/src/synth/images/pause.svg.js +10 -0
  60. package/src/synth/images/play.svg.js +9 -0
  61. package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
  62. package/src/synth/instrument-index-to-name.js +1 -16
  63. package/src/synth/load-note.js +37 -76
  64. package/src/synth/pitch-to-note-name.js +0 -15
  65. package/src/synth/pitches-to-perc.js +64 -0
  66. package/src/synth/place-note.js +78 -68
  67. package/src/synth/play-event.js +17 -18
  68. package/src/synth/register-audio-context.js +11 -23
  69. package/src/synth/sounds-cache.js +0 -15
  70. package/src/synth/supports-audio.js +9 -23
  71. package/src/synth/synth-controller.js +80 -49
  72. package/src/synth/synth-sequence.js +20 -34
  73. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  74. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  75. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  76. package/src/tablatures/instruments/string-patterns.js +277 -0
  77. package/src/tablatures/instruments/string-tablature.js +56 -0
  78. package/src/tablatures/instruments/tab-note.js +282 -0
  79. package/src/tablatures/instruments/tab-notes.js +41 -0
  80. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  81. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  82. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  83. package/src/tablatures/tab-absolute-elements.js +310 -0
  84. package/src/tablatures/tab-common.js +29 -0
  85. package/src/tablatures/tab-renderer.js +243 -0
  86. package/src/tablatures/transposer.js +110 -0
  87. package/src/test/abc_midi_lint.js +5 -22
  88. package/src/test/abc_midi_sequencer_lint.js +11 -14
  89. package/src/test/abc_parser_lint.js +136 -32
  90. package/src/test/abc_vertical_lint.js +94 -32
  91. package/src/test/rendering-lint.js +38 -5
  92. package/src/write/abc_absolute_element.js +112 -120
  93. package/src/write/abc_abstract_engraver.js +102 -253
  94. package/src/write/abc_beam_element.js +30 -290
  95. package/src/write/abc_brace_element.js +12 -121
  96. package/src/write/abc_create_clef.js +21 -32
  97. package/src/write/abc_create_key_signature.js +8 -26
  98. package/src/write/abc_create_note_head.js +107 -0
  99. package/src/write/abc_create_time_signature.js +2 -21
  100. package/src/write/abc_crescendo_element.js +3 -50
  101. package/src/write/abc_decoration.js +7 -30
  102. package/src/write/abc_dynamic_decoration.js +3 -37
  103. package/src/write/abc_ending_element.js +1 -57
  104. package/src/write/abc_engraver_controller.js +111 -234
  105. package/src/write/abc_glyphs.js +9 -19
  106. package/src/write/abc_relative_element.js +57 -97
  107. package/src/write/abc_renderer.js +10 -832
  108. package/src/write/abc_spacing.js +0 -15
  109. package/src/write/abc_staff_group_element.js +14 -349
  110. package/src/write/abc_tempo_element.js +9 -117
  111. package/src/write/abc_tie_element.js +5 -68
  112. package/src/write/abc_triplet_element.js +6 -124
  113. package/src/write/abc_voice_element.js +7 -222
  114. package/src/write/add-chord.js +103 -0
  115. package/src/write/add-text-if.js +33 -0
  116. package/src/write/bottom-text.js +79 -0
  117. package/src/write/calcHeight.js +17 -0
  118. package/src/write/classes.js +100 -0
  119. package/src/write/draw/absolute.js +68 -0
  120. package/src/write/draw/beam.js +56 -0
  121. package/src/write/draw/brace.js +106 -0
  122. package/src/write/draw/crescendo.js +38 -0
  123. package/src/write/draw/debug-box.js +8 -0
  124. package/src/write/draw/draw.js +56 -0
  125. package/src/write/draw/dynamics.js +20 -0
  126. package/src/write/draw/ending.js +46 -0
  127. package/src/write/draw/group-elements.js +66 -0
  128. package/src/write/draw/horizontal-line.js +25 -0
  129. package/src/write/draw/non-music.js +50 -0
  130. package/src/write/draw/print-line.js +24 -0
  131. package/src/write/draw/print-path.js +7 -0
  132. package/src/write/draw/print-stem.js +30 -0
  133. package/src/write/draw/print-symbol.js +59 -0
  134. package/src/write/draw/print-vertical-line.js +18 -0
  135. package/src/write/draw/relative.js +77 -0
  136. package/src/write/draw/round-number.js +5 -0
  137. package/src/write/draw/selectables.js +59 -0
  138. package/src/write/draw/separator.js +16 -0
  139. package/src/write/draw/set-paper-size.js +45 -0
  140. package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
  141. package/src/write/draw/staff-group.js +226 -0
  142. package/src/write/draw/staff-line.js +9 -0
  143. package/src/write/draw/staff.js +33 -0
  144. package/src/write/draw/tab-line.js +40 -0
  145. package/src/write/draw/tempo.js +45 -0
  146. package/src/write/draw/text.js +71 -0
  147. package/src/write/draw/tie.js +97 -0
  148. package/src/write/draw/triplet.js +46 -0
  149. package/src/write/draw/voice.js +102 -0
  150. package/src/write/format-jazz-chord.js +15 -0
  151. package/src/write/free-text.js +41 -0
  152. package/src/write/get-font-and-attr.js +37 -0
  153. package/src/write/get-text-size.js +56 -0
  154. package/src/write/highlight.js +11 -0
  155. package/src/write/layout/VoiceElements.js +121 -0
  156. package/src/write/layout/beam.js +213 -0
  157. package/src/write/layout/get-left-edge-of-staff.js +56 -0
  158. package/src/write/layout/getBarYAt.js +6 -0
  159. package/src/write/layout/layout.js +94 -0
  160. package/src/write/layout/setUpperAndLowerElements.js +232 -0
  161. package/src/write/layout/staffGroup.js +146 -0
  162. package/src/write/layout/triplet.js +75 -0
  163. package/src/write/layout/voice.js +137 -0
  164. package/src/write/selection.js +188 -70
  165. package/src/write/separator.js +10 -0
  166. package/src/write/set-class.js +21 -0
  167. package/src/write/subtitle.js +12 -0
  168. package/src/write/svg.js +95 -43
  169. package/src/write/top-text.js +54 -0
  170. package/src/write/unhighlight.js +11 -0
  171. package/temp.txt +17 -0
  172. package/test.js +27 -64
  173. package/types/index.d.ts +1095 -0
  174. package/version.js +1 -1
  175. package/.babelrc +0 -5
  176. package/.eslintrc +0 -3
  177. package/.gitmodules +0 -3
  178. package/abcjs-midi.css +0 -166
  179. package/build-utils/loadPresets.js +0 -14
  180. package/build-utils/presets/webpack.analyze.js +0 -6
  181. package/build-utils/presets/webpack.optimize.js +0 -30
  182. package/build-utils/webpack.development.js +0 -14
  183. package/build-utils/webpack.production.js +0 -35
  184. package/deploy-docs.sh +0 -25
  185. package/docs/README.md +0 -33
  186. package/fix-versions.sh +0 -23
  187. package/mei.js +0 -43
  188. package/midi.js +0 -62
  189. package/src/api/abc_tunebook_midi.js +0 -116
  190. package/src/midi/abc_midi_controls.js +0 -701
  191. package/src/midi/abc_midi_flattener.js +0 -1119
  192. package/src/midi/abc_midi_js_preparer.js +0 -243
  193. package/src/midi/abc_midi_sequencer.js +0 -401
  194. package/src/midi/abc_midi_ui_generator.js +0 -86
  195. package/src/plugin/abc_plugin_midi.js +0 -220
  196. package/src/synth/images/loop.svg +0 -61
  197. package/src/synth/images/pause.svg +0 -6
  198. package/src/synth/images/play.svg +0 -5
  199. package/src/transform/abc2abc_write.js +0 -395
  200. package/static-wrappers/basic.js +0 -2
  201. package/static-wrappers/midi.js +0 -2
  202. package/static-wrappers/plugin-midi.js +0 -6
  203. package/static-wrappers/plugin.js +0 -6
  204. package/webpack.config.js +0 -29
@@ -1,22 +1,10 @@
1
1
  // abc_tune.js: a computer usable internal structure representing one tune.
2
- // Copyright (C) 2010-2020 Paul Rosen (paul at paulrosen dot net)
3
- //
4
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
7
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
- //
9
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
- //
11
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
12
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
14
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
2
 
17
3
  var parseCommon = require('../parse/abc_common');
18
- var parseKeyVoice = require('../parse/abc_parse_key_voice');
19
4
  var spacing = require('../write/abc_spacing');
5
+ var sequence = require('../synth/abc_midi_sequencer');
6
+ var flatten = require('../synth/abc_midi_flattener');
7
+ var delineTune = require("./deline-tune");
20
8
 
21
9
  /**
22
10
  * This is the data for a single ABC tune. It is created and populated by the window.ABCJS.parse.Parse class.
@@ -24,6 +12,48 @@ var spacing = require('../write/abc_spacing');
24
12
  * @alternateClassName ABCJS.Tune
25
13
  */
26
14
  var Tune = function() {
15
+ this.reset = function () {
16
+ this.version = "1.1.0";
17
+ this.media = "screen";
18
+ this.metaText = {};
19
+ this.metaTextInfo = {};
20
+ this.formatting = {};
21
+ this.lines = [];
22
+ this.staffNum = 0;
23
+ this.voiceNum = 0;
24
+ this.lineNum = 0;
25
+ this.runningFonts = {};
26
+ delete this.visualTranspose;
27
+ };
28
+ this.reset();
29
+
30
+ function copy(dest, src, prop, attrs) {
31
+ for (var i = 0; i < attrs.length; i++)
32
+ dest[prop][attrs[i]] = src[prop][attrs[i]];
33
+ }
34
+
35
+ this.copyTopInfo = function(src) {
36
+ var attrs = ['tempo', 'title', 'header', 'rhythm', 'origin', 'composer', 'author', 'partOrder'];
37
+ copy(this, src, "metaText", attrs);
38
+ copy(this, src, "metaTextInfo", attrs);
39
+ };
40
+
41
+ this.copyBottomInfo = function(src) {
42
+ var attrs = ['unalignedWords',
43
+ 'book',
44
+ 'source',
45
+ 'discography',
46
+ 'notes',
47
+ 'transcription',
48
+ 'history',
49
+ 'abc-copyright',
50
+ 'abc-creator',
51
+ 'abc-edited-by',
52
+ 'footer']
53
+ copy(this, src, "metaText", attrs);
54
+ copy(this, src, "metaTextInfo", attrs);
55
+ };
56
+
27
57
  // The structure consists of a hash with the following two items:
28
58
  // metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc...
29
59
  // tempo: { noteLength: number (e.g. .125), bpm: number }
@@ -58,69 +88,49 @@ var Tune = function() {
58
88
  // if specified, { num: 99, den: 99 }
59
89
 
60
90
  this.getBeatLength = function() {
61
- for (var i = 0; i < this.lines.length; i++) {
62
- if (this.lines[i].staff) {
63
- for (var j = 0; j < this.lines[i].staff.length; j++) {
64
- if (this.lines[i].staff[j].meter) {
65
- var meter = this.lines[i].staff[j].meter;
66
- if (meter.type === "specified") {
67
- if (meter.value.length > 0) {
68
- var num = parseInt(meter.value[0].num, 10);
69
- var den = parseInt(meter.value[0].den, 10);
70
- if (num === 3 && den === 8) return 3/8;
71
- if (num === 6 && den === 8) return 3/8;
72
- if (num === 6 && den === 4) return 3/4;
73
- if (num === 9 && den === 8) return 3/8;
74
- if (num === 12 && den === 8) return 3/8;
75
- return 1/den;
76
- }
77
- else
78
- return 1/4; // No meter was specified, so use this default
79
- } else if (meter.type === 'cut_time') {
80
- return 1/2;
81
- } else {
82
- return 1/4; // TODO-PER: this works for common time, but not for the ancient meters.
91
+ // This returns a fraction: for instance 1/4 for a quarter
92
+ // There are two types of meters: compound and regular. Compound meter has 3 beats counted as one.
93
+ var meter = this.getMeterFraction();
94
+ var multiplier = 1;
95
+ if (meter.num === 6 || meter.num === 9 || meter.num === 12)
96
+ multiplier = 3;
97
+ else if (meter.num === 3 && meter.den === 8)
98
+ multiplier = 3;
99
+
100
+ return multiplier / meter.den;
101
+ };
102
+
103
+ function computePickupLength(lines, barLength) {
104
+ var pickupLength = 0;
105
+ for (var i = 0; i < lines.length; i++) {
106
+ if (lines[i].staff) {
107
+ for (var j = 0; j < lines[i].staff.length; j++) {
108
+ for (var v = 0; v < lines[i].staff[j].voices.length; v++) {
109
+ var voice = lines[i].staff[j].voices[v];
110
+ var tripletMultiplier = 1;
111
+ for (var el = 0; el < voice.length; el++) {
112
+ var isSpacer = voice[el].rest && voice[el].rest.type === "spacer";
113
+ if (voice[el].startTriplet)
114
+ tripletMultiplier = voice[el].tripletMultiplier;
115
+ if (voice[el].duration && !isSpacer && voice[el].el_type !== "tempo")
116
+ pickupLength += voice[el].duration * tripletMultiplier;
117
+ if (voice[el].endTriplet)
118
+ tripletMultiplier = 1;
119
+ if (pickupLength >= barLength)
120
+ pickupLength -= barLength;
121
+ if (voice[el].el_type === 'bar')
122
+ return pickupLength;
83
123
  }
84
124
  }
85
125
  }
86
126
  }
87
127
  }
88
- return 1/4; // No meter was specified, so use this default
89
- };
90
128
 
129
+ return pickupLength;
130
+ }
91
131
  this.getPickupLength = function() {
92
132
  var barLength = this.getBarLength();
93
- var computePickupLength = (function() {
94
- var pickupLength = 0;
95
- for (var i = 0; i < this.lines.length; i++) {
96
- if (this.lines[i].staff) {
97
- for (var j = 0; j < this.lines[i].staff.length; j++) {
98
- for (var v = 0; v < this.lines[i].staff[j].voices.length; v++) {
99
- var voice = this.lines[i].staff[j].voices[v];
100
- var hasNote = false;
101
- var tripletMultiplier = 1;
102
- for (var el = 0; el < voice.length; el++) {
103
- var isSpacer = voice[el].rest && voice[el].rest.type === "spacer";
104
- if (voice[el].startTriplet)
105
- tripletMultiplier = voice[el].tripletMultiplier;
106
- if (voice[el].duration && !isSpacer)
107
- pickupLength += voice[el].duration * tripletMultiplier;
108
- if (voice[el].endTriplet)
109
- tripletMultiplier = 1;
110
- if (pickupLength >= barLength)
111
- pickupLength -= barLength;
112
- if (voice[el].el_type === 'bar')
113
- return pickupLength;
114
- }
115
- }
116
- }
117
- }
118
- }
119
-
120
- return pickupLength;
121
- });
122
-
123
- var pickupLength = computePickupLength.apply(this);
133
+ var pickupLength = computePickupLength(this.lines, barLength);
124
134
 
125
135
  // If computed pickup length is very close to 0 or the bar length, we assume
126
136
  // that we actually have a full bar and hence no pickup.
@@ -132,6 +142,14 @@ var Tune = function() {
132
142
  return meter.num / meter.den;
133
143
  };
134
144
 
145
+ this.getTotalTime = function() {
146
+ return this.totalTime;
147
+ };
148
+
149
+ this.getTotalBeats = function() {
150
+ return this.totalBeats;
151
+ };
152
+
135
153
  this.millisecondsPerMeasure = function(bpmOverride) {
136
154
  var bpm;
137
155
  if (bpmOverride) {
@@ -150,831 +168,9 @@ var Tune = function() {
150
168
  };
151
169
 
152
170
  this.getBeatsPerMeasure = function() {
153
- var beatsPerMeasure;
154
- var meter = this.getMeterFraction();
155
- if (meter.num % 3 === 0) {
156
- beatsPerMeasure = meter.num / 3;
157
- } else {
158
- beatsPerMeasure = meter.num;
159
- }
160
- if (beatsPerMeasure <= 0) // This probably won't happen in any normal case - but it is possible that the meter could be set to something nonsensical.
161
- beatsPerMeasure = 1;
162
- return beatsPerMeasure;
163
- };
164
-
165
- this.reset = function () {
166
- this.version = "1.0.1";
167
- this.media = "screen";
168
- this.metaText = {};
169
- this.formatting = {};
170
- this.lines = [];
171
- this.staffNum = 0;
172
- this.voiceNum = 0;
173
- this.lineNum = 0;
174
- };
175
-
176
- this.resolveOverlays = function() {
177
- var madeChanges = false;
178
- for (var i = 0; i < this.lines.length; i++) {
179
- var line = this.lines[i];
180
- if (line.staff) {
181
- for (var j = 0; j < line.staff.length; j++) {
182
- var staff = line.staff[j];
183
- var overlayVoice = [];
184
- for (var k = 0; k < staff.voices.length; k++) {
185
- var voice = staff.voices[k];
186
- overlayVoice.push({ hasOverlay: false, voice: [], snip: []});
187
- var durationThisBar = 0;
188
- var inOverlay = false;
189
- var snipStart = -1;
190
- for (var kk = 0; kk < voice.length; kk++) {
191
- var event = voice[kk];
192
- if (event.el_type === "overlay" && !inOverlay) {
193
- madeChanges = true;
194
- inOverlay = true;
195
- snipStart = kk;
196
- overlayVoice[k].hasOverlay = true;
197
- } else if (event.el_type === "bar") {
198
- if (inOverlay) {
199
- // delete the overlay events from this array without messing up this loop.
200
- inOverlay = false;
201
- overlayVoice[k].snip.push({ start: snipStart, len: kk - snipStart});
202
- overlayVoice[k].voice.push(event); // Also end the overlay with the barline.
203
- } else {
204
- // This keeps the voices lined up: if the overlay isn't in the first measure then we need a bunch of invisible rests.
205
- if (durationThisBar > 0)
206
- overlayVoice[k].voice.push({ el_type: "note", duration: durationThisBar, rest: {type: "invisible"}, startChar: event.startChar, endChar: event.endChar });
207
- overlayVoice[k].voice.push(event);
208
- }
209
- durationThisBar = 0;
210
- } else if (event.el_type === "note") {
211
- if (inOverlay) {
212
- overlayVoice[k].voice.push(event);
213
- } else {
214
- durationThisBar += event.duration;
215
- }
216
- } else if (event.el_type === "scale" || event.el_type === "stem" || event.el_type === "overlay" || event.el_type === "style" || event.el_type === "transpose") {
217
- // These types of events are duplicated on the overlay layer.
218
- overlayVoice[k].voice.push(event);
219
- }
220
- }
221
- if (overlayVoice[k].hasOverlay && overlayVoice[k].snip.length === 0) {
222
- // there was no closing bar, so we didn't set the snip amount.
223
- overlayVoice[k].snip.push({ start: snipStart, len: voice.length - snipStart});
224
- }
225
- }
226
- for (k = 0; k < overlayVoice.length; k++) {
227
- var ov = overlayVoice[k];
228
- if (ov.hasOverlay) {
229
- staff.voices.push(ov.voice);
230
- for (var kkk = ov.snip.length-1; kkk >= 0; kkk--) {
231
- var snip = ov.snip[kkk];
232
- staff.voices[k].splice(snip.start, snip.len);
233
- }
234
- // remove ending marks from the overlay voice so they are not repeated
235
- for (kkk = 0; kkk < staff.voices[staff.voices.length-1].length; kkk++) {
236
- staff.voices[staff.voices.length-1][kkk] = parseCommon.clone(staff.voices[staff.voices.length-1][kkk]);
237
- var el = staff.voices[staff.voices.length-1][kkk];
238
- if (el.el_type === 'bar' && el.startEnding) {
239
- delete el.startEnding;
240
- }
241
- if (el.el_type === 'bar' && el.endEnding)
242
- delete el.endEnding;
243
- }
244
- }
245
- }
246
- }
247
- }
248
- }
249
- return madeChanges;
250
- };
251
-
252
- function fixTitles(lines) {
253
- // We might have name and subname defined. We now know what line everything is on, so we can determine which to use.
254
- var firstMusicLine = true;
255
- for (var i = 0; i < lines.length; i++) {
256
- var line = lines[i];
257
- if (line.staff) {
258
- for (var j = 0; j < line.staff.length; j++) {
259
- var staff = line.staff[j];
260
- if (staff.title) {
261
- var hasATitle = false;
262
- for (var k = 0; k < staff.title.length; k++) {
263
- if (staff.title[k]) {
264
- staff.title[k] = (firstMusicLine) ? staff.title[k].name : staff.title[k].subname;
265
- if (staff.title[k])
266
- hasATitle = true;
267
- else
268
- staff.title[k] = '';
269
- } else
270
- staff.title[k] = '';
271
- }
272
- if (!hasATitle)
273
- delete staff.title;
274
- }
275
- }
276
- firstMusicLine = false;
277
- }
278
- }
279
- }
280
-
281
- this.cleanUp = function(defWidth, defLength, barsperstaff, staffnonote, currSlur) {
282
- this.closeLine(); // Close the last line.
283
-
284
- // If the tempo was created with a string like "Allegro", then the duration of a beat needs to be set at the last moment, when it is most likely known.
285
- if (this.metaText.tempo && this.metaText.tempo.bpm && !this.metaText.tempo.duration)
286
- this.metaText.tempo.duration = [ this.getBeatLength() ];
287
-
288
- // Remove any blank lines
289
- var anyDeleted = false;
290
- var i, s, v;
291
- for (i = 0; i < this.lines.length; i++) {
292
- if (this.lines[i].staff !== undefined) {
293
- var hasAny = false;
294
- for (s = 0; s < this.lines[i].staff.length; s++) {
295
- if (this.lines[i].staff[s] === undefined) {
296
- anyDeleted = true;
297
- this.lines[i].staff[s] = null;
298
- //this.lines[i].staff[s] = { voices: []}; // TODO-PER: There was a part missing in the abc music. How should we recover?
299
- } else {
300
- for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
301
- if (this.lines[i].staff[s].voices[v] === undefined)
302
- this.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover?
303
- else
304
- if (this.containsNotes(this.lines[i].staff[s].voices[v])) hasAny = true;
305
- }
306
- }
307
- }
308
- if (!hasAny) {
309
- this.lines[i] = null;
310
- anyDeleted = true;
311
- }
312
- }
313
- }
314
- if (anyDeleted) {
315
- this.lines = parseCommon.compact(this.lines);
316
- parseCommon.each(this.lines, function(line) {
317
- if (line.staff)
318
- line.staff = parseCommon.compact(line.staff);
319
- });
320
- }
321
-
322
- // if we exceeded the number of bars allowed on a line, then force a new line
323
- if (barsperstaff) {
324
- while (wrapMusicLines(this.lines, barsperstaff)) {
325
- // This will keep wrapping until the end of the piece.
326
- }
327
- }
328
-
329
- // If we were passed staffnonote, then we want to get rid of all staffs that contain only rests.
330
- if (staffnonote) {
331
- anyDeleted = false;
332
- for (i = 0; i < this.lines.length; i++) {
333
- if (this.lines[i].staff !== undefined) {
334
- for (s = 0; s < this.lines[i].staff.length; s++) {
335
- var keepThis = false;
336
- for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
337
- if (this.containsNotesStrict(this.lines[i].staff[s].voices[v])) {
338
- keepThis = true;
339
- }
340
- }
341
- if (!keepThis) {
342
- anyDeleted = true;
343
- this.lines[i].staff[s] = null;
344
- }
345
- }
346
- }
347
- }
348
- if (anyDeleted) {
349
- parseCommon.each(this.lines, function(line) {
350
- if (line.staff)
351
- line.staff = parseCommon.compact(line.staff);
352
- });
353
- }
354
- }
355
-
356
- fixTitles(this.lines);
357
-
358
- // Remove the temporary working variables
359
- for (i = 0; i < this.lines.length; i++) {
360
- if (this.lines[i].staff) {
361
- for (s = 0; s < this.lines[i].staff.length; s++)
362
- delete this.lines[i].staff[s].workingClef;
363
- }
364
- }
365
-
366
- // If there are overlays, create new voices for them.
367
- while (this.resolveOverlays()) {
368
- // keep resolving overlays as long as any are found.
369
- }
370
-
371
- function cleanUpSlursInLine(line) {
372
- var x;
373
- // var lyr = null; // TODO-PER: debugging.
374
-
375
- var addEndSlur = function(obj, num, chordPos) {
376
- if (currSlur[chordPos] === undefined) {
377
- // There isn't an exact match for note position, but we'll take any other open slur.
378
- for (x = 0; x < currSlur.length; x++) {
379
- if (currSlur[x] !== undefined) {
380
- chordPos = x;
381
- break;
382
- }
383
- }
384
- if (currSlur[chordPos] === undefined) {
385
- var offNum = chordPos*100+1;
386
- parseCommon.each(obj.endSlur, function(x) { if (offNum === x) --offNum; });
387
- currSlur[chordPos] = [offNum];
388
- }
389
- }
390
- var slurNum;
391
- for (var i = 0; i < num; i++) {
392
- slurNum = currSlur[chordPos].pop();
393
- obj.endSlur.push(slurNum);
394
- // lyr.syllable += '<' + slurNum; // TODO-PER: debugging
395
- }
396
- if (currSlur[chordPos].length === 0)
397
- delete currSlur[chordPos];
398
- return slurNum;
399
- };
400
-
401
- var addStartSlur = function(obj, num, chordPos, usedNums) {
402
- obj.startSlur = [];
403
- if (currSlur[chordPos] === undefined) {
404
- currSlur[chordPos] = [];
405
- }
406
- var nextNum = chordPos*100+1;
407
- for (var i = 0; i < num; i++) {
408
- if (usedNums) {
409
- parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
410
- parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
411
- parseCommon.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
412
- }
413
- parseCommon.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
414
- parseCommon.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
415
-
416
- currSlur[chordPos].push(nextNum);
417
- obj.startSlur.push({ label: nextNum });
418
- // lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
419
- nextNum++;
420
- }
421
- };
422
-
423
- for (var i = 0; i < line.length; i++) {
424
- var el = line[i];
425
- // if (el.lyric === undefined) // TODO-PER: debugging
426
- // el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
427
- // lyr = el.lyric[0]; // TODO-PER: debugging
428
- // lyr.syllable = ''; // TODO-PER: debugging
429
- if (el.el_type === 'note') {
430
- if (el.gracenotes) {
431
- for (var g = 0; g < el.gracenotes.length; g++) {
432
- if (el.gracenotes[g].endSlur) {
433
- var gg = el.gracenotes[g].endSlur;
434
- el.gracenotes[g].endSlur = [];
435
- for (var ggg = 0; ggg < gg; ggg++)
436
- addEndSlur(el.gracenotes[g], 1, 20);
437
- }
438
- if (el.gracenotes[g].startSlur) {
439
- x = el.gracenotes[g].startSlur;
440
- addStartSlur(el.gracenotes[g], x, 20);
441
- }
442
- }
443
- }
444
- if (el.endSlur) {
445
- x = el.endSlur;
446
- el.endSlur = [];
447
- addEndSlur(el, x, 0);
448
- }
449
- if (el.startSlur) {
450
- x = el.startSlur;
451
- addStartSlur(el, x, 0);
452
- }
453
- if (el.pitches) {
454
- var usedNums = [];
455
- for (var p = 0; p < el.pitches.length; p++) {
456
- if (el.pitches[p].endSlur) {
457
- var k = el.pitches[p].endSlur;
458
- el.pitches[p].endSlur = [];
459
- for (var j = 0; j < k; j++) {
460
- var slurNum = addEndSlur(el.pitches[p], 1, p+1);
461
- usedNums.push(slurNum);
462
- }
463
- }
464
- }
465
- for (p = 0; p < el.pitches.length; p++) {
466
- if (el.pitches[p].startSlur) {
467
- x = el.pitches[p].startSlur;
468
- addStartSlur(el.pitches[p], x, p+1, usedNums);
469
- }
470
- }
471
- // Correct for the weird gracenote case where ({g}a) should match.
472
- // The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
473
- if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
474
- if (el.gracenotes[0].endSlur)
475
- el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);
476
- else
477
- el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
478
- if (el.pitches[0].endSlur.length === 1)
479
- delete el.pitches[0].endSlur;
480
- else if (el.pitches[0].endSlur[0] === 100)
481
- el.pitches[0].endSlur.shift();
482
- else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length-1] === 100)
483
- el.pitches[0].endSlur.pop();
484
- if (currSlur[1].length === 1)
485
- delete currSlur[1];
486
- else
487
- currSlur[1].pop();
488
- }
489
- }
490
- }
491
- }
492
- }
493
-
494
- // TODO-PER: This could be done faster as we go instead of as the last step.
495
- function fixClefPlacement(el) {
496
- parseKeyVoice.fixClef(el);
497
- //if (el.el_type === 'clef') {
498
- // var min = -2;
499
- // var max = 5;
500
- // switch(el.type) {
501
- // case 'treble+8':
502
- // case 'treble-8':
503
- // break;
504
- // case 'bass':
505
- // case 'bass+8':
506
- // case 'bass-8':
507
- // el.verticalPos = 20 + el.verticalPos; min += 6; max += 6;
508
- // break;
509
- // case 'tenor':
510
- // case 'tenor+8':
511
- // case 'tenor-8':
512
- // el.verticalPos = - el.verticalPos; min = -40; max = 40;
513
- //// el.verticalPos+=2; min += 6; max += 6;
514
- // break;
515
- // case 'alto':
516
- // case 'alto+8':
517
- // case 'alto-8':
518
- // el.verticalPos = - el.verticalPos; min = -40; max = 40;
519
- //// el.verticalPos-=2; min += 4; max += 4;
520
- // break;
521
- // }
522
- // if (el.verticalPos < min) {
523
- // while (el.verticalPos < min)
524
- // el.verticalPos += 7;
525
- // } else if (el.verticalPos > max) {
526
- // while (el.verticalPos > max)
527
- // el.verticalPos -= 7;
528
- // }
529
- //}
530
- }
531
-
532
- function wrapMusicLines(lines, barsperstaff) {
533
- for (i = 0; i < lines.length; i++) {
534
- if (lines[i].staff !== undefined) {
535
- for (s = 0; s < lines[i].staff.length; s++) {
536
- var permanentItems = [];
537
- for (v = 0; v < lines[i].staff[s].voices.length; v++) {
538
- var voice = lines[i].staff[s].voices[v];
539
- var barNumThisLine = 0;
540
- for (var n = 0; n < voice.length; n++) {
541
- if (voice[n].el_type === 'bar') {
542
- barNumThisLine++;
543
- if (barNumThisLine >= barsperstaff) {
544
- // push everything else to the next line, if there is anything else,
545
- // and there is a next line. If there isn't a next line, create one.
546
- if (n < voice.length - 1) {
547
- var nextLine = getNextMusicLine(lines, i);
548
- if (!nextLine) {
549
- var cp = JSON.parse(JSON.stringify(lines[i]));
550
- lines.push(parseCommon.clone(cp));
551
- nextLine = lines[lines.length - 1];
552
- for (var ss = 0; ss < nextLine.staff.length; ss++) {
553
- for (var vv = 0; vv < nextLine.staff[ss].voices.length; vv++)
554
- nextLine.staff[ss].voices[vv] = [];
555
- }
556
- }
557
- var startElement = n + 1;
558
- var section = lines[i].staff[s].voices[v].slice(startElement);
559
- lines[i].staff[s].voices[v] = lines[i].staff[s].voices[v].slice(0, startElement);
560
- nextLine.staff[s].voices[v] = permanentItems.concat(section.concat(nextLine.staff[s].voices[v]));
561
- return true;
562
- }
563
- }
564
- } else if (!voice[n].duration) {
565
- permanentItems.push(voice[n]);
566
- }
567
- }
568
- }
569
- }
570
- }
571
- }
572
- return false;
573
- }
574
-
575
- function getNextMusicLine(lines, currentLine) {
576
- currentLine++;
577
- while (lines.length > currentLine) {
578
- if (lines[currentLine].staff)
579
- return lines[currentLine];
580
- currentLine++;
581
- }
582
- return null;
583
- }
584
-
585
- for (this.lineNum = 0; this.lineNum < this.lines.length; this.lineNum++) {
586
- var staff = this.lines[this.lineNum].staff;
587
- if (staff) {
588
- for (this.staffNum = 0; this.staffNum < staff.length; this.staffNum++) {
589
- if (staff[this.staffNum].clef)
590
- fixClefPlacement(staff[this.staffNum].clef);
591
- for (this.voiceNum = 0; this.voiceNum < staff[this.staffNum].voices.length; this.voiceNum++) {
592
- var voice = staff[this.staffNum].voices[this.voiceNum];
593
- cleanUpSlursInLine(voice);
594
- for (var j = 0; j < voice.length; j++) {
595
- if (voice[j].el_type === 'clef')
596
- fixClefPlacement(voice[j]);
597
- }
598
- if (voice.length > 0 && voice[voice.length-1].barNumber) {
599
- // Don't hang a bar number on the last bar line: it should go on the next line.
600
- var nextLine = getNextMusicLine(this.lines, this.lineNum);
601
- if (nextLine)
602
- nextLine.staff[0].barNumber = voice[voice.length-1].barNumber;
603
- delete voice[voice.length-1].barNumber;
604
- }
605
- }
606
- }
607
- }
608
- }
609
-
610
- if (!this.formatting.pagewidth)
611
- this.formatting.pagewidth = defWidth;
612
- if (!this.formatting.pageheight)
613
- this.formatting.pageheight = defLength;
614
-
615
- // Remove temporary variables that the outside doesn't need to know about
616
- delete this.staffNum;
617
- delete this.voiceNum;
618
- delete this.lineNum;
619
- delete this.potentialStartBeam;
620
- delete this.potentialEndBeam;
621
- delete this.vskipPending;
622
-
623
- return currSlur;
624
- };
625
-
626
- this.reset();
627
-
628
- this.getLastNote = function() {
629
- if (this.lines[this.lineNum] && this.lines[this.lineNum].staff && this.lines[this.lineNum].staff[this.staffNum] &&
630
- this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]) {
631
- for (var i = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length-1; i >= 0; i--) {
632
- var el = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][i];
633
- if (el.el_type === 'note') {
634
- return el;
635
- }
636
- }
637
- }
638
- return null;
639
- };
640
-
641
- this.addTieToLastNote = function() {
642
- // TODO-PER: if this is a chord, which note?
643
- var el = this.getLastNote();
644
- if (el && el.pitches && el.pitches.length > 0) {
645
- el.pitches[0].startTie = {};
646
- return true;
647
- }
648
- return false;
649
- };
650
-
651
- this.getDuration = function(el) {
652
- if (el.duration) return el.duration;
653
- //if (el.pitches && el.pitches.length > 0) return el.pitches[0].duration;
654
- return 0;
655
- };
656
-
657
- this.closeLine = function() {
658
- if (this.potentialStartBeam && this.potentialEndBeam) {
659
- this.potentialStartBeam.startBeam = true;
660
- this.potentialEndBeam.endBeam = true;
661
- }
662
- delete this.potentialStartBeam;
663
- delete this.potentialEndBeam;
664
- };
665
-
666
- this.appendElement = function(type, startChar, endChar, hashParams)
667
- {
668
- var This = this;
669
- var pushNote = function(hp) {
670
- var currStaff = This.lines[This.lineNum].staff[This.staffNum];
671
- if (!currStaff) {
672
- // TODO-PER: This prevents a crash, but it drops the element. Need to figure out how to start a new line, or delay adding this.
673
- return;
674
- }
675
- if (hp.pitches !== undefined) {
676
- var mid = currStaff.workingClef.verticalPos;
677
- parseCommon.each(hp.pitches, function(p) { p.verticalPos = p.pitch - mid; });
678
- }
679
- if (hp.gracenotes !== undefined) {
680
- var mid2 = currStaff.workingClef.verticalPos;
681
- parseCommon.each(hp.gracenotes, function(p) { p.verticalPos = p.pitch - mid2; });
682
- }
683
- currStaff.voices[This.voiceNum].push(hp);
684
- };
685
- hashParams.el_type = type;
686
- if (startChar !== null)
687
- hashParams.startChar = startChar;
688
- if (endChar !== null)
689
- hashParams.endChar = endChar;
690
- var endBeamHere = function() {
691
- This.potentialStartBeam.startBeam = true;
692
- hashParams.endBeam = true;
693
- delete This.potentialStartBeam;
694
- delete This.potentialEndBeam;
695
- };
696
- var endBeamLast = function() {
697
- if (This.potentialStartBeam !== undefined && This.potentialEndBeam !== undefined) { // Do we have a set of notes to beam?
698
- This.potentialStartBeam.startBeam = true;
699
- This.potentialEndBeam.endBeam = true;
700
- }
701
- delete This.potentialStartBeam;
702
- delete This.potentialEndBeam;
703
- };
704
- if (type === 'note') { // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
705
- // Now, add the startBeam and endBeam where it is needed.
706
- // end_beam is already set on the places where there is a forced end_beam. We'll remove that here after using that info.
707
- // this.potentialStartBeam either points to null or the start beam.
708
- // this.potentialEndBeam either points to null or the start beam.
709
- // If we have a beam break (note is longer than a quarter, or an end_beam is on this element), then set the beam if we have one.
710
- // reset the variables for the next notes.
711
- var dur = This.getDuration(hashParams);
712
- if (dur >= 0.25) { // The beam ends on the note before this.
713
- endBeamLast();
714
- } else if (hashParams.force_end_beam_last && This.potentialStartBeam !== undefined) {
715
- endBeamLast();
716
- } else if (hashParams.end_beam && This.potentialStartBeam !== undefined) { // the beam is forced to end on this note, probably because of a space in the ABC
717
- if (hashParams.rest === undefined)
718
- endBeamHere();
719
- else
720
- endBeamLast();
721
- } else if (hashParams.rest === undefined) { // this a short note and we aren't about to end the beam
722
- if (This.potentialStartBeam === undefined) { // We aren't collecting notes for a beam, so start here.
723
- if (!hashParams.end_beam) {
724
- This.potentialStartBeam = hashParams;
725
- delete This.potentialEndBeam;
726
- }
727
- } else {
728
- This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
729
- }
730
- }
731
-
732
- // end_beam goes on rests and notes which precede rests _except_ when a rest (or set of adjacent rests) has normal notes on both sides (no spaces)
733
- // if (hashParams.rest !== undefined)
734
- // {
735
- // hashParams.end_beam = true;
736
- // var el2 = this.getLastNote();
737
- // if (el2) el2.end_beam = true;
738
- // // TODO-PER: implement exception mentioned in the comment.
739
- // }
740
- } else { // It's not a note, so there definitely isn't beaming after it.
741
- endBeamLast();
742
- }
743
- delete hashParams.end_beam; // We don't want this temporary variable hanging around.
744
- delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
745
- pushNote(hashParams);
746
- };
747
-
748
- this.appendStartingElement = function(type, startChar, endChar, hashParams2)
749
- {
750
- // If we're in the middle of beaming, then end the beam.
751
- this.closeLine();
752
-
753
- // We only ever want implied naturals the first time.
754
- var impliedNaturals;
755
- if (type === 'key') {
756
- impliedNaturals = hashParams2.impliedNaturals;
757
- delete hashParams2.impliedNaturals;
758
- delete hashParams2.explicitAccidentals;
759
- }
760
-
761
- // Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
762
- var hashParams = parseCommon.clone(hashParams2);
763
-
764
- if (this.lines[this.lineNum].staff) { // be sure that we are on a music type line before doing the following.
765
- // If this is the first item in this staff, then we might have to initialize the staff, first.
766
- if (this.lines[this.lineNum].staff.length <= this.staffNum) {
767
- this.lines[this.lineNum].staff[this.staffNum] = {};
768
- this.lines[this.lineNum].staff[this.staffNum].clef = parseCommon.clone(this.lines[this.lineNum].staff[0].clef);
769
- this.lines[this.lineNum].staff[this.staffNum].key = parseCommon.clone(this.lines[this.lineNum].staff[0].key);
770
- if (this.lines[this.lineNum].staff[0].meter)
771
- this.lines[this.lineNum].staff[this.staffNum].meter = parseCommon.clone(this.lines[this.lineNum].staff[0].meter);
772
- this.lines[this.lineNum].staff[this.staffNum].workingClef = parseCommon.clone(this.lines[this.lineNum].staff[0].workingClef);
773
- this.lines[this.lineNum].staff[this.staffNum].voices = [[]];
774
- }
775
- // If this is a clef type, then we replace the working clef on the line. This is kept separate from
776
- // the clef in case there is an inline clef field. We need to know what the current position for
777
- // the note is.
778
- if (type === 'clef') {
779
- this.lines[this.lineNum].staff[this.staffNum].workingClef = hashParams;
780
- }
781
-
782
- // These elements should not be added twice, so if the element exists on this line without a note or bar before it, just replace the staff version.
783
- var voice = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
784
- for (var i = 0; i < voice.length; i++) {
785
- if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
786
- hashParams.el_type = type;
787
- hashParams.startChar = startChar;
788
- hashParams.endChar = endChar;
789
- if (impliedNaturals)
790
- hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
791
- voice.push(hashParams);
792
- return;
793
- }
794
- if (voice[i].el_type === type) {
795
- hashParams.el_type = type;
796
- hashParams.startChar = startChar;
797
- hashParams.endChar = endChar;
798
- if (impliedNaturals)
799
- hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
800
- voice[i] = hashParams;
801
- return;
802
- }
803
- }
804
- // We didn't see either that type or a note, so replace the element to the staff.
805
- this.lines[this.lineNum].staff[this.staffNum][type] = hashParams2;
806
- }
807
- };
808
-
809
- this.getNumLines = function() {
810
- return this.lines.length;
811
- };
812
-
813
- this.pushLine = function(hash) {
814
- if (this.vskipPending) {
815
- hash.vskip = this.vskipPending;
816
- delete this.vskipPending;
817
- }
818
- this.lines.push(hash);
819
- };
820
-
821
- this.addSubtitle = function(str) {
822
- this.pushLine({subtitle: str});
823
- };
824
-
825
- this.addSpacing = function(num) {
826
- this.vskipPending = num;
827
- };
828
-
829
- this.addNewPage = function(num) {
830
- this.pushLine({newpage: num});
831
- };
832
-
833
- this.addSeparator = function(spaceAbove, spaceBelow, lineLength) {
834
- this.pushLine({separator: {spaceAbove: Math.round(spaceAbove), spaceBelow: Math.round(spaceBelow), lineLength: Math.round(lineLength)}});
835
- };
836
-
837
- this.addText = function(str) {
838
- this.pushLine({text: str});
839
- };
840
-
841
- this.addCentered = function(str) {
842
- this.pushLine({text: [{text: str, center: true }]});
843
- };
844
-
845
- this.containsNotes = function(voice) {
846
- for (var i = 0; i < voice.length; i++) {
847
- if (voice[i].el_type === 'note' || voice[i].el_type === 'bar')
848
- return true;
849
- }
850
- return false;
851
- };
852
-
853
- this.containsNotesStrict = function(voice) {
854
- for (var i = 0; i < voice.length; i++) {
855
- if (voice[i].el_type === 'note' && voice[i].rest === undefined)
856
- return true;
857
- }
858
- return false;
859
- };
860
-
861
- // anyVoiceContainsNotes: function(line) {
862
- // for (var i = 0; i < line.staff.voices.length; i++) {
863
- // if (this.containsNotes(line.staff.voices[i]))
864
- // return true;
865
- // }
866
- // return false;
867
- // },
868
- this.changeVoiceScale = function(scale) {
869
- var This = this;
870
- This.appendElement('scale', null, null, { size: scale} );
871
- };
872
-
873
- this.startNewLine = function(params) {
874
- // If the pointed to line doesn't exist, just create that. If the line does exist, but doesn't have any music on it, just use it.
875
- // If it does exist and has music, then increment the line number. If the new element doesn't exist, create it.
876
- var This = this;
877
- this.closeLine(); // Close the previous line.
878
- var createVoice = function(params) {
879
- var thisStaff = This.lines[This.lineNum].staff[This.staffNum];
880
- thisStaff.voices[This.voiceNum] = [];
881
- if (!thisStaff.title)
882
- thisStaff.title = [];
883
- thisStaff.title[This.voiceNum] = { name: params.name, subname: params.subname };
884
- if (params.style)
885
- This.appendElement('style', null, null, {head: params.style});
886
- if (params.stem)
887
- This.appendElement('stem', null, null, {direction: params.stem});
888
- else if (This.voiceNum > 0) {
889
- if (thisStaff.voices[0]!== undefined) {
890
- var found = false;
891
- for (var i = 0; i < thisStaff.voices[0].length; i++) {
892
- if (thisStaff.voices[0].el_type === 'stem')
893
- found = true;
894
- }
895
- if (!found) {
896
- var stem = { el_type: 'stem', direction: 'up' };
897
- thisStaff.voices[0].splice(0,0,stem);
898
- }
899
- }
900
- This.appendElement('stem', null, null, {direction: 'down'});
901
- }
902
- if (params.scale)
903
- This.appendElement('scale', null, null, { size: params.scale} );
904
- };
905
- var createStaff = function(params) {
906
- if (params.key && params.key.impliedNaturals) {
907
- params.key.accidentals = params.key.accidentals.concat(params.key.impliedNaturals);
908
- delete params.key.impliedNaturals;
909
- }
910
-
911
- This.lines[This.lineNum].staff[This.staffNum] = {voices: [ ], clef: params.clef, key: params.key, workingClef: params.clef };
912
- if (params.stafflines !== undefined) {
913
- This.lines[This.lineNum].staff[This.staffNum].clef.stafflines = params.stafflines;
914
- This.lines[This.lineNum].staff[This.staffNum].workingClef.stafflines = params.stafflines;
915
- }
916
- if (params.staffscale) {
917
- This.lines[This.lineNum].staff[This.staffNum].staffscale = params.staffscale;
918
- }
919
- if (params.tripletfont) This.lines[This.lineNum].staff[This.staffNum].tripletfont = params.tripletfont;
920
- if (params.vocalfont) This.lines[This.lineNum].staff[This.staffNum].vocalfont = params.vocalfont;
921
- if (params.bracket) This.lines[This.lineNum].staff[This.staffNum].bracket = params.bracket;
922
- if (params.brace) This.lines[This.lineNum].staff[This.staffNum].brace = params.brace;
923
- if (params.connectBarLines) This.lines[This.lineNum].staff[This.staffNum].connectBarLines = params.connectBarLines;
924
- if (params.barNumber) This.lines[This.lineNum].staff[This.staffNum].barNumber = params.barNumber;
925
- createVoice(params);
926
- // Some stuff just happens for the first voice
927
- if (params.part)
928
- This.appendElement('part', params.part.startChar, params.part.endChar, {title: params.part.title});
929
- if (params.meter !== undefined) This.lines[This.lineNum].staff[This.staffNum].meter = params.meter;
930
- };
931
- var createLine = function(params) {
932
- This.lines[This.lineNum] = {staff: []};
933
- createStaff(params);
934
- };
935
- if (this.lines[this.lineNum] === undefined) createLine(params);
936
- else if (this.lines[this.lineNum].staff === undefined) {
937
- this.lineNum++;
938
- this.startNewLine(params);
939
- } else if (this.lines[this.lineNum].staff[this.staffNum] === undefined) createStaff(params);
940
- else if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] === undefined) createVoice(params);
941
- else if (!this.containsNotes(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum])) return;
942
- else {
943
- this.lineNum++;
944
- this.startNewLine(params);
945
- }
946
- };
947
-
948
- this.setBarNumberImmediate = function(barNumber) {
949
- // If this is called right at the beginning of a line, then correct the measure number that is already written.
950
- // If this is called at the beginning of a measure, then correct the measure number that was just created.
951
- // If this is called in the middle of a measure, then subtract one from it, because it will be incremented before applied.
952
- var currentVoice = this.getCurrentVoice();
953
- if (currentVoice && currentVoice.length > 0) {
954
- var lastElement = currentVoice[currentVoice.length-1];
955
- if (lastElement.el_type === 'bar') {
956
- if (lastElement.barNumber !== undefined) // the measure number might not be written for this bar, don't override that.
957
- lastElement.barNumber = barNumber;
958
- } else
959
- return barNumber-1;
960
- }
961
- return barNumber;
962
- };
963
-
964
- this.hasBeginMusic = function() {
965
- // return true if there exists at least one line that contains "staff"
966
- for (var i = 0; i < this.lines.length; i++) {
967
- if (this.lines[i].staff)
968
- return true;
969
- }
970
- return false;
971
- };
972
-
973
- this.isFirstLine = function(index) {
974
- for (var i = index-1; i >= 0; i--) {
975
- if (this.lines[i].staff !== undefined) return false;
976
- }
977
- return true;
171
+ var beatLen = this.getBeatLength();
172
+ var barLen = this.getBarLength();
173
+ return barLen / beatLen;
978
174
  };
979
175
 
980
176
  this.getMeter = function() {
@@ -1025,42 +221,25 @@ var Tune = function() {
1025
221
  return { };
1026
222
  };
1027
223
 
1028
- this.getCurrentVoice = function() {
1029
- if (this.lines[this.lineNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] !== undefined)
1030
- return this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
1031
- else return null;
1032
- };
1033
-
1034
- this.setCurrentVoice = function(staffNum, voiceNum) {
1035
- this.staffNum = staffNum;
1036
- this.voiceNum = voiceNum;
224
+ this.getElementFromChar = function(char) {
1037
225
  for (var i = 0; i < this.lines.length; i++) {
1038
- if (this.lines[i].staff) {
1039
- if (this.lines[i].staff[staffNum] === undefined || this.lines[i].staff[staffNum].voices[voiceNum] === undefined ||
1040
- !this.containsNotes(this.lines[i].staff[staffNum].voices[voiceNum] )) {
1041
- this.lineNum = i;
1042
- return;
226
+ var line = this.lines[i];
227
+ if (line.staff) {
228
+ for (var j = 0; j < line.staff.length; j++) {
229
+ var staff = line.staff[j];
230
+ for (var k = 0; k < staff.voices.length; k++) {
231
+ var voice = staff.voices[k];
232
+ for (var ii = 0; ii < voice.length; ii++) {
233
+ var elem = voice[ii];
234
+ if (elem.startChar && elem.endChar &&
235
+ elem.startChar <= char && elem.endChar > char)
236
+ return elem;
237
+ }
238
+ }
1043
239
  }
1044
240
  }
1045
241
  }
1046
- this.lineNum = i;
1047
- };
1048
-
1049
- this.addMetaText = function(key, value) {
1050
- if (this.metaText[key] === undefined)
1051
- this.metaText[key] = value;
1052
- else
1053
- this.metaText[key] += "\n" + value;
1054
- };
1055
-
1056
- this.addMetaTextArray = function(key, value) {
1057
- if (this.metaText[key] === undefined)
1058
- this.metaText[key] = [value];
1059
- else
1060
- this.metaText[key].push(value);
1061
- };
1062
- this.addMetaTextObj = function(key, value) {
1063
- this.metaText[key] = value;
242
+ return null;
1064
243
  };
1065
244
 
1066
245
  function addVerticalInfo(timingEvents) {
@@ -1205,36 +384,45 @@ var Tune = function() {
1205
384
  this.makeVoicesArray = function() {
1206
385
  // First make a new array that is arranged by voice so that the repeats that span different lines are handled correctly.
1207
386
  var voicesArr = [];
387
+ var measureNumber = [];
388
+ var tempos = {};
1208
389
  for (var line = 0; line < this.engraver.staffgroups.length; line++) {
1209
390
  var group = this.engraver.staffgroups[line];
1210
- var firstStaff = group.staffs[0];
1211
- var middleC = firstStaff.absoluteY;
1212
- var top = middleC - firstStaff.top * spacing.STEP;
1213
- var lastStaff = group.staffs[group.staffs.length - 1];
1214
- middleC = lastStaff.absoluteY;
1215
- var bottom = middleC - lastStaff.bottom * spacing.STEP;
1216
- var height = bottom - top;
1217
-
1218
- var voices = group.voices;
1219
- for (var v = 0; v < voices.length; v++) {
1220
- var measureNumber = 0;
1221
- var noteFound = false;
1222
- if (!voicesArr[v])
1223
- voicesArr[v] = [];
1224
- var elements = voices[v].children;
1225
- for (var elem = 0; elem < elements.length; elem++) {
1226
- voicesArr[v].push({top: top, height: height, line: line, measureNumber: measureNumber, elem: elements[elem]});
1227
- 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.
1228
- measureNumber++;
1229
- if (elements[elem].type === 'note' || elements[elem].type === 'rest')
1230
- 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
+ }
1231
417
  }
1232
418
  }
1233
419
  }
420
+ this.tempoLocations = tempos; // This should be passed back, but the function is accessible publicly so that would break the interface.
1234
421
  return voicesArr;
1235
422
  };
1236
423
 
1237
- this.setupEvents = function(startingDelay, timeDivider, bpm) {
424
+ this.setupEvents = function(startingDelay, timeDivider, startingBpm, warp) {
425
+ if (!warp) warp = 1;
1238
426
  var timingEvents = [];
1239
427
 
1240
428
  var eventHash = {};
@@ -1244,24 +432,31 @@ var Tune = function() {
1244
432
  var isTiedState;
1245
433
  var nextIsBar = true;
1246
434
  var voices = this.makeVoicesArray();
435
+ var maxVoiceTimeMilliseconds = 0;
1247
436
  for (var v = 0; v < voices.length; v++) {
1248
437
  var voiceTime = time;
1249
438
  var voiceTimeMilliseconds = Math.round(voiceTime * 1000);
1250
439
  var startingRepeatElem = 0;
1251
440
  var endingRepeatElem = -1;
1252
441
  var elements = voices[v];
442
+ var bpm = startingBpm;
443
+ timeDivider = this.getBeatLength() * bpm / 60;
444
+ var tempoDone = -1;
1253
445
  for (var elem = 0; elem < elements.length; elem++) {
1254
- var element = elements[elem].elem;
1255
- if (element.abcelem.el_type === "tempo") {
1256
- var bpm = this.getBpm(element.abcelem);
1257
- var beatLength = this.getBeatLength();
1258
- var beatsPerSecond = bpm / 60;
1259
- timeDivider = beatLength * beatsPerSecond;
446
+ var thisMeasure = elements[elem].measureNumber;
447
+ if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) {
448
+ bpm = this.tempoLocations[thisMeasure];
449
+ timeDivider = warp * this.getBeatLength() * bpm / 60;
450
+ tempoDone = thisMeasure;
1260
451
  }
452
+ var element = elements[elem].elem;
1261
453
  var ret = this.addElementToEvents(eventHash, element, voiceTimeMilliseconds, elements[elem].top, elements[elem].height, elements[elem].line, elements[elem].measureNumber, timeDivider, isTiedState, nextIsBar);
1262
454
  isTiedState = ret.isTiedState;
1263
455
  nextIsBar = ret.nextIsBar;
1264
456
  voiceTime += ret.duration;
457
+ var lastHash;
458
+ if (element.duration > 0 && eventHash["event" + voiceTimeMilliseconds]) // This won't exist if this is the end of a tie.
459
+ lastHash = "event" + voiceTimeMilliseconds;
1265
460
  voiceTimeMilliseconds = Math.round(voiceTime * 1000);
1266
461
  if (element.type === 'bar') {
1267
462
  var barType = element.abcelem.type;
@@ -1269,16 +464,32 @@ var Tune = function() {
1269
464
  var startEnding = (element.abcelem.startEnding === '1');
1270
465
  var startRepeat = (barType === "bar_left_repeat" || barType === "bar_dbl_repeat" || barType === "bar_right_repeat");
1271
466
  if (endRepeat) {
467
+ // Force the end of the previous note to the position of the measure - the cursor won't go past the end repeat
468
+ if (elem > 0) {
469
+ eventHash[lastHash].endX = element.x;
470
+ }
471
+
1272
472
  if (endingRepeatElem === -1)
1273
473
  endingRepeatElem = elem;
474
+ var lastVoiceTimeMilliseconds = 0;
475
+ tempoDone = -1;
1274
476
  for (var el2 = startingRepeatElem; el2 < endingRepeatElem; el2++) {
477
+ thisMeasure = elements[el2].measureNumber;
478
+ if (tempoDone !== thisMeasure && this.tempoLocations[thisMeasure]) {
479
+ bpm = this.tempoLocations[thisMeasure];
480
+ timeDivider = warp * this.getBeatLength() * bpm / 60;
481
+ tempoDone = thisMeasure;
482
+ }
1275
483
  var element2 = elements[el2].elem;
1276
484
  ret = this.addElementToEvents(eventHash, element2, voiceTimeMilliseconds, elements[el2].top, elements[el2].height, elements[el2].line, elements[el2].measureNumber, timeDivider, isTiedState, nextIsBar);
1277
485
  isTiedState = ret.isTiedState;
1278
486
  nextIsBar = ret.nextIsBar;
1279
487
  voiceTime += ret.duration;
488
+ lastVoiceTimeMilliseconds = voiceTimeMilliseconds;
1280
489
  voiceTimeMilliseconds = Math.round(voiceTime * 1000);
1281
490
  }
491
+ if (eventHash["event" + lastVoiceTimeMilliseconds]) // This won't exist if it is the beginning of the next line. That's ok because we will just count the end of the last line as the end.
492
+ eventHash["event" + lastVoiceTimeMilliseconds].endX = elements[endingRepeatElem].elem.x;
1282
493
  nextIsBar = true;
1283
494
  endingRepeatElem = -1;
1284
495
  }
@@ -1288,12 +499,14 @@ var Tune = function() {
1288
499
  startingRepeatElem = elem;
1289
500
  }
1290
501
  }
502
+ maxVoiceTimeMilliseconds = Math.max(maxVoiceTimeMilliseconds, voiceTimeMilliseconds)
1291
503
  }
1292
504
  // now we have all the events, but if there are multiple voices then there may be events out of order or duplicated, so normalize it.
1293
505
  timingEvents = makeSortedArray(eventHash);
1294
506
  addVerticalInfo(timingEvents);
1295
- timingEvents.push({ type: "end", milliseconds: voiceTimeMilliseconds });
1296
- this.addUsefulCallbackInfo(timingEvents, bpm);
507
+ addEndPoints(this.lines, timingEvents)
508
+ timingEvents.push({ type: "end", milliseconds: maxVoiceTimeMilliseconds });
509
+ this.addUsefulCallbackInfo(timingEvents, bpm*warp);
1297
510
  return timingEvents;
1298
511
  };
1299
512
 
@@ -1305,16 +518,32 @@ var Tune = function() {
1305
518
  }
1306
519
  };
1307
520
 
1308
- function getVertical(group) {
1309
- var voices = group.voices;
1310
- var firstStaff = group.staffs[0];
1311
- var middleC = firstStaff.absoluteY;
1312
- var top = middleC - firstStaff.top*spacing.STEP;
1313
- var lastStaff = group.staffs[group.staffs.length-1];
1314
- middleC = lastStaff.absoluteY;
1315
- var bottom = middleC - lastStaff.bottom*spacing.STEP;
1316
- var height = bottom - top;
1317
- return { top: top, height: height };
521
+ function skipTies(elements, index) {
522
+ while (index < elements.length && elements[index].left === null)
523
+ index++;
524
+ return elements[index];
525
+ }
526
+ function addEndPoints(lines, elements) {
527
+ if (elements.length < 1)
528
+ return;
529
+ for (var i = 0; i < elements.length-1; i++) {
530
+ var el = elements[i];
531
+ var next = skipTies(elements, i+1);
532
+ if (el.left !== null) {
533
+ // If there is no left element that is because this is a tie so it should be skipped.
534
+ var endX = (next && el.top === next.top) ? next.left : lines[el.line].staffGroup.w;
535
+ // If this is already set, it is because the notes aren't sequential here, like the next thing is a repeat bar line.
536
+ // In that case, the right-most position is passed in. There could still be an intervening note in another voice, so always look for the closest position.
537
+ // If there is a repeat that stays on the same line, the endX set above won't be right because the next note will be before. In that case, use the endX that was calculated.
538
+ if (el.endX !== undefined) {
539
+ if (endX > el.left)
540
+ el.endX = Math.min(el.endX, endX);
541
+ } else
542
+ el.endX = endX;
543
+ }
544
+ }
545
+ var lastEl = elements[elements.length-1];
546
+ lastEl.endX = lines[lastEl.line].staffGroup.w;
1318
547
  }
1319
548
 
1320
549
  this.getBpm = function(tempo) {
@@ -1329,7 +558,7 @@ var Tune = function() {
1329
558
  bpm = 180;
1330
559
  // Compensate for compound meter, where the beat isn't a beat.
1331
560
  var meter = this.getMeterFraction();
1332
- if (meter && (meter.num % 3 === 0)) {
561
+ if (meter && meter.num !== 3 && (meter.num % 3 === 0)) {
1333
562
  bpm = 120;
1334
563
  }
1335
564
  }
@@ -1337,11 +566,25 @@ var Tune = function() {
1337
566
  };
1338
567
 
1339
568
  this.setTiming = function (bpm, measuresOfDelay) {
1340
- if (!bpm) {
1341
- var tempo = this.metaText ? this.metaText.tempo : null;
1342
- bpm = this.getBpm(tempo);
569
+ measuresOfDelay = measuresOfDelay || 0;
570
+ if (!this.engraver || !this.engraver.staffgroups) {
571
+ console.log("setTiming cannot be called before the tune is drawn.");
572
+ this.noteTimings = [];
573
+ return;
1343
574
  }
1344
575
 
576
+ var tempo = this.metaText ? this.metaText.tempo : null;
577
+ var naturalBpm = this.getBpm(tempo);
578
+ var warp = 1;
579
+ if (bpm) {
580
+ if (tempo)
581
+ warp = bpm / naturalBpm;
582
+ } else
583
+ bpm = naturalBpm;
584
+
585
+ // Calculate the basic midi data. We only care about the qpm variable here.
586
+ //this.setUpAudio({qpm: bpm});
587
+
1345
588
  var beatLength = this.getBeatLength();
1346
589
  var beatsPerSecond = bpm / 60;
1347
590
 
@@ -1352,8 +595,25 @@ var Tune = function() {
1352
595
  startingDelay -= this.getPickupLength() / beatLength / beatsPerSecond;
1353
596
  var timeDivider = beatLength * beatsPerSecond;
1354
597
 
1355
- this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm);
598
+ this.noteTimings = this.setupEvents(startingDelay, timeDivider, bpm, warp);
599
+ if (this.noteTimings.length > 0) {
600
+ this.totalTime = this.noteTimings[this.noteTimings.length - 1].milliseconds / 1000;
601
+ this.totalBeats = this.totalTime * beatsPerSecond;
602
+ } else {
603
+ this.totalTime = undefined;
604
+ this.totalBeats = undefined;
605
+ }
606
+ return this.noteTimings;
607
+ };
608
+
609
+ this.setUpAudio = function(options) {
610
+ if (!options) options = {};
611
+ var seq = sequence(this, options);
612
+ return flatten(seq, options, this.formatting.percmap);
1356
613
  };
614
+ this.deline = function(options) {
615
+ return delineTune(this.lines, options);
616
+ }
1357
617
  };
1358
618
 
1359
619
  module.exports = Tune;